Skip to content

fix(tui): replay reasoning_content for kimi k3.1 - #137

Open
asto18089 wants to merge 4 commits into
pinvou3-cleanfrom
fix/kimi-k31-replay-spellings
Open

asto18089 wants to merge 4 commits into
pinvou3-cleanfrom
fix/kimi-k31-replay-spellings

Conversation

@asto18089

@asto18089 asto18089 commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Teaches the reasoning-replay gate the K3.1 family spellings so a K3.1 tool round replays reasoning_content like bare K3 does.

The replay gate only learned the bare k3 ids through is_exact_direct_moonshot_k3_route / is_exact_kimi_code_k3_route. The k3.1-pro[-highspeed] Kimi Code membership spellings fell through to the generic heuristics, which never matched, so the sender omitted the assistant tool-call message's reasoning_content on the follow-up request — the Kimi Code error reference documents that missing field as HTTP 400. The kimi-k3.1-pro[-highspeed] direct-platform spellings did replay at default effort via the generic kimi- prefix heuristic, but were still stripped whenever a stored effort of off was present; the review-round arm (2026-11-04) makes all four spellings replay catalog-independently and effort-immune on both vendor routes, including the overseas api.kimi.ai/coding/v1 endpoint.

Contract

  • models-overview: the K3.1 spellings carry kimi-k3's parameter constraints verbatim ("参数约束也与 kimi-k3 完全一致") — always-reasoning, so the replay rule applies with no effort gate.
  • Kimi Code error reference (missing-reasoning-content-field): the replayed tool-call assistant message must carry reasoning_content.

Scope

Deliberately narrow: the gate gains its own K3.1 arm on the vendor's two official routes instead of extending the shared exact-K3 matchers, whose other consumers (effort shaping, route validation, pickers) carry different K3-only contracts. Regression pins both routes with a tool-call round (assistant thinking + tool use → tool result → assert reasoning_content replays).

Context

Surfaced by the pinvou-agent#825 review round 3 (the app's model-catalog refresh adds these presets); the consumer's wire-body harness is ready to pin the second request once this lands.

No-Issue: follow-up to the pinvoy-agent#825 model-catalog review; this repository has issues disabled.

@asto18089

asto18089 commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

Rebased onto the current #131 head (0583e19cb): this branch was cut from the pinned candidate head f1eb10efc, which has since advanced by 29 commits, and #131 is still open — so this PR's chain carried a stale slice of the candidate lineage.

The single K3.1 commit was replayed onto 0583e19cb with an identical patch-id (64c64a9b); message and DCO unchanged. Gates on the rebased tree: kimi_k31 regression 1/1, kimi + replay_reasoning suite 126/0. New head: 1d9b74bec.

Also retargeted this PR's base from main to feat/host-armed-guardrail-prompts (#131's head branch): with main as base the commits tab carried the whole candidate lineage (2,300+ commits); stacked on #131 it now shows exactly the one K3.1 commit. When #131 merges, this PR needs a re-cut onto the squash and a retarget back.

@asto18089
asto18089 changed the base branch from main to feat/host-armed-guardrail-prompts November 4, 2026 09:03
@asto18089

Copy link
Copy Markdown
Collaborator Author

Pushed the overseas-endpoint follow-up for the review P2 (5765736266 / 5765853804): 5e4545615 teaches both Kimi Code route gates the official api.kimi.ai/coding/v1 host the host catalog registers and prefs preserves verbatim.

  • Exact gate (is_exact_kimi_code_route) + loose gate (moonshot_base_url_uses_kimi_code) now accept the .ai host; neighboring paths, wrong scheme/port, and custom gateways stay outside (test matrix extended with .ai positives and .ai neighbors).
  • The K3.1 tool-round regression now pins both bare ids (k3.1-pro / k3.1-pro-highspeed) on the overseas route, alongside the existing .com / direct-platform arms.
  • Credential guidance strings updated to name both official hosts.
  • Negative validation: removing the .ai arm turns the endpoint-gate test red; full codewhale-config 644 pass / 0 fail, kimi family 125 pass / 0 fail, cargo fmt --check clean.

