fix(kosong): pass through chat reasoning effort (#581)

This commit is contained in:
7Sageer 2026-06-09 16:59:41 +08:00 committed by GitHub
parent 75a894e9ad
commit aa3471f5d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 25 additions and 59 deletions

View file

@ -0,0 +1,6 @@
---
"@moonshot-ai/kosong": patch
"@moonshot-ai/kimi-code": patch
---
Pass through xhigh reasoning effort for OpenAI-compatible chat completions requests.

View file

@ -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';

View file

@ -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;

View file

@ -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;

View file

@ -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');
});
});