diff --git a/.changeset/openai-compatible-xhigh-reasoning.md b/.changeset/openai-compatible-xhigh-reasoning.md new file mode 100644 index 000000000..947228b7c --- /dev/null +++ b/.changeset/openai-compatible-xhigh-reasoning.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/kosong": patch +"@moonshot-ai/kimi-code": patch +--- + +Pass through xhigh reasoning effort for OpenAI-compatible chat completions requests. diff --git a/packages/kosong/src/provider.ts b/packages/kosong/src/provider.ts index 1a47df361..c5d7624fe 100644 --- a/packages/kosong/src/provider.ts +++ b/packages/kosong/src/provider.ts @@ -8,9 +8,9 @@ import type { TokenUsage } from './usage'; * * Values above `high` are provider/model-specific and may be clamped by the * adapter when the native API has no matching level. OpenAI maps `max` to its - * `xhigh` ceiling on Responses and selected Chat Completions models; Kimi and - * Gemini cap `xhigh`/`max` at `high`; Anthropic supports `xhigh`/`max` only - * on selected models and otherwise clamps to `high`. + * `xhigh` ceiling; Kimi and Gemini cap `xhigh`/`max` at `high`; Anthropic + * supports `xhigh`/`max` only on selected models and otherwise clamps to + * `high`. */ export type ThinkingEffort = 'off' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'; diff --git a/packages/kosong/src/providers/capability-registry.ts b/packages/kosong/src/providers/capability-registry.ts index be36fe843..17ec9d6bd 100644 --- a/packages/kosong/src/providers/capability-registry.ts +++ b/packages/kosong/src/providers/capability-registry.ts @@ -21,15 +21,6 @@ const OPENAI_RESPONSES_DEVELOPER_ROLE_MODELS = new Set([ 'o4-mini', ]); -// OpenAI models that document `xhigh` and keep `v1/chat/completions` enabled. -// Pro/Codex xhigh models are Responses-only, so the Chat Completions adapter -// must clamp them at `high`. -const OPENAI_CHAT_COMPLETIONS_XHIGH_REASONING_MODELS = new Set([ - 'gpt-5.2', - 'gpt-5.4', - 'gpt-5.5', -]); - const OPENAI_VISION_TOOL_PREFIXES = [ 'gpt-4o', 'gpt-4-turbo', @@ -158,10 +149,6 @@ function normalizeModelName(modelName: string): string { return modelName.toLowerCase(); } -function modelIdLeaf(modelName: string): string { - return normalizeModelName(modelName).trim().split('/').at(-1) ?? ''; -} - function hasPrefix(modelName: string, prefixes: readonly string[]): boolean { return prefixes.some((prefix) => modelName.startsWith(prefix)); } @@ -206,10 +193,6 @@ export function getGoogleGenAIModelCapability(modelName: string): ModelCapabilit return GEMINI_MULTIMODAL_TOOL_CAPABILITY; } -export function supportsOpenAIChatCompletionsXHighReasoning(modelName: string): boolean { - return OPENAI_CHAT_COMPLETIONS_XHIGH_REASONING_MODELS.has(modelIdLeaf(modelName)); -} - export function usesOpenAIResponsesDeveloperRole(modelName: string): boolean { const normalized = normalizeModelName(modelName); if (OPENAI_RESPONSES_DEVELOPER_ROLE_MODELS.has(normalized)) return true; diff --git a/packages/kosong/src/providers/openai-legacy.ts b/packages/kosong/src/providers/openai-legacy.ts index 67060cdb4..23820c8db 100644 --- a/packages/kosong/src/providers/openai-legacy.ts +++ b/packages/kosong/src/providers/openai-legacy.ts @@ -12,10 +12,7 @@ import type { Tool } from '#/tool'; import type { TokenUsage } from '#/usage'; import OpenAI from 'openai'; -import { - getOpenAILegacyModelCapability, - supportsOpenAIChatCompletionsXHighReasoning, -} from './capability-registry'; +import { getOpenAILegacyModelCapability } from './capability-registry'; import { convertContentPart, convertOpenAIError, @@ -135,16 +132,6 @@ function normalizeGenerationKwargs( return kwargs; } -function clampChatCompletionsReasoningEffort( - reasoningEffort: string | undefined, - model: string, -): string | undefined { - if (reasoningEffort !== 'xhigh') { - return reasoningEffort; - } - return supportsOpenAIChatCompletionsXHighReasoning(model) ? 'xhigh' : 'high'; -} - function convertMessage( message: Message, reasoningKey: string | undefined, @@ -518,10 +505,7 @@ export class OpenAILegacyChatProvider implements ChatProvider { } withThinking(effort: ThinkingEffort): OpenAILegacyChatProvider { - const reasoningEffort = clampChatCompletionsReasoningEffort( - thinkingEffortToReasoningEffort(effort), - this._model, - ); + const reasoningEffort = thinkingEffortToReasoningEffort(effort); const clone = this._clone(); clone._reasoningEffort = reasoningEffort; return clone; diff --git a/packages/kosong/test/openai-legacy.test.ts b/packages/kosong/test/openai-legacy.test.ts index fd355afd3..8847f6626 100644 --- a/packages/kosong/test/openai-legacy.test.ts +++ b/packages/kosong/test/openai-legacy.test.ts @@ -718,8 +718,8 @@ describe('OpenAILegacyChatProvider', () => { expect(body['reasoning_effort']).toBe('high'); }); - it.each(['gpt-5.2', 'gpt-5.4', 'gpt-5.5', 'openai/gpt-5.5'])( - '.withThinking("xhigh") passes through for Chat Completions xhigh model %s', + it.each(['deepseek/deepseek-v4-flash', 'gpt-5.4-pro', 'some-model'])( + '.withThinking("xhigh") passes through reasoning_effort for model %s', async (model) => { const provider = createProvider({ model }).withThinking('xhigh'); const history: Message[] = [ @@ -732,40 +732,33 @@ describe('OpenAILegacyChatProvider', () => { }, ); - it.each(['gpt-5.1', 'gpt-5.4-mini', 'gpt-5.4-pro', 'kimi-k2.6', 'some-model'])( - '.withThinking("xhigh") clamps to high for Chat Completions model %s', - async (model) => { - const provider = createProvider({ model }).withThinking('xhigh'); - const history: Message[] = [ - { role: 'user', content: [{ type: 'text', text: 'Think' }], toolCalls: [] }, - ]; - const body = await captureRequestBody(provider, '', [], history); - - expect(body['reasoning_effort']).toBe('high'); - expect(provider.thinkingEffort).toBe('high'); - }, - ); - - it('.withThinking("max") uses xhigh only for Chat Completions xhigh models', async () => { + it('.withThinking("max") maps to xhigh without model-specific clamping', async () => { const history: Message[] = [ { role: 'user', content: [{ type: 'text', text: 'Think' }], toolCalls: [] }, ]; - const supported = await captureRequestBody( + const openAIChatModel = await captureRequestBody( createProvider({ model: 'gpt-5.5' }).withThinking('max'), '', [], history, ); - const unsupported = await captureRequestBody( + const openAIProModel = await captureRequestBody( createProvider({ model: 'gpt-5.5-pro' }).withThinking('max'), '', [], history, ); + const deepSeekModel = await captureRequestBody( + createProvider({ model: 'deepseek/deepseek-v4-pro' }).withThinking('max'), + '', + [], + history, + ); - expect(supported['reasoning_effort']).toBe('xhigh'); - expect(unsupported['reasoning_effort']).toBe('high'); + expect(openAIChatModel['reasoning_effort']).toBe('xhigh'); + expect(openAIProModel['reasoning_effort']).toBe('xhigh'); + expect(deepSeekModel['reasoning_effort']).toBe('xhigh'); }); });