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:
Danny Kopping
2026-04-15 07:59:37 +00:00
committed by GitHub
parent 730edba87a
commit 08045c2aac
17 changed files with 1326 additions and 163 deletions
+10
View File
@@ -203,6 +203,16 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"base_url": "string",
"key": "string"
},
"providers": [
{
"base_url": "string",
"bedrock_model": "string",
"bedrock_region": "string",
"bedrock_small_fast_model": "string",
"name": "string",
"type": "string"
}
],
"rate_limit": 0,
"retention": 0,
"send_actor_headers": true,
+82 -17
View File
@@ -455,6 +455,16 @@
"base_url": "string",
"key": "string"
},
"providers": [
{
"base_url": "string",
"bedrock_model": "string",
"bedrock_region": "string",
"bedrock_small_fast_model": "string",
"name": "string",
"type": "string"
}
],
"rate_limit": 0,
"retention": 0,
"send_actor_headers": true,
@@ -464,23 +474,24 @@
### Properties
| Name | Type | Required | Restrictions | Description |
|-------------------------------------|----------------------------------------------------------------------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------|
| `anthropic` | [codersdk.AIBridgeAnthropicConfig](#codersdkaibridgeanthropicconfig) | false | | |
| `bedrock` | [codersdk.AIBridgeBedrockConfig](#codersdkaibridgebedrockconfig) | false | | |
| `circuit_breaker_enabled` | boolean | false | | Circuit breaker protects against cascading failures from upstream AI provider rate limits (429, 503, 529 overloaded). |
| `circuit_breaker_failure_threshold` | integer | false | | |
| `circuit_breaker_interval` | integer | false | | |
| `circuit_breaker_max_requests` | integer | false | | |
| `circuit_breaker_timeout` | integer | false | | |
| `enabled` | boolean | false | | |
| `inject_coder_mcp_tools` | boolean | false | | Deprecated: Injected MCP in AI Bridge is deprecated and will be removed in a future release. |
| `max_concurrency` | integer | false | | |
| `openai` | [codersdk.AIBridgeOpenAIConfig](#codersdkaibridgeopenaiconfig) | false | | |
| `rate_limit` | integer | false | | |
| `retention` | integer | false | | |
| `send_actor_headers` | boolean | false | | |
| `structured_logging` | boolean | false | | |
| Name | Type | Required | Restrictions | Description |
|-------------------------------------|-----------------------------------------------------------------------------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `anthropic` | [codersdk.AIBridgeAnthropicConfig](#codersdkaibridgeanthropicconfig) | false | | Deprecated: Use Providers with indexed CODER_AIBRIDGE_PROVIDER_<N>_* env vars instead. |
| `bedrock` | [codersdk.AIBridgeBedrockConfig](#codersdkaibridgebedrockconfig) | false | | Deprecated: Use Providers with indexed CODER_AIBRIDGE_PROVIDER_<N>_* env vars instead. |
| `circuit_breaker_enabled` | boolean | false | | Circuit breaker protects against cascading failures from upstream AI provider rate limits (429, 503, 529 overloaded). |
| `circuit_breaker_failure_threshold` | integer | false | | |
| `circuit_breaker_interval` | integer | false | | |
| `circuit_breaker_max_requests` | integer | false | | |
| `circuit_breaker_timeout` | integer | false | | |
| `enabled` | boolean | false | | |
| `inject_coder_mcp_tools` | boolean | false | | Deprecated: Injected MCP in AI Bridge is deprecated and will be removed in a future release. |
| `max_concurrency` | integer | false | | |
| `openai` | [codersdk.AIBridgeOpenAIConfig](#codersdkaibridgeopenaiconfig) | false | | Deprecated: Use Providers with indexed CODER_AIBRIDGE_PROVIDER_<N>_* env vars instead. |
| `providers` | array of [codersdk.AIBridgeProviderConfig](#codersdkaibridgeproviderconfig) | false | | Providers holds provider instances populated from CODER_AIBRIDGE_PROVIDER_<N>_<KEY> env vars and/or the deprecated LegacyOpenAI/LegacyAnthropic/LegacyBedrock fields above. |
| `rate_limit` | integer | false | | |
| `retention` | integer | false | | |
| `send_actor_headers` | boolean | false | | |
| `structured_logging` | boolean | false | | |
## codersdk.AIBridgeInterception
@@ -732,6 +743,30 @@
| `base_url` | string | false | | |
| `key` | string | false | | |
## codersdk.AIBridgeProviderConfig
```json
{
"base_url": "string",
"bedrock_model": "string",
"bedrock_region": "string",
"bedrock_small_fast_model": "string",
"name": "string",
"type": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|----------------------------|--------|----------|--------------|--------------------------------------------------------------------------------------------|
| `base_url` | string | false | | Base URL is the base URL of the upstream provider API. |
| `bedrock_model` | string | false | | |
| `bedrock_region` | string | false | | |
| `bedrock_small_fast_model` | string | false | | |
| `name` | string | false | | Name is the unique instance identifier used for routing. Defaults to Type if not provided. |
| `type` | string | false | | Type is the provider type: "openai", "anthropic", or "copilot". |
## codersdk.AIBridgeProxyConfig
```json
@@ -1234,6 +1269,16 @@
"base_url": "string",
"key": "string"
},
"providers": [
{
"base_url": "string",
"bedrock_model": "string",
"bedrock_region": "string",
"bedrock_small_fast_model": "string",
"name": "string",
"type": "string"
}
],
"rate_limit": 0,
"retention": 0,
"send_actor_headers": true,
@@ -3258,6 +3303,16 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"base_url": "string",
"key": "string"
},
"providers": [
{
"base_url": "string",
"bedrock_model": "string",
"bedrock_region": "string",
"bedrock_small_fast_model": "string",
"name": "string",
"type": "string"
}
],
"rate_limit": 0,
"retention": 0,
"send_actor_headers": true,
@@ -3837,6 +3892,16 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"base_url": "string",
"key": "string"
},
"providers": [
{
"base_url": "string",
"bedrock_model": "string",
"bedrock_region": "string",
"bedrock_small_fast_model": "string",
"name": "string",
"type": "string"
}
],
"rate_limit": 0,
"retention": 0,
"send_actor_headers": true,