feat(ai): Kimi K3 editor support — context meter, plan doc, reasoning spike - #26
Merged
Conversation
… spike Companion to the thin.ly gateway change (serve moonshotai/kimi-k3 as a selectable Pro model). That ships without an editor release; these are the editor-side pieces. - providers/catalog.js — a CAPS row for moonshotai/kimi-k3 (1M context). The roster feeds the PICKER's context, but the in-run meter reads local CAPS and would otherwise default to 200K and warn "full" 5× too early. No `fast` (a 2.8T reasoning model is a poor ghost-text completer). tools:true matches K3's agentic intent — flip to false ONLY if the spike (below) shows it breaks the tool loop. - test/catalog.test.js — pins the 1M resolution. - scripts/kimi-k3-spike.js — the reasoning-loop validation (docs/KIMI-K3.md §3). K3 is always-on reasoning and the OpenAI-compat agent path drops the separate reasoning field; deepseek-reasoner is tools:false for exactly this, and K3 has no non-reasoning fallback. The spike drives the REAL streamOpenAIAgentTurn twice, building turn 2 as agent.js does (assistant tool_use, reasoning dropped, then tool_result), and prints PASS/FAIL. Defaults to the Path-B route (OpenRouter) so it validates production; needs the gateway's OPENROUTER_API_KEY, no user key. - docs/KIMI-K3.md — the full scope + implementation plan: both integration paths, the OpenRouter routing reality, the 4× cost, the capacity caveat, the spike, tests. Verified: client suites green; node --check + a standalone load of the spike (no vscode dependency); the spike's no-key path exits 2 with guidance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds editor-side support material for the Kimi K3 model so LevelCode’s UI/context meter and internal “can this model survive the tool loop?” validation align with the gateway roster change.
Changes:
- Adds a
CAPSentry formoonshotai/kimi-k3(1,048,576 ctx) so the in-run context meter doesn’t default to 200k. - Pins the expected 1,048,576-token resolution in unit tests.
- Introduces a standalone spike script + accompanying plan doc to validate whether K3 can function as an agent model given reasoning-field handling.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| extensions/levelcode-ai/providers/catalog.js | Adds a CAPS row for moonshotai/kimi-k3 to correct context meter behavior and mark it as reasoning-capable. |
| extensions/levelcode-ai/test/catalog.test.js | Adds a regression assertion for K3’s 1,048,576 context window resolution. |
| extensions/levelcode-ai/scripts/kimi-k3-spike.js | New spike script to exercise the real OpenAI-compat agent tool loop over two turns and report PASS/FAIL. |
| docs/KIMI-K3.md | New implementation plan doc covering paths, risks, and the reasoning/tool-loop validation gate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+73
to
+97
| const to = withTimeout(60000); | ||
| let res; | ||
| try { | ||
| res = await fetch(BASE + '/chat/completions', { | ||
| method: 'POST', signal: to.signal, | ||
| headers: Object.assign({ 'content-type': 'application/json', authorization: 'Bearer ' + KEY }, HEADERS), | ||
| body: JSON.stringify(body) | ||
| }); | ||
| } finally { /* keep timer until stream ends */ } | ||
| if (!res.ok) { to.done(); return { ok: false, status: res.status, text: await res.text().catch(() => '') }; } | ||
| await readLines(res, (line) => { | ||
| const s = line.trim(); | ||
| if (!s.startsWith('data:')) { return; } | ||
| const data = s.slice(5).trim(); | ||
| if (!data || data === '[DONE]') { return; } | ||
| let ev; try { ev = JSON.parse(data); } catch { return; } | ||
| const d = ev.choices && ev.choices[0] && ev.choices[0].delta; | ||
| if (!d) { return; } | ||
| if (typeof d.content === 'string' && d.content) { tally.content++; if (/<think\b/i.test(d.content)) { tally.thinkTag = true; } } | ||
| if (d.reasoning != null && d.reasoning !== '') { tally.reasoning++; if (!tally.sample) { tally.sample = String(d.reasoning).slice(0, 60); } } | ||
| if (d.reasoning_content != null && d.reasoning_content !== '') { tally.reasoning_content++; if (!tally.sample) { tally.sample = String(d.reasoning_content).slice(0, 60); } } | ||
| if (Array.isArray(d.tool_calls) && d.tool_calls.length) { tally.tool_calls++; } | ||
| }); | ||
| to.done(); | ||
| return { ok: true, tally }; |
|
|
||
| const finalText = t2.content.filter((b) => b.type === 'text').map((b) => b.text).join('').trim(); | ||
| console.log(' turn 2 stop_reason: ' + t2.stop_reason + ' · answer: ' + JSON.stringify((finalText || answer).slice(0, 80))); | ||
| const looksRight = /42/.test(finalText || answer); |
| | Model id | **`kimi-k3`** (canonical; "K3 Max" / "Swarm Max" are product names, not API strings) | | ||
| | Streaming | Yes — emits **separate `reasoning_content` and `content`** deltas | | ||
| | Tools | Yes — official tool-calling + dynamic-loading guides exist | | ||
| | Context | 1,000,000 tokens | |
|
|
||
| **A2 — per-model caps** · `providers/catalog.js` `CAPS` table (~`:27-56`): | ||
| ```js | ||
| 'kimi-k3': { context: 1000000, tools: true, reasoning: true }, |
… ctx = 2^20 Addresses the PR #26 review (Copilot). - Stage 1's 60s timeout timer wasn't cleared if the initial fetch threw (DNS/auth) — the empty `finally` only "kept the timer for the stream". Wrapped fetch + the !ok return + the stream in a single try/finally that always calls `to.done()`, so the timer is cleared on a throw, an early return, or a normal end, and its abort can't fire spuriously. - The verdict used `/42/`, which false-positives on "142", "420", or prose like "…in 42 steps". Since the prompt asks for ONLY the number, tightened to `^\D*42\D*$` — 42 must be the sole number in the answer. Verified: accepts "42" / "42." / "= **42**"; rejects "142" / "420" / "1420" / mixed-digit prose. - docs/KIMI-K3.md said the context is "1,000,000 tokens" while the shipped caps (and their test) use 1,048,576 (2^20, what OpenRouter serves). Aligned the §2 table and the Path A caps example to 1,048,576 so a future edit can't "fix" the doc into breaking the meter alignment. Verified: node --check passes; the no-key path still exits 2 with guidance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Editor-side companion to the thin.ly gateway change (serve
moonshotai/kimi-k3as a selectable Pro model). That one ships without an editor release; this is the rest.providers/catalog.jsmoonshotai/kimi-k3(1M ctx). The roster feeds the picker's context, but the in-run meter reads local CAPS and would otherwise default to 200K — warning "full" 5× too early.test/catalog.test.jsscripts/kimi-k3-spike.jsdocs/KIMI-K3.mdNo
fastcap (a 2.8T reasoning model is a poor ghost-text completer).tools:truematches K3's agentic intent — flip tofalseonly if the spike shows it breaks the tool loop.The spike (
scripts/kimi-k3-spike.js)K3 is always-on reasoning, and the OpenAI-compat agent path (
streamOpenAIAgentTurn) drops the separate reasoning field.deepseek-reasoneristools:falsefor exactly this — "its tool loop needs reasoning_content round-tripping the boundary doesn't carry" — and K3 has no non-reasoning fallback.The spike drives the real
streamOpenAIAgentTurntwice, building turn 2's transcript the wayagent.jsdoes (assistanttool_usewith reasoning dropped, thentool_result), and prints PASS/FAIL. It defaults to the Path-B route (OpenRouter,moonshotai/kimi-k3) so it validates production, and distinguishes the §3 failure (400 needing reasoning) from a 429 (capacity). Runs server-side with the gateway's own key:export OPENROUTER_API_KEY=… node extensions/levelcode-ai/scripts/kimi-k3-spike.jsPASS → keep
tools:true, safe to announce. FAIL → set the catalogkimi-k3to chat-only, or add reasoning capture to the adapter.Verification
node --check+ standalone load of the spike (novscodedependency — theopenaiCompat/translate/sserequire chain resolves under plain node).Not verified
The live spike itself — it needs the gateway's
OPENROUTER_API_KEY, which I don't handle. Run it before announcing K3.🤖 Generated with Claude Code