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:
@@ -82,7 +82,7 @@ func (m *model) do(ctx context.Context, req llm.Request, stream bool) (*http.Res
|
||||
Model: m.id,
|
||||
Status: http.StatusUnauthorized,
|
||||
Code: "missing_api_key",
|
||||
Message: "no API key configured: set OPENAI_API_KEY or use WithAPIKey",
|
||||
Message: "no API key configured: set " + m.p.apiKeyName + " or use WithAPIKey",
|
||||
}
|
||||
}
|
||||
body, err := json.Marshal(m.buildRequest(req, stream))
|
||||
|
||||
@@ -34,6 +34,7 @@ const defaultBaseURL = "https://api.openai.com/v1"
|
||||
type Provider struct {
|
||||
name string
|
||||
apiKey string
|
||||
apiKeyName string
|
||||
baseURL string
|
||||
client *http.Client
|
||||
caps llm.Capabilities
|
||||
@@ -64,6 +65,14 @@ func WithHTTPClient(c *http.Client) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithAPIKeyName sets the environment-variable name shown in the missing-key
|
||||
// error (default "OPENAI_API_KEY"). Why: the same client serves compat
|
||||
// endpoints keyed by other env vars (e.g. KIMI_API_KEY), and the error should
|
||||
// name the one the operator actually needs to set.
|
||||
func WithAPIKeyName(name string) Option {
|
||||
return func(p *Provider) { p.apiKeyName = name }
|
||||
}
|
||||
|
||||
// WithName overrides the registry name ("openai" by default). Why: the same
|
||||
// client serves many OpenAI-compatible endpoints, and each needs a distinct
|
||||
// name in "provider/model" specs and error reporting.
|
||||
@@ -104,11 +113,12 @@ func defaultCapabilities() llm.Capabilities {
|
||||
// 401-style *llm.APIError at request time, not at construction.
|
||||
func New(opts ...Option) *Provider {
|
||||
p := &Provider{
|
||||
name: "openai",
|
||||
apiKey: os.Getenv("OPENAI_API_KEY"),
|
||||
baseURL: defaultBaseURL,
|
||||
client: http.DefaultClient,
|
||||
caps: defaultCapabilities(),
|
||||
name: "openai",
|
||||
apiKey: os.Getenv("OPENAI_API_KEY"),
|
||||
apiKeyName: "OPENAI_API_KEY",
|
||||
baseURL: defaultBaseURL,
|
||||
client: http.DefaultClient,
|
||||
caps: defaultCapabilities(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
|
||||
Reference in New Issue
Block a user