Skip to content

Conversation

@daniel-lxs
Copy link
Member

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

Summary

Wires the RooMessage foundation (PR #11380, now merged) into the entire codebase. Messages are now stored as ModelMessage + metadata and passed directly to providers with zero conversion.

Built on: PR #11380 (feature/ext-646-design-modelmessage-schema-migration-strategy, merged)

Changes (~80 files)

Storage Layer

  • apiConversationHistory type: ApiMessage[]RooMessage[]
  • Read: readApiMessagesreadRooMessages (auto-converts old Anthropic format)
  • Write: saveApiMessagessaveRooMessages (versioned v2 format)

Message Construction (Task.ts)

  • userMessageContent: Anthropic.TextBlockParam[]Array<TextPart | ImagePart>
  • New pendingToolResults: Array<ToolResultPart> — tool results separated from user content
  • addToApiConversationHistory accepts RooMessage directly
  • pushToolResultToUserContent accepts ToolResultPart (AI SDK format)
  • buildCleanConversationHistory: ~140 lines → ~20 lines (messages pass through)

Provider Interface

  • ApiHandler.createMessage(): Anthropic.Messages.MessageParam[]RooMessage[]
  • All 30 providers updated to accept RooMessage[]
  • Removed convertToAiSdkMessages() from 22 AI SDK providers — messages go directly to streamText()

Supporting Systems

  • condense, messageManager, ClineProvider, validateToolResultIds, export-markdown
  • Resume logic fully rewritten for RooMessage format
  • ~45 test files updated

Test Results

  • 5536 tests pass, 0 failures
  • 0 compile errors

Resolves EXT-647

Important

Wired the RooMessage foundation into the entire codebase, migrating from Anthropic SDK message types to a new internal RooMessage format based on AI SDK types across all 40+ API providers, message handling utilities, and task persistence logic.

Summary

Wires the RooMessage foundation (PR #11380, now merged) into the entire codebase. Messages are now stored as ModelMessage + metadata and passed directly to providers with zero conversion.
Built on: PR #11380 (feature/ext-646-design-modelmessage-schema-migration-strategy, merged)

Changes (~80 files)

Storage Layer

  • apiConversationHistory type: ApiMessage[] â�� RooMessage[]
  • Read: readApiMessages â�� readRooMessages (auto-converts old Anthropic format)
  • Write: saveApiMessages â�� saveRooMessages (versioned v2 format)

Message Construction (Task.ts)

  • userMessageContent: Anthropic.TextBlockParam[] â�� Array<TextPart | ImagePart>
  • New pendingToolResults: Array<ToolResultPart> â�� tool results separated from user content
  • addToApiConversationHistory accepts RooMessage directly
  • pushToolResultToUserContent accepts ToolResultPart (AI SDK format)
  • buildCleanConversationHistory: ~140 lines â�� ~20 lines (messages pass through)

Provider Interface

  • ApiHandler.createMessage(): Anthropic.Messages.MessageParam[] â�� RooMessage[]
  • All 40+ providers updated to accept RooMessage[]
  • Removed convertToAiSdkMessages() from 22 AI SDK providers â�� messages go directly to streamText()

Supporting Systems

  • condense, messageManager, ClineProvider, validateToolResultIds, export-markdown
  • Resume logic fully rewritten for RooMessage format
  • ~45 test files updated

Test Results

  • 5536 tests pass, 0 failures
  • 0 compile errors
    Resolves EXT-647

Detailed Changes

Message Type Migration

  • New RooMessage Type System: Created rooMessage.ts defining RooMessage union type with variants for user, assistant, tool, and reasoning messages, all extending AI SDK types with Roo-specific metadata (timestamps, condense/truncation tracking).
  • Message Persistence: Implemented readRooMessages() and saveRooMessages() functions in apiMessages.ts to handle v2 versioned format with automatic fallback to legacy claude_messages.json and conversion from old Anthropic format.
  • Anthropic-to-Roo Conversion: Added convertAnthropicToRooMessages() converter in new anthropicToRoo.ts module to transform legacy ApiMessage arrays to RooMessage format, handling all message types, reasoning formats, and metadata preservation.

API Provider Updates

  • All 40+ API Providers: Updated createMessage() method signatures to accept RooMessage[] instead of Anthropic.Messages.MessageParam[] across all providers (OpenAI, Anthropic, Azure, Gemini, Bedrock, etc.).
  • Message Conversion: Replaced convertToAiSdkMessages() calls with direct type casts to ModelMessage[] in providers that use AI SDK (Anthropic, Azure, Baseten, Bedrock, DeepSeek, Fireworks, Gemini, LM Studio, Minimax, Mistral, OpenAI, OpenRouter, Requesty, Sambanova, Vercel AI Gateway, Vertex, ZAI).
  • Provider-Specific Adjustments:
    • Simplified cache control methods in Anthropic and Bedrock providers by removing complex message index mapping.
    • Updated Bedrock to check for role property existence before accessing it.
    • Modified Minimax to handle message merging with type casts.
    • Updated OpenAI native reasoning to use RooMessage[] in helper functions.

Task and Message Management

  • Task Persistence: Updated Task.ts to use RooMessage[] for apiConversationHistory, separated user message content and tool results into userMessageContent and pendingToolResults properties.
  • Tool Results: Changed tool result structure from Anthropic format (tool_use_id, content, is_error) to internal format (toolCallId, toolName, output with nested type and value).
  • Message Utilities: Added flattenModelMessagesToStringContent() utility to convert message content arrays to strings where applicable, with options to control flattening behavior.
  • Condense Logic: Updated getMessagesSinceLastSummary() and getEffectiveApiHistory() in condense/index.ts to use RooMessage[] with type guards (isRooUserMessage, isRooAssistantMessage, isRooToolMessage).
  • Message Merging: Updated mergeConsecutiveApiMessages() to work with RooMessage type and pass through RooReasoningMessage items unmerged.

Assistant Message Handling

  • Tool Result Transformation: Updated presentAssistantMessage.ts to transform tool results to internal format with toolCallId, toolName, and output structure, prepending [ERROR] prefix for errors instead of using is_error flag.
  • Image Block Handling: Updated image block transformation to use ImagePart format with image and mediaType fields.
  • Test Updates: Updated all assistant message tests to use pendingToolResults instead of userMessageContent and verify new tool result structure.

Test Coverage

  • New Test Files: Added comprehensive test suites for rooMessage.ts, rooMessages.ts, messageUtils.ts, and anthropicToRoo.ts covering type guards, message detection, persistence, and conversion logic.
  • Test Updates: Updated 30+ test files across API providers, task management, and message handling to use RooMessage[] type and new message structure.
  • Type Assertions: Added as any type assertions throughout tests to handle type compatibility during migration.

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

@dosubot dosubot bot added size:L This PR changes 100-499 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

Re-reviewed after fef98f6. The injectSyntheticToolResults and getToolResultContent fixes are confirmed resolved. Two prior issues remain unaddressed, and one new issue found in the Anthropic providers.

  • injectSyntheticToolResults is silently a no-op for new RooMessage conversations (searches for tool_use/tool_result types instead of tool-call/tool-result, and checks role: "user" instead of role: "tool")
  • getToolResultContent in convertToOpenAiMessages checks part.result ?? part.content but new RooMessage tool results store content in output -- tool result content silently becomes "(empty)" for non-AI-SDK providers
  • reasoning_details stored at message top-level by addToApiConversationHistory but buildCleanConversationHistory docstring claims it passes through via providerOptions -- the AI SDK ignores the top-level field, breaking round-trip for OpenRouter + Gemini 3
  • toolResultToText in condense does not handle the { type, value } output shape returned by getToolResultContent for new-format AI SDK tool results -- content falls through to bare [Tool Result] with no body, degrading condensation summaries
  • RooReasoningMessage not filtered in Anthropic and Anthropic-Vertex providers before casting to ModelMessage[] -- Bedrock and Gemini filter these, but Anthropic/Vertex do not, causing role-less objects to reach streamText when switching from OpenAI Native mid-conversation
Previous reviews

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

@ellipsis-dev
Copy link
Contributor

ellipsis-dev bot commented Feb 11, 2026

⚠️ This PR is too big for Ellipsis, but support for larger PRs is coming soon. If you want us to prioritize this feature, let us know at help@ellipsis.dev


Generated with ❤️ by ellipsis.dev

@hannesrudolph
Copy link
Collaborator

Note: This review was generated as a test using Claude Code with a 10-agent review team. Take it with a grain of salt.


PR #11386 Review: Wire RooMessage Storage into Task.ts and All Providers

Reviewed: 104 files, +4200/-2167, 4 commits (including fix commit 17981340)

What's Done Well

  • Architecture is sound -- RooMessage extending AI SDK ModelMessage via intersection types enables zero-cost structural compatibility at provider boundaries
  • Task.ts dramatically simplified -- buildCleanConversationHistory went from ~140 to ~20 lines, properly documented with defensive fallback
  • Persistence layer is solid -- atomic writes via safeWriteJson, proper format detection, non-destructive legacy reads with auto-conversion
  • Transform layer rewrite -- openai-format.ts dual-format helpers correctly handle both AI SDK and Anthropic formats for backward compatibility
  • Converter is thorough -- anthropicToRoo.ts handles all message types with good test coverage (1076 lines of tests)
  • Provider hierarchy consistent -- all 30+ providers updated uniformly
  • Fix commit resolved critical issues -- ApiHandler.createMessage interface updated, validateAndFixToolResultIds properly restored, buildCleanConversationHistory invariant documented
  • 5536 tests pass, 0 failures

Issues Found

MEDIUM

1. export-markdown.ts not updated for new message format

downloadTask() still checks block.type === "tool_result" and block.is_error. New-format conversations use tool-result (hyphen). Tool result blocks from post-PR conversations will fall through to the default case and render as [Unexpected content type: tool-result]. This is a user-visible bug for anyone who exports a conversation after upgrading.

2. Condense isError detection broken for new format

toolResultToText() checks (block as any).isError but the AI SDK ToolResultPart has no isError field -- it's always undefined for new-format blocks. The (Error) suffix in condensed text will never appear for new-format error tool results. The [ERROR] prefix in text content partially compensates, but error detection in the condense path is semantically broken.

3. RooReasoningMessage not filtered in Anthropic/Vertex providers

Bedrock correctly filters RooReasoningMessage items (which have type: "reasoning" but no role) before casting to ModelMessage[]. Anthropic and Anthropic-Vertex do not filter. If a reasoning message reaches these providers, the cast sends a role-less object to the AI SDK, which will likely cause an API error.

4. 235 net new as any casts (+36 production, +199 tests)

The fix commit actually increased the cast count from ~152 to ~235. Test fixtures universally use any[] instead of RooMessage[], so tests no longer validate type correctness at compile time. Production hotspots: condense/index.ts (13), Task.ts (6), openai-native.ts (5), validateToolResultIds.ts (4). This is acknowledged as transitional ("Task D"), but it's a significant type safety regression.

LOW

5. tiktoken.ts not updated

Still checks block.type === "tool_result" and block.type === "tool_use" but not tool-result/tool-call. New-format tool blocks silently skipped during token counting, leading to underestimates.

6. Double cast typo in bedrock.ts

filteredMessages as ModelMessage[] as ModelMessage[] (line ~1677) -- should be single cast.

7. Stale JSDoc on cache control methods

applyCacheControlToAiSdkMessages / applyCachePointsToAiSdkMessages in Anthropic, Vertex, and Bedrock providers still document the old parallel-walk behavior and convertToAiSdkMessages() splitting logic that no longer exists.

8. Dead code: convertToAiSdkMessages

Function (~200 lines) and its tests in src/api/transform/ai-sdk.ts are now dead -- all call sites replaced. Should be removed.

9. Content-part type guards not re-exported

New isTextPart, isToolCallPart, isToolResultPart, isImagePart in rooMessage.ts are not re-exported from the index.ts barrel. Missing isFilePart and isReasoningPart for completeness. No tests for the new guards.

10. No AI SDK format test cases in transform/validate tests

openai-format.spec.ts and validateToolResultIds.spec.ts only test legacy Anthropic-format data (tool_use/tool_result). The new AI SDK code paths (tool-call/tool-result with toolCallId/toolName) are untested.

11. ToolResponse type in tools.ts still references Anthropic types

export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>

Should be updated to TextPart | ImagePart to match the migration.

12. Converter emits separate reasoning parts (diverges from reference)

convertAnthropicToRooMessages emits each reasoning block as a separate ReasoningPart, while the reference convertToAiSdkMessages joined multiple reasoning blocks into one. Low practical impact since multiple reasoning blocks per message are uncommon.

INFORMATIONAL

  • RooMessageHistory.version is hardcoded to literal 2 instead of typeof ROO_MESSAGE_VERSION
  • Future v3+ format files would be misclassified as "legacy" and return [] (no specific warning)
  • flattenModelMessagesToStringContent silently drops reasoning parts (by design, but underdocumented)
  • processUserContentMentions return type cast is unsafe for legacy tool_result blocks passing through backward-compat guard

Summary

The architectural foundation is strong -- aligning internal storage with AI SDK's ModelMessage is the right call and the intersection type approach is clean. The fix commit resolved the most critical functional issues from the initial review. The main gaps are downstream consumers (export-markdown, tiktoken, condense error detection) that weren't updated for the new discriminator format (tool-result vs tool_result), and the type safety regression from pervasive as any casts.

Suggested priorities:

  1. Fix export-markdown.ts to handle tool-result/tool-call discriminators (user-visible)
  2. Fix condense isError detection for new format
  3. Add reasoning message filter to Anthropic/Vertex providers (match Bedrock pattern)
  4. Track as any cleanup as concrete follow-up work

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 11, 2026
@ellipsis-dev
Copy link
Contributor

ellipsis-dev bot commented Feb 11, 2026

⚠️ This PR is too big for Ellipsis, but support for larger PRs is coming soon. If you want us to prioritize this feature, let us know at help@ellipsis.dev


Generated with ❤️ by ellipsis.dev

daniel-lxs and others added 6 commits February 10, 2026 22:20
…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.
…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
Complete migration from Anthropic.MessageParam to RooMessage (ModelMessage + metadata)
for internal message storage and the entire provider pipeline.

Key changes:
- Task.ts: apiConversationHistory is now RooMessage[], stored via readRooMessages/saveRooMessages
- Message construction: userMessageContent uses TextPart/ImagePart, pendingToolResults uses ToolResultPart
- ApiHandler.createMessage() accepts RooMessage[] instead of Anthropic.Messages.MessageParam[]
- All 30 providers updated: messages passed directly to streamText()/generateText()
- Removed convertToAiSdkMessages() calls from all AI SDK providers
- buildCleanConversationHistory simplified from ~140 lines to ~20 lines
- Resume logic rewritten for RooMessage format
- Supporting systems updated: condense, messageManager, ClineProvider

Old Anthropic-format conversations auto-convert on first open via readRooMessages().
New conversations stored in versioned v2 format.

5536 tests pass, 0 failures.

EXT-647
Replace `as any` / `as unknown as` casts with proper RooMessage types
across the storage and API pipeline:

- Add UserContentPart alias and content-part type guards to rooMessage.ts
- Migrate maybeRemoveImageBlocks, formatResponse, processUserContentMentions,
  validateAndFixToolResultIds, condense system, and convertToOpenAiMessages
  from Anthropic SDK types to AI SDK RooMessage types
- Change initiateTaskLoop/recursivelyMakeClineRequests signatures from
  Anthropic.Messages.ContentBlockParam[] to UserContentPart[]
- Type the assistant message builder in Task.ts, remove double-casts
  in the API pipeline
- Remove unused Anthropic imports from 7 source files
- Update ToolResponse type and presentAssistantMessage to use ImagePart
- All functions accept both AI SDK (tool-call/tool-result) and legacy
  Anthropic (tool_use/tool_result) formats for backward compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add RooRoleMessage type, dual-format legacy block interfaces
(LegacyToolUseBlock, LegacyToolResultBlock), union types
(AnyToolCallBlock, AnyToolResultBlock), type guards, and accessor
helpers to rooMessage.ts. Replace scattered `as any` casts across
condense, context-management, Task.ts, validateToolResultIds.ts, and
openai-format.ts with these shared typed utilities. Remove leaked
Anthropic SDK import from context-management in favor of a
provider-agnostic ContentBlockParam type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@daniel-lxs daniel-lxs force-pushed the feature/ext-647-implement-modelmessage-storage-layer branch from fc24684 to fef98f6 Compare February 11, 2026 03:31
const isError = getToolResultIsError(block)
const errorSuffix = isError ? " (Error)" : ""
// AI SDK uses `output`, legacy uses `content`
const rawContent = getToolResultContent(block)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getToolResultContent now returns block.output for AI SDK tool results, which is { type: "text", value: "..." }. toolResultToText checks for string and Array but not for this object shape, so the content falls through to the bare [Tool Result] return on line 85 -- losing all tool result text. This affects condensation summaries for new-format conversations since every tool result renders without its content. openai-format.ts (lines 377, 426) already handles this with a "value" in rawContent branch; the same check is needed here.

Fix it with Roo Code or mention @roomote and request a fix.

- toolResultToText: handle { type, value } object shape returned by
  getToolResultContent for AI SDK ToolResultPart.output, preventing
  tool result content from being lost during condensation summaries
- export-markdown: add tool-call/tool-result (AI SDK format) handling
  alongside legacy tool_use/tool_result, preventing post-migration
  conversations from rendering as [Unexpected content type: tool-call]

Addresses review feedback from PR #11386.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

// Convert messages to AI SDK format
const aiSdkMessages = convertToAiSdkMessages(messages)
const aiSdkMessages = messages as ModelMessage[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RooReasoningMessage items (which have type: "reasoning" and no role) are cast directly to ModelMessage[] without filtering. Bedrock and Gemini both filter these out before the cast (e.g. messages.filter(msg => msg.type !== "reasoning")), but Anthropic and Anthropic-Vertex do not. If a user switches providers mid-conversation from OpenAI Native (which produces standalone encrypted reasoning items) to Anthropic, these role-less objects will be sent to streamText, likely causing an API error. The same filter pattern from bedrock.ts lines 204-210 is needed here.

Suggested change
const aiSdkMessages = messages as ModelMessage[]
// Filter out standalone reasoning meta-items (e.g. encrypted reasoning from OpenAI Native)
// that lack a `role` property and would break the AI SDK.
const aiSdkMessages = messages.filter(
(msg) => !((msg as { type?: string }).type === "reasoning"),
) as ModelMessage[]

Fix it with Roo Code or mention @roomote and request a fix.


// Convert messages to AI SDK format
const aiSdkMessages = convertToAiSdkMessages(messages)
const aiSdkMessages = messages as ModelMessage[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as in anthropic.ts: RooReasoningMessage items (no role, just type: "reasoning") are not filtered before the cast. Bedrock and Gemini both filter these out. If the conversation history contains standalone encrypted reasoning items from a previous OpenAI Native session, they will be sent as role-less objects to streamText.

Suggested change
const aiSdkMessages = messages as ModelMessage[]
// Filter out standalone reasoning meta-items (e.g. encrypted reasoning from OpenAI Native)
// that lack a `role` property and would break the AI SDK.
const aiSdkMessages = messages.filter(
(msg) => !((msg as { type?: string }).type === "reasoning"),
) as ModelMessage[]

Fix it with Roo Code or mention @roomote and request a fix.

@hannesrudolph hannesrudolph merged commit a7ba3b5 into main Feb 11, 2026
9 of 10 checks passed
@hannesrudolph hannesrudolph deleted the feature/ext-647-implement-modelmessage-storage-layer branch February 11, 2026 04:25
@github-project-automation github-project-automation bot moved this from New to Done in Roo Code Roadmap Feb 11, 2026
hannesrudolph added a commit that referenced this pull request Feb 11, 2026
Create src/api/transform/cache-breakpoints.ts with applyCacheBreakpoints()
that replaces duplicated targeting+apply logic in:
- anthropic.ts (applyCacheControlToAiSdkMessages removed)
- anthropic-vertex.ts (applyCacheControlToAiSdkMessages removed)
- minimax.ts (applyCacheControlToAiSdkMessages removed)
- bedrock.ts (applyCachePointsToAiSdkMessages removed)

Key improvement: targets non-assistant batches (user + tool messages)
instead of only role=user. After PR #11386, tool results are separate
role=tool messages that now correctly receive cache breakpoints.

The shared utility supports per-provider configuration:
- Anthropic/Vertex/Minimax: 2 breakpoints (last 2 non-assistant batches)
- Bedrock: 3 breakpoints + anchor at ~1/3 for 20-block lookback coverage

8 new tests in cache-breakpoints.spec.ts, 0 regressions in provider tests.
mrubens added a commit that referenced this pull request Feb 11, 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:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants