mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: configure multiple AI Bridge providers of the same type (#23948)
_Disclaimer: produced mostly by Claude Opus 4.6 following detailed planning._ ## Summary - Support multiple instances of the same AI Bridge provider type via indexed env vars (`CODER_AIBRIDGE_PROVIDER_<N>_<KEY>`), following the `CODER_EXTERNAL_AUTH_<N>_<KEY>` pattern - Existing single-provider env vars (`CODER_AIBRIDGE_OPENAI_KEY`, etc.) continue to work unchanged - Setting both a legacy env var and an indexed provider with the same name errors at startup to prevent silent misconfiguration - Mark legacy provider fields (`OpenAI`, `Anthropic`, `Bedrock`) as deprecated in `AIBridgeConfig` in favor of `Providers` ## Example ```sh CODER_AIBRIDGE_PROVIDER_0_TYPE=anthropic CODER_AIBRIDGE_PROVIDER_0_NAME=anthropic-corp CODER_AIBRIDGE_PROVIDER_0_KEY=sk-ant-corp-xxx CODER_AIBRIDGE_PROVIDER_0_BASE_URL=https://llm-proxy.internal.example.com/anthropic CODER_AIBRIDGE_PROVIDER_1_TYPE=anthropic CODER_AIBRIDGE_PROVIDER_1_NAME=anthropic-direct CODER_AIBRIDGE_PROVIDER_1_KEY=sk-ant-direct-yyy ``` Each instance is routed by name: - /api/v2/aibridge/**anthropic-corp**/v1/messages - /api/v2/aibridge/**anthropic-direct**/v1/messages Closes [AIGOV-157](https://linear.app/codercom/issue/AIGOV-157/spike-to-understand-if-there-is-a-simple-way-to-handle-multi-api-key) --------- Signed-off-by: Danny Kopping <danny@coder.com>
This commit is contained in:
+52
-25
@@ -3650,7 +3650,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The base URL of the OpenAI API.",
|
||||
Flag: "aibridge-openai-base-url",
|
||||
Env: "CODER_AIBRIDGE_OPENAI_BASE_URL",
|
||||
Value: &c.AI.BridgeConfig.OpenAI.BaseURL,
|
||||
Value: &c.AI.BridgeConfig.LegacyOpenAI.BaseURL,
|
||||
Default: "https://api.openai.com/v1/",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
YAML: "openai_base_url",
|
||||
@@ -3660,7 +3660,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The key to authenticate against the OpenAI API.",
|
||||
Flag: "aibridge-openai-key",
|
||||
Env: "CODER_AIBRIDGE_OPENAI_KEY",
|
||||
Value: &c.AI.BridgeConfig.OpenAI.Key,
|
||||
Value: &c.AI.BridgeConfig.LegacyOpenAI.Key,
|
||||
Default: "",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
Annotations: serpent.Annotations{}.Mark(annotationSecretKey, "true"),
|
||||
@@ -3670,7 +3670,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The base URL of the Anthropic API.",
|
||||
Flag: "aibridge-anthropic-base-url",
|
||||
Env: "CODER_AIBRIDGE_ANTHROPIC_BASE_URL",
|
||||
Value: &c.AI.BridgeConfig.Anthropic.BaseURL,
|
||||
Value: &c.AI.BridgeConfig.LegacyAnthropic.BaseURL,
|
||||
Default: "https://api.anthropic.com/",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
YAML: "anthropic_base_url",
|
||||
@@ -3680,7 +3680,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The key to authenticate against the Anthropic API.",
|
||||
Flag: "aibridge-anthropic-key",
|
||||
Env: "CODER_AIBRIDGE_ANTHROPIC_KEY",
|
||||
Value: &c.AI.BridgeConfig.Anthropic.Key,
|
||||
Value: &c.AI.BridgeConfig.LegacyAnthropic.Key,
|
||||
Default: "",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
Annotations: serpent.Annotations{}.Mark(annotationSecretKey, "true"),
|
||||
@@ -3691,7 +3691,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
"over CODER_AIBRIDGE_BEDROCK_REGION.",
|
||||
Flag: "aibridge-bedrock-base-url",
|
||||
Env: "CODER_AIBRIDGE_BEDROCK_BASE_URL",
|
||||
Value: &c.AI.BridgeConfig.Bedrock.BaseURL,
|
||||
Value: &c.AI.BridgeConfig.LegacyBedrock.BaseURL,
|
||||
Default: "",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
YAML: "bedrock_base_url",
|
||||
@@ -3702,7 +3702,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
"'https://bedrock-runtime.<region>.amazonaws.com'.",
|
||||
Flag: "aibridge-bedrock-region",
|
||||
Env: "CODER_AIBRIDGE_BEDROCK_REGION",
|
||||
Value: &c.AI.BridgeConfig.Bedrock.Region,
|
||||
Value: &c.AI.BridgeConfig.LegacyBedrock.Region,
|
||||
Default: "",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
YAML: "bedrock_region",
|
||||
@@ -3712,7 +3712,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The access key to authenticate against the AWS Bedrock API.",
|
||||
Flag: "aibridge-bedrock-access-key",
|
||||
Env: "CODER_AIBRIDGE_BEDROCK_ACCESS_KEY",
|
||||
Value: &c.AI.BridgeConfig.Bedrock.AccessKey,
|
||||
Value: &c.AI.BridgeConfig.LegacyBedrock.AccessKey,
|
||||
Default: "",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
Annotations: serpent.Annotations{}.Mark(annotationSecretKey, "true"),
|
||||
@@ -3722,7 +3722,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The access key secret to use with the access key to authenticate against the AWS Bedrock API.",
|
||||
Flag: "aibridge-bedrock-access-key-secret",
|
||||
Env: "CODER_AIBRIDGE_BEDROCK_ACCESS_KEY_SECRET",
|
||||
Value: &c.AI.BridgeConfig.Bedrock.AccessKeySecret,
|
||||
Value: &c.AI.BridgeConfig.LegacyBedrock.AccessKeySecret,
|
||||
Default: "",
|
||||
Group: &deploymentGroupAIBridge,
|
||||
Annotations: serpent.Annotations{}.Mark(annotationSecretKey, "true"),
|
||||
@@ -3732,7 +3732,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The model to use when making requests to the AWS Bedrock API.",
|
||||
Flag: "aibridge-bedrock-model",
|
||||
Env: "CODER_AIBRIDGE_BEDROCK_MODEL",
|
||||
Value: &c.AI.BridgeConfig.Bedrock.Model,
|
||||
Value: &c.AI.BridgeConfig.LegacyBedrock.Model,
|
||||
Default: "global.anthropic.claude-sonnet-4-5-20250929-v1:0", // See https://docs.claude.com/en/api/claude-on-amazon-bedrock#accessing-bedrock.
|
||||
Group: &deploymentGroupAIBridge,
|
||||
YAML: "bedrock_model",
|
||||
@@ -3742,7 +3742,7 @@ Write out the current server config as YAML to stdout.`,
|
||||
Description: "The small fast model to use when making requests to the AWS Bedrock API. Claude Code uses Haiku-class models to perform background tasks. See https://docs.claude.com/en/docs/claude-code/settings#environment-variables.",
|
||||
Flag: "aibridge-bedrock-small-fastmodel",
|
||||
Env: "CODER_AIBRIDGE_BEDROCK_SMALL_FAST_MODEL",
|
||||
Value: &c.AI.BridgeConfig.Bedrock.SmallFastModel,
|
||||
Value: &c.AI.BridgeConfig.LegacyBedrock.SmallFastModel,
|
||||
Default: "global.anthropic.claude-haiku-4-5-20251001-v1:0", // See https://docs.claude.com/en/api/claude-on-amazon-bedrock#accessing-bedrock.
|
||||
Group: &deploymentGroupAIBridge,
|
||||
YAML: "bedrock_small_fast_model",
|
||||
@@ -3940,17 +3940,15 @@ Write out the current server config as YAML to stdout.`,
|
||||
YAML: "key_file",
|
||||
},
|
||||
{
|
||||
Name: "AI Bridge Proxy Domain Allowlist",
|
||||
Description: "Comma-separated list of AI provider domains for which HTTPS traffic will be decrypted and routed through AI Bridge. " +
|
||||
"Requests to other domains will be tunneled directly without decryption. " +
|
||||
"Supported domains: api.anthropic.com, api.openai.com, api.individual.githubcopilot.com, api.business.githubcopilot.com, api.enterprise.githubcopilot.com, chatgpt.com.",
|
||||
Flag: "aibridge-proxy-domain-allowlist",
|
||||
Env: "CODER_AIBRIDGE_PROXY_DOMAIN_ALLOWLIST",
|
||||
Value: &c.AI.BridgeProxyConfig.DomainAllowlist,
|
||||
Default: "api.anthropic.com,api.openai.com,api.individual.githubcopilot.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,chatgpt.com",
|
||||
Hidden: true,
|
||||
Group: &deploymentGroupAIBridgeProxy,
|
||||
YAML: "domain_allowlist",
|
||||
Name: "AI Bridge Proxy Domain Allowlist",
|
||||
Description: "Deprecated: This value is now derived automatically from the configured AI Bridge providers' base URLs. Setting this value has no effect. This option will be removed in a future release.",
|
||||
Flag: "aibridge-proxy-domain-allowlist",
|
||||
Env: "CODER_AIBRIDGE_PROXY_DOMAIN_ALLOWLIST",
|
||||
Value: &c.AI.BridgeProxyConfig.DomainAllowlist,
|
||||
Default: "",
|
||||
Hidden: true,
|
||||
Group: &deploymentGroupAIBridgeProxy,
|
||||
YAML: "domain_allowlist",
|
||||
},
|
||||
{
|
||||
Name: "AI Bridge Proxy Upstream Proxy",
|
||||
@@ -4047,10 +4045,16 @@ Write out the current server config as YAML to stdout.`,
|
||||
}
|
||||
|
||||
type AIBridgeConfig struct {
|
||||
Enabled serpent.Bool `json:"enabled" typescript:",notnull"`
|
||||
OpenAI AIBridgeOpenAIConfig `json:"openai" typescript:",notnull"`
|
||||
Anthropic AIBridgeAnthropicConfig `json:"anthropic" typescript:",notnull"`
|
||||
Bedrock AIBridgeBedrockConfig `json:"bedrock" typescript:",notnull"`
|
||||
Enabled serpent.Bool `json:"enabled" typescript:",notnull"`
|
||||
// Deprecated: Use Providers with indexed CODER_AIBRIDGE_PROVIDER_<N>_* env vars instead.
|
||||
LegacyOpenAI AIBridgeOpenAIConfig `json:"openai" typescript:",notnull"`
|
||||
// Deprecated: Use Providers with indexed CODER_AIBRIDGE_PROVIDER_<N>_* env vars instead.
|
||||
LegacyAnthropic AIBridgeAnthropicConfig `json:"anthropic" typescript:",notnull"`
|
||||
// Deprecated: Use Providers with indexed CODER_AIBRIDGE_PROVIDER_<N>_* env vars instead.
|
||||
LegacyBedrock AIBridgeBedrockConfig `json:"bedrock" typescript:",notnull"`
|
||||
// Providers holds provider instances populated from CODER_AIBRIDGE_PROVIDER_<N>_<KEY>
|
||||
// env vars and/or the deprecated LegacyOpenAI/LegacyAnthropic/LegacyBedrock fields above.
|
||||
Providers []AIBridgeProviderConfig `json:"providers,omitempty"`
|
||||
// Deprecated: Injected MCP in AI Bridge is deprecated and will be removed in a future release.
|
||||
InjectCoderMCPTools serpent.Bool `json:"inject_coder_mcp_tools" typescript:",notnull"`
|
||||
Retention serpent.Duration `json:"retention" typescript:",notnull"`
|
||||
@@ -4086,6 +4090,29 @@ type AIBridgeBedrockConfig struct {
|
||||
SmallFastModel serpent.String `json:"small_fast_model" typescript:",notnull"`
|
||||
}
|
||||
|
||||
// AIBridgeProviderConfig represents a single AI Bridge provider instance,
|
||||
// parsed from CODER_AIBRIDGE_PROVIDER_<N>_<KEY> environment variables.
|
||||
// This follows the same indexed pattern as ExternalAuthConfig.
|
||||
type AIBridgeProviderConfig struct {
|
||||
// Type is the provider type: "openai", "anthropic", or "copilot".
|
||||
Type string `json:"type"`
|
||||
// Name is the unique instance identifier used for routing.
|
||||
// Defaults to Type if not provided.
|
||||
Name string `json:"name"`
|
||||
// Key is the API key for authenticating with the upstream provider.
|
||||
Key string `json:"-"`
|
||||
// BaseURL is the base URL of the upstream provider API.
|
||||
BaseURL string `json:"base_url"`
|
||||
|
||||
// Bedrock fields (only applicable when Type == "anthropic").
|
||||
BedrockBaseURL string `json:"-"`
|
||||
BedrockRegion string `json:"bedrock_region,omitempty"`
|
||||
BedrockAccessKey string `json:"-"`
|
||||
BedrockAccessKeySecret string `json:"-"`
|
||||
BedrockModel string `json:"bedrock_model,omitempty"`
|
||||
BedrockSmallFastModel string `json:"bedrock_small_fast_model,omitempty"`
|
||||
}
|
||||
|
||||
type AIBridgeProxyConfig struct {
|
||||
Enabled serpent.Bool `json:"enabled" typescript:",notnull"`
|
||||
ListenAddr serpent.String `json:"listen_addr" typescript:",notnull"`
|
||||
|
||||
Reference in New Issue
Block a user