Files
majordomo/docs/adr/0026-kimi-builtin.md
T
steveandClaude Opus 4.8 2bfffff47a
CI / Tidy (pull_request) Successful in 9m29s
CI / Build & Test (pull_request) Successful in 10m23s
Gadfly review (reusable) / review (pull_request) Successful in 18m38s
Adversarial Review (Gadfly) / review (pull_request) Successful in 18m38s
feat: kimi (Moonshot AI) built-in provider (ADR-0026)
Add a first-class `kimi` provider and `kimi://` DSN scheme for Moonshot AI's
OpenAI-compatible Chat Completions API. Both reuse provider/openai (no new
client, mirroring llama-swap's chat path). Default endpoint is the
international host; the China endpoint is reachable via a kimi:// LLM_* DSN.

- Credential is KIMI_API_KEY, read through the registry's injected envLookup
  so it stays hermetically testable. WithAPIKey is passed unconditionally so
  an unset KIMI_API_KEY can never fall through to the openai client's
  OPENAI_API_KEY default.
- New openai.WithAPIKeyName option customizes the missing-key error hint
  (default OPENAI_API_KEY); kimi names KIMI_API_KEY.
- Hermetic tests: built-in base URL + bearer, missing-key hint names
  KIMI_API_KEY with no OPENAI fallthrough and no network hit, kimi:// scheme
  round-trips against the China host.
- Docs in sync: README built-in table + DSN scheme list + support matrix,
  .env.example, env.go DSN doc, ADR-0026 (+ index, backfilling 0024/0025),
  progress.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 02:59:30 -04:00

3.2 KiB

ADR-0026: Kimi (Moonshot AI) built-in provider

Status: Accepted — 2026-07-18

Context

Moonshot AI's Kimi models (Kimi K2, moonshot-v1-*, and the vision variants) are served over an OpenAI-compatible Chat Completions API at https://api.moonshot.ai/v1 (https://api.moonshot.cn/v1 for China), authenticated with a bearer key. mort wants Kimi as a first-class failover tier, so kimi/kimi-k2-... should parse, chain, and alias out of the box with a dedicated KIMI_API_KEY env var — the same ergonomics as openai, anthropic, and google.

Two tensions:

  • The wire protocol is byte-for-byte OpenAI Chat Completions, so a hand-rolled client would duplicate provider/openai for zero gain (ADR-0007 forbids it), exactly as ADR-0015 found for llama-swap.
  • The README's current stance is that arbitrary OpenAI-compatible endpoints (Groq, Together, …) are consumer-registered, not baked in. Blessing Kimi as a built-in is a deliberate, narrow exception justified by the north star: mort names Kimi directly in its tiers, and a built-in with KIMI_API_KEY keeps mort's config free of boilerplate openai.New(WithName/WithBaseURL) wiring.

Decision

  • No new package. The kimi built-in and kimi:// DSN scheme both construct provider/openai pointed at the Moonshot base URL — the chat path inherits every openai feature/fix automatically (like llama-swap's chat).
  • The built-in reads its key through the registry's injected envLookup (KIMI_API_KEY only — no MOONSHOT_API_KEY alias, per the project owner) so it stays hermetically testable via WithEnvLookup.
  • WithAPIKey is passed unconditionally, even when empty. openai.New defaults its key to OPENAI_API_KEY; without an explicit override an unset KIMI_API_KEY would silently authenticate Kimi with the OpenAI key. Passing the (possibly empty) lookup result severs that fallthrough.
  • New openai.WithAPIKeyName("KIMI_API_KEY") option customizes only the synthetic-401 missing-key hint (default OPENAI_API_KEY), so a keyless kimi call tells the operator the right variable to set.
  • The default endpoint is the international host (kimiBaseURL). The China endpoint (or any other host) is reachable with a kimi:// DSN, e.g. LLM_KCN=kimi://[email protected]/v1. The kimi:// scheme is an OpenAI-compatible target labeled kimi with the same key-name hint; it is intentionally near-identical to openai:// — its value is a clear name in specs and error reporting.

Consequences

  • kimi/<model> is first-class in Parse, chains, aliases, and health/failover with no consumer wiring; model ids pass through verbatim (no catalog).
  • Chat, streaming, tools, and structured output ride the openai client. Image inputs work at the client level but only the Moonshot vision models accept them (matrix footnote ³).
  • WithAPIKeyName is a small, generally useful addition to provider/openai; the default preserves existing behavior for every other openai-compat target.
  • Blessing one third-party endpoint as a built-in sets a precedent; future ones should clear the same bar (a named consumer needs it in-config), not be added reflexively — RegisterProvider/LLM_* remain the path for the rest.