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]>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user