fix(openai): drop reasoning replay for tool-only turns
This commit is contained in:
@@ -193,11 +193,7 @@
|
|||||||
"@sinclair/typebox": "0.34.47"
|
"@sinclair/typebox": "0.34.47"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
"@mariozechner/pi-ai@0.42.2": "patches/@mariozechner__pi-ai@0.42.2.patch"
|
||||||
"@mariozechner/pi-ai@0.42.2": "patches/@mariozechner__pi-ai@0.42.2.patch",
|
|
||||||
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
|
||||||
"playwright-core@1.57.0": "patches/playwright-core@1.57.0.patch",
|
|
||||||
"qrcode-terminal": "patches/qrcode-terminal.patch"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vitest": {
|
"vitest": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/dist/providers/google-gemini-cli.js b/dist/providers/google-gemini-cli.js
|
diff --git a/dist/providers/google-gemini-cli.js b/dist/providers/google-gemini-cli.js
|
||||||
index 93aa26c..beb585e 100644
|
index 93aa26c395e9bd0df64376408a13d15ee9e7cce7..beb585e2f2c13eec3bca98acade761101e4572ff 100644
|
||||||
--- a/dist/providers/google-gemini-cli.js
|
--- a/dist/providers/google-gemini-cli.js
|
||||||
+++ b/dist/providers/google-gemini-cli.js
|
+++ b/dist/providers/google-gemini-cli.js
|
||||||
@@ -248,6 +248,11 @@ export const streamGoogleGeminiCli = (model, context, options) => {
|
@@ -248,6 +248,11 @@ export const streamGoogleGeminiCli = (model, context, options) => {
|
||||||
@@ -15,10 +15,42 @@ index 93aa26c..beb585e 100644
|
|||||||
if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
|
if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
|
||||||
// Use server-provided delay or exponential backoff
|
// Use server-provided delay or exponential backoff
|
||||||
diff --git a/dist/providers/openai-codex-responses.js b/dist/providers/openai-codex-responses.js
|
diff --git a/dist/providers/openai-codex-responses.js b/dist/providers/openai-codex-responses.js
|
||||||
index 188a829..4555c9f 100644
|
index 188a8294f26fe1bfe3fb298a7f58e4d8eaf2a529..a3aeb6a7ff53bc4f7f44362adb950b2c55455332 100644
|
||||||
--- a/dist/providers/openai-codex-responses.js
|
--- a/dist/providers/openai-codex-responses.js
|
||||||
+++ b/dist/providers/openai-codex-responses.js
|
+++ b/dist/providers/openai-codex-responses.js
|
||||||
@@ -515,7 +521,7 @@ function convertTools(tools) {
|
@@ -433,9 +433,15 @@ function convertMessages(model, context) {
|
||||||
|
}
|
||||||
|
else if (msg.role === "assistant") {
|
||||||
|
const output = [];
|
||||||
|
+ // OpenAI Responses rejects `reasoning` items that are not followed by a `message`.
|
||||||
|
+ // Tool-call-only turns (thinking + function_call) are valid assistant turns, but
|
||||||
|
+ // their stored reasoning items must not be replayed as standalone `reasoning` input.
|
||||||
|
+ const hasTextBlock = msg.content.some((b) => b.type === "text");
|
||||||
|
for (const block of msg.content) {
|
||||||
|
if (block.type === "thinking" && msg.stopReason !== "error") {
|
||||||
|
if (block.thinkingSignature) {
|
||||||
|
+ if (!hasTextBlock)
|
||||||
|
+ continue;
|
||||||
|
const reasoningItem = JSON.parse(block.thinkingSignature);
|
||||||
|
output.push(reasoningItem);
|
||||||
|
}
|
||||||
|
@@ -470,6 +476,15 @@ function convertMessages(model, context) {
|
||||||
|
}
|
||||||
|
if (output.length === 0)
|
||||||
|
continue;
|
||||||
|
+ // OpenAI rejects standalone reasoning items when replaying a tool-only turn.
|
||||||
|
+ // Only submit reasoning items when we also submit an assistant message item.
|
||||||
|
+ const hasMessage = output.some((item) => item?.type === "message");
|
||||||
|
+ if (!hasMessage) {
|
||||||
|
+ for (let i = output.length - 1; i >= 0; i -= 1) {
|
||||||
|
+ if (output[i]?.type === "reasoning")
|
||||||
|
+ output.splice(i, 1);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
messages.push(...output);
|
||||||
|
}
|
||||||
|
else if (msg.role === "toolResult") {
|
||||||
|
@@ -515,7 +530,7 @@ function convertTools(tools) {
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
parameters: tool.parameters,
|
parameters: tool.parameters,
|
||||||
@@ -27,86 +59,23 @@ index 188a829..4555c9f 100644
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
function mapStopReason(status) {
|
function mapStopReason(status) {
|
||||||
diff --git a/dist/providers/openai-completions.js b/dist/providers/openai-completions.js
|
diff --git a/dist/providers/openai-responses.js b/dist/providers/openai-responses.js
|
||||||
index 5d0813a..e0ef676 100644
|
index f07085c64390b211340d6a826b28ea9c2e77302f..523ed38a5a6151d6ff08dd89120315e7aaaf19b6 100644
|
||||||
--- a/dist/providers/openai-completions.js
|
--- a/dist/providers/openai-responses.js
|
||||||
+++ b/dist/providers/openai-completions.js
|
+++ b/dist/providers/openai-responses.js
|
||||||
@@ -71,6 +71,18 @@ export const streamOpenAICompletions = (model, context, options) => {
|
@@ -436,6 +436,15 @@ function convertMessages(model, context) {
|
||||||
stream.push({ type: "start", partial: output });
|
}
|
||||||
let currentBlock = null;
|
if (output.length === 0)
|
||||||
const blocks = output.content;
|
continue;
|
||||||
+ const pendingToolCalls = new Map();
|
+ // OpenAI rejects standalone reasoning items when replaying a tool-only turn.
|
||||||
+ const isCompleteJsonObject = (text) => {
|
+ // Only submit reasoning items when we also submit an assistant message item.
|
||||||
+ if (!text || text.trim() === "")
|
+ const hasMessage = output.some((item) => item?.type === "message");
|
||||||
+ return false;
|
+ if (!hasMessage) {
|
||||||
+ try {
|
+ for (let i = output.length - 1; i >= 0; i -= 1) {
|
||||||
+ JSON.parse(text);
|
+ if (output[i]?.type === "reasoning")
|
||||||
+ return true;
|
+ output.splice(i, 1);
|
||||||
+ }
|
+ }
|
||||||
+ catch {
|
+ }
|
||||||
+ return false;
|
messages.push(...output);
|
||||||
+ }
|
}
|
||||||
+ };
|
else if (msg.role === "toolResult") {
|
||||||
const blockIndex = () => blocks.length - 1;
|
|
||||||
const finishCurrentBlock = (block) => {
|
|
||||||
if (block) {
|
|
||||||
@@ -193,31 +205,41 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
||||||
}
|
|
||||||
if (choice?.delta?.tool_calls) {
|
|
||||||
for (const toolCall of choice.delta.tool_calls) {
|
|
||||||
+ const index = typeof toolCall.index === "number" ? toolCall.index : 0;
|
|
||||||
+ const pending = pendingToolCalls.get(index) || {
|
|
||||||
+ type: "toolCall",
|
|
||||||
+ id: "",
|
|
||||||
+ name: "",
|
|
||||||
+ arguments: {},
|
|
||||||
+ partialArgs: "",
|
|
||||||
+ };
|
|
||||||
+ if (toolCall.id)
|
|
||||||
+ pending.id = toolCall.id;
|
|
||||||
+ if (toolCall.function?.name)
|
|
||||||
+ pending.name = toolCall.function.name;
|
|
||||||
+ let delta = "";
|
|
||||||
+ if (toolCall.function && "arguments" in toolCall.function) {
|
|
||||||
+ delta = toolCall.function.arguments || "";
|
|
||||||
+ pending.partialArgs += delta;
|
|
||||||
+ pending.arguments = parseStreamingJson(pending.partialArgs);
|
|
||||||
+ }
|
|
||||||
+ pendingToolCalls.set(index, pending);
|
|
||||||
+ // Delay emitting tool calls until the arguments JSON is complete.
|
|
||||||
+ // Some providers (e.g. LM Studio) stream an initial empty chunk.
|
|
||||||
+ if (!isCompleteJsonObject(pending.partialArgs)) {
|
|
||||||
+ continue;
|
|
||||||
+ }
|
|
||||||
if (!currentBlock ||
|
|
||||||
currentBlock.type !== "toolCall" ||
|
|
||||||
- (toolCall.id && currentBlock.id !== toolCall.id)) {
|
|
||||||
+ (pending.id && currentBlock.id !== pending.id)) {
|
|
||||||
finishCurrentBlock(currentBlock);
|
|
||||||
- currentBlock = {
|
|
||||||
- type: "toolCall",
|
|
||||||
- id: toolCall.id || "",
|
|
||||||
- name: toolCall.function?.name || "",
|
|
||||||
- arguments: {},
|
|
||||||
- partialArgs: "",
|
|
||||||
- };
|
|
||||||
+ currentBlock = pending;
|
|
||||||
output.content.push(currentBlock);
|
|
||||||
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
|
|
||||||
}
|
|
||||||
if (currentBlock.type === "toolCall") {
|
|
||||||
- if (toolCall.id)
|
|
||||||
- currentBlock.id = toolCall.id;
|
|
||||||
- if (toolCall.function?.name)
|
|
||||||
- currentBlock.name = toolCall.function.name;
|
|
||||||
- let delta = "";
|
|
||||||
- if (toolCall.function?.arguments) {
|
|
||||||
- delta = toolCall.function.arguments;
|
|
||||||
- currentBlock.partialArgs += toolCall.function.arguments;
|
|
||||||
- currentBlock.arguments = parseStreamingJson(currentBlock.partialArgs);
|
|
||||||
- }
|
|
||||||
+ currentBlock.partialArgs = pending.partialArgs;
|
|
||||||
+ currentBlock.arguments = pending.arguments;
|
|
||||||
stream.push({
|
|
||||||
type: "toolcall_delta",
|
|
||||||
contentIndex: blockIndex(),
|
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -12,7 +12,7 @@ patchedDependencies:
|
|||||||
hash: 01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4
|
hash: 01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4
|
||||||
path: patches/@mariozechner__pi-agent-core.patch
|
path: patches/@mariozechner__pi-agent-core.patch
|
||||||
'@mariozechner/pi-ai@0.42.2':
|
'@mariozechner/pi-ai@0.42.2':
|
||||||
hash: d96b73dca0a7eac3a298a277864b5c8555ec7a5376901159b9629316430e4bd8
|
hash: 53800c5f6cd7b43591675f7d836e599255e4a39cdd38d166b8e35947c835db93
|
||||||
path: patches/@mariozechner__pi-ai@0.42.2.patch
|
path: patches/@mariozechner__pi-ai@0.42.2.patch
|
||||||
'@mariozechner/pi-coding-agent':
|
'@mariozechner/pi-coding-agent':
|
||||||
hash: 58af7c712ebe270527c2ad9d3351fac39d6cd4b81cc475a258d87840b446b90e
|
hash: 58af7c712ebe270527c2ad9d3351fac39d6cd4b81cc475a258d87840b446b90e
|
||||||
@@ -48,7 +48,7 @@ importers:
|
|||||||
version: 0.42.2(patch_hash=01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4)(ws@8.19.0)(zod@4.3.5)
|
version: 0.42.2(patch_hash=01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4)(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-ai':
|
'@mariozechner/pi-ai':
|
||||||
specifier: ^0.42.2
|
specifier: ^0.42.2
|
||||||
version: 0.42.2(patch_hash=d96b73dca0a7eac3a298a277864b5c8555ec7a5376901159b9629316430e4bd8)(ws@8.19.0)(zod@4.3.5)
|
version: 0.42.2(patch_hash=53800c5f6cd7b43591675f7d836e599255e4a39cdd38d166b8e35947c835db93)(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-coding-agent':
|
'@mariozechner/pi-coding-agent':
|
||||||
specifier: ^0.42.2
|
specifier: ^0.42.2
|
||||||
version: 0.42.2(patch_hash=58af7c712ebe270527c2ad9d3351fac39d6cd4b81cc475a258d87840b446b90e)(ws@8.19.0)(zod@4.3.5)
|
version: 0.42.2(patch_hash=58af7c712ebe270527c2ad9d3351fac39d6cd4b81cc475a258d87840b446b90e)(ws@8.19.0)(zod@4.3.5)
|
||||||
@@ -3789,7 +3789,7 @@ snapshots:
|
|||||||
|
|
||||||
'@mariozechner/pi-agent-core@0.42.2(patch_hash=01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4)(ws@8.19.0)(zod@4.3.5)':
|
'@mariozechner/pi-agent-core@0.42.2(patch_hash=01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4)(ws@8.19.0)(zod@4.3.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mariozechner/pi-ai': 0.42.2(patch_hash=d96b73dca0a7eac3a298a277864b5c8555ec7a5376901159b9629316430e4bd8)(ws@8.19.0)(zod@4.3.5)
|
'@mariozechner/pi-ai': 0.42.2(patch_hash=53800c5f6cd7b43591675f7d836e599255e4a39cdd38d166b8e35947c835db93)(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-tui': 0.42.2
|
'@mariozechner/pi-tui': 0.42.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@modelcontextprotocol/sdk'
|
- '@modelcontextprotocol/sdk'
|
||||||
@@ -3799,7 +3799,7 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@mariozechner/pi-ai@0.42.2(patch_hash=d96b73dca0a7eac3a298a277864b5c8555ec7a5376901159b9629316430e4bd8)(ws@8.19.0)(zod@4.3.5)':
|
'@mariozechner/pi-ai@0.42.2(patch_hash=53800c5f6cd7b43591675f7d836e599255e4a39cdd38d166b8e35947c835db93)(ws@8.19.0)(zod@4.3.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@anthropic-ai/sdk': 0.71.2(zod@4.3.5)
|
'@anthropic-ai/sdk': 0.71.2(zod@4.3.5)
|
||||||
'@google/genai': 1.34.0
|
'@google/genai': 1.34.0
|
||||||
@@ -3823,7 +3823,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@mariozechner/clipboard': 0.3.0
|
'@mariozechner/clipboard': 0.3.0
|
||||||
'@mariozechner/pi-agent-core': 0.42.2(patch_hash=01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4)(ws@8.19.0)(zod@4.3.5)
|
'@mariozechner/pi-agent-core': 0.42.2(patch_hash=01312ceb1f6be7e42822c24c9a7a4f7db56b24ae114a364855bd3819779d1cf4)(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-ai': 0.42.2(patch_hash=d96b73dca0a7eac3a298a277864b5c8555ec7a5376901159b9629316430e4bd8)(ws@8.19.0)(zod@4.3.5)
|
'@mariozechner/pi-ai': 0.42.2(patch_hash=53800c5f6cd7b43591675f7d836e599255e4a39cdd38d166b8e35947c835db93)(ws@8.19.0)(zod@4.3.5)
|
||||||
'@mariozechner/pi-tui': 0.42.2
|
'@mariozechner/pi-tui': 0.42.2
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
|
|||||||
Reference in New Issue
Block a user