From f0c7a1007b8005451535477aa56e87f3cd21cdb7 Mon Sep 17 00:00:00 2001 From: Eryk Stec Date: Tue, 27 Jan 2026 17:57:30 +0000 Subject: [PATCH] fix: normalize Moonshot thinking signatures on message receipt Moonshot models via OpenRouter return thinkingSignature: "reasoning" but the API expects "reasoning_content" when tool calls are present. Instead of sanitizing on every send, normalize once when the message is received from the API. This is cleaner and more efficient. Fixes: Error "thinking is enabled but reasoning_content is missing in assistant tool call message" --- src/agents/pi-embedded-runner/run/attempt.ts | 2 ++ ...pi-embedded-subscribe.handlers.messages.ts | 19 +++++++++++++++++++ src/agents/pi-embedded-subscribe.types.ts | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 7b5193054..ff6c64cec 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -612,6 +612,8 @@ export async function runEmbeddedAttempt( onAssistantMessageStart: params.onAssistantMessageStart, onAgentEvent: params.onAgentEvent, enforceFinalTag: params.enforceFinalTag, + provider: params.provider, + modelId: params.modelId, }); const { diff --git a/src/agents/pi-embedded-subscribe.handlers.messages.ts b/src/agents/pi-embedded-subscribe.handlers.messages.ts index 1f515e113..ed72a60da 100644 --- a/src/agents/pi-embedded-subscribe.handlers.messages.ts +++ b/src/agents/pi-embedded-subscribe.handlers.messages.ts @@ -165,6 +165,25 @@ export function handleMessageEnd( const assistantMessage = msg as AssistantMessage; promoteThinkingTagsToBlocks(assistantMessage); + // Normalize Moonshot thinking signatures on receipt (not on send) + // Moonshot via OpenRouter returns "reasoning" but expects "reasoning_content" on replay + if ( + ctx.params.provider === "openrouter" && + ctx.params.modelId?.startsWith("moonshotai/kimi-") && + Array.isArray(assistantMessage.content) + ) { + for (const block of assistantMessage.content) { + if ( + block && + typeof block === "object" && + (block as { type?: unknown }).type === "thinking" && + (block as { thinkingSignature?: unknown }).thinkingSignature === "reasoning" + ) { + (block as { thinkingSignature: string }).thinkingSignature = "reasoning_content"; + } + } + } + const rawText = extractAssistantText(assistantMessage); appendRawStream({ ts: Date.now(), diff --git a/src/agents/pi-embedded-subscribe.types.ts b/src/agents/pi-embedded-subscribe.types.ts index 766ff7f18..bcb7bf5bf 100644 --- a/src/agents/pi-embedded-subscribe.types.ts +++ b/src/agents/pi-embedded-subscribe.types.ts @@ -31,6 +31,10 @@ export type SubscribeEmbeddedPiSessionParams = { onAssistantMessageStart?: () => void | Promise; onAgentEvent?: (evt: { stream: string; data: Record }) => void | Promise; enforceFinalTag?: boolean; + /** Provider ID for provider-specific message normalization */ + provider?: string; + /** Model ID for model-specific message normalization */ + modelId?: string; }; export type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";