fix(openai): avoid invalid reasoning replay

This commit is contained in:
Peter Steinberger
2026-01-10 00:45:10 +00:00
parent 626b085c85
commit cb10682d3e
8 changed files with 495 additions and 6 deletions

View File

@@ -12,6 +12,53 @@ index 0000000..1111111 100644
+ console.log(`[pi-ai] 429 rate limit - failing fast to rotate account`);
+ throw new Error(`Cloud Code Assist API error (${response.status}): ${errorText}`);
+ }
// Check if retryable
if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
// Use server-provided delay or exponential backoff
// Check if retryable
if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
// Use server-provided delay or exponential backoff
diff --git a/dist/providers/openai-responses.js b/dist/providers/openai-responses.js
index 0000000..1111111 100644
--- a/dist/providers/openai-responses.js
+++ b/dist/providers/openai-responses.js
@@ -397,9 +397,17 @@ 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) {
// Do not submit thinking blocks if the completion had an error (i.e. abort)
if (block.type === "thinking" && msg.stopReason !== "error") {
if (block.thinkingSignature) {
+ if (!hasTextBlock)
+ continue;
const reasoningItem = JSON.parse(block.thinkingSignature);
output.push(reasoningItem);
}
}
else if (block.type === "text") {
diff --git a/dist/providers/openai-codex-responses.js b/dist/providers/openai-codex-responses.js
index 0000000..1111111 100644
--- a/dist/providers/openai-codex-responses.js
+++ b/dist/providers/openai-codex-responses.js
@@ -434,9 +434,17 @@ 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);
}
}
else if (block.type === "text") {