diff --git a/src/server/routes/proxy/upstreamEndpoint.test.ts b/src/server/routes/proxy/upstreamEndpoint.test.ts index 01eb370..60cffc6 100644 --- a/src/server/routes/proxy/upstreamEndpoint.test.ts +++ b/src/server/routes/proxy/upstreamEndpoint.test.ts @@ -614,6 +614,71 @@ describe('buildUpstreamEndpointRequest', () => { expect(request.headers['Content-Type']).toBe('application/json'); }); + it('strips unsupported openai parameters like frequency_penalty for Gemini models on chat endpoints', () => { + const request = buildUpstreamEndpointRequest({ + endpoint: 'chat', + modelName: 'gemini-1.5-pro', + stream: false, + tokenValue: 'sk-test', + sitePlatform: 'gemini', + openaiBody: { + model: 'gemini-1.5-pro', + messages: [{ role: 'user', content: 'hello' }], + frequency_penalty: 0.5, + presence_penalty: 0.2, + logit_bias: { '100': 1 }, + logprobs: true, + top_logprobs: 2, + store: true, + temperature: 0.8, + top_p: 1.0, + }, + downstreamFormat: 'openai', + }); + + expect(request.path).toBe('/v1beta/openai/chat/completions'); + expect(request.body.frequency_penalty).toBeUndefined(); + expect(request.body.presence_penalty).toBeUndefined(); + expect(request.body.logit_bias).toBeUndefined(); + expect(request.body.logprobs).toBeUndefined(); + expect(request.body.top_logprobs).toBeUndefined(); + expect(request.body.store).toBeUndefined(); + expect(request.body.temperature).toBe(0.8); + expect(request.body.top_p).toBe(1.0); + }); + + it('strips unsupported openai parameters like frequency_penalty for Gemini models on responses endpoints', () => { + const request = buildUpstreamEndpointRequest({ + endpoint: 'responses', + modelName: 'gemini-1.5-pro', + stream: false, + tokenValue: 'sk-test', + sitePlatform: 'gemini', + openaiBody: { + model: 'gemini-1.5-pro', + messages: [{ role: 'user', content: 'hello' }], + frequency_penalty: 0.5, + presence_penalty: 0.2, + logit_bias: { '100': 1 }, + logprobs: true, + top_logprobs: 2, + store: true, + temperature: 0.8, + top_p: 1.0, + }, + downstreamFormat: 'openai', + }); + + expect(request.path).toBe('/v1beta/openai/responses'); + expect(request.body.frequency_penalty).toBeUndefined(); + expect(request.body.presence_penalty).toBeUndefined(); + expect(request.body.logit_bias).toBeUndefined(); + expect(request.body.logprobs).toBeUndefined(); + expect(request.body.top_logprobs).toBeUndefined(); + expect(request.body.store).toBeUndefined(); + expect(request.body.temperature).toBe(0.8); + }); + it('preserves structured responses content blocks instead of flattening them', () => { const request = buildUpstreamEndpointRequest({ endpoint: 'responses', diff --git a/src/server/routes/proxy/upstreamEndpoint.ts b/src/server/routes/proxy/upstreamEndpoint.ts index 5698bd9..29f0298 100644 --- a/src/server/routes/proxy/upstreamEndpoint.ts +++ b/src/server/routes/proxy/upstreamEndpoint.ts @@ -437,6 +437,7 @@ export function buildUpstreamEndpointRequest(input: { } { const sitePlatform = normalizePlatformName(input.sitePlatform); const isClaudeUpstream = sitePlatform === 'claude'; + const isGeminiUpstream = sitePlatform === 'gemini'; const resolveGeminiEndpointPath = (endpoint: UpstreamEndpoint): string => { const normalizedSiteUrl = asTrimmedString(input.siteUrl).toLowerCase(); @@ -452,7 +453,7 @@ export function buildUpstreamEndpointRequest(input: { }; const resolveEndpointPath = (endpoint: UpstreamEndpoint): string => { - if (sitePlatform === 'gemini') { + if (isGeminiUpstream) { return resolveGeminiEndpointPath(endpoint); } @@ -480,6 +481,21 @@ export function buildUpstreamEndpointRequest(input: { commonHeaders.Authorization = `Bearer ${input.tokenValue}`; } + const openaiBody = { ...input.openaiBody }; + if (isGeminiUpstream) { + const blacklist = [ + 'frequency_penalty', + 'presence_penalty', + 'logit_bias', + 'logprobs', + 'top_logprobs', + 'store', + ]; + for (const key of blacklist) { + delete openaiBody[key]; + } + } + if (input.endpoint === 'messages') { const claudeHeaders = input.downstreamFormat === 'claude' ? extractClaudePassthroughHeaders(input.downstreamHeaders) @@ -514,7 +530,7 @@ export function buildUpstreamEndpointRequest(input: { const sanitizedBody = nativeClaudeBody ?? normalizedClaudeBody ?? sanitizeAnthropicMessagesBody( - convertOpenAiBodyToAnthropicMessagesBody(input.openaiBody, input.modelName, input.stream), + convertOpenAiBodyToAnthropicMessagesBody(openaiBody, input.modelName, input.stream), ); const headers = ensureStreamAcceptHeader({ @@ -542,7 +558,7 @@ export function buildUpstreamEndpointRequest(input: { model: input.modelName, stream: input.stream, } - : convertOpenAiBodyToResponsesBodyViaTransformer(input.openaiBody, input.modelName, input.stream) + : convertOpenAiBodyToResponsesBodyViaTransformer(openaiBody, input.modelName, input.stream) ); const body = sanitizeResponsesBodyForProxyViaTransformer(rawBody, input.modelName, input.stream); @@ -563,7 +579,7 @@ export function buildUpstreamEndpointRequest(input: { path: resolveEndpointPath('chat'), headers, body: { - ...input.openaiBody, + ...openaiBody, model: input.modelName, stream: input.stream, },