Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4138656
fix: fall back to the model default for thinking efforts outside supp…
kimi-agent-bot Aug 20, 2026
be43c7e
fix: update thinking-effort rejection hint for declared-list fallback
kimi-agent-bot Aug 20, 2026
b191946
test(kosong): update thinking-effort rejection hint expectation
kimi-agent-bot Aug 20, 2026
a24ef25
fix: fall back to the declared default effort for models without a th…
kimi-agent-bot Aug 20, 2026
08902a0
fix(agent-core-v2): normalize persisted thinking efforts after sessio…
kimi-agent-bot Aug 20, 2026
f1217ec
fix(agent-core): warn when a Kimi env thinking-effort override is unl…
kimi-agent-bot Aug 20, 2026
d32ed94
fix: note the forced-effort exception in thinking-effort rejection gu…
kimi-agent-bot Aug 20, 2026
2a1f750
fix: suppress the thinking-effort fallback warning when a forced effo…
kimi-agent-bot Aug 20, 2026
4b8c8da
fix(agent-core): warn when a config reload strands the current thinki…
kimi-agent-bot Aug 20, 2026
f2c856c
fix(agent-core): warn on createSession efforts that fall back to the …
kimi-agent-bot Aug 20, 2026
07b6f6d
fix(kimi-code): reject /effort values outside a model's declared list
kimi-agent-bot Aug 20, 2026
5b64151
fix(kimi-code): ignore blank support_efforts entries in the /effort c…
kimi-agent-bot Aug 21, 2026
119db0f
fix: treat a declared support_efforts list as thinking support under …
kimi-agent-bot Aug 21, 2026
2a32e56
fix(agent-core): trim declared thinking efforts before matching and f…
kimi-agent-bot Aug 21, 2026
5d3ea30
fix(kimi-code): match /effort against trimmed declared effort names
kimi-agent-bot Aug 21, 2026
21bd007
fix: finish normalizing declared thinking-effort lists
kimi-agent-bot Aug 21, 2026
baff362
fix: align declared-effort handling across the TUI and request diagno…
kimi-agent-bot Aug 21, 2026
5c71641
fix(kimi-code): resolve /effort on to the model default before lazy s…
kimi-agent-bot Aug 21, 2026
15ce9aa
fix(kimi-code): reject an unlisted declared default effort in the TUI
kimi-agent-bot Aug 21, 2026
bfc60b9
fix(kimi-code): match the top thinking tier against the normalized de…
kimi-agent-bot Aug 21, 2026
b8fa016
fix: align the picker's unlisted-default fallback and the 400 hint wi…
kimi-agent-bot Aug 21, 2026
04ad3e2
fix(agent-core): report an unlisted env thinking-effort override only…
kimi-agent-bot Aug 21, 2026
63980b0
fix(agent-core): recheck an env-pinned effort against reloaded declar…
kimi-agent-bot Aug 21, 2026
2318d63
fix(kimi-code): hydrate unlisted configured thinking efforts to the m…
kimi-agent-bot Aug 21, 2026
f8bbc7a
fix: normalize hydrated efforts and warn when a reload strands the st…
kimi-agent-bot Aug 21, 2026
f468c16
fix(agent-core-v2): republish status on reload fallback and watch pro…
kimi-agent-bot Aug 21, 2026
134c1f6
fix: republish status on effort restoration and match declared effort…
kimi-agent-bot Aug 21, 2026
534780d
fix(agent-core-v2): coalesce thinking-effort revalidation across a co…
kimi-agent-bot Aug 21, 2026
4a4a92c
fix: keep inherited default_effort covered by a padded override list
kimi-agent-bot Aug 21, 2026
742c370
test(agent-core-v2): drive thinking-effort revalidation with an injec…
kimi-agent-bot Aug 21, 2026
9799e01
fix: match declared thinking efforts case-insensitively in diagnostic…
kimi-agent-bot Aug 21, 2026
fd09758
fix: include the resolved fallback in thinking-effort warning dedupli…
kimi-agent-bot Aug 21, 2026
611438d
test(agent-core-v2): drop an inline comment from the reload fallback …
kimi-agent-bot Aug 21, 2026
b290ac3
fix(agent-core-v2): track the last published thinking effort for relo…
kimi-agent-bot Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thinking-effort-fallback-declared-efforts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Models with a declared support_efforts list now fall back to their default thinking effort when the configured effort is not in the list.
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ export class AgentLLMRequesterService implements IAgentLLMRequesterService {
signal,
),
};
this.warnAboutAnthropicThinkingEffort(request);
this.warnAboutThinkingEffortNotListed(request);
const logInput: LLMRequestLogInput = {
protocol: request.model.protocol,
providerType: request.model.providerType,
Expand Down Expand Up @@ -514,21 +514,15 @@ export class AgentLLMRequesterService implements IAgentLLMRequesterService {
return assigned === part.id ? part : { ...part, id: assigned };
}

private warnAboutAnthropicThinkingEffort(request: ResolvedLLMRequest): void {
if (request.model.protocol !== 'anthropic') return;
private warnAboutThinkingEffortNotListed(request: ResolvedLLMRequest): void {
const effort = request.thinkingEffort;
if (effort === 'on' || effort === 'off') return;

let code: string;
let message: string;
let knownEfforts: string | undefined;
const supportEfforts = request.model.supportEfforts?.filter((value) => value.length > 0);
if (supportEfforts === undefined || supportEfforts.length === 0) return;
if (supportEfforts.includes(effort)) return;
Comment thread
kimi-agent-bot marked this conversation as resolved.
Outdated
Comment thread
kimi-agent-bot marked this conversation as resolved.
Outdated
code = 'anthropic-thinking-effort-not-listed';
knownEfforts = supportEfforts.join(',');
message = `Thinking effort "${effort}" is not listed for model "${request.model.name}" (known: ${supportEfforts.join(', ')}). The configured value will be sent unchanged to the Anthropic-compatible backend.`;

const code = 'thinking-effort-not-listed';
const knownEfforts = supportEfforts.join(',');
const message = `Thinking effort "${effort}" is not listed for model "${request.model.name}" (known: ${supportEfforts.join(', ')}). The value will be sent unchanged to the backend.`;
Comment thread
kimi-agent-bot marked this conversation as resolved.
const key = [code, request.modelAlias, request.model.name, effort, knownEfforts].join('\u0000');
if (this.emittedThinkingEffortWarnings.has(key)) return;
this.emittedThinkingEffortWarnings.add(key);
Expand Down
85 changes: 48 additions & 37 deletions packages/agent-core-v2/src/agent/profile/profileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
normalizeRequestedThinkingEffort,
resolveForcedThinkingEffort,
resolveThinkingEffortForModel,
resolveThinkingEffortForModelWithFallback,
resolveThinkingKeep,
requiresStrictThinkingValidation,
type ThinkingConfig,
Expand Down Expand Up @@ -202,6 +203,12 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
}
}),
);
this._register(
this.dispatcher.hooks.onDidRestore.register('profile', async (_ctx, next) => {
this.warnAboutThinkingEffortFallback(this.profileState.thinkingLevel);
await next();
}),
);
}

