Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .changeset/catalog-followup-correctness.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"@moonshot-ai/kimi-code-sdk": patch
"@moonshot-ai/agent-core": patch
"@moonshot-ai/agent-core-v2": patch
"@moonshot-ai/kap-server": patch
"@moonshot-ai/kimi-code": patch
---

Fix a set of small correctness issues on top of the catalog metadata work: configured efforts (config or the KIMI_MODEL_THINKING_EFFORT env override) are now normalized instead of being sent upstream as invalid values; a model's declared input limit can no longer exceed its effective context window, and the clamp now copies the record instead of mutating the user's config in place; context-usage percentages share one denominator (the effective input cap) across status endpoints, clamped to 1 where the wire schema bounds it while event streams keep the documented raw overflow signal; a provider-observed smaller context window now actually wins over the catalog's declared input cap during overflow recovery; per-model endpoints declared with an unrecognized override SDK are preserved via the OpenAI-compatible fallback, while known proprietary SDKs stay refused; and the model inspector attributes input-limit fields to their actual config, override, or clamp provenance.
Fix catalog-imported Claude models being wrongly locked into always-on thinking, and stop offering a misleading thinking Off option for models that cannot truly disable reasoning (such as Gemini 3). Also normalizes configured thinking effort values and unifies context-usage reporting.
5 changes: 4 additions & 1 deletion docs/en/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,13 @@ Each entry in the `models` table defines a model alias (the name used in `defaul
| `provider` | `string` | Yes | Name of the provider to use; must be defined in `providers` |
| `model` | `string` | Yes | Model identifier sent to the server when calling the API |
| `max_context_size` | `integer` | Yes | Maximum context length in tokens; must be at least 1 |
| `max_input_size` | `integer` | No | Declared per-request input limit when it sits below the total window (e.g. gpt-5: 400k window, 272k input). Compaction, context-overflow checks, and usage ratios prefer it; completion budgeting keeps the total window. Resolution clamps it to `max_context_size` |
| `max_output_size` | `integer` | No | Per-request output token cap (maps to `max_tokens`). Currently only the `anthropic` provider honors it. When set for a Claude model, this explicit value overrides the built-in server-side maximum |
| `capabilities` | `array<string>` | No | Capability tags to add explicitly: `thinking`, `always_thinking`, `image_in`, `video_in`, `audio_in`, `tool_use`. Unioned with the capabilities auto-detected by the provider — entries can only be added, never removed |
| `support_efforts` | `array<string>` | No | Thinking effort levels the model accepts. For `kimi`, selecting another value at runtime fails; when model resolution carries an unsupported configured or previous value, the session falls back to the target model's `default_effort` and reports that effective value to the UI. A Thinking-capable Kimi model without this field uses boolean `on` / `off`. Other providers pass concrete values unchanged when their protocol has a native effort field; protocols that expose only levels or token budgets perform the required format conversion. Managed and open-platform refreshes may rewrite this field; to pin it manually, set `[models."<alias>".overrides] support_efforts` instead |
| `default_effort` | `string` | No | Default thinking effort for the model. Managed and open-platform refreshes may rewrite this field; to pin it manually, set `[models."<alias>".overrides] default_effort` instead |
| `off_effort` | `string` | No | Effort value sent on the wire to disable thinking (e.g. `none` for xai grok). Only meaningful for models that declare such an encoding (catalog imports set it): turning thinking Off then sends this value instead of omitting the effort field — the only way to actually stop reasoning on models that reason by default |
| `base_url` | `string` | No | Per-model endpoint override (written by catalog imports for gateway models served away from the provider default). Resolution prefers it over the provider's `base_url`; only takes effect together with `protocol` |
| `display_name` | `string` | No | Name shown in the UI; falls back to `model` when unset |
| `reasoning_key` | `string` | No | `openai` provider only. Override the field name used for reasoning content when the gateway returns it under a non-standard name; by default `reasoning_content`, `reasoning_details`, and `reasoning` are auto-detected |
| `adaptive_thinking` | `boolean` | No | `anthropic` provider only. Force adaptive thinking on or off, overriding the version inference based on the model name. Omit to infer automatically (Claude ≥ 4.6 uses adaptive) |
Expand Down Expand Up @@ -181,7 +184,7 @@ max_context_size = 131072
display_name = "Kimi for Coding (custom)"
```

`[models."<alias>".overrides]` accepts ordinary model fields such as `max_context_size`, `max_output_size`, `capabilities`, `display_name`, `reasoning_key`, `adaptive_thinking`, `support_efforts`, and `default_effort`. It does not accept identity / routing fields: `provider`, `model`, `protocol`, `beta_api`, and `base_url`.
`[models."<alias>".overrides]` accepts ordinary model fields such as `max_context_size`, `max_input_size`, `max_output_size`, `capabilities`, `display_name`, `reasoning_key`, `adaptive_thinking`, `support_efforts`, `default_effort`, and `off_effort`. It does not accept identity / routing fields: `provider`, `model`, `protocol`, `beta_api`, and `base_url`.

You can also switch models temporarily without touching the config file — by setting `KIMI_MODEL_*` environment variables, the CLI synthesizes a temporary provider in memory that does not persist after restart. See [Define a model from environment variables](./env-vars.md#define-a-model-from-environment-variables-kimi_model).

Expand Down
2 changes: 1 addition & 1 deletion docs/en/configuration/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The manager displays providers as a list of entries grouped by source. Navigatio

Two paths when adding:

- **Known third-party provider**: fetches the model catalog from [models.dev](https://models.dev/), select a provider → enter an API key → select a default model
- **Known third-party provider**: fetches the model catalog from [models.dev](https://models.dev/), select a provider → enter an API key → select a default model. Vendors whose protocol the catalog does not declare (e.g. xai, openrouter, and other vendor-specific SDKs) are imported as OpenAI-compatible with a "guessed" note; when the catalog provides no usable endpoint, a base URL prompt appears first; proprietary protocols (Amazon Bedrock, Cohere) and unrecognized explicit protocols are refused. Deprecated and alpha-status models are excluded from the import list
- **Custom registry (api.json)**: paste a custom registry URL and Bearer token; the CLI automatically creates the `providers` / `models` entries. On later startup, providers from the same registry URL are refreshed together, so upstream provider additions, removals, and model metadata changes are synced.

::: warning
Expand Down
5 changes: 4 additions & 1 deletion docs/zh/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,13 @@ KIMI_BASE_URL = "https://api.moonshot.ai/v1"
| `provider` | `string` | 是 | 使用的供应商名称,必须在 `providers` 中定义 |
| `model` | `string` | 是 | 调用 API 时实际传给服务端的模型 ID |
| `max_context_size` | `integer` | 是 | 最大上下文长度(token 数),必须 ≥ 1 |
| `max_input_size` | `integer` | 否 | 模型声明的单次请求输入上限(当低于总窗口时,如 gpt-5 的 400k 窗口 / 272k 输入)。压缩、上下文溢出检查和用量比率优先使用它;补全预算仍使用总窗口。解析时会被钳制到不超过 `max_context_size` |
| `max_output_size` | `integer` | 否 | 单次请求的输出 token 上限(对应 `max_tokens`)。目前仅 `anthropic` 供应商读取。为 Claude 模型设置后,这个显式值会覆盖内置的服务端最大值 |
| `capabilities` | `array<string>` | 否 | 显式追加的能力标签:`thinking`、`always_thinking`、`image_in`、`video_in`、`audio_in`、`tool_use`。与供应商自动识别的能力取并集,只能追加不能移除 |
| `support_efforts` | `array<string>` | 否 | 模型接受的 Thinking 档位。对 `kimi` 而言,在运行时选择列表外的值会报错;模型解析时若配置值或之前的值不受目标模型支持,会回落到目标模型的 `default_effort`,并将该有效值同步给 UI。支持 Thinking 但没有此字段的 Kimi 模型使用布尔 `on` / `off`。其他 provider 在协议提供原生 effort 字段时会原样传递具体值;协议仅提供等级或 token budget 时,只做必要的格式转换。managed 和 open-platform 刷新可能会改写该字段;如需手动固定,请改用 `[models."<alias>".overrides] support_efforts` |
| `default_effort` | `string` | 否 | 模型的默认 Thinking 档位。managed 和 open-platform 刷新可能会改写该字段;如需手动固定,请改用 `[models."<alias>".overrides] default_effort` |
| `off_effort` | `string` | 否 | 关闭 Thinking 时在线上传输的 effort 编码(如 xai grok 的 `none`)。仅对声明了该编码的模型(catalog 会导入)有意义:设置后选择 Off 会发送这个值而不是省略 effort 字段——对默认就会推理的模型,这是真正关闭推理的唯一方式 |
| `base_url` | `string` | 否 | 模型级端点覆盖(catalog 导入网关模型时写入,这些模型与供应商默认端点不同)。解析时优先于供应商的 `base_url`;仅在与 `protocol` 配合时生效 |
| `display_name` | `string` | 否 | UI 中显示的名称,未设时回退到 `model` |
| `reasoning_key` | `string` | 否 | 仅 `openai` 供应商。当网关用非标准字段名返回推理内容时才需要设置;默认自动识别 `reasoning_content` / `reasoning_details` / `reasoning` |
| `adaptive_thinking` | `boolean` | 否 | 仅 `anthropic` 供应商。强制开启或关闭 adaptive thinking,覆盖按模型名推断的逻辑。省略时自动推断(Claude ≥ 4.6 使用 adaptive) |
Expand Down Expand Up @@ -181,7 +184,7 @@ max_context_size = 131072
display_name = "Kimi for Coding (custom)"
```

`[models."<alias>".overrides]` 接受普通模型字段,例如 `max_context_size`、`max_output_size`、`capabilities`、`display_name`、`reasoning_key`、`adaptive_thinking`、`support_efforts` 和 `default_effort`。不接受身份 / 路由字段:`provider`、`model`、`protocol`、`beta_api` 和 `base_url`。
`[models."<alias>".overrides]` 接受普通模型字段,例如 `max_context_size`、`max_input_size`、`max_output_size`、`capabilities`、`display_name`、`reasoning_key`、`adaptive_thinking`、`support_efforts`、`default_effort` 和 `off_effort`。不接受身份 / 路由字段:`provider`、`model`、`protocol`、`beta_api` 和 `base_url`。

无需修改配置文件也可以临时切换模型——通过 `KIMI_MODEL_*` 环境变量在内存里合成一个临时供应商,详见[用环境变量定义模型](./env-vars.md#用环境变量定义模型-kimi-model)。

Expand Down
2 changes: 1 addition & 1 deletion docs/zh/configuration/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Kimi Code CLI 支持同时接入多家 LLM 平台——用 Kimi Code 托管服

添加时有两条路径:

- **Known third-party provider**:从 [models.dev](https://models.dev/) 拉取模型目录,选供应商 → 输入 API 密钥 → 选默认模型
- **Known third-party provider**:从 [models.dev](https://models.dev/) 拉取模型目录,选供应商 → 输入 API 密钥 → 选默认模型。目录未声明协议类型的供应商(如 xai、openrouter 这类厂商专用 SDK)会按 OpenAI 兼容协议导入并显示 "guessed" 提示;目录没有可用端点时会先弹出 base URL 输入框;Amazon Bedrock / Cohere 等专有协议和无法识别的显式协议会被拒绝导入。已下线(deprecated)和 alpha 状态的模型不会出现在导入列表中
- **Custom registry (api.json)**:粘贴自定义 registry 地址和 Bearer token,CLI 自动创建 `providers` / `models` 条目。后续启动时,同一个 registry 地址下的供应商会一起刷新,因此上游新增、删除供应商以及模型元数据变化都会同步。

::: warning
Expand Down
2 changes: 1 addition & 1 deletion docs/zh/reference/kimi-command.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ kimi provider catalog list anthropic

#### `kimi provider catalog add <providerId>`

按 id 从 catalog 直接导入一个已知供应商,协议类型、base URL、模型信息均由 catalog 提供,只需提供 API key。catalog 未声明协议的供应商(如 xai、openrouter 这类专有 SDK)按 OpenAI 兼容协议导入,并在输出中标注 "guessed";catalog 未提供可用端点时需用 `--base-url` 显式指定。专有协议(如 Amazon Bedrock)无法导入。
按 id 从 catalog 直接导入一个已知供应商,协议类型、base URL、模型信息均由 catalog 提供,只需提供 API key。catalog 未声明协议的供应商(如 xai、openrouter 这类厂商专用 SDK)按 OpenAI 兼容协议导入,并在输出中标注 "guessed";catalog 未提供可用端点时需用 `--base-url` 显式指定。专有协议(如 Amazon Bedrock)无法导入。

| 参数 / 选项 | 说明 |
| --- | --- |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,23 +451,17 @@ export class AgentLLMRequesterService implements IAgentLLMRequesterService {
private warnAboutAnthropicThinkingEffort(request: ResolvedLLMRequest): void {
if (request.model.protocol !== 'anthropic') return;
const effort = request.thinkingEffort;
if (effort === 'on') return;
if (effort === 'on' || effort === 'off') return;

let code: string;
let message: string;
let knownEfforts: string | undefined;
if (effort === 'off') {
if (!request.model.alwaysThinking) return;
code = 'anthropic-thinking-cannot-disable';
message = `Model "${request.model.name}" declares always-on thinking. The configured effort "off" will be sent unchanged to the Anthropic-compatible backend.`;
} else {
const supportEfforts = request.model.supportEfforts?.filter((value) => value.length > 0);
if (supportEfforts === undefined || supportEfforts.length === 0) return;
if (supportEfforts.includes(effort)) return;
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 supportEfforts = request.model.supportEfforts?.filter((value) => value.length > 0);
if (supportEfforts === undefined || supportEfforts.length === 0) return;
if (supportEfforts.includes(effort)) return;
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 key = [code, request.modelAlias, request.model.name, effort, knownEfforts].join('\u0000');
if (this.emittedThinkingEffortWarnings.has(key)) return;
Expand Down
18 changes: 6 additions & 12 deletions packages/agent-core-v2/src/agent/profile/profileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,22 +546,16 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
const model = this.tryResolveRawModel();
if (model?.protocol !== 'anthropic') return;
const effort = this.getEffectiveThinkingLevel();
if (effort === 'on') return;
if (effort === 'on' || effort === 'off') return;

let code: string;
let message: string;
let knownEfforts = '';
if (effort === 'off') {
if (!model.alwaysThinking) return;
code = 'anthropic-thinking-cannot-disable';
message = `Model "${model.name}" declares always-on thinking. The configured effort "off" will be sent unchanged to the Anthropic-compatible backend.`;
} else {
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 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 (this.emittedThinkingEffortWarnings.has(key)) return;
Expand Down
30 changes: 14 additions & 16 deletions packages/agent-core-v2/src/kosong/model/thinking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
* (protocol, providerType) pair contains a `withThinking` hook). Neither
* hardcodes a vendor or protocol string — trait-driven thinking means
* "thinking is driven by traits", which the registry answers.
* `requiresStrictThinkingValidation` reads the same identity for the
* strict-validation flag. Strict gates only listed-effort validation
* and the `'on'` projection; the always-on clamp is UNCONDITIONAL — a
* model that declares `always_thinking` never resolves to `'off'` on
* any wire (a claimed off state would be a lie, since upstream keeps
* reasoning at its default when no off encoding exists). Unlisted
* concrete efforts stay lenient on compatible transports
* (warn-and-send, `anthropic-thinking-effort-not-listed`) because the
* backend may accept values the local catalog does not list. The
* strict flag is declared by `kimiOpenAITrait` — Kimi's native API
* rejects unlisted efforts — and deliberately NOT by
* `kimiAnthropicTrait`.
*/

import { z } from 'zod';
Expand Down Expand Up @@ -95,23 +107,9 @@ export function usesTraitDrivenThinking(
}

/**
* ⚠ PHASE 6 PARITY PATCH — v1 `provider.type === 'kimi'` gate restored.
*
* Whether client-side thinking-effort validation must be STRICT for the
* (protocol, providerType) pair: the resolved traits take thinking over and
* the last `withThinking` declarer marks `strictThinkingValidation`.
*
* This is the gate for client-side effort strictness (validation, the
* always-on clamp, and the `'on'` projection). The strict flag is declared
* by `kimiOpenAITrait` — Kimi's native API rejects unlisted efforts — and
* deliberately NOT by `kimiAnthropicTrait`: over the Anthropic
* transport the backend may accept efforts the local catalog metadata does
* not list, so the profile must stay lenient there (warn-and-send, with the
* `anthropic-thinking-*` warnings) instead of rejecting or rewriting the
* effort. Gating on plain `usesTraitDrivenThinking` (true for the
* anthropic pair registration too) made `setThinking` throw for Kimi-managed
* Anthropic models and left the warning path unreachable — a v1 behavioral
* regression.
* (protocol, providerType) pair — answered through the resolved adapter
* identity's `strictThinkingValidation` flag.
*/
export function requiresStrictThinkingValidation(
registry: IProtocolAdapterRegistry,
Expand Down
Loading
Loading