diff --git a/src/server/transformers/openai/responses/conversion.test.ts b/src/server/transformers/openai/responses/conversion.test.ts index 25c93d0..e3206bc 100644 --- a/src/server/transformers/openai/responses/conversion.test.ts +++ b/src/server/transformers/openai/responses/conversion.test.ts @@ -863,6 +863,51 @@ describe('convertOpenAiBodyToResponsesBody', () => { ]); }); + it('drops tool outputs whose prior tool call cannot be converted into an OpenAI-compatible tool call', () => { + const result = convertResponsesBodyToOpenAiBody( + { + model: 'gpt-5', + input: [ + { + type: 'function_call', + call_id: 'call_invalid', + name: ' ', + arguments: '{"plan":true}', + }, + { + type: 'function_call_output', + call_id: 'call_invalid', + output: 'unsupported call', + }, + { + type: 'message', + role: 'assistant', + content: [ + { + type: 'output_text', + text: 'continue normally', + }, + ], + }, + ], + }, + 'gpt-5', + false, + ); + + expect(result.messages).toEqual([ + { + role: 'assistant', + content: [ + { + type: 'text', + text: 'continue normally', + }, + ], + }, + ]); + }); + it('shortens long MCP tool names consistently across tools, tool_choice and assistant tool calls', () => { const sharedSuffix = 'server__execute_super_long_nested_tool_name_that_needs_shortening'; const firstName = `mcp__alpha_workspace__${sharedSuffix}`; diff --git a/src/server/transformers/openai/responses/conversion.ts b/src/server/transformers/openai/responses/conversion.ts index c2507c7..c7ba482 100644 --- a/src/server/transformers/openai/responses/conversion.ts +++ b/src/server/transformers/openai/responses/conversion.ts @@ -844,9 +844,14 @@ export function convertResponsesBodyToOpenAiBody( const input = stripOrphanedResponsesToolOutputs(normalizedBody.input); let functionCallIndex = 0; let pendingToolCalls: OpenAiToolCall[] = []; + const emittedToolCallIds = new Set(); const flushPendingToolCalls = () => { if (pendingToolCalls.length <= 0) return; + for (const toolCall of pendingToolCalls) { + const callId = asTrimmedString(toolCall.id); + if (callId) emittedToolCallIds.add(callId); + } messages.push({ role: 'assistant', content: '', @@ -894,7 +899,9 @@ export function convertResponsesBodyToOpenAiBody( if (itemType === 'function_call_output' || itemType === 'custom_tool_call_output') { flushPendingToolCalls(); - pushToolOutputMessage(item.call_id ?? item.id, item.output ?? item.content); + const toolCallId = asTrimmedString(item.call_id ?? item.id); + if (!toolCallId || !emittedToolCallIds.has(toolCallId)) return; + pushToolOutputMessage(toolCallId, item.output ?? item.content); return; } @@ -924,7 +931,9 @@ export function convertResponsesBodyToOpenAiBody( const content = toOpenAiMessageContent(item.content ?? item.input ?? item); if (normalizedRole === 'tool') { - pushToolOutputMessage(item.tool_call_id ?? item.call_id ?? item.id, item.content); + const toolCallId = asTrimmedString(item.tool_call_id ?? item.call_id ?? item.id); + if (!toolCallId || !emittedToolCallIds.has(toolCallId)) return; + pushToolOutputMessage(toolCallId, item.content); return; }