From 889c472ddbdb71631a71989b9216575897cf2ac1 Mon Sep 17 00:00:00 2001 From: gnanam1990 Date: Mon, 6 Apr 2026 16:58:16 +0530 Subject: [PATCH] fix: preserve malformed Bash JSON literals --- src/services/api/openaiShim.test.ts | 73 ++++++++++++++++++- src/services/api/toolArgumentNormalization.ts | 2 +- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/services/api/openaiShim.test.ts b/src/services/api/openaiShim.test.ts index a2b927d9..b47d84f6 100644 --- a/src/services/api/openaiShim.test.ts +++ b/src/services/api/openaiShim.test.ts @@ -562,7 +562,7 @@ test('normalizes plain string Bash tool arguments from OpenAI-compatible respons ]) }) -test('normalizes Bash tool arguments that are valid JSON literals', async () => { +test('normalizes Bash tool arguments that are valid JSON strings', async () => { globalThis.fetch = (async (_input, _init) => { return new Response( JSON.stringify({ @@ -578,7 +578,7 @@ test('normalizes Bash tool arguments that are valid JSON literals', async () => type: 'function', function: { name: 'Bash', - arguments: '123', + arguments: '"pwd"', }, }, ], @@ -617,11 +617,78 @@ test('normalizes Bash tool arguments that are valid JSON literals', async () => type: 'tool_use', id: 'function-call-1', name: 'Bash', - input: { command: '123' }, + input: { command: 'pwd' }, }, ]) }) +test.each([ + ['false', false], + ['null', null], + ['[]', []], +])( + 'preserves malformed Bash JSON literals as parsed values in non-streaming responses: %s', + async (argumentsValue, expectedInput) => { + globalThis.fetch = (async (_input, _init) => { + return new Response( + JSON.stringify({ + id: 'chatcmpl-1', + model: 'google/gemini-3.1-pro-preview', + choices: [ + { + message: { + role: 'assistant', + tool_calls: [ + { + id: 'function-call-1', + type: 'function', + function: { + name: 'Bash', + arguments: argumentsValue, + }, + }, + ], + }, + finish_reason: 'tool_calls', + }, + ], + usage: { + prompt_tokens: 12, + completion_tokens: 4, + total_tokens: 16, + }, + }), + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + }) as FetchType + + const client = createOpenAIShimClient({}) as OpenAIShimClient + + const message = await client.beta.messages.create({ + model: 'google/gemini-3.1-pro-preview', + system: 'test system', + messages: [{ role: 'user', content: 'Use Bash' }], + max_tokens: 64, + stream: false, + }) as { + content?: Array> + } + + expect(message.content).toEqual([ + { + type: 'tool_use', + id: 'function-call-1', + name: 'Bash', + input: expectedInput, + }, + ]) + }, +) + test('keeps terminal empty Bash tool arguments invalid in non-streaming responses', async () => { globalThis.fetch = (async (_input, _init) => { return new Response( diff --git a/src/services/api/toolArgumentNormalization.ts b/src/services/api/toolArgumentNormalization.ts index 131a17ec..82106569 100644 --- a/src/services/api/toolArgumentNormalization.ts +++ b/src/services/api/toolArgumentNormalization.ts @@ -45,7 +45,7 @@ export function normalizeToolArguments( } return wrapPlainStringToolArguments(toolName, parsed) ?? parsed } - return wrapPlainStringToolArguments(toolName, rawArguments) ?? rawArguments + return parsed } if (typeof parsed === 'string') { return wrapPlainStringToolArguments(toolName, parsed) ?? parsed