feat(fleet): Kimi as a microVM backend — the URL settled by measurement
The base URL took three measurements to find, and the first two were wrong in instructive ways. `api.moonshot.ai/anthropic/v1/messages` EXISTS and speaks the protocol — it answers with Moonshot's own structured error rather than a 404. It also rejects an `sk-kimi-` key, because it belongs to the platform.moonshot.ai account namespace. Two endpoints that both "work" for different accounts is precisely the shape that makes a guessed URL look like a broken key, and it is why this was refused rather than guessed for as long as it was. The Kimi CODE service is the one an `sk-kimi-` key belongs to: `POST https://api.kimi.com/coding/v1/messages` returns a real Anthropic Messages body — `msg_` id, `content` blocks, a `thinking` block with a signature. So `ANTHROPIC_BASE_URL=https://api.kimi.com/coding`, WITHOUT the `/v1`: Claude Code appends `/v1/messages` itself, and `/v1/v1/messages` would 404 in a way that reads as a broken image rather than a bad URL. Two more measured, each otherwise a silent failure at the first turn: `Authorization: Bearer` is accepted (so ANTHROPIC_AUTH_TOKEN is the right injection channel), and a `claude-*` model id is ACCEPTED AND ANSWERED — Kimi maps it onto `kimi-for-coding` exactly as z.ai does, so no ANTHROPIC_MODEL override is needed. Claude Code rather than Moonshot's own `kimi` CLI, deliberately. The mission harness is Claude-Code-shaped throughout: `--agents` JSON roles, the verifier's tool allowlist, the `Stop` hook behind the completion gate, the per-subagent transcripts counted as delegation evidence. `kimi` has none of those flags — its equivalents are TOML files and markdown agent dirs — so using it would mean a second executor with its own untested failure modes. TWO STALE MAPS, caught by the rootfs harness refusing to bless the image: both `fc-build-rootfs.sh` and the node's `required_cli` expected backend `kimi` to contain Moonshot's `kimi` binary. That assumption predates the measurement, and it failed a rootfs that was correct. Both now say `claude` for glm and kimi alike — the binary is the same in all three images; only the endpoint differs. Egress for `kimi` is `api.kimi.com` alone: not moonshot.ai (wrong namespace), not z.ai, not Anthropic. Asserted both ways, like the other two. The image and rootfs are built on tank and the rootfs passes all four checks (boots, git, writable /mission, `claude --version`). 534 tests, clippy clean. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d3a53e7bf1
commit
742724e53c
@@ -66,6 +66,11 @@ fn provider_hosts(backend: Option<&str>) -> &'static [&'static str] {
|
||||
&["api.anthropic.com", ".anthropic.com"]
|
||||
}
|
||||
Some("glm") => &["api.z.ai"],
|
||||
// The Kimi CODE service, which is where an `sk-kimi-` key is valid —
|
||||
// NOT `api.moonshot.ai`, whose Anthropic endpoint exists but belongs to
|
||||
// a different account namespace and rejects that key. Only the host the
|
||||
// `agent-kimi` image bakes in.
|
||||
Some("kimi") => &["api.kimi.com"],
|
||||
// Fail closed: a backend nobody taught this function about reaches the
|
||||
// forge and no model API. It cannot silently borrow another provider's
|
||||
// door, which is the failure this split exists to prevent.
|
||||
@@ -373,6 +378,12 @@ mod tests {
|
||||
assert!(l.iter().any(|h| h == "git.redclaw.dev"), "{l:?}");
|
||||
}
|
||||
|
||||
let kimi = allow_list_for(Some("kimi"));
|
||||
assert!(kimi.iter().any(|h| h == "api.kimi.com"), "{kimi:?}");
|
||||
for other in ["api.z.ai", "api.anthropic.com"] {
|
||||
assert!(!kimi.iter().any(|h| h == other), "{kimi:?}");
|
||||
}
|
||||
|
||||
// An unknown backend gets no model API at all rather than borrowing
|
||||
// somebody's: it cannot run anyway, and failing at a closed door beats
|
||||
// reaching the wrong endpoint with a credential.
|
||||
|
||||
Reference in New Issue
Block a user