Skip to content

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

Open
asto18089 wants to merge 4 commits into
feat/host-armed-guardrail-promptsfrom
fix/kimi-k31-replay-spellings
Open

asto18089 wants to merge 4 commits into
feat/host-armed-guardrail-promptsfrom
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.

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>
@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
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>
@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.

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

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>
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