diff --git a/src/providers/openrouter/chatComplete.test.ts b/src/providers/openrouter/chatComplete.test.ts new file mode 100644 index 000000000..4a4800c0b --- /dev/null +++ b/src/providers/openrouter/chatComplete.test.ts @@ -0,0 +1,72 @@ +import { Params } from '../../types/requestBody'; +import { OpenrouterChatCompleteConfig } from './chatComplete'; + +describe('OpenrouterChatCompleteConfig', () => { + it('preserves OpenRouter chat and routing parameters', () => { + const passthroughParameters = [ + 'model', + 'max_tokens', + 'max_completion_tokens', + 'temperature', + 'cache_control', + 'debug', + 'frequency_penalty', + 'image_config', + 'logit_bias', + 'logprobs', + 'metadata', + 'min_p', + 'modalities', + 'models', + 'parallel_tool_calls', + 'plugins', + 'presence_penalty', + 'provider', + 'reasoning', + 'reasoning_effort', + 'repetition_penalty', + 'response_format', + 'route', + 'seed', + 'service_tier', + 'session_id', + 'stop', + 'stop_server_tools_when', + 'stream', + 'stream_options', + 'tool_choice', + 'tools', + 'top_a', + 'top_k', + 'top_logprobs', + 'top_p', + 'trace', + 'transforms', + 'usage', + 'user', + ]; + + for (const parameter of passthroughParameters) { + expect(OpenrouterChatCompleteConfig[parameter]).toMatchObject({ + param: parameter, + }); + } + }); + + it('maps developer messages to OpenRouter-compatible system messages', () => { + const params = { + model: 'google/gemini-2.5-flash', + messages: [{ role: 'developer', content: 'Follow this instruction' }], + } as Params; + + const messageConfig = OpenrouterChatCompleteConfig.messages; + expect(Array.isArray(messageConfig)).toBe(false); + const transformed = !Array.isArray(messageConfig) + ? messageConfig.transform?.(params, { provider: 'openrouter' }) + : undefined; + + expect(transformed).toEqual([ + { role: 'system', content: 'Follow this instruction' }, + ]); + }); +}); diff --git a/src/providers/openrouter/chatComplete.ts b/src/providers/openrouter/chatComplete.ts index 049c41717..717f0635e 100644 --- a/src/providers/openrouter/chatComplete.ts +++ b/src/providers/openrouter/chatComplete.ts @@ -10,7 +10,6 @@ import { generateErrorResponse, generateInvalidProviderResponseError, } from '../utils'; -import { transformReasoningParams, transformUsageOptions } from './utils'; export const OpenrouterChatCompleteConfig: ProviderConfig = { model: { @@ -34,7 +33,7 @@ export const OpenrouterChatCompleteConfig: ProviderConfig = { min: 0, }, max_completion_tokens: { - param: 'max_tokens', + param: 'max_completion_tokens', default: 100, min: 0, }, @@ -47,17 +46,39 @@ export const OpenrouterChatCompleteConfig: ProviderConfig = { modalities: { param: 'modalities', }, + cache_control: { + param: 'cache_control', + }, + debug: { + param: 'debug', + }, + frequency_penalty: { + param: 'frequency_penalty', + min: -2, + max: 2, + }, + image_config: { + param: 'image_config', + }, + logit_bias: { + param: 'logit_bias', + }, + logprobs: { + param: 'logprobs', + }, + metadata: { + param: 'metadata', + }, + min_p: { + param: 'min_p', + min: 0, + max: 1, + }, reasoning: { param: 'reasoning', - transform: (params: Params) => { - return transformReasoningParams(params); - }, }, reasoning_effort: { - param: 'reasoning', - transform: (params: Params) => { - return transformReasoningParams(params); - }, + param: 'reasoning_effort', }, top_p: { param: 'top_p', @@ -65,6 +86,38 @@ export const OpenrouterChatCompleteConfig: ProviderConfig = { min: 0, max: 1, }, + parallel_tool_calls: { + param: 'parallel_tool_calls', + }, + plugins: { + param: 'plugins', + }, + presence_penalty: { + param: 'presence_penalty', + min: -2, + max: 2, + }, + repetition_penalty: { + param: 'repetition_penalty', + }, + route: { + param: 'route', + }, + seed: { + param: 'seed', + }, + service_tier: { + param: 'service_tier', + }, + session_id: { + param: 'session_id', + }, + stop: { + param: 'stop', + }, + stop_server_tools_when: { + param: 'stop_server_tools_when', + }, tools: { param: 'tools', }, @@ -82,23 +135,32 @@ export const OpenrouterChatCompleteConfig: ProviderConfig = { }, usage: { param: 'usage', - transform: (params: Params) => { - return transformUsageOptions(params); - }, }, stream: { param: 'stream', default: false, }, stream_options: { - param: 'usage', - transform: (params: Params) => { - return transformUsageOptions(params); - }, + param: 'stream_options', }, response_format: { param: 'response_format', }, + top_a: { + param: 'top_a', + }, + top_k: { + param: 'top_k', + }, + top_logprobs: { + param: 'top_logprobs', + }, + trace: { + param: 'trace', + }, + user: { + param: 'user', + }, }; interface OpenrouterUsageDetails { diff --git a/src/providers/openrouter/utils.ts b/src/providers/openrouter/utils.ts deleted file mode 100644 index 72ab76108..000000000 --- a/src/providers/openrouter/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Params } from '../../types/requestBody'; - -interface OpenrouterUsageParam { - include?: boolean; -} - -interface OpenRouterParams extends Params { - reasoning?: OpenrouterReasoningParam; - usage?: OpenrouterUsageParam; - stream_options?: { - include_usage?: boolean; - }; -} - -type OpenrouterReasoningParam = { - effort?: 'low' | 'medium' | 'high' | string; - max_tokens?: number; - exclude?: boolean; -}; - -export const transformReasoningParams = (params: OpenRouterParams) => { - let reasoning: OpenrouterReasoningParam = { ...params.reasoning }; - if (params.reasoning_effort) { - reasoning.effort = params.reasoning_effort; - } - return Object.keys(reasoning).length > 0 ? reasoning : null; -}; - -export const transformUsageOptions = (params: OpenRouterParams) => { - let usage: OpenrouterUsageParam = { ...params.usage }; - if (params.stream_options?.include_usage) { - usage.include = params.stream_options?.include_usage; - } - return Object.keys(usage).length > 0 ? usage : null; -};