fix: improve reasoning_details accumulation and serialization (#10285)

This commit is contained in:
Hannes Rudolph 2025-12-22 19:33:02 -07:00 • committed by GitHub
parent 518a4402a7
commit 44a7ba5072
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 23 additions and 6 deletions

View file

@ -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) {
@ -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

View file

@ -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) => {
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)