openclaw/patches/@mariozechner__pi-ai@0.49.3.patch
kiranjd 7b3d5739da 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 <noreply@anthropic.com>
2026-01-27 21:43:41 +05:30

59 lines
3.1 KiB
Diff

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.