From b0036af57bf6aeb9b312362b92943b4aecda47e0 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Tue, 31 Mar 2026 16:00:37 +0100 Subject: [PATCH] feat: register multiple Copilot providers for business and enterprise upstreams (#23811) ## Description Adds support for multiple Copilot provider instances to route requests to different Copilot upstreams (individual, business, enterprise). Each instance has its own name and base URL, enabling per-upstream metrics, logs, circuit breakers, API dump, and routing. ## Changes * Add Copilot business and enterprise provider names and host constants * Register three Copilot provider instances in aibridged (default, business, enterprise) * Update `defaultAIBridgeProvider` in `aibridgeproxy` to route new Copilot hosts to their corresponding providers ## Related * Depends on: https://github.com/coder/aibridge/pull/240 * Closes: https://github.com/coder/aibridge/issues/152 Note: documentation changes will be added in a follow-up PR. _Disclaimer: initially produced by Claude Opus 4.6, heavily modified and reviewed by @ssncferreira ._ --- cli/testdata/server-config.yaml.golden | 8 ++++++-- coderd/aibridge/aibridge.go | 8 ++++++++ codersdk/deployment.go | 20 +++++++++++--------- enterprise/aibridgeproxyd/aibridgeproxyd.go | 4 ++++ enterprise/cli/aibridged.go | 14 ++++++++++++++ go.mod | 2 +- go.sum | 4 ++-- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 591ae7065b..b44c6accb2 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -857,13 +857,17 @@ aibridgeproxy: # 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. - # (default: api.anthropic.com,api.openai.com,api.individual.githubcopilot.com, + # api.openai.com, api.individual.githubcopilot.com, + # api.business.githubcopilot.com, api.enterprise.githubcopilot.com. + # (default: + # api.anthropic.com,api.openai.com,api.individual.githubcopilot.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com, # type: string-array) domain_allowlist: - api.anthropic.com - api.openai.com - api.individual.githubcopilot.com + - api.business.githubcopilot.com + - api.enterprise.githubcopilot.com # URL of an upstream HTTP proxy to chain tunneled (non-allowlisted) requests # through. Format: http://[user:pass@]host:port or https://[user:pass@]host:port. # (default: , type: string) diff --git a/coderd/aibridge/aibridge.go b/coderd/aibridge/aibridge.go index 215d1f62ec..74484fca3a 100644 --- a/coderd/aibridge/aibridge.go +++ b/coderd/aibridge/aibridge.go @@ -20,6 +20,14 @@ const HeaderCoderToken = "X-Coder-AI-Governance-Token" //nolint:gosec // This is // request forwarded to aibridged for cross-service log correlation. const HeaderCoderRequestID = "X-Coder-AI-Governance-Request-Id" +// Copilot provider. +const ( + ProviderCopilotBusiness = "copilot-business" + HostCopilotBusiness = "api.business.githubcopilot.com" + ProviderCopilotEnterprise = "copilot-enterprise" + HostCopilotEnterprise = "api.enterprise.githubcopilot.com" +) + // IsBYOK reports whether the request is using BYOK mode, determined // by the presence of the X-Coder-AI-Governance-Token header. func IsBYOK(header http.Header) bool { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 0b6b291fe5..3fa8c38266 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3923,15 +3923,17 @@ 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.", - 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", - Hidden: true, - Group: &deploymentGroupAIBridgeProxy, - YAML: "domain_allowlist", + 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.", + 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", + Hidden: true, + Group: &deploymentGroupAIBridgeProxy, + YAML: "domain_allowlist", }, { Name: "AI Bridge Proxy Upstream Proxy", diff --git a/enterprise/aibridgeproxyd/aibridgeproxyd.go b/enterprise/aibridgeproxyd/aibridgeproxyd.go index a0bfb2aeca..475e9a2f4a 100644 --- a/enterprise/aibridgeproxyd/aibridgeproxyd.go +++ b/enterprise/aibridgeproxyd/aibridgeproxyd.go @@ -776,6 +776,10 @@ func defaultAIBridgeProvider(host string) string { return aibridge.ProviderOpenAI case HostCopilot: return aibridge.ProviderCopilot + case agplaibridge.HostCopilotBusiness: + return agplaibridge.ProviderCopilotBusiness + case agplaibridge.HostCopilotEnterprise: + return agplaibridge.ProviderCopilotEnterprise default: return "" } diff --git a/enterprise/cli/aibridged.go b/enterprise/cli/aibridged.go index 09108ab55c..2dd5ca6c78 100644 --- a/enterprise/cli/aibridged.go +++ b/enterprise/cli/aibridged.go @@ -10,6 +10,7 @@ import ( "github.com/coder/aibridge" "github.com/coder/aibridge/config" + agplaibridge "github.com/coder/coder/v2/coderd/aibridge" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/aibridged" @@ -37,18 +38,31 @@ func newAIBridgeDaemon(coderAPI *coderd.API) (*aibridged.Server, error) { // Setup supported providers with circuit breaker config. providers := []aibridge.Provider{ aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{ + Name: aibridge.ProviderOpenAI, BaseURL: cfg.OpenAI.BaseURL.String(), Key: cfg.OpenAI.Key.String(), CircuitBreaker: cbConfig, SendActorHeaders: cfg.SendActorHeaders.Value(), }), aibridge.NewAnthropicProvider(aibridge.AnthropicConfig{ + Name: aibridge.ProviderAnthropic, BaseURL: cfg.Anthropic.BaseURL.String(), Key: cfg.Anthropic.Key.String(), CircuitBreaker: cbConfig, SendActorHeaders: cfg.SendActorHeaders.Value(), }, getBedrockConfig(cfg.Bedrock)), aibridge.NewCopilotProvider(aibridge.CopilotConfig{ + Name: aibridge.ProviderCopilot, + CircuitBreaker: cbConfig, + }), + aibridge.NewCopilotProvider(aibridge.CopilotConfig{ + Name: agplaibridge.ProviderCopilotBusiness, + BaseURL: "https://" + agplaibridge.HostCopilotBusiness, + CircuitBreaker: cbConfig, + }), + aibridge.NewCopilotProvider(aibridge.CopilotConfig{ + Name: agplaibridge.ProviderCopilotEnterprise, + BaseURL: "https://" + agplaibridge.HostCopilotEnterprise, CircuitBreaker: cbConfig, }), } diff --git a/go.mod b/go.mod index c32287ae8a..1e86143642 100644 --- a/go.mod +++ b/go.mod @@ -483,7 +483,7 @@ require ( github.com/anthropics/anthropic-sdk-go v1.19.0 github.com/brianvoe/gofakeit/v7 v7.14.0 github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 - github.com/coder/aibridge v1.0.8-0.20260331124826-77d597aa123b + github.com/coder/aibridge v1.0.8-0.20260331143727-519b082ad666 github.com/coder/aisdk-go v0.0.9 github.com/coder/boundary v0.8.4-0.20260304164748-566aeea939ab github.com/coder/preview v1.0.8 diff --git a/go.sum b/go.sum index e2b9255c82..2abfe5bd79 100644 --- a/go.sum +++ b/go.sum @@ -314,8 +314,8 @@ github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 h1:tRIViZ5JRmzdOEo5wUWngaGEFBG8OaE1o2GIHN5ujJ8= github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225/go.mod h1:rNLVpYgEVeu1Zk29K64z6Od8RBP9DwqCu9OfCzh8MR4= -github.com/coder/aibridge v1.0.8-0.20260331124826-77d597aa123b h1:geM9hIle5JCLUlnzEIladbpltBGyDH5R+bUv9+eDCME= -github.com/coder/aibridge v1.0.8-0.20260331124826-77d597aa123b/go.mod h1:u6WvGLMQQbk3ByeOw+LBdVgDNc/v/ujAtUc6MfvzQb4= +github.com/coder/aibridge v1.0.8-0.20260331143727-519b082ad666 h1:y4bqYPv4N4nIFpQ2XumBKj9suQJqFSGWQvKtUsN1t8c= +github.com/coder/aibridge v1.0.8-0.20260331143727-519b082ad666/go.mod h1:u6WvGLMQQbk3ByeOw+LBdVgDNc/v/ujAtUc6MfvzQb4= github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo= github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M= github.com/coder/boundary v0.8.4-0.20260304164748-566aeea939ab h1:HrlxyTmMQpOHfSKzRU1vf5TxrmV6vL5OiWq+Dvn5qh0=