Skip to content

Conversation

@daniel-lxs
Copy link
Member

@daniel-lxs daniel-lxs commented Feb 10, 2026

Summary

Adds foundation infrastructure for migrating message storage from Anthropic format to AI SDK ModelMessage format (EXT-646).

No behavior change — purely additive. Existing code paths are untouched.

What's Added

Type System (rooMessage.ts)

  • RooMessage = AI SDK ModelMessage + Roo metadata (ts, condenseId, truncationId, etc.)
  • Uses AI SDK types directly (UserModelMessage, AssistantModelMessage, ToolModelMessage from "ai")
  • Only custom additions: RooMessageMetadata and RooReasoningMessage (encrypted reasoning)
  • Type guards: isRooUserMessage(), isRooAssistantMessage(), isRooToolMessage(), isRooReasoningMessage()

Legacy Converter (converters/anthropicToRoo.ts)

  • convertAnthropicToRooMessages() — Converts old Anthropic-format conversations to RooMessage on first open
  • Handles all content types: text, images, tool_use/tool_result, thinking/reasoning blocks, signatures, provider options
  • Tool name resolution via first-pass tool call ID mapping
  • Metadata preservation (ts, condense, truncation fields)

Versioned Storage (apiMessages.ts)

  • readRooMessages() — Reads JSON, auto-detects format (v2 envelope vs legacy Anthropic array), converts on read
  • saveRooMessages() — Writes in v2 format: { version: 2, messages: [...] }
  • detectFormat() — Format detection utility
  • Existing readApiMessages()/saveApiMessages() untouched

Utility (messageUtils.ts)

  • flattenModelMessagesToStringContent() — For providers needing string content (e.g., DeepSeek on SambaNova)

Design Principles

  1. Store as ModelMessage — Messages use AI SDK format directly, passed to streamText() with zero conversion
  2. Forward-only migration — Old conversations convert on read; no backward path to Anthropic format
  3. No type duplication — Content parts (TextPart, ImagePart, ToolCallPart, etc.) come from the "ai" package

Test Coverage

  • 101 tests across 6 test files
  • All 5536 existing tests continue to pass

Next Steps

  • EXT-647: Wire Task.ts to use readRooMessages()/saveRooMessages() and construct RooMessage directly
  • EXT-648: Delete per-provider converters (convertToAiSdkMessages, convertToOpenAiMessages, etc.)

Resolves EXT-646

Important

Introduces RooMessage type system and storage layer for migrating from Anthropic to AI SDK ModelMessage format, with conversion functions and comprehensive tests.

  • Type System:
    • Introduces RooMessage types in rooMessage.ts, extending AI SDK ModelMessage with metadata.
    • Adds type guards: isRooUserMessage(), isRooAssistantMessage(), isRooToolMessage(), isRooReasoningMessage().
  • Conversion:
    • Adds convertAnthropicToRooMessages() in converters/anthropicToRoo.ts to convert Anthropic-format messages to RooMessage.
    • Handles all content types and preserves metadata.
  • Storage:
    • Implements readRooMessages() and saveRooMessages() in apiMessages.ts for versioned message storage.
    • Detects format and converts legacy messages on read.
  • Utilities:
    • Adds flattenModelMessagesToStringContent() in messageUtils.ts for flattening message content.
  • Tests:
    • 101 new tests across 6 files, ensuring comprehensive coverage.
    • Tests for type guards, conversion logic, storage functions, and utilities.

This description was created by Ellipsis for e8ea492. You can customize this summary. It will automatically update as commits are pushed.

…igration

Add foundation infrastructure for migrating message storage from Anthropic
format to AI SDK ModelMessage format (EXT-646).

- RooMessage type = AI SDK ModelMessage + Roo metadata (ts, condense, truncation)
- Anthropic-to-RooMessage converter for migrating old conversations on read
- Versioned storage (readRooMessages/saveRooMessages) with auto-detection
- flattenModelMessagesToStringContent utility for providers needing string content

No behavior change - purely additive. Existing code paths untouched.
EXT-647 will wire Task.ts to use these new functions.
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. Enhancement New feature or request labels Feb 10, 2026
@roomote
Copy link
Contributor

roomote bot commented Feb 10, 2026

Rooviewer Clock   See task

Reviewed 1 changed file in the latest commit (mocked safeWriteJson in the write-failure test for cross-platform reliability). No new issues found.

Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 10, 2026
…s/index.ts and unnecessary re-exports

- Narrow summary from any[] to Array<{ type: string; text: string }> matching EncryptedReasoningItem
- Delete unused converters/index.ts barrel (knip: unused file)
- Remove UserContent, AssistantContent, ToolContent re-exports (importable from 'ai' directly)
- Remove FlattenMessagesOptions re-export from index.ts
@daniel-lxs
Copy link
Member Author

The platform-unit-test (windows-latest) job is failing on one test:

rooMessages.spec.ts > saveRooMessages > returns false on write failure (line 222)

Root cause

The test expects saveRooMessages to throw (and return false) when given globalStoragePath: "/nonexistent/readonly/path". The intent is that getTaskDirectoryPath or safeWriteJson will fail on a nonexistent/invalid path.

However, getTaskDirectoryPath calls fs.mkdir(taskDir, { recursive: true }), which successfully creates the directory on Windows because /nonexistent/readonly/path resolves to a valid absolute path on the current drive (e.g. D:\nonexistent\readonly\path\tasks\task-fail). Unlike Linux/macOS where writing to /nonexistent/... requires root, Windows happily creates it under the current drive root. So the write succeeds and the function returns true.

Fix

The test needs a path that will reliably fail on all platforms. Two options:

  1. Use a path with invalid characters that will fail on Windows, e.g. globalStoragePath: path.join(os.tmpdir(), "roo-test-\0-invalid") (null byte is invalid everywhere).

  2. Mock getTaskDirectoryPath or safeWriteJson to throw, rather than relying on filesystem behavior. This is the more robust approach since it doesn't depend on OS-specific path validation. For example:

import * as storage from "../../utils/storage"

vi.spyOn(storage, "getTaskDirectoryPath").mockRejectedValueOnce(new Error("simulated write failure"))

Option 2 is probably the better path since the test is checking the error-handling logic in saveRooMessages, not the OS filesystem behavior.

@daniel-lxs
Copy link
Member Author

@roomote Fix the test based on my comment

@roomote
Copy link
Contributor

roomote bot commented Feb 10, 2026

Fixaroo Clock   See task

Fixed the cross-platform test failure by mocking safeWriteJson instead of relying on OS-specific filesystem behavior. All tests and checks pass.

View commit | Revert commit

@daniel-lxs daniel-lxs merged commit 50e5d98 into main Feb 10, 2026
10 checks passed
@daniel-lxs daniel-lxs deleted the feature/ext-646-design-modelmessage-schema-migration-strategy branch February 10, 2026 21:41
@github-project-automation github-project-automation bot moved this from New to Done in Roo Code Roadmap Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants