feat(harness-node): add Kimi (Moonshot) provider worker - #173
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis pull request introduces a new Kimi (Moonshot) LLM provider worker to the harness-node system, including SSE-based streaming, message translation, credential handling, and comprehensive tests. It simultaneously refactors context-compaction summarization to route through the provider-router instead of config-based selection, removing deprecated summarizer environment variables. ChangesKimi Provider Implementation and Integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
skill-check — worker0 verified, 11 skipped (no docs/).
Three for three. Nicely done. |
Adds a Kimi (Moonshot) LLM provider to the harness-node worker bundle, exposing provider::kimi::stream and provider::kimi::complete on the iii bus. Kimi K2 is OpenAI-Chat-Completions wire compatible; the new worker mirrors the provider-openai scaffold (SSE state machine, message shaping, tool-call deltas, error classification) and talks to https://api.moonshot.ai/v1/chat/completions by default. Surface: - src/provider-kimi/ — 12 files (main, register, config, types, auth, complete, stream, stream-fn, sse, wire-messages, wire-tools, iii.worker.yaml) - 26 vitest cases under tests/provider-kimi/, tests/auth-credentials/, tests/models-catalog/, tests/turn-orchestrator/, and tests/context-compaction/ covering SSE parsing, wire translation, fetch-mocked stream happy path, HTTP 401/429/5xx classification, transport failures, credential resolution, catalog seeds, router decisions, and /compact provider routing Integration: - src/index.ts wires provider-kimi into the composite entry-point - src/auth-credentials/types.ts adds kimi -> MOONSHOT_API_KEY alongside the existing kimi-coding row - src/models-catalog/models.json seeds the two flagship multimodal K2 variants (kimi-k2.5, kimi-k2.6) for the chat picker - src/turn-orchestrator/provider-router.ts extends RouteDecision with a kimi arm and a heuristic for model ids starting with kimi- or moonshot-v1- - src/context-compaction/summarize.ts routes /compact through the canonical provider-router so the session's own model is used (fixes NOT_FOUND_ERROR when summarising kimi sessions); drops the COMPACT_SUMMARIZER_PROVIDER/MODEL env-var overrides that hid the mis-route - config.yaml, package.json (dev:provider-kimi + iii-provider-kimi bin), README.md, docs/architecture.md, docs/workers/provider-kimi.md, docs/workers/context-compaction.md Workaround for the live runtime: the credential lookup uses the kimi-coding slug because the compiled Rust auth-credentials binary predates the bare kimi row. Once the Rust worker is rebuilt or replaced with the harness-node port, the bare kimi slug also resolves.
134e84d to
c8bdf1e
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
harness-node/docs/superpowers/plans/2026-05-20-provider-kimi.md (1)
100-102: 💤 Low valueAdd language specifiers to shell code blocks.
Multiple shell command blocks (lines 100, 117, 125, 173, 216, 224, and many others throughout the file) are missing language specifiers. Add
bashorshellafter the opening backticks to satisfy markdownlint and improve syntax highlighting.Example fix for line 100
-``` +```bash pnpm test tests/auth-credentials/env-map-kimi.test.ts</details> Apply the same pattern to all shell command blocks throughout the file. Also applies to: 117-119, 125-129, 173-175, 216-218, 224-228 <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@harness-node/docs/superpowers/plans/2026-05-20-provider-kimi.mdaround lines
100 - 102, Several fenced code blocks containing shell commands (e.g., the block
showing "pnpm test tests/auth-credentials/env-map-kimi.test.ts" and other blocks
around lines referenced) are missing language specifiers; update every shell
command fence by adding a language tag (bash or shell) immediately after the
opening triple backticks (e.g., ```bash) for each occurrence (including the
blocks at the reported ranges and the other similar blocks throughout the
document) so markdownlint passes and syntax highlighting works.</details> </blockquote></details> <details> <summary>harness-node/src/provider-kimi/complete.ts (1)</summary><blockquote> `12-17`: _⚡ Quick win_ **Consider schema-based validation for consistency.** The `complete` handler uses manual parsing with `requireString` and type guards, while `stream-fn.ts` uses `ProviderStreamRuntimeInputSchema.parse()`. Using a schema (e.g., `ProviderCompleteRuntimeInputSchema`) would improve consistency, type safety, and maintainability across both endpoints. <details> <summary>📋 Suggested refactor</summary> Define a schema in `types/provider.ts` (or similar location): ```typescript export const ProviderCompleteRuntimeInputSchema = z.object({ model: z.string(), system_prompt: z.string().optional(), messages: z.array(z.any()).optional(), // or AgentMessageSchema tools: z.array(z.any()).optional(), // or AgentFunctionSchema }); ``` Then replace manual parsing: ```diff export function register(iii: ISdk, worker: WorkerConfig): void { iii.registerFunction( 'provider::kimi::complete', async (payload: unknown) => { - const obj = (payload ?? {}) as Record<string, unknown>; - const model = requireString(obj, 'model'); - const system_prompt = typeof obj.system_prompt === 'string' ? obj.system_prompt : ''; - const messages = Array.isArray(obj.messages) ? (obj.messages as AgentMessage[]) : []; - const tools = Array.isArray(obj.tools) ? (obj.tools as AgentFunction[]) : []; + const input = ProviderCompleteRuntimeInputSchema.parse(payload); + const model = input.model; + const system_prompt = input.system_prompt ?? ''; + const messages = (input.messages ?? []) as AgentMessage[]; + const tools = (input.tools ?? []) as AgentFunction[]; const cfg = await buildConfig(iii, worker, model); return await collect(streamKimi({ cfg, system_prompt, messages, tools })); ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@harness-node/src/provider-kimi/complete.ts` around lines 12 - 17, The complete handler currently manually extracts fields using requireString and type guards (see the async handler, requireString, system_prompt, messages, tools) — replace that ad hoc parsing with a Zod schema (e.g., add ProviderCompleteRuntimeInputSchema in types/provider.ts) and call ProviderCompleteRuntimeInputSchema.parse(payload) at the start of the async handler to validate and coerce model, system_prompt, messages, and tools; adjust subsequent code to use the parsed object and remove the manual requireString/type checks so the handler matches stream-fn.ts’s ProviderStreamRuntimeInputSchema approach. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.Inline comments:
In@harness-node/docs/superpowers/plans/2026-05-20-provider-kimi.md:
- Around line 823-840: The documentation snippet for fetchCredential is
inconsistent: the iii.trigger call uses payload: { provider: 'kimi' } but
runtime uses 'kimi-coding' as a workaround; update the payload in the example to
payload: { provider: 'kimi-coding' } or add a one-line comment next to the
iii.trigger invocation explaining the runtime workaround (refer to function
fetchCredential and the iii.trigger payload field) so the docs match the
implementation.In
@harness-node/docs/superpowers/specs/2026-05-20-provider-kimi-design.md:
- Around line 196-212: Update the diagram comment to match the runtime
implementation by changing the auth trigger payload provider value from 'kimi'
to 'kimi-coding' in the provider-kimi::stream-fn auth flow (the
iii.trigger(auth::get_token, { provider: 'kimi' }) comment); ensure the
auth.buildConfig / iii.trigger line and any adjacent auth credential note
reflect provider: 'kimi-coding' so the diagram matches the actual use of
kimi-coding at runtime.- Line 86: Update the design spec to match the implementation by changing the
documented provider value used in auth.ts/auth::get_token: where the spec says
the payload should use provider: 'kimi' and returned config.provider_name
'kimi', change it to provider: 'kimi-coding' and provider_name: 'kimi-coding'
(or explicitly note this as a documented deviation) so the spec aligns with the
runtime workaround implemented in provider-openai/auth.ts and the
auth::get_token behavior.In
@harness-node/docs/workers/provider-kimi.md:
- Around line 8-9: Update the docs to match the runtime workaround: change the
reference to the auth worker call from auth::get_token with provider 'kimi' to
the actual implementation used at runtime (provider 'kimi-coding'), or add a
short note explaining that provider-kimi/auth.ts currently uses 'kimi-coding' as
a temporary workaround; reference the auth::get_token call and the
provider-kimi/auth.ts module so readers can find the implementation.In
@harness-node/src/provider-kimi/stream.ts:
- Around line 92-124: The SSE parser can drop trailing buffered data and only
matches "data: " exactly—fix by treating both "data:" and "data: " as valid in
parseDataLine, accept single-newline as an event separator in addition to
"\n\n", and after the read loop completes (or when done is true) process any
remaining buf by running it through parseDataLine/JSON parse/handleChunk before
yielding the final done event; update the loop that uses reader.read(),
decoder.decode, buf, parseDataLine, handleChunk, buildFinal and
syntheticErrorEvent so leftover data is parsed rather than discarded and ensure
buildFinal/syntheticErrorEvent are only yielded once at the end.- Around line 49-59: The upstream fetch in the block that assigns resp is
missing a timeout and can hang; wrap the fetch call with an AbortController,
pass controller.signal to fetch, and set a timer (configurable via cfg.timeout
or a sane default like 30s) to call controller.abort() when elapsed; handle the
abort/timeout case in the catch (detect AbortError or aborted signal) and yield
syntheticErrorEvent with a clear timeout message (including cfg.model and
cfg.provider_name) before returning, and ensure the timer is cleared on
successful response to avoid leaks.In
@harness-node/src/turn-orchestrator/provider-router.ts:
- Around line 22-32: The code's current path silently maps any unknown explicit
req.provider to Anthropic; update the logic in decide (use the p = (req.provider
?? '').toLowerCase() branch) so that if p is non-empty and is not one of the
recognized providers ('openai' or 'kimi' — and 'anthropic' if you want it
explicit) you do not fall back to Anthropic but instead return/throw a clear
error or sentinel (e.g., throw new Error or return { error: 'unsupported
provider' }) indicating an unsupported provider; modify the early-if checks
around p and the final return to ensure only empty p triggers heuristic
model-based routing and explicit-but-unknown p triggers the error path.
Nitpick comments:
In@harness-node/docs/superpowers/plans/2026-05-20-provider-kimi.md:
- Around line 100-102: Several fenced code blocks containing shell commands
(e.g., the block showing "pnpm test tests/auth-credentials/env-map-kimi.test.ts"
and other blocks around lines referenced) are missing language specifiers;
update every shell command fence by adding a language tag (bash or shell)
immediately after the opening triple backticks (e.g., ```bash) for each
occurrence (including the blocks at the reported ranges and the other similar
blocks throughout the document) so markdownlint passes and syntax highlighting
works.In
@harness-node/src/provider-kimi/complete.ts:
- Around line 12-17: The complete handler currently manually extracts fields
using requireString and type guards (see the async handler, requireString,
system_prompt, messages, tools) — replace that ad hoc parsing with a Zod schema
(e.g., add ProviderCompleteRuntimeInputSchema in types/provider.ts) and call
ProviderCompleteRuntimeInputSchema.parse(payload) at the start of the async
handler to validate and coerce model, system_prompt, messages, and tools; adjust
subsequent code to use the parsed object and remove the manual
requireString/type checks so the handler matches stream-fn.ts’s
ProviderStreamRuntimeInputSchema approach.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Organization UI **Review profile**: CHILL **Plan**: Pro **Run ID**: `1f6664e6-aeda-4c9e-97e8-e7d3e18eb2ef` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 241509a70c6f537eda3cb3dd9595e8ad278f5cc0 and 134e84de4d4aa806a268980534e2ebda05f28ff7. </details> <details> <summary>📒 Files selected for processing (33)</summary> * `harness-node/README.md` * `harness-node/config.yaml` * `harness-node/docs/architecture.md` * `harness-node/docs/superpowers/plans/2026-05-20-provider-kimi.md` * `harness-node/docs/superpowers/specs/2026-05-20-provider-kimi-design.md` * `harness-node/docs/workers/context-compaction.md` * `harness-node/docs/workers/provider-kimi.md` * `harness-node/package.json` * `harness-node/src/auth-credentials/types.ts` * `harness-node/src/context-compaction/config.ts` * `harness-node/src/context-compaction/summarize.ts` * `harness-node/src/index.ts` * `harness-node/src/models-catalog/models.json` * `harness-node/src/provider-kimi/auth.ts` * `harness-node/src/provider-kimi/complete.ts` * `harness-node/src/provider-kimi/config.ts` * `harness-node/src/provider-kimi/iii.worker.yaml` * `harness-node/src/provider-kimi/main.ts` * `harness-node/src/provider-kimi/register.ts` * `harness-node/src/provider-kimi/sse.ts` * `harness-node/src/provider-kimi/stream-fn.ts` * `harness-node/src/provider-kimi/stream.ts` * `harness-node/src/provider-kimi/types.ts` * `harness-node/src/provider-kimi/wire-messages.ts` * `harness-node/src/provider-kimi/wire-tools.ts` * `harness-node/src/turn-orchestrator/provider-router.ts` * `harness-node/tests/auth-credentials/env-map-kimi.test.ts` * `harness-node/tests/context-compaction/summarize.test.ts` * `harness-node/tests/models-catalog/seed-kimi.test.ts` * `harness-node/tests/provider-kimi/sse.test.ts` * `harness-node/tests/provider-kimi/stream.test.ts` * `harness-node/tests/provider-kimi/wire-messages.test.ts` * `harness-node/tests/turn-orchestrator/provider-router.test.ts` </details> <details> <summary>💤 Files with no reviewable changes (1)</summary> * harness-node/src/context-compaction/config.ts </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
|
|
||
| ```ts | ||
| import type { Credential } from '../auth-credentials/types.js'; | ||
| import type { ISdk } from '../runtime/iii.js'; | ||
| import type { WorkerConfig } from './config.js'; | ||
| import { type ChatCompletionsConfig, configFromCredential } from './types.js'; | ||
|
|
||
| export async function fetchCredential(iii: ISdk): Promise<Credential> { | ||
| const cred = await iii.trigger<unknown, Credential | null>({ | ||
| function_id: 'auth::get_token', | ||
| payload: { provider: 'kimi' }, | ||
| timeoutMs: 5_000, | ||
| }); | ||
| if (!cred || typeof cred !== 'object' || !('type' in cred)) { | ||
| throw new Error('auth::get_token returned no credential for provider=kimi'); | ||
| } | ||
| return cred; | ||
| } |
There was a problem hiding this comment.
Documentation inconsistency with implementation.
The code snippet in Task 5 shows payload: { provider: 'kimi' } (line 833), but according to the PR objectives, the actual implementation uses provider: 'kimi-coding' as a runtime workaround. Update the snippet to match the implementation or add a comment explaining the workaround.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@harness-node/docs/superpowers/plans/2026-05-20-provider-kimi.md` around lines
823 - 840, The documentation snippet for fetchCredential is inconsistent: the
iii.trigger call uses payload: { provider: 'kimi' } but runtime uses
'kimi-coding' as a workaround; update the payload in the example to payload: {
provider: 'kimi-coding' } or add a one-line comment next to the iii.trigger
invocation explaining the runtime workaround (refer to function fetchCredential
and the iii.trigger payload field) so the docs match the implementation.
| | `main.ts` | `provider-openai/main.ts` | `bootstrapWorker` name → `provider-kimi`; description updated | | ||
| | `register.ts` | `provider-openai/register.ts` | Loads `provider_kimi` config section | | ||
| | `config.ts` | `provider-openai/config.ts` | Section key → `provider_kimi`; `DEFAULT_API_URL` → `https://api.moonshot.ai/v1/chat/completions` | | ||
| | `auth.ts` | `provider-openai/auth.ts` | `auth::get_token` payload `provider: 'kimi'`; `provider_name: 'kimi'` in returned config | |
There was a problem hiding this comment.
Documentation inconsistency with implementation.
Line 86 states that auth.ts should use payload with provider: 'kimi', but according to the PR objectives, the actual implementation uses provider: 'kimi-coding' as a runtime workaround. Update the design spec to reflect the current implementation or document this as a known deviation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@harness-node/docs/superpowers/specs/2026-05-20-provider-kimi-design.md` at
line 86, Update the design spec to match the implementation by changing the
documented provider value used in auth.ts/auth::get_token: where the spec says
the payload should use provider: 'kimi' and returned config.provider_name
'kimi', change it to provider: 'kimi-coding' and provider_name: 'kimi-coding'
(or explicitly note this as a documented deviation) so the spec aligns with the
runtime workaround implemented in provider-openai/auth.ts and the
auth::get_token behavior.
| ``` | ||
| turn-orchestrator | ||
| │ | ||
| ▼ iii.trigger(provider::kimi::stream, { model, system_prompt, messages, tools, writer_ref }) | ||
| provider-kimi::stream-fn | ||
| │ (auto-hydrates writer_ref into ChannelWriter) | ||
| ├──▶ auth.buildConfig | ||
| │ └──▶ iii.trigger(auth::get_token, { provider: 'kimi' }) | ||
| │ └──▶ auth-credentials (stored cred or MOONSHOT_API_KEY env) | ||
| ├──▶ stream.streamKimi | ||
| │ ├──▶ wire-messages.toOpenaiMessages | ||
| │ ├──▶ wire-tools.functionsToOpenai | ||
| │ ├──▶ fetch POST api.moonshot.ai/v1/chat/completions | ||
| │ └──▶ sse.handleChunk loop | ||
| └──▶ writer.sendMessage(JSON.stringify(event)) per AssistantMessageEvent | ||
| writer.close() on terminal event | ||
| ``` |
There was a problem hiding this comment.
Documentation inconsistency in data flow diagram.
Line 204 shows provider: 'kimi' in the auth flow comment, but the actual implementation uses provider: 'kimi-coding' as a runtime workaround. Update the diagram comment to match the implementation.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 196-196: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@harness-node/docs/superpowers/specs/2026-05-20-provider-kimi-design.md`
around lines 196 - 212, Update the diagram comment to match the runtime
implementation by changing the auth trigger payload provider value from 'kimi'
to 'kimi-coding' in the provider-kimi::stream-fn auth flow (the
iii.trigger(auth::get_token, { provider: 'kimi' }) comment); ensure the
auth.buildConfig / iii.trigger line and any adjacent auth credential note
reflect provider: 'kimi-coding' so the diagram matches the actual use of
kimi-coding at runtime.
| The iii-side bridge to Moonshot's Chat Completions API. It pulls a | ||
| credential from the auth worker (`auth::get_token`, provider `kimi`), |
There was a problem hiding this comment.
Documentation inconsistency with implementation.
The documentation states the worker calls auth::get_token with provider: 'kimi', but according to the PR objectives, the actual implementation in provider-kimi/auth.ts uses provider: 'kimi-coding' as a runtime workaround. Update this line to reflect the current implementation or add a note about the temporary workaround.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@harness-node/docs/workers/provider-kimi.md` around lines 8 - 9, Update the
docs to match the runtime workaround: change the reference to the auth worker
call from auth::get_token with provider 'kimi' to the actual implementation used
at runtime (provider 'kimi-coding'), or add a short note explaining that
provider-kimi/auth.ts currently uses 'kimi-coding' as a temporary workaround;
reference the auth::get_token call and the provider-kimi/auth.ts module so
readers can find the implementation.
| let resp: Response; | ||
| try { | ||
| resp = await fetch(cfg.url, { | ||
| method: 'POST', | ||
| headers, | ||
| body: JSON.stringify(body), | ||
| }); | ||
| } catch (err) { | ||
| yield syntheticErrorEvent(`kimi fetch failed: ${String(err)}`, cfg.model, cfg.provider_name); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Add a timeout to the upstream fetch call.
This external call can hang indefinitely on network stalls, which can pin worker resources and degrade reliability under load.
Suggested fix
let resp: Response;
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 30_000);
try {
resp = await fetch(cfg.url, {
method: 'POST',
headers,
body: JSON.stringify(body),
+ signal: controller.signal,
});
} catch (err) {
yield syntheticErrorEvent(`kimi fetch failed: ${String(err)}`, cfg.model, cfg.provider_name);
return;
+ } finally {
+ clearTimeout(timeout);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let resp: Response; | |
| try { | |
| resp = await fetch(cfg.url, { | |
| method: 'POST', | |
| headers, | |
| body: JSON.stringify(body), | |
| }); | |
| } catch (err) { | |
| yield syntheticErrorEvent(`kimi fetch failed: ${String(err)}`, cfg.model, cfg.provider_name); | |
| return; | |
| } | |
| let resp: Response; | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), 30_000); | |
| try { | |
| resp = await fetch(cfg.url, { | |
| method: 'POST', | |
| headers, | |
| body: JSON.stringify(body), | |
| signal: controller.signal, | |
| }); | |
| } catch (err) { | |
| yield syntheticErrorEvent(`kimi fetch failed: ${String(err)}`, cfg.model, cfg.provider_name); | |
| return; | |
| } finally { | |
| clearTimeout(timeout); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@harness-node/src/provider-kimi/stream.ts` around lines 49 - 59, The upstream
fetch in the block that assigns resp is missing a timeout and can hang; wrap the
fetch call with an AbortController, pass controller.signal to fetch, and set a
timer (configurable via cfg.timeout or a sane default like 30s) to call
controller.abort() when elapsed; handle the abort/timeout case in the catch
(detect AbortError or aborted signal) and yield syntheticErrorEvent with a clear
timeout message (including cfg.model and cfg.provider_name) before returning,
and ensure the timer is cleared on successful response to avoid leaks.
| const p = (req.provider ?? '').toLowerCase(); | ||
| if (p === 'openai') return { provider: 'openai', model: req.model }; | ||
| if (p === 'kimi') return { provider: 'kimi', model: req.model }; | ||
| // Heuristic for missing provider: model name disambiguates. | ||
| if (!p && /^gpt-|^o\d-/i.test(req.model)) { | ||
| return { provider: 'openai', model: req.model }; | ||
| } | ||
| if (!p && /^kimi-|^moonshot-v1-/i.test(req.model)) { | ||
| return { provider: 'kimi', model: req.model }; | ||
| } | ||
| return { provider: 'anthropic', model: req.model }; |
There was a problem hiding this comment.
Avoid silent fallback to Anthropic for unknown explicit providers.
When req.provider is present but unsupported, decide() currently returns Anthropic. That can silently misroute /compact (now session-provider-driven) to the wrong worker/model pair.
💡 Suggested fix
export function decide(req: RouteRequest): RouteDecision {
const p = (req.provider ?? '').toLowerCase();
+ if (p && p !== 'anthropic' && p !== 'openai' && p !== 'kimi') {
+ throw new Error(`Unsupported provider: ${req.provider}`);
+ }
if (p === 'openai') return { provider: 'openai', model: req.model };
if (p === 'kimi') return { provider: 'kimi', model: req.model };
// Heuristic for missing provider: model name disambiguates.
if (!p && /^gpt-|^o\d-/i.test(req.model)) {
return { provider: 'openai', model: req.model };
}
if (!p && /^kimi-|^moonshot-v1-/i.test(req.model)) {
return { provider: 'kimi', model: req.model };
}
return { provider: 'anthropic', model: req.model };
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const p = (req.provider ?? '').toLowerCase(); | |
| if (p === 'openai') return { provider: 'openai', model: req.model }; | |
| if (p === 'kimi') return { provider: 'kimi', model: req.model }; | |
| // Heuristic for missing provider: model name disambiguates. | |
| if (!p && /^gpt-|^o\d-/i.test(req.model)) { | |
| return { provider: 'openai', model: req.model }; | |
| } | |
| if (!p && /^kimi-|^moonshot-v1-/i.test(req.model)) { | |
| return { provider: 'kimi', model: req.model }; | |
| } | |
| return { provider: 'anthropic', model: req.model }; | |
| const p = (req.provider ?? '').toLowerCase(); | |
| if (p && p !== 'anthropic' && p !== 'openai' && p !== 'kimi') { | |
| throw new Error(`Unsupported provider: ${req.provider}`); | |
| } | |
| if (p === 'openai') return { provider: 'openai', model: req.model }; | |
| if (p === 'kimi') return { provider: 'kimi', model: req.model }; | |
| // Heuristic for missing provider: model name disambiguates. | |
| if (!p && /^gpt-|^o\d-/i.test(req.model)) { | |
| return { provider: 'openai', model: req.model }; | |
| } | |
| if (!p && /^kimi-|^moonshot-v1-/i.test(req.model)) { | |
| return { provider: 'kimi', model: req.model }; | |
| } | |
| return { provider: 'anthropic', model: req.model }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@harness-node/src/turn-orchestrator/provider-router.ts` around lines 22 - 32,
The code's current path silently maps any unknown explicit req.provider to
Anthropic; update the logic in decide (use the p = (req.provider ??
'').toLowerCase() branch) so that if p is non-empty and is not one of the
recognized providers ('openai' or 'kimi' — and 'anthropic' if you want it
explicit) you do not fall back to Anthropic but instead return/throw a clear
error or sentinel (e.g., throw new Error or return { error: 'unsupported
provider' }) indicating an unsupported provider; modify the early-if checks
around p and the final return to ensure only empty p triggers heuristic
model-based routing and explicit-but-unknown p triggers the error path.
Summary
provider-kimiworker to the harness-node bundle, exposingprovider::kimi::streamandprovider::kimi::completeon the iii bus. Mirrors theprovider-openaiscaffold againsthttps://api.moonshot.ai/v1/chat/completions. Kimi K2 is OpenAI-Chat-Completions wire compatible, so SSE parsing, wire translation, and tool-call deltas are reused unchanged.kimi-k2.5andkimi-k2.6inmodels-catalog(the two flagship multimodal K2 variants surface in the chat picker). Env map:kimi → MOONSHOT_API_KEYalongside the legacykimi-codingslug. Router: extendsturn-orchestrator/provider-routerwith akimiarm and a heuristic for model ids starting withkimi-ormoonshot-v1-./compactdefect: the summariser's binary openai-vs-anthropic ternary silently routed every other provider toprovider::anthropic::stream, producingNOT_FOUND_ERRORwhen summarising a non-openai/non-anthropic session.context-compaction/summarize.tsnow routes through the canonicalprovider-routerand always uses the session's own model (theCOMPACT_SUMMARIZER_PROVIDER/MODELenv-var overrides are removed — they were a footgun that hid the mis-route).Caveats
provider-kimi/auth.tscallsauth::get_tokenwithprovider: 'kimi-coding'(not'kimi'). The compiled Rustauth-credentialsbinary (the live handler on most engines today) mapskimi-coding → MOONSHOT_API_KEYbut doesn't yet know the barekimislug we added here. Once the Rust worker is rebuilt or replaced with the harness-node port, the barekimislug also resolves.context-compactionbinary has the same/compactdefect this PR fixes in the harness-node port. The Rust patch is out of scope for this PR.Test plan
pnpm typecheck— cleanpnpm test— 473/473 pass, including 26 new kimi-specific casespnpm build && node dist/index.js --manifestlistsprovider-kimibetweenprovider-openaiandllm-budgetgit diff main...HEADtouches onlyharness-node/(31 files, +1165/−46)MOONSHOT_API_KEY(orauth::set_token { provider: 'kimi-coding' }), triggerprovider::kimi::completewithmodel: 'kimi-k2.5', expect a non-errorAssistantMessage/compactin a Kimi K2.5 session succeeds (the bus call now lands onprovider::kimi::stream, not anthropic)models::list { provider: 'kimi' }returns exactlykimi-k2.5andkimi-k2.6References
harness-node/docs/workers/provider-kimi.md