Skip to content

feat(ai): Kimi K3 editor support — context meter, plan doc, reasoning spike - #26

Merged
ndemianc merged 2 commits into
developfrom
feat/gateway-kimi-k3-context
Jul 21, 2026
Merged

feat(ai): Kimi K3 editor support — context meter, plan doc, reasoning spike#26
ndemianc merged 2 commits into
developfrom
feat/gateway-kimi-k3-context

Conversation

@ndemianc

Copy link
Copy Markdown
Contributor

What

Editor-side companion to the thin.ly gateway change (serve moonshotai/kimi-k3 as a selectable Pro model). That one ships without an editor release; this is the rest.

File Why
providers/catalog.js CAPS row for moonshotai/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.js Pins the 1M resolution.
scripts/kimi-k3-spike.js The reasoning-loop validation (§3).
docs/KIMI-K3.md The full scope + implementation plan.

No fast cap (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 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-reasoner is tools:false for 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 streamOpenAIAgentTurn twice, building turn 2's transcript the way agent.js does (assistant tool_use with reasoning dropped, then tool_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.js

PASS → keep tools:true, safe to announce. FAIL → set the catalog kimi-k3 to chat-only, or add reasoning capture to the adapter.

Verification

  • Client suites green.
  • node --check + standalone load of the spike (no vscode dependency — the openaiCompat/translate/sse require chain resolves under plain node).
  • The spike's no-key path exits 2 with guidance.

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

… 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>
Copilot AI review requested due to automatic review settings July 21, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 CAPS entry for moonshotai/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);
Comment thread docs/KIMI-K3.md Outdated
| 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 |
Comment thread docs/KIMI-K3.md Outdated

**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>
@ndemianc
ndemianc merged commit c233545 into develop Jul 21, 2026
1 check passed
@ndemianc
ndemianc deleted the feat/gateway-kimi-k3-context branch July 21, 2026 17:06
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.

2 participants