mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
## Description Registers `/api/v2/ai-gateway/*` as the new API path for AI Gateway, replacing `/api/v2/aibridge/*`. Both prefixes share the same route builder (`aiBridgeRoutes`) backed by a single in-memory handler, so existing `/aibridge` endpoints continue to work. New endpoints must be registered on the enterprise API handler under `/api/v2/ai-gateway` only. Swagger annotations now point to `/api/v2/ai-gateway` paths with a backward-compatibility note referencing `/aibridge`. The legacy `/aibridge` routes are skipped in the swagger documentation test. ## Changes - Store one raw handler (`aiGatewayHandler`) instead of two prefix-stripped handlers - Register `/ai-gateway` and `/ai-gateway/proxy` route aliases alongside legacy `/aibridge` routes - Move `/aibridge/keys` to `/ai-gateway/keys` - Update in-process transport to use `/api/v2/ai-gateway` prefix - Update SDK client URLs and proxy forwarding URL - Swap `@Router` and `@Tags` annotations from `aibridge`/`AI Bridge` to `ai-gateway`/`AI Gateway` - Rename user-facing error messages from "AI Bridge" to "AI Gateway" - Define consts for route prefixes (`AIGatewayRootPath`, `AIBridgeRootPath`) - Update tests and comments to use new paths Note: the following will be addressed in follow-up PRs: - Frontend API URLs - Frontend routes and redirects - Dogfood main.tf updates - Hand-written documentation URL updates - aibridge internal comments and nits - Scale tests path updates Refs https://linear.app/coder/issue/AIGOV-230 > Generated with the assistance of Coder Agents (@ssncferreira)
72 lines
2.5 KiB
Go
72 lines
2.5 KiB
Go
// Package aibridge provides utilities for the AI Bridge feature.
|
|
package aibridge
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// HeaderCoderToken is a header set by clients opting into BYOK
|
|
// (Bring Your Own Key) mode. It carries the Coder token so
|
|
// that Authorization and X-Api-Key can carry the user's own LLM
|
|
// credentials. When present, AI Bridge forwards the user's LLM
|
|
// headers unchanged instead of injecting the centralized key.
|
|
//
|
|
// The AI Bridge proxy also sets this header automatically for clients
|
|
// that use per-user LLM credentials but cannot set custom headers.
|
|
const HeaderCoderToken = "X-Coder-AI-Governance-Token" //nolint:gosec // This is a header name, not a credential.
|
|
|
|
// HeaderCoderRequestID is a header set by aibridgeproxyd on each
|
|
// 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"
|
|
)
|
|
|
|
// ChatGPT provider.
|
|
const (
|
|
ProviderChatGPT = "chatgpt"
|
|
HostChatGPT = "chatgpt.com"
|
|
BaseURLChatGPT = "https://" + HostChatGPT + "/backend-api/codex"
|
|
)
|
|
|
|
// API route prefixes for the AI Gateway and legacy AI Bridge endpoints.
|
|
const (
|
|
// AIGatewayRootPath is the URL prefix the AI Gateway handler
|
|
// registers all of its routes under.
|
|
AIGatewayRootPath = "/api/v2/ai-gateway"
|
|
// AIBridgeRootPath is the legacy prefix kept for backward compatibility.
|
|
AIBridgeRootPath = "/api/v2/aibridge"
|
|
)
|
|
|
|
// 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 {
|
|
return strings.TrimSpace(header.Get(HeaderCoderToken)) != ""
|
|
}
|
|
|
|
// ExtractAuthToken extracts a token from HTTP headers.
|
|
// It checks the BYOK header first (set by clients opting into BYOK),
|
|
// then falls back to Authorization: Bearer and X-Api-Key for direct
|
|
// centralized mode. If none are present, an empty string is returned.
|
|
func ExtractAuthToken(header http.Header) string {
|
|
if token := strings.TrimSpace(header.Get(HeaderCoderToken)); token != "" {
|
|
return token
|
|
}
|
|
if auth := strings.TrimSpace(header.Get("Authorization")); auth != "" {
|
|
fields := strings.Fields(auth)
|
|
if len(fields) == 2 && strings.EqualFold(fields[0], "Bearer") {
|
|
return fields[1]
|
|
}
|
|
}
|
|
if apiKey := strings.TrimSpace(header.Get("X-Api-Key")); apiKey != "" {
|
|
return apiKey
|
|
}
|
|
return ""
|
|
}
|