private get activeToolNamesOverlay(): readonly string[] | undefined {
Expand Down Expand Up @@ -247,8 +254,13 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
this.activeProfile = undefined;
}
if (Object.keys(configChanged).length > 0) {
void this.dispatcher.dispatch(new ConfigUpdate(this.resolveConfigPayload(configChanged)));
this.afterConfigDispatch(configChanged);
const thinkingRequested =
configChanged.thinkingLevel ??
(this.modelAlias === undefined ? undefined : this.thinkingLevel);
void this.dispatcher.dispatch(
new ConfigUpdate(this.resolveConfigPayload(configChanged, thinkingRequested)),
);
this.afterConfigDispatch(configChanged, thinkingRequested);
}
if (activeToolNames !== undefined) {
this.setActiveTools(activeToolNames);
Expand Down Expand Up @@ -283,7 +295,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
environmentDisclosure: snapshot.environmentDisclosure,
agentsMdPaths,
disallowedTools: snapshot.disallowedTools ?? [],
});
}, snapshot.thinkingLevel);
this.agentsMdReminder.seedInjected(agentsMdPaths, this.sessionContext.cwd);
}

Expand Down Expand Up @@ -324,10 +336,9 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
this.activeProfile = profile;
this.cacheAgentsMdWarning(context);

