fix(ai): handle same-provider different-model handoff in OpenAI Responses API

When switching between OpenAI models (e.g., gpt-5-mini to gpt-5.2-codex),
function_call IDs with fc_ prefix trigger pairing validation errors because
OpenAI tracks which fc_xxx IDs were paired with rs_xxx reasoning items.

The fix omits the id field for function_calls from different models, which
avoids the pairing validation while keeping call_id for matching with
function_call_output.

Fixes #886
This commit is contained in:
Mario Zechner 2026-01-22 00:58:49 +01:00
parent de58391085
commit d327b9c768
3 changed files with 279 additions and 142 deletions

View file

@ -488,6 +488,15 @@ function convertMessages(model: Model<"openai-responses">, context: Context): Re
}
} else if (msg.role === "assistant") {
const output: ResponseInput = [];
const assistantMsg = msg as AssistantMessage;
// Check if this message is from a different model (same provider, different model ID).
// For such messages, tool call IDs with fc_ prefix need to be stripped to avoid
// OpenAI's reasoning/function_call pairing validation errors.
const isDifferentModel =
assistantMsg.model !== model.id &&
assistantMsg.provider === model.provider &&
assistantMsg.api === model.api;
for (const block of msg.content) {
if (block.type === "thinking") {
@ -513,10 +522,20 @@ function convertMessages(model: Model<"openai-responses">, context: Context): Re
} satisfies ResponseOutputMessage);
} else if (block.type === "toolCall") {
const toolCall = block as ToolCall;
const callId = toolCall.id.split("|")[0];
let itemId: string | undefined = toolCall.id.split("|")[1];
// For different-model messages, set id to undefined to avoid pairing validation.
// OpenAI tracks which fc_xxx IDs were paired with rs_xxx reasoning items.
// By omitting the id, we avoid triggering that validation (like cross-provider does).
if (isDifferentModel && itemId?.startsWith("fc_")) {
itemId = undefined;
}
output.push({
type: "function_call",
id: toolCall.id.split("|")[1],
call_id: toolCall.id.split("|")[0],
id: itemId,
call_id: callId,
name: toolCall.name,
arguments: JSON.stringify(toolCall.arguments),
});