378 lines
13 KiB
Go
378 lines
13 KiB
Go
// This file defines the central provider registry (US-001): a single source of
|
|
// truth for every built-in provider's metadata — its name, the environment
|
|
// variables that carry its API key (in precedence order), its default base URL,
|
|
// wire protocol, auth scheme, any extra headers, and provider-specific base-URL
|
|
// override env vars.
|
|
//
|
|
// The registry is deliberately additive and self-contained: later nodes wire
|
|
// auth resolution (auth.go), the --provider flag (main.go), base_url overrides,
|
|
// and per-provider construction (providers.go) to READ from it. This node only
|
|
// introduces the data + a lookup, so it does not change existing behavior.
|
|
//
|
|
// Data source: the PRD "Technical Considerations" table
|
|
// (tasks/prd-provider-env-parity.md), derived from pi's env-api-keys.ts and the
|
|
// per-provider *.models.ts files.
|
|
//
|
|
// Security: the registry holds only env var NAMES, never secret values. Keys are
|
|
// resolved from the environment at request time (see auth.go) and never logged.
|
|
package provider
|
|
|
|
// ProviderSpec is the metadata describing one built-in provider. It is the
|
|
// single source of truth consumed by auth resolution, the --provider flag,
|
|
// base_url override handling, and per-provider wiring.
|
|
type ProviderSpec struct {
|
|
// Name is the canonical provider name (e.g. "deepseek", "zai-coding-cn").
|
|
Name string
|
|
// EnvVars lists the environment variables checked (in precedence order) for
|
|
// this provider's API key. The first non-empty value wins.
|
|
EnvVars []string
|
|
// DefaultBaseURL is the provider's default API endpoint. It may be a template
|
|
// (containing placeholders like {region}) for providers whose endpoint is
|
|
// composed from additional parameters (Bedrock, Vertex, Cloudflare, Azure).
|
|
DefaultBaseURL string
|
|
// Protocol is the wire protocol the provider speaks: "openai" (OpenAI Chat
|
|
// Completions) or "anthropic" (Anthropic Messages).
|
|
Protocol string
|
|
// AuthScheme names how credentials are attached: "bearer", "x-api-key",
|
|
// "aws", "azure", or "special".
|
|
AuthScheme string
|
|
// ExtraHeaders are provider-specific headers attached to every request (may
|
|
// be nil).
|
|
ExtraHeaders map[string]string
|
|
// BaseURLEnvVars lists provider-specific base-URL override environment
|
|
// variables (e.g. AZURE_OPENAI_BASE_URL), in precedence order. May be empty;
|
|
// the generic <PROVIDER>_BASE_URL convention is handled by callers.
|
|
BaseURLEnvVars []string
|
|
}
|
|
|
|
// Protocol values.
|
|
const (
|
|
ProtocolOpenAI = "openai"
|
|
ProtocolAnthropic = "anthropic"
|
|
)
|
|
|
|
// AuthScheme values.
|
|
const (
|
|
AuthBearer = "bearer"
|
|
AuthXAPIKey = "x-api-key"
|
|
AuthAWS = "aws"
|
|
AuthAzure = "azure"
|
|
AuthSpecial = "special"
|
|
)
|
|
|
|
// providerRegistry is the ordered list of all built-in provider specs. Order is
|
|
// stable so callers that enumerate providers (e.g. --help) get a deterministic
|
|
// list. LookupProviderSpec indexes it by name.
|
|
var providerRegistry = []ProviderSpec{
|
|
{
|
|
Name: "anthropic",
|
|
EnvVars: []string{"ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "CLAUDE_API_KEY"},
|
|
DefaultBaseURL: anthropicBaseURL, // https://api.anthropic.com/v1
|
|
Protocol: ProtocolAnthropic,
|
|
AuthScheme: AuthXAPIKey,
|
|
},
|
|
{
|
|
Name: "openai",
|
|
EnvVars: []string{"OPENAI_API_KEY"},
|
|
DefaultBaseURL: "https://api.openai.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "ant-ling",
|
|
EnvVars: []string{"ANT_LING_API_KEY"},
|
|
DefaultBaseURL: "https://api.ant-ling.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "deepseek",
|
|
EnvVars: []string{"DEEPSEEK_API_KEY"},
|
|
DefaultBaseURL: "https://api.deepseek.com",
|
|
Protocol: ProtocolOpenAIResponses,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "nvidia",
|
|
EnvVars: []string{"NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY"},
|
|
DefaultBaseURL: nvidiaBaseURL, // https://integrate.api.nvidia.com/v1
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "google",
|
|
EnvVars: []string{"GEMINI_API_KEY", "GOOGLE_API_KEY"},
|
|
DefaultBaseURL: "https://generativelanguage.googleapis.com/v1beta",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "groq",
|
|
EnvVars: []string{"GROQ_API_KEY"},
|
|
DefaultBaseURL: "https://api.groq.com/openai/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "cerebras",
|
|
EnvVars: []string{"CEREBRAS_API_KEY"},
|
|
DefaultBaseURL: "https://api.cerebras.ai/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "xai",
|
|
EnvVars: []string{"XAI_API_KEY"},
|
|
DefaultBaseURL: "https://api.x.ai/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "openrouter",
|
|
EnvVars: []string{"OPENROUTER_API_KEY"},
|
|
DefaultBaseURL: openRouterBaseURL, // https://openrouter.ai/api/v1
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "vercel-ai-gateway",
|
|
EnvVars: []string{"AI_GATEWAY_API_KEY"},
|
|
DefaultBaseURL: "https://ai-gateway.vercel.sh",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "zai",
|
|
EnvVars: []string{"ZAI_API_KEY"},
|
|
DefaultBaseURL: "https://api.z.ai/api/coding/paas/v4",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "zai-coding-cn",
|
|
EnvVars: []string{"ZAI_CODING_CN_API_KEY"},
|
|
DefaultBaseURL: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "mistral",
|
|
EnvVars: []string{"MISTRAL_API_KEY"},
|
|
DefaultBaseURL: "https://api.mistral.ai",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "minimax",
|
|
EnvVars: []string{"MINIMAX_API_KEY"},
|
|
DefaultBaseURL: "https://api.minimax.io/anthropic",
|
|
Protocol: ProtocolAnthropic,
|
|
AuthScheme: AuthXAPIKey,
|
|
},
|
|
{
|
|
Name: "minimax-cn",
|
|
EnvVars: []string{"MINIMAX_CN_API_KEY"},
|
|
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
|
Protocol: ProtocolAnthropic,
|
|
AuthScheme: AuthXAPIKey,
|
|
},
|
|
{
|
|
Name: "moonshotai",
|
|
EnvVars: []string{"MOONSHOT_API_KEY"},
|
|
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "moonshotai-cn",
|
|
EnvVars: []string{"MOONSHOT_API_KEY"},
|
|
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "huggingface",
|
|
EnvVars: []string{"HF_TOKEN"},
|
|
DefaultBaseURL: "https://router.huggingface.co/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "fireworks",
|
|
EnvVars: []string{"FIREWORKS_API_KEY"},
|
|
DefaultBaseURL: "https://api.fireworks.ai/inference",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "together",
|
|
EnvVars: []string{"TOGETHER_API_KEY"},
|
|
DefaultBaseURL: "https://api.together.ai/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "opencode",
|
|
EnvVars: []string{"OPENCODE_API_KEY"},
|
|
DefaultBaseURL: "https://opencode.ai/zen",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "opencode-go",
|
|
EnvVars: []string{"OPENCODE_API_KEY"},
|
|
DefaultBaseURL: "https://opencode.ai/zen/go",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "kimi-coding",
|
|
EnvVars: []string{"KIMI_API_KEY"},
|
|
DefaultBaseURL: "https://api.kimi.com/coding",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "xiaomi",
|
|
EnvVars: []string{"XIAOMI_API_KEY"},
|
|
DefaultBaseURL: "https://api.xiaomimimo.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "xiaomi-token-plan-cn",
|
|
EnvVars: []string{"XIAOMI_TOKEN_PLAN_CN_API_KEY"},
|
|
DefaultBaseURL: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "xiaomi-token-plan-ams",
|
|
EnvVars: []string{"XIAOMI_TOKEN_PLAN_AMS_API_KEY"},
|
|
DefaultBaseURL: "https://token-plan-ams.xiaomimimo.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "xiaomi-token-plan-sgp",
|
|
EnvVars: []string{"XIAOMI_TOKEN_PLAN_SGP_API_KEY"},
|
|
DefaultBaseURL: "https://token-plan-sgp.xiaomimimo.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
// Chinese cloud LLM platforms. All four expose OpenAI-compatible endpoints
|
|
// authenticated with a plain Bearer API key, so they reuse the standard
|
|
// OpenAI-compatible driver with no bespoke auth. Base URLs are the platforms'
|
|
// OpenAI-compatible endpoints as documented at implementation time.
|
|
{
|
|
// Baidu AI Cloud Qianfan (Baidu Qianfan).
|
|
Name: "qianfan",
|
|
EnvVars: []string{"QIANFAN_API_KEY"},
|
|
DefaultBaseURL: "https://qianfan.baidubce.com/v2",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
// ByteDance Volcengine Ark (Volcengine Ark). ARK_API_KEY is the platform's
|
|
// conventional variable; VOLCENGINE_API_KEY is accepted as a fallback.
|
|
Name: "volcengine",
|
|
EnvVars: []string{"ARK_API_KEY", "VOLCENGINE_API_KEY"},
|
|
DefaultBaseURL: "https://ark.cn-beijing.volces.com/api/v3",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
// Alibaba Cloud DashScope (DashScope), OpenAI-compatible mode.
|
|
Name: "dashscope",
|
|
EnvVars: []string{"DASHSCOPE_API_KEY"},
|
|
DefaultBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
// Tencent Hunyuan (Hunyuan), OpenAI-compatible endpoint.
|
|
Name: "hunyuan",
|
|
EnvVars: []string{"HUNYUAN_API_KEY"},
|
|
DefaultBaseURL: "https://api.hunyuan.cloud.tencent.com/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "azure-openai-responses",
|
|
EnvVars: []string{"AZURE_OPENAI_API_KEY"},
|
|
// Endpoint is composed from AZURE_OPENAI_BASE_URL / AZURE_OPENAI_RESOURCE_NAME
|
|
// per the Azure OpenAI convention; there is no fixed public default.
|
|
DefaultBaseURL: "",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthAzure,
|
|
BaseURLEnvVars: []string{"AZURE_OPENAI_BASE_URL"},
|
|
},
|
|
{
|
|
Name: "amazon-bedrock",
|
|
EnvVars: []string{"AWS_BEARER_TOKEN_BEDROCK"},
|
|
// Region-specific runtime endpoint; {AWS_REGION} defaults to us-east-1.
|
|
DefaultBaseURL: "https://bedrock-runtime.{AWS_REGION}.amazonaws.com",
|
|
Protocol: ProtocolAnthropic,
|
|
AuthScheme: AuthAWS,
|
|
},
|
|
{
|
|
Name: "google-vertex",
|
|
EnvVars: []string{"GOOGLE_CLOUD_API_KEY"},
|
|
// Location-specific endpoint; protocol varies by model (Gemini vs Claude).
|
|
DefaultBaseURL: "https://{location}-aiplatform.googleapis.com",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthSpecial,
|
|
},
|
|
{
|
|
Name: "cloudflare-workers-ai",
|
|
EnvVars: []string{"CLOUDFLARE_API_KEY"},
|
|
// {id} is CLOUDFLARE_ACCOUNT_ID.
|
|
DefaultBaseURL: "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1",
|
|
Protocol: ProtocolOpenAI,
|
|
AuthScheme: AuthBearer,
|
|
},
|
|
{
|
|
Name: "cloudflare-ai-gateway",
|
|
EnvVars: []string{"CLOUDFLARE_API_KEY"},
|
|
// {acct}/{gw} are CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_GATEWAY_ID.
|
|
DefaultBaseURL: "https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/anthropic",
|
|
Protocol: ProtocolAnthropic,
|
|
AuthScheme: AuthXAPIKey,
|
|
},
|
|
}
|
|
|
|
// providerRegistryByName indexes providerRegistry by provider name for O(1)
|
|
// lookup. Built once at package init.
|
|
var providerRegistryByName = func() map[string]ProviderSpec {
|
|
m := make(map[string]ProviderSpec, len(providerRegistry))
|
|
for _, spec := range providerRegistry {
|
|
m[spec.Name] = spec
|
|
}
|
|
return m
|
|
}()
|
|
|
|
// LookupProviderSpec returns the ProviderSpec for a provider name and whether it
|
|
// is a known built-in provider. The returned spec is a copy; mutating its slice
|
|
// or map fields is discouraged as they are shared with the registry.
|
|
func LookupProviderSpec(name string) (ProviderSpec, bool) {
|
|
spec, ok := providerRegistryByName[name]
|
|
return spec, ok
|
|
}
|
|
|
|
// ProviderSpecs returns all built-in provider specs in registry (display) order.
|
|
// Callers must not mutate the returned specs' slice/map fields.
|
|
func ProviderSpecs() []ProviderSpec {
|
|
out := make([]ProviderSpec, len(providerRegistry))
|
|
copy(out, providerRegistry)
|
|
return out
|
|
}
|
|
|
|
// ProviderNames returns all built-in provider names in registry order.
|
|
func ProviderNames() []string {
|
|
out := make([]string, len(providerRegistry))
|
|
for i, spec := range providerRegistry {
|
|
out[i] = spec.Name
|
|
}
|
|
return out
|
|
}
|