From 4c3d98d5eb24c9d981d93fdc03fc0a2fb83d493a Mon Sep 17 00:00:00 2001 From: codex Date: Thu, 3 Sep 2026 23:53:21 +0800 Subject: [PATCH 1/2] fix: forward responses additional tools --- AGENTS.md | 1 - lib/server/proxy/responses.ts | 13 +++++++- tests/server/units.test.ts | 58 +++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 204cdc1..6c70bc3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,6 @@ ## Commit Rules - Use English conventional commits, for example `feat: reorganize source layout`. -- Use the Codex identity for Codex-authored commits: `codex `. - Do not commit until `bun run lint`, `bun run format:check`, `bun run typecheck`, `bun run test:coverage`, and `bun run build` all pass. - Unit test coverage must stay at or above 90%; do not commit code below the enforced coverage threshold. diff --git a/lib/server/proxy/responses.ts b/lib/server/proxy/responses.ts index e4ed402..07b5292 100644 --- a/lib/server/proxy/responses.ts +++ b/lib/server/proxy/responses.ts @@ -29,6 +29,7 @@ interface ResponsesInputItem { output?: unknown; name?: string; call_id?: string; + tools?: Array<{ type?: string; name?: string } & Record>; } interface SupportedChatTool { @@ -969,6 +970,15 @@ const prepareTranscript = async ( typeof body.model === 'string' && body.model.trim() ? body.model : (resolvedPreviousSession?.model ?? (await getDefaultModel())); + const additionalTools = Array.isArray(body.input) + ? body.input.flatMap((item) => + item?.type === 'additional_tools' && Array.isArray(item.tools) + ? item.tools + : [], + ) + : []; + const baseTools = body.tools ?? resolvedPreviousSession?.defaults.tools; + const requestTools = [...(baseTools ?? []), ...additionalTools]; const defaults = { instructions: body.instructions ?? @@ -976,7 +986,7 @@ const prepareTranscript = async ( undefined, metadata: body.metadata ?? resolvedPreviousSession?.defaults.metadata ?? undefined, - tools: body.tools ?? resolvedPreviousSession?.defaults.tools ?? undefined, + tools: requestTools.length > 0 ? requestTools : baseTools, tool_choice: body.tool_choice ?? resolvedPreviousSession?.defaults.tool_choice ?? @@ -994,6 +1004,7 @@ const prepareTranscript = async ( transcript.push({ role: 'user', content: body.input }); } else if (Array.isArray(body.input)) { body.input.forEach((item) => { + if (item.type === 'additional_tools') return; transcript.push(mapInputItemToMessage(item)); }); } diff --git a/tests/server/units.test.ts b/tests/server/units.test.ts index 216003e..13ab852 100644 --- a/tests/server/units.test.ts +++ b/tests/server/units.test.ts @@ -4717,6 +4717,64 @@ describe('server units', () => { }); }); + it('extracts additional_tools input items before proxying responses', async () => { + process.env.CODEBUDDY_AUTH_MODE = 'api_key'; + process.env.CODEBUDDY_API_KEY = 'cb-key'; + + const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( + makeJsonResponse({ + choices: [{ message: { content: 'done' } }], + }), + ); + + await handleResponsesRequest( + makeNextRequest('http://localhost/v1/responses', { method: 'POST' }), + { + input: [ + { + role: 'developer', + type: 'additional_tools', + tools: [ + { + name: 'workspace', + tools: [ + { + name: 'read_file', + parameters: { type: 'object', properties: {} }, + type: 'function', + }, + ], + type: 'namespace', + }, + ], + }, + { role: 'user', content: 'read the file' }, + ], + model: 'gpt-5.5', + }, + ); + + const upstreamBody = JSON.parse( + String((fetchMock.mock.calls[0]?.[1] as RequestInit).body), + ) as { + messages: Array<{ content: string; role: string }>; + tools: Array<{ function: { name: string } }>; + }; + + expect(upstreamBody.tools).toEqual([ + { + type: 'function', + function: { + name: 'workspace__read_file', + parameters: { type: 'object', properties: {} }, + }, + }, + ]); + expect(upstreamBody.messages).toEqual([ + { role: 'user', content: 'read the file' }, + ]); + }); + it('flattens tools with function semantics into chat function tools', () => { const result = translateResponsesToolsToChat([ { type: 'file_search' }, From 972aaa00b7ebb1ae4d7ac5cc4d9202eb2600e146 Mon Sep 17 00:00:00 2001 From: orangeboyChen Date: Fri, 4 Sep 2026 00:13:05 +0800 Subject: [PATCH 2/2] test: cover inherited responses tools --- tests/server/units.test.ts | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/server/units.test.ts b/tests/server/units.test.ts index 13ab852..f519229 100644 --- a/tests/server/units.test.ts +++ b/tests/server/units.test.ts @@ -4775,6 +4775,68 @@ describe('server units', () => { ]); }); + it('appends additional tools to tools inherited from a response session', async () => { + process.env.CODEBUDDY_AUTH_MODE = 'api_key'; + process.env.CODEBUDDY_API_KEY = 'cb-key'; + + const fetchMock = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValueOnce( + makeJsonResponse({ choices: [{ message: { content: 'first' } }] }), + ) + .mockResolvedValueOnce( + makeJsonResponse({ choices: [{ message: { content: 'second' } }] }), + ); + const request = makeNextRequest('http://localhost/v1/responses', { + method: 'POST', + }); + + const firstResponse = await handleResponsesRequest(request, { + input: 'start', + model: 'gpt-5.5', + tools: [ + { + name: 'tool_search', + type: 'tool_search', + }, + ], + }); + const firstPayload = (await firstResponse.json()) as { id: string }; + + await handleResponsesRequest(request, { + input: [ + { + role: 'developer', + type: 'additional_tools', + tools: [ + { + name: 'workspace', + tools: [ + { + name: 'read_file', + parameters: { type: 'object', properties: {} }, + type: 'function', + }, + ], + type: 'namespace', + }, + ], + }, + { role: 'user', content: 'continue' }, + ], + previous_response_id: firstPayload.id, + }); + + const upstreamBody = JSON.parse( + String((fetchMock.mock.calls[1]?.[1] as RequestInit).body), + ) as { tools: Array<{ function: { name: string } }> }; + + expect(upstreamBody.tools.map((tool) => tool.function.name)).toEqual([ + 'tool_search', + 'workspace__read_file', + ]); + }); + it('flattens tools with function semantics into chat function tools', () => { const result = translateResponsesToolsToChat([ { type: 'file_search' },