const thinkingLevel = this.resolveThinkingEffort(
input.thinking ?? (currentProfileName !== undefined ? this.thinkingLevel : undefined),
model,
);
const thinkingRequested =
input.thinking ?? (currentProfileName !== undefined ? this.thinkingLevel : undefined);
const thinkingLevel = this.resolveThinkingEffort(thinkingRequested, model);

this.activeToolNamesOverlay = undefined;
await this.dispatcher.dispatch(new ProfileBind({
Expand All @@ -348,7 +359,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
thinkingLevel,
systemPrompt: rendered.text,
disallowedTools: profile.disallowedTools ?? [],
});
}, thinkingRequested);
this.seedAgentsMdReminder(context);

this.publishAgentsMdWarning();
Expand Down Expand Up @@ -568,15 +579,14 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ

private resolveConfigPayload(
changed: Omit<ProfileUpdateData, 'activeToolNames'>,
thinkingRequested: string | undefined,
): ConfigUpdatePayload {
const payload: ConfigUpdatePayload = { agentId: this.scopeContext.agentId };
if (changed.modelAlias !== undefined) payload.modelAlias = changed.modelAlias;
if (changed.profileName !== undefined) payload.profileName = changed.profileName;
if (changed.thinkingLevel !== undefined || changed.modelAlias !== undefined) {
const model = this.resolveModelForThinking(changed.modelAlias ?? this.modelAlias);
const requested =
changed.thinkingLevel ?? (this.modelAlias === undefined ? undefined : this.thinkingLevel);
payload.thinkingEffort = this.resolveThinkingEffort(requested, model);
payload.thinkingEffort = this.resolveThinkingEffort(thinkingRequested, model);
}
if (changed.systemPrompt !== undefined) {
payload.systemPrompt = changed.systemPrompt;
Expand All @@ -593,7 +603,10 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
return payload;
}

private afterConfigDispatch(changed: Omit<ProfileUpdateData, 'activeToolNames'>): void {
private afterConfigDispatch(
changed: Omit<ProfileUpdateData, 'activeToolNames'>,
thinkingRequested: string | undefined,
): void {
if (changed.modelAlias !== undefined) {
const model = this.tryResolveRawModel();
this.telemetryContext.set({
Expand All @@ -602,30 +615,36 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
});
}
if (changed.modelAlias !== undefined || changed.thinkingLevel !== undefined) {
this.warnAboutAnthropicThinkingEffort();
this.warnAboutThinkingEffortFallback(thinkingRequested);
}
this.emitStatusUpdated(
changed.modelAlias !== undefined || changed.thinkingLevel !== undefined,
);
}

private warnAboutAnthropicThinkingEffort(): void {
private warnAboutThinkingEffortFallback(requested: string | undefined): void {
try {
const model = this.tryResolveRawModel();
if (model?.protocol !== 'anthropic') return;
const effort = this.getEffectiveThinkingLevel();
if (effort === 'on' || effort === 'off') return;

let code: string;
let message: string;
let knownEfforts = '';
const efforts = model.supportEfforts?.filter((value) => value.length > 0);
if (efforts === undefined || efforts.length === 0 || efforts.includes(effort)) return;
knownEfforts = efforts.join(',');
code = 'anthropic-thinking-effort-not-listed';
message = `Thinking effort "${effort}" is not listed for model "${model.name}" (known: ${efforts.join(', ')}). The configured value will be sent unchanged to the Anthropic-compatible backend.`;

const key = [code, model.id, model.name, effort, knownEfforts].join('\u0000');
if (model === undefined) return;
const thinking = this.config.get<ThinkingConfig>(THINKING_SECTION);
const { effort, fallback } = resolveThinkingEffortForModelWithFallback(
requested,
thinking,
model,
this.strictThinkingValidation(model),
);
if (fallback === undefined) return;
const forced = resolveForcedThinkingEffort(
thinking?.forcedEffort,
effort,
drivesThinkingThroughTraits(model.providerType),
);
if (forced !== undefined) return;
const efforts = model.supportEfforts?.filter((value) => value.length > 0) ?? [];
const knownEfforts = efforts.join(',');
const code = 'thinking-effort-not-listed';
const message = `Thinking effort "${fallback.configured}" is not listed for model "${model.name}" (known: ${efforts.join(', ')}). Falling back to the model's default effort "${fallback.resolved}".`;
const key = [code, model.id, model.name, fallback.configured, knownEfforts].join('\u0000');
Comment thread
kimi-agent-bot marked this conversation as resolved.
Outdated
if (this.emittedThinkingEffortWarnings.has(key)) return;
this.emittedThinkingEffortWarnings.add(key);
void this.dispatcher.dispatch(new WarningIssued({ agentId: this.scopeContext.agentId, code, message }));
Expand Down Expand Up @@ -696,11 +715,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
}

private get thinkingLevel(): ThinkingEffort {
const stored = this.profileState.thinkingLevel;
if (stored === 'off' && this.alwaysThinkingModel) {
return this.resolveThinkingEffort(stored, this.tryResolveRawModel());
}
return stored;
return this.resolveThinkingEffort(this.profileState.thinkingLevel, this.tryResolveRawModel());
Comment thread
kimi-agent-bot marked this conversation as resolved.
}

private resolveThinkingState(model: Model | undefined): {
Expand Down Expand Up @@ -741,10 +756,6 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
return modelSupportsThinkingEffort(effort, model, this.strictThinkingValidation(model));
}

private get alwaysThinkingModel(): boolean {
return this.tryResolveRawModel()?.alwaysThinking === true;
}

private tryResolveRawModel(): Model | undefined {
const alias = this.modelAlias;
return this.resolveModelForThinking(alias);
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core-v2/src/kosong/contract/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ function appendThinkingEffortConfigHint(statusCode: number, message: string): st
if (message.includes(THINKING_EFFORT_CONFIG_DOCS_URL)) return message;
return `${message}

The provider rejected the configured thinking effort. Non-Kimi providers receive effort strings without client-side mapping; choose an effort supported by the selected model. For Kimi models, check support_efforts and default_effort. See ${THINKING_EFFORT_CONFIG_DOCS_URL}`;
The provider rejected the configured thinking effort. Efforts outside a model's declared support_efforts fall back to the model default, except a forced effort (forced_effort or KIMI_MODEL_THINKING_EFFORT), which is always sent unchanged; models without a declared list pass efforts to non-Kimi providers unchanged. Choose an effort supported by the selected model. See ${THINKING_EFFORT_CONFIG_DOCS_URL}`;
}

export function isContextOverflowErrorCode(code: string | null | undefined): boolean {
Expand Down
52 changes: 40 additions & 12 deletions packages/agent-core-v2/src/kosong/model/thinking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ function effortsFor(model: ModelThinkingMetadata | undefined): readonly string[]
return model?.supportEfforts?.map(nonEmpty).filter((v): v is string => v !== undefined) ?? [];
}

function declaredDefaultEffortFor(
model: ModelThinkingMetadata | undefined,
efforts: readonly string[],
): ThinkingEffort {
const declaredDefault = nonEmpty(model?.defaultEffort);
return (declaredDefault !== undefined && efforts.includes(declaredDefault)
? declaredDefault
: middleOf(efforts)) as ThinkingEffort;
}

export function modelSupportsThinking(model: ModelThinkingMetadata | undefined): boolean {
if (model === undefined) return false;
return (
Expand All @@ -116,12 +126,7 @@ export function defaultThinkingEffortForModel(
): ThinkingEffort {
if (model === undefined || !modelSupportsThinking(model)) return 'off';
const efforts = effortsFor(model);
if (efforts.length > 0) {
const declaredDefault = nonEmpty(model.defaultEffort);
return (declaredDefault !== undefined && efforts.includes(declaredDefault)
? declaredDefault
: middleOf(efforts)) as ThinkingEffort;
}
if (efforts.length > 0) return declaredDefaultEffortFor(model, efforts);
Comment thread
kimi-agent-bot marked this conversation as resolved.
return 'on';
}

Expand All @@ -144,9 +149,11 @@ function normalizeThinkingEffortForModel(
if (effort === 'off' && model?.alwaysThinking !== true) return 'off';
const efforts = effortsFor(model);
if (!strictValidation) {
return effort === 'on' && efforts.length > 0
? defaultThinkingEffortForModel(model)
: effort;
if (efforts.length === 0) return effort;
if (effort === 'on' || !efforts.includes(effort)) {
return declaredDefaultEffortFor(model, efforts);
Comment thread
kimi-agent-bot marked this conversation as resolved.
Outdated
Comment thread
kimi-agent-bot marked this conversation as resolved.
Outdated
Comment thread
kimi-agent-bot marked this conversation as resolved.
Outdated
}
return effort;
}
if (!modelSupportsThinking(model)) return 'off';
if (efforts.length === 0) return 'on';
Expand All @@ -156,12 +163,17 @@ function normalizeThinkingEffortForModel(
return effort;
}

export function resolveThinkingEffortForModel(
export interface ThinkingEffortFallback {
readonly configured: ThinkingEffort;
readonly resolved: ThinkingEffort;
}

export function resolveThinkingEffortForModelWithFallback(
requested: string | undefined,
defaults: ThinkingDefaults | undefined,
model: ModelThinkingMetadata | undefined,
strictValidation = false,
): ThinkingEffort {
): { readonly effort: ThinkingEffort; readonly fallback: ThinkingEffortFallback | undefined } {
const configured = normalizeRequestedThinkingEffort(defaults?.effort);
const normalized = normalizeRequestedThinkingEffort(requested);
let effort: ThinkingEffort;
Expand All @@ -179,7 +191,23 @@ export function resolveThinkingEffortForModel(
? configured
: defaultThinkingEffortForModel(model);
}
return normalizeThinkingEffortForModel(effort, model, strictValidation);
const resolved = normalizeThinkingEffortForModel(effort, model, strictValidation);
const efforts = effortsFor(model);
const fallback: ThinkingEffortFallback | undefined =
effort !== 'on' && effort !== 'off' && efforts.length > 0 && !efforts.includes(effort)
? { configured: effort, resolved }
: undefined;
return { effort: resolved, fallback };
}

export function resolveThinkingEffortForModel(
requested: string | undefined,
defaults: ThinkingDefaults | undefined,
model: ModelThinkingMetadata | undefined,
strictValidation = false,
): ThinkingEffort {
return resolveThinkingEffortForModelWithFallback(requested, defaults, model, strictValidation)
.effort;
}

const KEEP_OFF_VALUES = new Set(['0', 'false', 'no', 'off', 'none', 'null']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ describe('AgentLLMRequesterService measured anchors', () => {
});
});

describe('AgentLLMRequesterService Anthropic effort diagnostics', () => {
describe('AgentLLMRequesterService thinking effort diagnostics', () => {
it('warns and sends when the effort is not listed by the model', async () => {
const calls = { value: 0 };
const requester = createRequester(calls, null);
Expand All @@ -312,9 +312,28 @@ describe('AgentLLMRequesterService Anthropic effort diagnostics', () => {
expect(events.filter((event) => event.type === 'warning')).toEqual([
expect.objectContaining({
type: 'warning',
code: 'anthropic-thinking-effort-not-listed',
code: 'thinking-effort-not-listed',
message:
'Thinking effort "high" is not listed for model "wire-model" (known: max). The configured value will be sent unchanged to the Anthropic-compatible backend.',
'Thinking effort "high" is not listed for model "wire-model" (known: max). The value will be sent unchanged to the backend.',
}),
]);
});

it('warns for unlisted efforts on any protocol', async () => {
const calls = { value: 0 };
const requester = createRequester(calls, null);
Object.defineProperty(requester.model, 'protocol', { value: 'openai' });
Object.defineProperty(requester.model, 'supportEfforts', { value: ['max'] });
const { service, events } = createService(requester, undefined, { thinkingLevel: 'high' });

await service.request();

expect(events.filter((event) => event.type === 'warning')).toEqual([
expect.objectContaining({
type: 'warning',
code: 'thinking-effort-not-listed',
message:
'Thinking effort "high" is not listed for model "wire-model" (known: max). The value will be sent unchanged to the backend.',
}),
]);
});
Expand Down
Loading
Loading