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;