From 1f464dc03d68a7612b724ec9c9d190a368849740 Mon Sep 17 00:00:00 2001 From: cita-777 Date: Sat, 18 Apr 2026 13:46:57 +0800 Subject: [PATCH] Close out the last review edge-cases before final merge The remaining review threads were down to route alias hygiene, proxy-safe header ordering, and a Gemini canonical tool-result mismatch. This commit makes the alias/websocket behavior explicit, preserves contributor helper safety, and teaches the canonical Gemini path to recover tool names and wrap function results the same way the OpenAI-shaped path already does. Constraint: Keep the last review fixes limited to the current branch so the release PR stays easy to reason about Rejected: Defer the Gemini canonical fix to a later refactor | the review found a real functional mismatch and the fix stayed localized to the request bridge Confidence: high Scope-risk: narrow Directive: Keep canonical-to-provider bridges internally consistent; if one path resolves tool names or wraps results, the sibling path should not diverge silently Tested: Focused vitest for Gemini request bridge, route aliases, upstream request builder, responses response bridge, contributor updater; typecheck; git diff --check Not-tested: Full npm test; any broader end-to-end Gemini live-provider behavior outside the covered bridge tests --- src/server/routes/proxy/responsesWebsocket.ts | 4 +- .../generate-content/requestBridge.test.ts | 53 +++++++++++++++++++ .../gemini/generate-content/requestBridge.ts | 23 ++++++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/server/routes/proxy/responsesWebsocket.ts b/src/server/routes/proxy/responsesWebsocket.ts index 74ec6c7..ab8ed84 100644 --- a/src/server/routes/proxy/responsesWebsocket.ts +++ b/src/server/routes/proxy/responsesWebsocket.ts @@ -552,8 +552,8 @@ async function handleResponsesWebsocketConnection( request: IncomingMessage, authContext: ResponsesWebsocketAuthContext, ) { - const websocketSessionId = headerValueToTrimmedString(request.headers['session_id']) - || headerValueToTrimmedString(request.headers['session-id']) + const websocketSessionId = headerValueToTrimmedString(request.headers['session-id']) + || headerValueToTrimmedString(request.headers['session_id']) || headerValueToTrimmedString(request.headers['conversation-id']) || headerValueToTrimmedString(request.headers['conversation_id']) || randomUUID(); diff --git a/src/server/transformers/gemini/generate-content/requestBridge.test.ts b/src/server/transformers/gemini/generate-content/requestBridge.test.ts index c2ef3f7..de3ac8a 100644 --- a/src/server/transformers/gemini/generate-content/requestBridge.test.ts +++ b/src/server/transformers/gemini/generate-content/requestBridge.test.ts @@ -262,4 +262,57 @@ describe('gemini generate-content request bridge', () => { expect(functionCallCount).toBe(2); expect(functionResponseCount).toBe(2); }); + + it('uses canonical tool-call names for Gemini functionResponse parts', () => { + const body = buildCanonicalRequestToGeminiGenerateContentBody({ + operation: 'generate', + surface: 'gemini-generate-content', + cliProfile: 'generic', + requestedModel: 'gemini-2.5-pro', + stream: false, + messages: [ + { + role: 'assistant', + parts: [{ + type: 'tool_call', + id: 'call_weather', + name: 'lookup_weather', + argumentsJson: '{"city":"Paris"}', + }], + }, + { + role: 'tool', + parts: [{ + type: 'tool_result', + toolCallId: 'call_weather', + resultJson: { temperature: '22C' }, + }], + }, + ], + }); + + expect(body.contents).toEqual([ + { + role: 'model', + parts: [{ + functionCall: { + id: 'call_weather', + name: 'lookup_weather', + args: { city: 'Paris' }, + }, + }], + }, + { + role: 'user', + parts: [{ + functionResponse: { + name: 'lookup_weather', + response: { + result: { temperature: '22C' }, + }, + }, + }], + }, + ]); + }); }); diff --git a/src/server/transformers/gemini/generate-content/requestBridge.ts b/src/server/transformers/gemini/generate-content/requestBridge.ts index 4839096..d88dcd7 100644 --- a/src/server/transformers/gemini/generate-content/requestBridge.ts +++ b/src/server/transformers/gemini/generate-content/requestBridge.ts @@ -328,7 +328,10 @@ export function buildGeminiGenerateContentRequestFromOpenAi(input: { return request; } -function canonicalPartToGeminiPart(part: CanonicalContentPart): Record | null { +function canonicalPartToGeminiPart( + part: CanonicalContentPart, + toolNameById?: ReadonlyMap, +): Record | null { if (part.type === 'text') { return { text: part.text, @@ -391,10 +394,13 @@ function canonicalPartToGeminiPart(part: CanonicalContentPart): Record { const contents: Array> = []; const systemParts: Array> = []; + const toolNameById = new Map(); for (const message of request.messages) { if (message.role === 'system' || message.role === 'developer') { systemParts.push( ...message.parts - .map((part) => canonicalPartToGeminiPart(part)) + .map((part) => canonicalPartToGeminiPart(part, toolNameById)) .filter((part): part is Record => !!part), ); continue; } + for (const part of message.parts) { + if (part.type === 'tool_call') { + toolNameById.set(part.id, part.name); + } + } + const parts = message.parts - .map((part) => canonicalPartToGeminiPart(part)) + .map((part) => canonicalPartToGeminiPart(part, toolNameById)) .filter((part): part is Record => !!part); if (parts.length <= 0) continue;