Uh oh!
There was an error while loading. Please reload this page.
fix: thinking strength now follows the chosen level on Kimi/StepFun/MiniMax coding plans - #2067
Conversation
Kimi Coding Plan was a pre-#2050 orphan: it was missing from the sync-model-metadata PROVIDERS map, so its metadata stayed a hand-written static block (k3 pinned to a single 'max' effort, k3-256k absent), and the runtime wire hard-coded max and never read the level. - sync-model-metadata.mjs maps kimi-coding-plan to models.dev's kimi-for-coding provider; the snapshot now declares k3 (toggle + low/high/max) and k3-256k (low/high/max), and the stale static block is deleted (regenerated snapshot also carries a little openrouter drift from upstream) - buildProviderOptions passes the chosen level through both Kimi wires (anthropic effort and openai-chat reasoningEffort), defaulting to max when unset; k3-256k joins the K3 adaptive-thinking wire - the thinking wire contract test now sweeps kimi-coding-plan's declared levels automatically, closing the silent-forget gap for this provider Live-check pending: confirm Kimi's coding API accepts low/high for k3/k3-256k before relying on non-max levels.
stepfun-step-plan was a dormant twin of the kimi-coding-plan orphan: the sync map only covered stepfun / stepfun-ai / stepfun-ai-step-plan, so stepfun-step-plan lived on a hand-written static block that expanded stepfun (API) records and pinned reasoning facts by hand. - sync-model-metadata.mjs maps stepfun-step-plan to its own models.dev provider; the snapshot now declares all four Step Plan models with upstream effort levels (step-3.7-flash low/medium/high, the 3.5-flash pair low/high) and the static block is deleted - provider-registry.ts: stepfun-step-plan modelsDevId was mislabeled as stepfun; now resolves its own provider facts and the model guard checks the stepfun-step-plan snapshot segment - note: step-router-v1's reasoning capability now follows models.dev (false) instead of the hand-written true; no thinking options were declared either way, so the wire behavior is unchanged Declared levels are swept by the existing thinking wire contract, so this closes the same silent-forget gap for stepfun-step-plan.
…d guard the sync map The MiniMax Coding Plan access path was the last alias-style provider: it declared modelsDevId minimax and reached metadata through a static alias to the MiniMax API segment, so the two models.dev segments could drift apart without anyone noticing (their docsUrl already differs). - sync-model-metadata.mjs maps minimax-coding-plan to its own models.dev provider; the snapshot now carries a dedicated segment (7 models, MiniMax-M3 toggle preserved) and the alias line is deleted - provider-registry.ts: minimax-coding-plan modelsDevId resolves its own segment facts and the fallback model guard checks its own segment - new models-dev-sync-contract test closes the silent-forget gap for good: every registry modelsDevId must resolve to the provider's own snapshot segment (or alias metadata), and every snapshot segment must be declared by the registry with its own id — adding a provider to the sync map without wiring its registry entry is now a test failure - registry modelsDevId now declared for every snapshot-backed provider: anthropic, openai, google, deepseek, kimi-coding-plan, zai-coding-plan, MiniMax, MiniMax-cn, gemini-cli (aliases xai-oauth/opencode-free keep their existing alias ids)
…nc contract Independent review of the previous fix found the sync contract only covered the already-declared universe: a registry provider with no modelsDevId and no sync-map entry (the exact kimi-coding-plan bug shape) was invisible to both directions, and the forward check was tautological (every modelsDevId was assigned from the same generated facts object it compared against). - sync-model-metadata.mjs now also emits GENERATED_MODELS_DEV_DIRECTORY: the complete models.dev provider catalog (id, name, api), not just the mapped subset - models-dev-sync-contract.test.ts becomes three-way: * forward: a declared modelsDevId must name a provider that exists in the directory (catches sync keys pointing at nonexistent sources) * reverse: every snapshot segment must be declared by the registry (unchanged) * directory: a registry provider whose base URL host matches a directory provider must declare modelsDevId — the kimi bug shape is now a test failure. Localhost/self-hosted endpoints are excluded - registry: kimi-coding-plan fallback models now guarded at import time like stepfun/minimax; claude-subscription and openai-codex declare their anthropic/openai segment sources; kimi-for-coding comment notes the intentionally-missing knob is not a sync gap - runtime: kimi case explicitly rejects off (future-proofing for a models.dev 'none' declaration) instead of the dead level !== 'off' guard; openai-chat namespace comment documents the ai-sdk camelCase alias dependency; highspeed branch comment states the no-wire intent - tests: openai-chat high passthrough asserted, kimi off entry-gate behavior asserted, step-router-v1 no-variants intent locked Verified the new contract has teeth: removing kimi's modelsDevId makes both the reverse and directory checks fail.
…ract, main() coverage
Two independent external reviews (Claude Opus, Codex) both found no
P0/P1, but converged on real gaps in the previous review round:
- kimi 'off' rejection was dead code: the entry gate normalized off to
undefined before the case guard, so an explicit off silently became
max (the test even locked that in). The case now checks the raw
thinkingLevel argument and rejects off with empty options.
- the sync contract could not see alias-type orphans: a provider
declaring a *neighbour* segment id (the exact pre-fix stepfun/minimax
shape) passed all three checks, and claude-subscription pointing at
openai passed too. Replaced host-based matching with a deterministic
completeness check: every provider must have its own segment, a
declared alias (xai-oauth/opencode-free/claude-subscription/openai-
codex), or a whitelist reason (9 local/user-configured/volcengine
entries). Both mutation shapes now fail the suite.
- sync-model-metadata.mjs main() had zero coverage (the place the
orphan bug lived): added an end-to-end fixture test over the real
main() path, and exported PROVIDERS/main(argv) to make it testable.
- the wire-contract sweep only exercised kimi's anthropic branch: added
an explicit openai-chat sweep for kimi-coding-plan.
- GENERATED_MODELS_DEV_DIRECTORY shrank to { api?: string } (name was
never consumed); dropped the unused FACTS_BY_PROVIDER alias.
- stepfun guard now covers all 4 fallback models (slice(0,3) was stale);
minimax guard label says MiniMax Coding Plan.
Deferred (recorded, not implemented): k3 low/high live check against
the Kimi API (B1), kimi protocol-decision consolidation onto
resolveModelRuntime (B3) and camelCase namespace cleanup (D3) as a
runtime refactor, kimi-for-coding forced-max semantics pending the live
check, user-visible upstream drift noted in the PR description.
Verified: core 772/772, runtime 3089 pass / 4 pre-existing macOS symlink
failures, sync scripts 9/9, lint/format/typecheck clean. Mutation tests:
claude-subscription→openai and minimax-coding-plan→MiniMax.id both now
fail the contract suite.Astro-Han
commented
Aug 4, 2026
Review round (edef7eb) — internal subagent reviewAn internal multi-angle review found three gaps in the initial fix; all addressed in this commit:
Also: kimi fallback import-time guard (matching the stepfun/minimax convention), Verified: core 772/772 (the new directory check caught a real lm-studio false positive on its first run, fixed by excluding localhost), runtime 3089 pass / 4 pre-existing macOS symlink failures, scripts 8/8, lint/format/typecheck clean. |
Astro-Han
commented
Aug 4, 2026
External review round (Claude Opus + Codex) — b3f8471Two independent external reviews (Claude Opus, 55 turns; Codex) both found no P0/P1, but converged on three real gaps in the previous round; all fixed:
Additional fixes: wire-contract sweep gained an explicit kimi openai-chat variant (Claude B2); Deferred with reasons: k3 low/high live check → now confirmed closed (the Kimi API accepts low/high/max; see the follow-up comment), protocol-decision consolidation onto Verified: core 772/772, runtime 3077 pass / 4 pre-existing macOS symlink failures, scripts 9/9, lint/format/typecheck clean. StepFun official docs cross-checked: step-router-v1 is a routing model ( |
Astro-Han
commented
Aug 4, 2026
B1 confirmed closed — external fact verifiedThe Kimi Coding Plan API accepts
Remaining deferred items (recorded in the review rounds, not blocking merge):
|
Uh oh!
There was an error while loading. Please reload this page.
Problem
Kimi Coding Plan did not expose thinking-strength controls. The root cause was a pre-#2050 orphan:
kimi-coding-planwas missing from thePROVIDERSmap inscripts/sync-model-metadata.mjs, so its metadata stayed frozen in a hand-written static block from #1164 (k3 pinned to a singlemaxeffort, k3-256k absent), and the runtime wire hard-codedmaxwithout ever reading the chosen level.A systematic audit found two more instances of the same class:
stepfun-step-plan— same orphan shape (upstream segment existed, sync map missing, hand-written static block expanding thestepfunAPI segment,modelsDevIdmislabeled asstepfun).minimax-coding-plan— alias debt (declaredmodelsDevId: minimaxand reached metadata through a static alias to the MiniMax API segment; the two models.dev segments could drift apart silently).Changes (5 commits)
kimi-coding-plan (f2fdc7b): sync map gains
kimi-coding-plan → kimi-for-coding; the snapshot now declares k3 (toggle + low/high/max) and k3-256k (low/high/max); the hand-written static block is deleted;buildProviderOptionspasses the chosen level through both wires (anthropiceffortand openai-chatreasoningEffort), defaulting to max when unset; k3-256k joins the K3 adaptive-thinking wire.stepfun-step-plan (d61e178): sync map gains its own models.dev segment; the static block expanding
stepfunrecords is deleted;modelsDevIdcorrected from the mislabeledstepfunto its own segment, and the import-time model guard now checks thestepfun-step-plansegment.minimax-coding-plan + sync contract (3a98f74): sync map gains its own segment (7 models, MiniMax-M3 toggle preserved); the alias line is deleted; registry
modelsDevIdand the fallback guard point at its own segment. Addsmodels-dev-sync-contract.test.tsand declaresmodelsDevIdfor every snapshot-backed provider (anthropic, openai, google, deepseek, kimi-coding-plan, zai-coding-plan, MiniMax, MiniMax-cn, gemini-cli, claude-subscription, openai-codex).Three-way sync contract (edef7eb): sync script emits the complete models.dev directory (
GENERATED_MODELS_DEV_DIRECTORY); the contract gains a directory direction (registry base-URL host matching a directory provider must declare it); kimioffis explicitly rejected in the wire; fallback guards and intent comments added.External review round (Claude Opus + Codex) (b3f8471): both independent reviews found no P0/P1 but converged on real gaps, all fixed:
offrejection was dead code (the entry gate normalizedofftoundefinedbefore the case guard, silently turning an explicit off into max; a test even locked that in). The case now checks the rawthinkingLevelargument and rejectsoffwith empty options.claude-subscription → openaipassed too. Replaced host-based matching with a deterministic completeness check: every provider must have its own segment, a declared alias (xai-oauth/opencode-free/claude-subscription/openai-codex), or a whitelist reason (9 local/user-configured/volcengine entries). Mutation-tested: both bug shapes now fail the suite.main()in the sync script had zero coverage (the place the orphan bug lived): added an end-to-end fixture test over the realmain()path (9/9 script tests).GENERATED_MODELS_DEV_DIRECTORYshrank to{ api?: string }(name was never consumed); dropped the unusedFACTS_BY_PROVIDERalias.slice(0, 3)was stale); minimax guard label says MiniMax Coding Plan.Verification
/var→/private/varsymlink failures (identical on main, unrelated)[low, high, max]; step-3.7-flash →[low, medium, high]; MiniMax-M3 toggle-only, no regressionreasoning: falseis correct; the old hand-writtentruewas wrong), and the low/medium/high effort levels match the snapshotConfirmed external facts
low/high/maxfor k3 / k3-256k (verified by the user; no live-check script needed). The snapshot's effort levels are safe to pass through; no STATIC narrowing back to['max'].Deferred (tracked, not blocking)
resolveModelRuntime+ provider-options camelCase cleanup (runtime refactor, suggested as a separate PR)effort: 'max'despite having no declared reasoning_options (pre-existing behavior, not a regression; models.dev declares none, the UI offers no menu, and default-max is the sensible wire)maxOutputTokensdrift for 4 models (snapshot refresh, noted in the generated diff)