From 7b3d5739da4bd4266fc6d1d48926f0bd4c898f81 Mon Sep 17 00:00:00 2001 From: kiranjd Date: Tue, 27 Jan 2026 21:43:41 +0530 Subject: [PATCH] fix(providers): preserve all reasoning_details types for OpenRouter models OpenRouter models like Kimi K2.5 return reasoning_details with type reasoning.text at message level. The pi-ai library only handled reasoning.encrypted type. This patch stores all reasoning_details and passes them back in subsequent requests. Fixes multi-turn tool calls with Kimi K2.5 and similar models. Co-Authored-By: Claude Opus 4.5 --- package.json | 3 ++ patches/@mariozechner__pi-ai@0.49.3.patch | 58 +++++++++++++++++++++++ pnpm-lock.yaml | 15 ++++-- 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 patches/@mariozechner__pi-ai@0.49.3.patch diff --git a/package.json b/package.json index 1a6d65178..ea32edb9a 100644 --- a/package.json +++ b/package.json @@ -246,6 +246,9 @@ "@sinclair/typebox": "0.34.47", "hono": "4.11.4", "tar": "7.5.4" + }, + "patchedDependencies": { + "@mariozechner/pi-ai@0.49.3": "patches/@mariozechner__pi-ai@0.49.3.patch" } }, "vitest": { diff --git a/patches/@mariozechner__pi-ai@0.49.3.patch b/patches/@mariozechner__pi-ai@0.49.3.patch new file mode 100644 index 000000000..523359b76 --- /dev/null +++ b/patches/@mariozechner__pi-ai@0.49.3.patch @@ -0,0 +1,58 @@ +diff --git a/dist/providers/openai-completions.js b/dist/providers/openai-completions.js +index c4b291d86ce40b2df319c439263d16d67592d22e..449da02d0d2c85309979e392909df5d3fb2ad083 100644 +--- a/dist/providers/openai-completions.js ++++ b/dist/providers/openai-completions.js +@@ -228,7 +228,13 @@ export const streamOpenAICompletions = (model, context, options) => { + } + const reasoningDetails = choice.delta.reasoning_details; + if (reasoningDetails && Array.isArray(reasoningDetails)) { ++ // Store ALL reasoning_details for OpenRouter compatibility (not just encrypted) ++ if (!output._rawReasoningDetails) { ++ output._rawReasoningDetails = []; ++ } + for (const detail of reasoningDetails) { ++ output._rawReasoningDetails.push(detail); ++ // Also handle encrypted type tied to tool calls (existing behavior) + if (detail.type === "reasoning.encrypted" && detail.id && detail.data) { + const matchingToolCall = output.content.find((b) => b.type === "toolCall" && b.id === detail.id); + if (matchingToolCall) { +@@ -500,19 +506,27 @@ function convertMessages(model, context, compat) { + arguments: JSON.stringify(tc.arguments), + }, + })); +- const reasoningDetails = toolCalls +- .filter((tc) => tc.thoughtSignature) +- .map((tc) => { +- try { +- return JSON.parse(tc.thoughtSignature); +- } +- catch { +- return null; ++ // First check for raw reasoning_details stored on the message (OpenRouter models like Kimi) ++ const rawDetails = msg._rawReasoningDetails; ++ if (rawDetails && Array.isArray(rawDetails) && rawDetails.length > 0) { ++ assistantMsg.reasoning_details = rawDetails; ++ } ++ else { ++ // Fall back to extracting from tool call thoughtSignatures (encrypted type) ++ const reasoningDetails = toolCalls ++ .filter((tc) => tc.thoughtSignature) ++ .map((tc) => { ++ try { ++ return JSON.parse(tc.thoughtSignature); ++ } ++ catch { ++ return null; ++ } ++ }) ++ .filter(Boolean); ++ if (reasoningDetails.length > 0) { ++ assistantMsg.reasoning_details = reasoningDetails; + } +- }) +- .filter(Boolean); +- if (reasoningDetails.length > 0) { +- assistantMsg.reasoning_details = reasoningDetails; + } + } + // Skip assistant messages that have no content and no tool calls. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1c55dd8d..d5ee1d150 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,11 @@ overrides: hono: 4.11.4 tar: 7.5.4 +patchedDependencies: + '@mariozechner/pi-ai@0.49.3': + hash: 8fada8af40beb6c2780b3adec72505288272a5df0ec6227b710caef6afb1dc32 + path: patches/@mariozechner__pi-ai@0.49.3.patch + importers: .: @@ -45,7 +50,7 @@ importers: version: 0.49.3(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-ai': specifier: 0.49.3 - version: 0.49.3(ws@8.19.0)(zod@4.3.6) + version: 0.49.3(patch_hash=8fada8af40beb6c2780b3adec72505288272a5df0ec6227b710caef6afb1dc32)(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-coding-agent': specifier: 0.49.3 version: 0.49.3(ws@8.19.0)(zod@4.3.6) @@ -6989,7 +6994,7 @@ snapshots: '@mariozechner/pi-agent-core@0.49.3(ws@8.19.0)(zod@4.3.6)': dependencies: - '@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.49.3(patch_hash=8fada8af40beb6c2780b3adec72505288272a5df0ec6227b710caef6afb1dc32)(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-tui': 0.49.3 transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -7000,7 +7005,7 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.49.3(ws@8.19.0)(zod@4.3.6)': + '@mariozechner/pi-ai@0.49.3(patch_hash=8fada8af40beb6c2780b3adec72505288272a5df0ec6227b710caef6afb1dc32)(ws@8.19.0)(zod@4.3.6)': dependencies: '@anthropic-ai/sdk': 0.71.2(zod@4.3.6) '@aws-sdk/client-bedrock-runtime': 3.972.0 @@ -7027,7 +7032,7 @@ snapshots: '@mariozechner/clipboard': 0.3.0 '@mariozechner/jiti': 2.6.5 '@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.49.3(patch_hash=8fada8af40beb6c2780b3adec72505288272a5df0ec6227b710caef6afb1dc32)(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-tui': 0.49.3 '@silvia-odwyer/photon-node': 0.3.4 chalk: 5.6.2 @@ -9104,7 +9109,7 @@ snapshots: '@line/bot-sdk': 10.6.0 '@lydell/node-pty': 1.2.0-beta.3 '@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.49.3(patch_hash=8fada8af40beb6c2780b3adec72505288272a5df0ec6227b710caef6afb1dc32)(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-coding-agent': 0.49.3(ws@8.19.0)(zod@4.3.6) '@mariozechner/pi-tui': 0.49.3 '@mozilla/readability': 0.6.0