From e7b9eebdc55f3c28b3c5f4d671bf7e0459daa4bb Mon Sep 17 00:00:00 2001 From: An Ton Date: Mon, 1 Dec 2025 21:25:50 +0700 Subject: [PATCH 1/3] fix: strip leading {} from streaming tool call arguments When streaming, some models return an initial empty `{}` followed by the actual arguments, resulting in `{}{...}`. This fix detects and strips the leading `{}` prefix while preserving legitimate empty arguments. --- .../src/openaiChatCompletionsStreaming.ts | 3 + .../openaiChatCompletionsStreaming.test.ts | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/agents-openai/src/openaiChatCompletionsStreaming.ts b/packages/agents-openai/src/openaiChatCompletionsStreaming.ts index e9d16a62..41c8d5d8 100644 --- a/packages/agents-openai/src/openaiChatCompletionsStreaming.ts +++ b/packages/agents-openai/src/openaiChatCompletionsStreaming.ts @@ -138,6 +138,9 @@ export async function* convertChatCompletionsStreamToResponses( } for (const function_call of Object.values(state.function_calls)) { + if (function_call.arguments.startsWith('{}{')) { + function_call.arguments = function_call.arguments.slice(2); + } outputs.push(function_call); } diff --git a/packages/agents-openai/test/openaiChatCompletionsStreaming.test.ts b/packages/agents-openai/test/openaiChatCompletionsStreaming.test.ts index 8ee957b1..d4084ed2 100644 --- a/packages/agents-openai/test/openaiChatCompletionsStreaming.test.ts +++ b/packages/agents-openai/test/openaiChatCompletionsStreaming.test.ts @@ -265,4 +265,59 @@ describe('convertChatCompletionsStreamToResponses', () => { rawContent: [{ type: 'reasoning_text', text: 'foobar' }], }); }); + + it('strips leading {} from tool call arguments when followed by real args', async () => { + const resp = { id: 'r' } as any; + + async function* stream() { + yield makeChunk({ + tool_calls: [ + { index: 0, id: 'call1', function: { name: 'fn', arguments: '{}' } }, + ], + }); + yield makeChunk({ + tool_calls: [{ index: 0, function: { arguments: '{"key":"value"}' } }], + }); + } + + const events: any[] = []; + for await (const e of convertChatCompletionsStreamToResponses( + resp, + stream() as any, + )) { + events.push(e); + } + + const final = events[events.length - 1]; + const functionCall = final.response.output.find( + (o: any) => o.type === 'function_call', + ); + expect(functionCall.arguments).toBe('{"key":"value"}'); + }); + + it('preserves {} for legitimate empty tool call arguments', async () => { + const resp = { id: 'r' } as any; + + async function* stream() { + yield makeChunk({ + tool_calls: [ + { index: 0, id: 'call1', function: { name: 'fn', arguments: '{}' } }, + ], + }); + } + + const events: any[] = []; + for await (const e of convertChatCompletionsStreamToResponses( + resp, + stream() as any, + )) { + events.push(e); + } + + const final = events[events.length - 1]; + const functionCall = final.response.output.find( + (o: any) => o.type === 'function_call', + ); + expect(functionCall.arguments).toBe('{}'); + }); }); From 42f81c2236f4417f872fa9bc78cc50abbb5ebab6 Mon Sep 17 00:00:00 2001 From: An Ton Date: Mon, 1 Dec 2025 21:30:35 +0700 Subject: [PATCH 2/3] chore: add changeset --- .changeset/fix-empty-streaming-tool-args.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-empty-streaming-tool-args.md diff --git a/.changeset/fix-empty-streaming-tool-args.md b/.changeset/fix-empty-streaming-tool-args.md new file mode 100644 index 00000000..7e431c88 --- /dev/null +++ b/.changeset/fix-empty-streaming-tool-args.md @@ -0,0 +1,5 @@ +--- +"@openai/agents-openai": patch +--- + +Fix streaming tool call arguments when providers like Bedrock return an initial empty `{}` followed by actual arguments, resulting in malformed `{}{...}` JSON. From 36b4f8a696b281f9846f87e8c0c65fc8433035c3 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 2 Dec 2025 15:31:10 +0900 Subject: [PATCH 3/3] Update packages/agents-openai/src/openaiChatCompletionsStreaming.ts --- packages/agents-openai/src/openaiChatCompletionsStreaming.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/agents-openai/src/openaiChatCompletionsStreaming.ts b/packages/agents-openai/src/openaiChatCompletionsStreaming.ts index 41c8d5d8..c35dcb29 100644 --- a/packages/agents-openai/src/openaiChatCompletionsStreaming.ts +++ b/packages/agents-openai/src/openaiChatCompletionsStreaming.ts @@ -138,6 +138,9 @@ export async function* convertChatCompletionsStreamToResponses( } for (const function_call of Object.values(state.function_calls)) { + // Some providers, such as Bedrock, may send two items: + // 1) an empty argument, and 2) the actual argument data. + // This is a workaround for that specific behavior. if (function_call.arguments.startsWith('{}{')) { function_call.arguments = function_call.arguments.slice(2); }