Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/api/providers/roo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
if (deltaWithReasoning.reasoning_details && Array.isArray(deltaWithReasoning.reasoning_details)) {
for (const detail of deltaWithReasoning.reasoning_details) {
const index = detail.index ?? 0
const key = `${detail.type}-${index}`
// Use id as key when available to merge chunks that share the same reasoning block id
// This ensures that reasoning.summary and reasoning.encrypted chunks with the same id
// are merged into a single object, matching the provider's expected format
const key = detail.id ?? `${detail.type}-${index}`
const existing = reasoningDetailsAccumulator.get(key)

if (existing) {
Expand All @@ -194,6 +197,8 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
existing.data = (existing.data || "") + detail.data
}
// Update other fields if provided
// Note: Don't update type - keep original type (e.g., reasoning.summary)
// even when encrypted data chunks arrive with type reasoning.encrypted
if (detail.id !== undefined) existing.id = detail.id
if (detail.format !== undefined) existing.format = detail.format
if (detail.signature !== undefined) existing.signature = detail.signature
Expand Down
22 changes: 17 additions & 5 deletions src/api/transform/openai-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,28 @@ export function convertToOpenAiMessages(

// Check if the message has reasoning_details (used by Gemini 3, etc.)
const messageWithDetails = anthropicMessage as any
const baseMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {

// Build message with reasoning_details BEFORE tool_calls to preserve
// the order expected by providers like Roo. Property order matters
// when sending messages back to some APIs.
const baseMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam & { reasoning_details?: any[] } = {
role: "assistant",
content,
// Cannot be an empty array. API expects an array with minimum length 1, and will respond with an error if it's empty
tool_calls: tool_calls.length > 0 ? tool_calls : undefined,
}

// Preserve reasoning_details if present (will be processed by provider if needed)
// Add reasoning_details first (before tool_calls) to preserve provider-expected order
// Strip the id field from each reasoning detail as it's only used internally for accumulation
if (messageWithDetails.reasoning_details && Array.isArray(messageWithDetails.reasoning_details)) {
;(baseMessage as any).reasoning_details = messageWithDetails.reasoning_details
baseMessage.reasoning_details = messageWithDetails.reasoning_details.map((detail: any) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

convertToOpenAiMessages() strips id from reasoning_details for all OpenAI-compatible providers. If a downstream API expects reasoning_details[].id for correlation (for example matching encrypted reasoning blocks to a specific tool call or preserving block identity across turns), removing it can change semantics or trigger validation errors. Consider preserving id by default and making stripping provider-specific (or behind an option).

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

const { id, ...rest } = detail
return rest
})
}

// Add tool_calls after reasoning_details
// Cannot be an empty array. API expects an array with minimum length 1, and will respond with an error if it's empty
if (tool_calls.length > 0) {
baseMessage.tool_calls = tool_calls
}

openAiMessages.push(baseMessage)
Expand Down
Loading