feat(plugins): add kimi-effort plugin for reasoning effort auto-detec… - #3661
feat(plugins): add kimi-effort plugin for reasoning effort auto-detec…#3661WENGENG-boop wants to merge 3 commits into
Conversation
…tion and adjustment
🦋 Changeset detectedLatest commit: 10fb652 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a7b02c4ee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (let i = 0; i < lines.length; i++) { | ||
| const match = lines[i].match(keyRegex); | ||
| if (match) { | ||
| const indent = match[1]; | ||
| lines[i] = `${indent}${key} = ${newValueFormatted}`; | ||
| found = true; |
There was a problem hiding this comment.
Preserve multiline TOML arrays before updating them
When a model uses a valid multiline support_efforts array, parseTomlValue records only the opening "[", so the SessionStart hook treats the model as unconfigured; this replacement then rewrites only the first line while leaving the array elements and closing bracket behind, producing an invalid config.toml. Parse the file with a TOML-aware implementation or replace the complete multiline value span before writing.
Useful? React with 👍 / 👎.
| 1. Run: | ||
| ```bash | ||
| node "C:/Users/weo/plugins/kimi-effort/scripts/effort-cli.mjs" status $target | ||
| ``` |
There was a problem hiding this comment.
Run the skill CLI from its installed directory
On every installation outside this author's Windows directory, the status action runs a nonexistent C:/Users/weo/... script; the set, detect, and list branches repeat the same path, so the plugin command cannot perform any advertised operation. Use the already-supported ${KIMI_SKILL_DIR}/../../scripts/effort-cli.mjs path in every action and remove the internal user identifier from public plugin text.
AGENTS.md reference: AGENTS.md:L73-L73
Useful? React with 👍 / 👎.
| name: effort | ||
| description: Check, auto-detect, or adjust reasoning/thinking effort level for models in Kimi Code CLI |
There was a problem hiding this comment.
Route
/effort subcommands to a reachable handler
The TUI resolves built-in commands before skills or plugin commands, and /effort is already a built-in that only accepts a single effort level. Consequently /effort detect, /effort list, and the other advertised plugin flows never load this command and instead report an unsupported effort; this plugin command is only reachable as /kimi-effort:effort. Either wire these subcommands into the built-in handler or advertise and use the namespaced command.
Useful? React with 👍 / 👎.
| // Check if support_efforts is already configured and has elements | ||
| const hasConfiguredEfforts = Array.isArray(supportedEfforts) && supportedEfforts.length > 0; | ||
|
|
||
| if (hasConfiguredEfforts) { | ||
| // Already configured, nothing to do | ||
| return; | ||
| } |
There was a problem hiding this comment.
Remember completed non-reasoning detections
When detection concludes that a model is non-reasoning, it saves support_efforts = [], but this condition considers only a non-empty array configured. Every subsequent SessionStart therefore performs the live POST and metadata probes again, repeatedly adding startup latency and unnecessary provider traffic for the same model. Treat an explicitly present empty support_efforts value, or a separate detection marker, as a completed result.
Useful? React with 👍 / 👎.
| if (res.ok) { | ||
| // 200 OK with reasoning_effort accepted! | ||
| return { | ||
| isReasoningModel: true, | ||
| supportedEfforts: ['low', 'medium', 'high'], | ||
| defaultEffort: 'medium', |
There was a problem hiding this comment.
Avoid advertising effort levels that were never probed
A successful request with reasoning_effort: "low" proves at most that this one payload was accepted; third-party compatible gateways can support only a subset of levels or silently ignore unknown fields. Returning and persisting low, medium, and high here makes the CLI expose unverified choices, so selecting one can later fail or have no effect. Probe each level or obtain the supported set from authoritative provider metadata.
Useful? React with 👍 / 👎.
| if (fs.existsSync(installedJsonPath)) { | ||
| try { | ||
| const raw = fs.readFileSync(installedJsonPath, 'utf8'); | ||
| const parsed = JSON.parse(raw); | ||
| if (parsed && Array.isArray(parsed.plugins)) { | ||
| installedData = parsed; | ||
| } | ||
| } catch (err) { | ||
| warn(`Existing installed.json could not be parsed, creating fresh state: ${err.message}`); | ||
| } |
There was a problem hiding this comment.
Preserve installed plugin state when parsing fails
If installed.json is malformed or temporarily truncated, this catch keeps the fresh { plugins: [] } value and the later write replaces the original file, deleting every existing plugin registration. The core plugin manager deliberately refuses writes while this state is corrupt so it can be repaired; this installer should likewise abort or back up the file instead of treating a parse failure as an empty installation.
Useful? React with 👍 / 👎.
| // 1. Active Probing / Live Capability Probe | ||
| if (!options.skipProbe && providerConfig && providerConfig.base_url) { | ||
| try { | ||
| const probeResult = await probeModelEffort(providerConfig, actualModelName, options); |
There was a problem hiding this comment.
Probe providers that use their default endpoint
A provider base_url is optional in the supported configuration schema because the Anthropic and Google clients can select their standard endpoint themselves. This gate skips both live and metadata probing whenever that optional field is absent, so a valid provider using its default endpoint is reduced to model-name heuristics and custom-named reasoning models are classified as non-reasoning. Resolve the provider's effective endpoint or probe through the existing provider abstraction.
Useful? React with 👍 / 👎.
| async function probeGoogleGenAI(baseUrl, apiKey, targetModel, timeoutMs) { | ||
| // Google GenAI REST: e.g. /v1beta/models/{model}:generateContent?key={apiKey} | ||
| const url = `${baseUrl}/v1beta/models/${encodeURIComponent(targetModel)}:generateContent?key=${apiKey}`; |
There was a problem hiding this comment.
Avoid duplicating the Google API version in probe URLs
Kimi Code forwards a configured Google base_url verbatim to the GenAI SDK, and valid configurations commonly include the API version, such as https://gateway.example/v1beta. This construction turns that into .../v1beta/v1beta/models/..., so every live probe receives a 404 and falls back to heuristics. Append only the model route when the configured URL already ends in /v1beta.
Useful? React with 👍 / 👎.
| if (providerType === 'openai') { | ||
| return await probeOpenAI(baseUrl, apiKey, targetModel, timeoutMs); | ||
| } else if (providerType === 'anthropic') { | ||
| return await probeAnthropic(baseUrl, apiKey, targetModel, timeoutMs); | ||
| } else if (providerType === 'google-genai') { | ||
| return await probeGoogleGenAI(baseUrl, apiKey, targetModel, timeoutMs); |
There was a problem hiding this comment.
Probe supported OpenAI Responses providers
openai_responses is a supported Kimi Code provider type, but this dispatch handles only openai, Anthropic, and Google, so Responses providers never receive a live capability probe. Unless their model name happens to match a heuristic or their /models response exposes the expected nonstandard metadata, they are incorrectly recorded as non-reasoning despite supporting effort through the Responses API. Add an appropriate /responses probe for this provider type.
Useful? React with 👍 / 👎.
| const modelEntry = (config.models && config.models[modelAlias]) || null; | ||
| const supportedEfforts = (modelEntry && Array.isArray(modelEntry.support_efforts)) | ||
| ? modelEntry.support_efforts | ||
| : []; |
There was a problem hiding this comment.
Reject unknown target aliases before updating config
When set receives a misspelled or nonexistent target alias, modelEntry becomes null but execution continues through the permissive standard-level branch. setThinkingEffort then changes the global [thinking].effort, cannot update the nonexistent model section, and the CLI reports that the named model was updated successfully. Match the existence validation used by status before performing any write.
Useful? React with 👍 / 👎.
…ion detection in kimi-effort
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10fb652462
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } else if (header.startsWith('models.')) { | ||
| let key = header.slice('models.'.length); | ||
| key = unquote(key); | ||
| result.models[key] = result.models[key] || {}; |
There was a problem hiding this comment.
Preserve nested model overrides during detection
For a valid unquoted table such as [models.foo.overrides], this branch creates a separate foo.overrides model instead of attaching the table to foo. The advertised detect all flow—and the installer's automatic invocation of it—then probes that phantom model and calls saveModelEffort("foo.overrides", [], ""), overwriting the user's nested support_efforts and default_effort values with empty entries. Parse nested override tables as part of their parent model and exclude them from detection targets.
Useful? React with 👍 / 👎.
| endIndex = lines.length; | ||
| } else { | ||
| const sectionLines = lines.slice(startIndex, endIndex); | ||
| updateKeyValueInLines(sectionLines, 'effort', `"${effortLevel}"`); |
There was a problem hiding this comment.
Keep targeted changes from overwriting the global effort
When the optional target names an inactive model, this still rewrites the global [thinking].effort. The runtime's resolveThinkingEffort gives that global value precedence over model defaults, so set max claude while gemini is active also changes Gemini—and every subsequently selected model—to max; updating Claude's default_effort later in this function does not scope the selection. Either reject inactive targets or persist targeted selections without changing the global effort.
Useful? React with 👍 / 👎.
| thinkingConfig: { | ||
| thinkingBudget: 1024 | ||
| } |
There was a problem hiding this comment.
Probe Gemini 3 with thinking levels
For Gemini 3 models, this probe sends thinkingBudget, although the repository's Google provider explicitly uses thinkingLevel (MINIMAL, LOW, MEDIUM, or HIGH) for that family. A Gemini 3 endpoint that rejects the obsolete field returns a 400 mentioning thinkingConfig or thinkingBudget; the code consequently records the model as non-reasoning and persists an empty support_efforts list instead of falling back to the correct probe. Select the probe parameter according to the model family.
Useful? React with 👍 / 👎.
| max_tokens: 2048, | ||
| thinking: { | ||
| type: 'enabled', | ||
| budget_tokens: 1024 | ||
| }, |
There was a problem hiding this comment.
Probe adaptive Anthropic models with their runtime payload
When a model has adaptive_thinking = true, or is a Claude 4.6-or-newer model inferred as adaptive, the runtime sends thinking: { type: "adaptive" } plus output_config.effort; this probe always sends the legacy enabled/budget form instead. Compatible endpoints that reject budget_tokens return a 400 mentioning thinking, which this detector treats as definitive evidence that the model is non-reasoning and then persists empty effort support. Honor modelConfig.adaptive_thinking and the same version inference used by the Anthropic provider before constructing the probe.
Useful? React with 👍 / 👎.
| */ | ||
| export async function probeModelEffort(providerConfig, targetModel, options = {}) { | ||
| const timeoutMs = options.timeout || 5000; | ||
| const providerType = (providerConfig?.type || 'openai').toLowerCase(); |
There was a problem hiding this comment.
Honor per-model protocol overrides when probing
When a model declares protocol = "anthropic", the runtime routes it through the Anthropic transport regardless of its provider's configured type, but this dispatch considers only providerConfig.type. A model on a kimi provider therefore receives no live probe, while one on an openai provider is probed through /chat/completions instead of /messages; custom-named reasoning models then fall through to heuristics and are persisted as non-reasoning. Derive the probe transport from modelConfig.protocol before falling back to the provider type.
Useful? React with 👍 / 👎.
may be closed. -->
Thinking, DeepSeek-R1, and custom gateways), the CLI does not automatically detect whether a model supports reasoning effort adjustments unless
manually hardcoded with
support_effortsanddefault_effortinconfig.toml.fly.
combined with intelligent heuristic sniffing across common reasoning model families (
o1/o3/gpt-5/6,claude-3-7/opus-4,gemini-2.5/3,deepseek-r1).-
scripts/config-manager.mjs: Provides non-destructive read/write logic forconfig.toml, updatingsupport_efforts,default_effort,and ensuring
"thinking"is present in modelcapabilitieswhile preserving comments, indentation, and structure.-
scripts/effort-cli.mjs: Command-line engine supportingstatus,detect [model|all],set <effort> [model], andlistoperations.-
skills/effort/SKILL.md&commands/effort.md: Exposes the/effortslash command for querying and tuning effort levels (low,medium,high,max).-
hooks/session-start.mjs: Lightweight, fail-openSessionStartlifecycle hook (< 2s budget) that automatically triggers backgrounddetection when an unconfigured model is active.