Sequencing note for the parent pin: once this and the #131 stack land on pinvou3-clean, the codewhale-hq#825 fork-guard registration and gitlink flip to the public head and the parent fast-gate goes green — the current pin (1d9b74bec) will need one more bump to this SHA after the public advance.

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh multi-lane review (7 lanes + 2 verification lanes: gate semantics, shared-matcher consumer audit, Kimi-family sweep, other-vendor sweep incl. all pinvoy-agent#825 families, mutation testing at both heads, smuggle/CI classification, claim verification). All findings verified at head 5e4545615. Verdict: the fix is real, root-caused, and well-scoped; requesting changes on three items before merge.

Verified-good

  • Root cause confirmed: build_chat_messages_with_reasoning emits reasoning_content on the replayed tool-call assistant message only when should_replay_reasoning_content_for_provider_on_route returns true (chat.rs:2885,2918-2920); pre-PR the gate only learned bare K3 via the exact matchers. The new arm is catalog-independent, effort-immune (consistent with the always-reasoning contract), and correctly ordered (no shadowing with the K3 arms — spelling sets are disjoint, all matchers exact-compare).
  • Dedicated arm (vs extending the shared matchers) is justified: ~23 production consumers of is_exact_direct_moonshot_k3_route/is_exact_kimi_code_k3_route gate K3-only contracts (effort dialects, route shapes, picker ladders, output limits, search timeout floor, two display labels); teaching the shared matchers K3.1 would have flipped all of them at once. PR body claim verified factually accurate.
  • Commit 2's .ai gate arm is exactly as strict as the .com arm (is_exact_https_route reuse), with negative neighbors (case/path/scheme/port/query) in provider.rs:2226-2253. Loose-helper over-match safety verified (host+path pinned, no substring matching).
  • Smuggle: clean — 4 files, every hunk serves the disclosed purpose; no deps/submodule/workflow changes. Both commits DCO-signed, conventional subjects.
  • Mutations at 5e4545615: arm deletion, Kimi-Code URL, .ai URL, and 3 of 4 spellings all go red when removed. fmt rc=0; applicable clippy gates rc=0; kimi/replay suites 125/75 green; ubuntu CI passed the full suite with the PR applied.

Must fix

  • M1 — doc-comment hijack (chat.rs:3485-3504): the helper is_kimi_k31_model_spelling was inserted between the gate fn's doc block and the gate fn, so the entire "Route-aware reasoning replay policy." block now rustdoc-attaches to the 4-line spelling helper; the gate fn is left undocumented; "see the arm inside" (3494) points backwards; sentences 3492-3494 vs 3495-3496 duplicate each other. The inherited sentence "only Kimi Code's exact membership-plan endpoint has this replay contract" (3489-3490) is contradicted by the direct-platform K3 arm (3513) and the new arm itself (3533-3534) as the block now stands. Compiles clean, so CI cannot catch it.
  • M2 — commit 2 is incomplete on the config side (split-brain): the TUI exact gate and the TUI copy of the loose fallback learned api.kimi.ai/coding/v1, but the config-crate twins did not: moonshot_base_url_uses_kimi_code (crates/config/src/lib.rs:4619-4624, still .com-only) and provider_base_url_is_official(Moonshot, …) (lib.rs:4874-4882, delegates to it). Verified consequence: a Moonshot route on api.kimi.ai/coding/v1 with no model resolves the default to kimi-k2.7-code (the config-crate fallback misses), which validate_kimi_code_api_model_id then hard-rejects as a direct-platform id on the membership endpoint — a config-resolution failure on exactly the route commit 2 exists to support (the same config works on api.kimi.com/coding/v1). provider_base_url_is_official returning false also contradicts its own "so route canonicalization and credential scoping cannot disagree" doctrine (lib.rs:4852-4856); consumers include client/responses.rs:905, cli lib.rs:3117, app-server chat_completions.rs:153/255.
  • M3 — required link gate red ×3, PR-caused: the body has no line-start Closes #N / No-Issue: anchor (checked pr-issue-link.yml's regex; the mid-sentence "pinvoy-agent#825" mentions don't satisfy it). Will add the line in the fix wave.

Minors (will fix in the fix wave unless noted)

  • m — PR body overstates the pre-PR bug: the two kimi-k3.1-pro[-highspeed] spellings already replayed at default effort via the kimi- prefix heuristic (models.rs:447); the actual pre-PR failures were (a) bare k3.1-pro[-highspeed] on Kimi Code never replaying and (b) all four spellings stripped at stored effort=off. Will recalibrate the body wording.
  • m — "the host catalog registers api.kimi.ai" (provider.rs:517-518, client.rs:9386-9387, commit 2 body): no host catalog in this repo registers it (docs still document api.kimi.com only); name the app-side catalog explicitly or update docs/PROVIDERS.md/docs/CONFIGURATION.md.
  • m — typo "pinjou-agent#825" ×3 in code (provider.rs:518, client.rs:9328, client.rs:9387) + commit 2 body.
  • m — stale adjacent strings: the membership-rejection error still names only api.kimi.com/coding/v1 (config.rs:10227); Moonshot guidance suggests only the .com URL (config.rs:7075).
  • m — test-strength (mutation-verified): unpinned — the direct-platform matcher (masked by the generic kimi- prefix; an effort=off direct leg would pin it), the provider == Moonshot route restriction (no custom-gateway negative), case/trim normalization (no variant input), and the 4th spelling kimi-k3.1-pro-highspeed. Sibling K3 tests (chat.rs:5166-5191, 6228-6282) set the negative-test norm.
  • m — commit 2's .ai widening legitimately extends K3 effort shaping / sampling / route shape / billing / pickers to the overseas endpoint, but only the gate + replay legs are tested on .ai.

Follow-ups disclosed (not for this PR)

  • OpenRouter moonshotai/kimi-k3.1-pro (a codewhale-hq#825 row): not deterministically covered — the provider == Moonshot restriction is faithful to the aggregator precedent, but the generic heuristics miss the moonshotai/ prefix (only k2.x org ids enumerated, models.rs:473-476), so replay depends on the runtime models.dev cache (fails offline/fresh installs); the same id on Volcengine replays via the bare-kimi- prefix — a pure prefix artifact. Cheap fix: add the org-prefixed spellings to the recognition list.
  • MiniMax M3.1-Flash (codewhale-hq#825-served, always-thinking): minimax-m3.1-flash is in no recognition list → no replay, no stream classification. Different vendor contract; genuine follow-up.
  • Other codewhale-hq#825-family recognition staleness (pre-existing): zai-org/GLM-5.3 on SiliconFlow, minimax/minimax-m3.1-flash and anthropic/claude-opus-5.5 org-prefixes on OpenRouter, Qwen/Qwen3.8-27B, mimo v2.6 ids, gemini-4 thought-signature matcher, kimi-k2.5 preserve_thinking verification, and the app-side bridge-arm gap for Mistral/Together/Meta/Groq (belongs on pinvoy-agent#825, not here).
  • CI reds other than link are base/environmental (verified against base 0583e19cb): Lint = pre-existing clippy::collapsible_if at turn_loop.rs:5611 (untouched file); cargo-audit/cargo-deny = RUSTSEC-2026-0430 in the untouched lockfile; Lint & Type Check = release-data drift; Version drift / Integrations / Test-windows = unrelated subsystems (the PR's own test passed on Windows).
  • Upstream-first note: the gate is foundation code; fork-policy letter prefers upstream-first for all-embedder fixes — worth an upstream PR once the stack lands.
  • Known pre-existing flake observed during verification: forkguard_model_operation_auxiliary_retry_keys (operation_tests.rs:379) is order-dependent, passes in isolation, unrelated to this PR.

Fix wave incoming for M1-M3 + the minors; follow-ups stay disclosed.

@asto18089

Copy link
Copy Markdown
Collaborator Author

Fix wave pushed as fd0aef4 + e11b90e (head e11b90ec5), addressing the review round:

M1 — doc-comment hijack (e11b90e): the spelling helper now carries its own doc line; the gate's "Route-aware reasoning replay policy." block is restored directly above should_replay_reasoning_content_for_provider_on_route with an accurate K3.1 paragraph (the duplicated sentence pair and the backwards "see the arm inside" pointer are gone).

M2 — config-crate .ai split-brain (fd0aef4): moonshot_base_url_uses_kimi_code in the config crate now recognizes both membership hosts, with the exact-gate-subset doctrine documented on the helper, and provider_base_url_is_official(Moonshot, …) follows automatically (it delegates). New test kimi_code_loose_route_recognition_covers_both_membership_hosts pins: both hosts exact↔loose↔official agreement, neighboring /coding paths (loose+official but deliberately not exact), case variants, lookalike hosts/scheme downgrades as negatives, and the direct platform staying official-but-not-membership.

M3 — link gate: body now carries a line-start No-Issue: line and the pre-PR claim is recalibrated (the direct-platform spellings did replay at default effort via the generic kimi- prefix; the true pre-PR failures were the bare Kimi Code spellings never replaying and the effort=off stripping — body now says exactly that).

Minors: route-gate comment premise reworded to "the app's model catalog registers api.kimi.ai" (provider.rs); membership-rejection and plan-key guidance strings now name both hosts; remaining pinjou typo in the overseas test comment fixed.

Test-strength (e11b90e): the K3.1 regression test now pins all four spellings across both hosts, plus — each verified red under its targeted mutation before revert —

  • effort=off direct-platform leg → pins the direct-platform matcher (mutation: matcher removed → red at exactly this leg; the generic kimi- prefix masks this at default effort),
  • custom-gateway + non-Moonshot-provider negatives → pin the route restriction (mutation: provider == Moonshot removed → red at the non-Moonshot leg),
  • case and padding variants on the bare Kimi Code ids (mutations: case-sensitized / trim removed → red at exactly those legs).

Erratum to the review comment: the pinjou-agent#825 typo list included client.rs:9328 — that line was already correct; the typo existed only at client.rs:9388 (now fixed) plus provider.rs:518 (fixed) and commit 2's message (history, left as-is).

Local gates: cargo fmt --check rc=0; clippy --workspace --all-features --locked with the fork-ci allowance set, both --lib --bins and --all-targets, rc=0; codewhale-config --lib -- kimi 8/8; tui kimi 125/0, replay 75/0, kimi_code 46/0, upgraded kimi_k31 1/0.

Disclosed, not fixed here (follow-ups): OpenRouter moonshotai/kimi-k3.1-pro recognition (generic heuristics miss the moonshotai/ prefix; replay there depends on the runtime models.dev cache), MiniMax M3.1-Flash recognition (minimax-m3.1-flash in no recognition list), and the other codewhale-hq#825-family enumeration staleness items from the review comment. Upstream-first contribution of the gate change is also worth a follow-up per fork-policy.

asto18089 added a commit to Pinvou/pinvou-agent that referenced this pull request Nov 5, 2026
Jensen's review 5779604267 (P2): after the rebase onto main 78dcc1c
the PR's child is the public maintenance head 11cba403d, whose
exact-route exceptions only recognize k3/k3-256k - a tool turn on
the bare k3.1-pro ids omits the required assistant reasoning_content
and the Kimi Code gateway answers 400. Defer the two bare rows from
the Kimi Coding Plan group (en/ja keys follow; the row-scoped FE
imageCapable annotation goes with them) until the pinned child
carries the upstream replay repair (Pinvou/CodeWhale#137). The
prefixed platform rows ride the kimi-k3 fallback and stay; the
custom row still permits manual ids. Test expectations flipped to
the deferred semantics with the restore condition documented.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit to Pinvou/pinvou-agent that referenced this pull request Nov 5, 2026
Jensen's review 5779604267 (P2): after the rebase onto main 78dcc1c
the PR's child is the public maintenance head 11cba403d, whose
exact-route exceptions only recognize k3/k3-256k - a tool turn on
the bare k3.1-pro ids omits the required assistant reasoning_content
and the Kimi Code gateway answers 400. Defer the two bare rows from
the Kimi Coding Plan group (en/ja keys follow; the row-scoped FE
imageCapable annotation goes with them) until the pinned child
carries the upstream replay repair (Pinvou/CodeWhale#137). The
prefixed platform rows ride the kimi-k3 fallback and stay; the
custom row still permits manual ids. Test expectations flipped to
the deferred semantics with the restore condition documented.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Nov 5, 2026
The Moonshot chat path prefixes a digit-led registry tool name with `_`
to satisfy Kimi's documented `name` pattern, but the loop only ran
outbound: the response decoder deliberately keeps the wire spelling, no
layer mapped `_3d_render` back to `3d_render`, and every model call for
such a tool died at dispatch with "tool not available" — the tools array
kept advertising the name, so the model would retry the same spelling
until the turn ended.

Two halves now close the loop:

- Engine dispatch: after every exact and alias lookup misses,
  `resolve_tool_definition` falls back to the unprefixed name for the
  exact `_<digit>` shape the prefix produces. A genuinely underscore-led
  name is never stripped, and a registry holding both spellings still
  resolves exactly (the fallback only fires after the exact lookups
  missed), so approval and policy gates see the canonical registry name.
- Replay: assistant tool_calls persist the registry spelling, so the
  Moonshot message builder re-applies the wire prefix on replayed
  `tool_calls` (shared `prefix_digit_led_function_name` helper with the
  tools-array pass). The history now matches the tools array and stays
  inside the documented pattern; pre-fixture histories that already
  carry `_` pass through untouched.

Also pins the hand-copied sampling-route matcher to the config-crate
reference on the shared host (the twin covers the overseas host until
PR #137 widens the shared matcher; that assertion is what flips when it
lands), and restores the doc comment that `sanitize_moonshot_chat_tools`
had lost when the prefix function was inserted between the comment and
its function.

No-Issue: completes the digit-led fix in this PR's own series; the
repository has issues disabled.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit to Pinvou/pinvou-agent that referenced this pull request Nov 6, 2026
Jensen's review 5779604267 (P2): after the rebase onto main 78dcc1c
the PR's child is the public maintenance head 11cba403d, whose
exact-route exceptions only recognize k3/k3-256k - a tool turn on
the bare k3.1-pro ids omits the required assistant reasoning_content
and the Kimi Code gateway answers 400. Defer the two bare rows from
the Kimi Coding Plan group (en/ja keys follow; the row-scoped FE
imageCapable annotation goes with them) until the pinned child
carries the upstream replay repair (Pinvou/CodeWhale#137). The
prefixed platform rows ride the kimi-k3 fallback and stay; the
custom row still permits manual ids. Test expectations flipped to
the deferred semantics with the restore condition documented.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit to Pinvou/pinvou-agent that referenced this pull request Nov 6, 2026
Jensen's review 5779604267 (P2): after the rebase onto main 78dcc1c
the PR's child is the public maintenance head 11cba403d, whose
exact-route exceptions only recognize k3/k3-256k - a tool turn on
the bare k3.1-pro ids omits the required assistant reasoning_content
and the Kimi Code gateway answers 400. Defer the two bare rows from
the Kimi Coding Plan group (en/ja keys follow; the row-scoped FE
imageCapable annotation goes with them) until the pinned child
carries the upstream replay repair (Pinvou/CodeWhale#137). The
prefixed platform rows ride the kimi-k3 fallback and stay; the
custom row still permits manual ids. Test expectations flipped to
the deferred semantics with the restore condition documented.

Signed-off-by: asto <asto18089@126.com>
The K3.1 spellings (kimi-k3.1-pro[-highspeed] on the direct platform,
k3.1-pro[-highspeed] on Kimi Code) inherit the K3 contract verbatim,
including the replay rule: the API requires the assistant
reasoning_content on later tool turns and answers HTTP 400 when a
replayed tool-call message misses it (Kimi Code error reference,
missing-reasoning-content-field).

The reasoning-replay gate only learned the bare k3 ids through the
exact-K3 matchers, so a K3.1 tool round fell through to the generic
heuristics and stripped the required field. The gate carries its own
K3.1 arm on the vendor's two official routes; it is deliberately
narrower than extending the shared matchers, whose other consumers
(effort shaping, route validation, pickers) carry different K3-only
contracts. Regression pins both routes with a tool-call round.

Signed-off-by: asto18089 <asto18089@126.com>
The host catalog registers https://api.kimi.ai/coding/v1 as an official
alias of the Kimi Code membership plan and saved settings keep it
verbatim, but the exact route gate only learned api.kimi.com — so on
the overseas route the K3.1 tool-turn replay contract (and the default
model fallback, credential guidance, and billing presentation) still
missed. Teach both the exact and the loose Kimi Code route gates the
.ai host, keeping neighboring paths, lookalike hosts, and custom
gateways outside the membership semantics. The K3.1 regression now
pins both bare ids on the overseas route.

Review: pinjou-agent#825 5765736266 / 5765853804
Signed-off-by: asto <asto18089@126.com>
Commit 2 taught the TUI-side loose fallback about
api.kimi.ai/coding/v1 but left the config-crate twin and
provider_base_url_is_official behind, so the two layers disagreed
about the official endpoint family: a model-less Moonshot route on
the overseas host resolved the default direct-platform id and then
hard-failed membership validation on exactly the route the commit
exists to support. Widen the loose membership recognition to both
hosts, document the exact-gate subset doctrine it must keep, and
pin the relation (and the lookalike/neighbor negatives) with tests.

Also fixes the route-gate comment premise (the overseas alias is
registered by the app-side model catalog, pinvoy-agent#825) and
its pinjou typo.

Signed-off-by: asto <asto18089@126.com>
The K3.1 helper was inserted between the replay gate and its doc
block, so the whole route-aware replay policy comment rustdoc-
attaches to the 4-line spelling matcher while the gate itself is
left undocumented, with a duplicated sentence pair and a backwards
"see the arm inside" pointer. Give the helper its own doc line and
restore the gate's policy doc with an accurate K3.1 paragraph.

The regression test now pins what the arm actually promises: all
four spellings across both vendor hosts, the stale-effort-off
always-replay case (which is what the direct-platform matcher is
for — the generic kimi- prefix masks it at default effort), the
case/padding normalization on the bare Kimi Code ids, and the
custom-gateway / non-Moonshot dialect boundary as negatives.

Also aligns the membership-rejection and plan-key guidance strings
with both membership hosts and fixes the remaining pinjou typo in
the overseas test comment.

Signed-off-by: asto <asto18089@126.com>
@asto18089
asto18089 force-pushed the fix/kimi-k31-replay-spellings branch from e11b90e to 2602208 Compare November 13, 2026 16:53
@asto18089
asto18089 changed the base branch from feat/host-armed-guardrail-prompts to pinvou3-clean November 13, 2026 16:53

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh multi-lane review (6 lanes: claims audit incl. live vendor-doc verification, deep gate semantics, smuggling + #138 overlap + commit hygiene, stream-side completeness, build/test/fmt/clippy + 10-leg mutation testing, elegance/engineering). Rebased first: the parent #131 merged as squash e2a0e14fd, so the stack was re-cut onto pinvou3-clean 6f7a3a748 — zero conflicts, patch content identical (hunk offsets only), no kimi-related drift on the touched files between the old cut point and the new base (0583e19cb..6f7a3a748 touched only 78804c83e, error-source, unrelated). New head 2602208af, pushed; base retargeted feat/host-armed-guardrail-prompts → pinvou3-clean (MERGEABLE). DCO intact on all 4 commits.

Verdict up front: the replay fix itself is real, root-caused, and strongly pinned — but I'm requesting changes (posted as a comment; GitHub refuses request-changes on the author's own PR) on the undisclosed stream-side gap and two truthfulness minors.

Verified-good

  • Root cause confirmed at base: the gate learned only bare K3 via is_exact_direct_moonshot_k3_route (kimi-k3, direct) / is_exact_kimi_code_k3_route (k3/k3-256k, membership); bare k3.1-pro[-highspeed] fell through every static heuristic (DeepSeek-only requires_reasoning_content, exact-id-only catalog, no kimi- prefix) → stripped; kimi-k3.1-pro[-highspeed] replayed at default effort via the kimi- prefix but died at the effort=off early-return. Body claims 1-4 match code exactly.
  • The arm is correctly placed and equivalent: precedes the effort check like bare K3 (mutation M10 — moving it after — goes RED at exactly the effort-off leg); case/trim semantics proved equivalent to the sibling eq_ignore_ascii_case arms; no shadowing (spelling sets disjoint, full-string compares); moonshot_base_url_is_exact_direct_platform covers both .cn and .ai direct hosts.
  • is_exact_https_route adversarial sweep clean: scheme/host case-fold, path case-sensitive, rejects http, :443, query, fragment, userinfo, //host, double trailing slash — the .ai arm mirrors the .com arm exactly.
  • Split-brain sweep clean: every production membership-semantics site is .ai-aware via the two canonical gates (default-model fallback, official-host scoping, billing, web-search capability, credential help, replay/billing delegators). Remaining .com-only sites are comments/test fixtures only (see follow-ups).
  • The .ai widening of is_exact_kimi_code_route is the right move, not the lazy one: a narrower K3.1-only overseas predicate would leave bare K3 itself broken on api.kimi.ai/coding/v1 (no effort dialect, no replay). Live Kimi Code docs confirm .ai as the official overseas endpoint. The final state's exact⊆loose doctrine is documented and tested (kimi_code_loose_route_recognition_covers_both_membership_hosts).
  • Dedicated-arm justification verified, not overstated: ~23 production consumers of the shared K3 matchers gate K3-only contracts (max_completion_tokens, nested thinking.effort dialect, temperature/top_p strip, output ceiling, RouteShape, pickers ×3, effort normalization, timeout floor, labels, stream class). Extending them would have flipped all at once.
  • Claims audit: models-overview always-reasoning contract TRUE (live platform.kimi.com/docs/api/models-overview.md: "K3 始终进行推理思考…"; K3.1 rows subject to the K3-series column; verbatim 「参数约束也与 kimi-k3 完全一致」 present in the codewhale-hq#825 record); HTTP 400 substance TRUE (live Kimi Code error reference documents "thinking is enabled but reasoning_content is missing in assistant tool call message" → 400); api.kimi.ai/coding/v1 registration TRUE (local app model-catalog.js:366-372 endpointAlias + prefs/model.rs verbatim); codewhale-hq#825 consumption real (merged 2026-11-06; its fork pins were deliberately reverted, dependency direction documented — "pin the second request once this lands" is accurate).
  • Smuggle: CLEAN — all 17 hunks across the 6 files serve the disclosed claims or the disclosed prior-round fixes; no deps/lockfile/workflow/submodule/i18n/version changes; no #138 debris.
  • Gates: cargo check 0 warnings; tui kimi 125/0, kimi_code 46/0, config lib 645/0/1-ignored, both new tests green on the restored tree; fmt --check clean; clippy at the CI profile (ci.yml:359) — 0 PR-introduced lints (2 pre-existing collapsible_if at turn_loop.rs:5662, subagent/mod.rs:8058, byte-identical on base).
  • Mutations 10/10 RED (each reverted, tree clean after): arm deletion, provider conjunct, case-sensitize, trim removal, .ai exact line, config-crate .ai loose lines, direct-platform leg, kimi-code leg, highspeed spellings, and the ordering survivor check — no survivors; the test genuinely pins arm-before-effort-check.

Must fix

  • M1 — stream-side classification gap, undisclosed (chat.rs:3617-3659): is_reasoning_model_for_stream_on_route did not learn K3.1. Matrix (Moonshot provider, head): kimi-k3.1-pro[-highspeed] stream-classify via the generic kimi- prefix on all routes; the two bare membership spellings k3.1-pro[-highspeed] — the heart of this PR — get ReasoningStreamStyle::None on every route, because arm 1 is bare-K3-only and model_supports_reasoning has no bundled k3.1 row and no matching prefix. Consequence: on Kimi Code + k3.1-pro + streaming, every turn streams the CoT as answer text (parse_sse_chunk_with_reasoning_style → push_text_delta) and the trace re-sent on later tool turns as assistant content — context pollution of the tool loop, on the same route where bare k3 gets Thinking cells (pinned by bare_k3_reasoning_semantics_are_scoped_to_exact_kimi_code_route). Non-stream is safe (parse_chat_message_for_route promotes reasoning_content unconditionally), and the sanitizer placeholder path incidentally starts working for K3.1 via the widened replay gate — but that behavior is unpinned here. The closing comment on duplicate #138 shows the author knew exactly this; the body does not disclose it. Fix is a ~7-line mirror arm + a stream pin mirroring the replay test — in this PR or an immediately-pushed companion, with body disclosure either way. Not grading BLOCKER only because no hard failure occurs (placeholder keeps requests valid).
  • M2 — commit-message truthfulness: 2602208af's body claims it "fixes the remaining pinjou typo in the overseas test comment" — it is still there (client.rs:9450 pinjou-agent#825), and the round's earlier "fix" replaced pinjou with a different misspelling at provider.rs:518 (pinvoy-agent#825); the PR body's No-Issue line carries pinvoy-agent#825 too (line 14 is correct). Three residual misspellings + one false fix claim. Trivial to fix, but a fix-wave commit must not claim a fix it doesn't make.

Minors

  • m1 — effort shaping sends a vendor-rejected payload to K3.1 at stored effort=off: bare K3 on the membership route gets {thinking:{enabled,effort:low}} (Off→Low, chat.rs:195-216); K3.1 falls to the generic arm (client.rs:4095-4099) and gets {thinking:{type:disabled}} — the payload chat.rs:222-224 itself calls "not truthful" for the always-thinking family, and live docs state K3.1 Pro rejects disabled-thinking requests (400). The #3024 premise ("Kimi models accept thinking enable/disable") predates K3.1. Pre-existing generic behavior, not worsened here — but this PR is what makes K3.1 on these routes its advertised purpose, so disclose it (or teach the shaping layer the K3.1 spellings / extend the defensive Off→Low) in the same follow-up as M1's seam closure.
  • m2 — newly codified exact⊆loose invariant breaks on whitespace-padded input: the exact gate trims (is_exact_https_route does base_url.trim()), both loose copies only trim_end_matches('/') (config/lib.rs:4625, tui config.rs:9943), so " https://api.kimi.com/coding/v1" is exact-true/loose-false (and provider_base_url_is_official passes the raw string through). Inherited asymmetry, but the PR makes the invariant load-bearing doc + test — trim in the loose copies or scope the doc claim.
  • m3 — commit slicing: commits 2+3 leave a broken intermediate (after cf8a27b4c the config-crate exact gate knows .ai but the loose gate and provider_base_url_is_official don't — the exact⊄loose state commit 3's own body admits; a model-less .ai route hard-fails between them) → bisect hazard; squash 2+3. Commit 4 also mixes commit 1's doc-attachment repair with unrelated copy changes. History is being rewritten anyway with the rebase push — cheap to fix now.
  • m4 — test legs shielded by the generic heuristic: the two direct-platform default-effort legs (kimi-k3.1-*) pass via the kimi- prefix even with the arm dead (test docstring acknowledges this); the uniquely pinning legs are the effort-off leg and the bare membership ids, both present and mutation-proven. Keep or re-comment — no action strictly required.
  • m5 — "missing-reasoning-content-field" cited as if a documented code: the live error reference documents the 400 by message text; that literal anchor appears nowhere on the page. Substance true, citation label imprecise — same precision bar as the sibling URL+date citations.

Nits

  • fn-doc K3.1 paragraph (chat.rs:3494-3502) and the arm comment (3520-3531) substantially repeat each other.
  • Citations lack the URL + verification-date form siblings use (chat.rs:927 pattern).
  • Doc maps spellings→routes (kimi-k3.1-* direct / k3.1-* membership) but the arm accepts the union — kimi-k3.1-pro on the membership route replays too. Harmless (same vendor family), doc-vs-code precision only.
  • No effort=off leg on the Kimi Code route (only direct platform has one); same code path, low incremental value.
  • k31 vs the repo's k3_1m/K3_256K underscore abbreviation; cf8a27b4c is fix(tui) while touching crates/config/src/provider.rs.

Disclosed follow-ups (not this PR)

  • Stream/effort seam closure should promote the four spellings to one shared constant (the roster doctrine's anti-drift rationale activates at the second consumer — that consumer now exists).
  • K3.1 roster/picker staleness: KIMI_CODE_MEMBERSHIP_MODELS (4 ids) and MOONSHOT_DIRECT_PLATFORM_MODELS (3 ids) lack k3.1 — validation passes through (fail-open by design), but the pickers won't offer the codewhale-hq#825 presets.
  • OpenRouter moonshotai/kimi-k3.1-pro (no org-prefix recognition; replay there depends on the runtime models.dev cache); MiniMax M3.1-Flash recognition; .com-only comment/fixtures at agent/src/lib.rs:785, provider_native_search/kimi.rs:376-390, route_billing.rs tests; sanitizer placeholder pin for K3.1; upstream-first contribution of the gate change per fork policy; models.dev runtime-cache caveat on the body's "never matched" framing (static heuristics — a runtime-refreshed exact k3.1 catalog row could replay pre-PR).

Environmental (base-verified, not chargeable)

  • event_handoff_replays_and_dedupes_interaction_prompts_without_a_gap stack-overflows on this arm64 host in the PR tree and identically at base — replay suite run with --skip for it (74/0 otherwise).
  • The 2 clippy errors above; base CI reds unrelated (audit RUSTSEC in untouched lockfile etc.).

Fix wave on offer for M1/M2 + minors; the stream arm and the typo sweep are mechanical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant