feat: add /api/v2/ai-gateway API route aliases (#26475)

## 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)
This commit is contained in:
Susana Ferreira
2026-06-23 12:15:10 +01:00
committed by GitHub
parent 4691ef39cd
commit 970bd73691
28 changed files with 727 additions and 630 deletions
+3 -10
View File
@@ -12,13 +12,6 @@ import (
"github.com/coder/coder/v2/coderd/aibridge"
)
// aibridgeRootPath is the URL prefix the in-memory aibridged handler
// registers all of its routes under. The in-process round-tripper
// prepends this plus the provider name to every request before
// dispatch so callers can hand it upstream-shaped requests without
// knowing the daemon's mount layout.
const aibridgeRootPath = "/api/v2/aibridge"
// NewTransportFactory returns an [aibridge.TransportFactory] whose RoundTripper
// dispatches requests to handler in-process, streaming the response body
// through an [io.Pipe] so SSE/NDJSON/chunked responses propagate token-by-token
@@ -37,7 +30,7 @@ type transportFactory struct {
// TransportFor returns an in-process [http.RoundTripper] that dispatches
// requests through the aibridged handler. The provider name is the routing
// key the daemon mounts on; the round-tripper rewrites each request's URL
// path to "/api/v2/aibridge/<providerName>/..." before dispatching so
// path to "/api/v2/ai-gateway/<providerName>/..." before dispatching so
// callers can build upstream-shaped requests and stay agnostic of the
// daemon's mount layout. The source is attached to the request context for
// downstream logging; routing does not depend on it.
@@ -69,10 +62,10 @@ func (t *inMemoryRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
}
// Adapt the caller's upstream-shaped URL to the daemon's mount layout:
// "/api/v2/aibridge/<providerName>/<original-path>". Done here so
// "/api/v2/ai-gateway/<providerName>/<original-path>". Done here so
// callers do not need to encode the mount prefix or the provider
// routing key into the requests they hand to the transport.
newPath, err := url.JoinPath(aibridgeRootPath, t.providerName, req.URL.Path)
newPath, err := url.JoinPath(aibridge.AIGatewayRootPath, t.providerName, req.URL.Path)
if err != nil {
return nil, xerrors.Errorf("rewrite request URL for provider %q: %w", t.providerName, err)
}
+3 -3
View File
@@ -48,8 +48,8 @@ func TestTransportFactory_TransportFor(t *testing.T) {
t.Parallel()
// The round-tripper must adapt an upstream-shaped URL.Path
// ("/v1/messages") to the aibridge mount layout
// ("/api/v2/aibridge/<provider>/v1/messages") so callers don't
// ("/v1/messages") to the ai-gateway mount layout
// ("/api/v2/ai-gateway/<provider>/v1/messages") so callers don't
// have to encode the daemon's routing key into their requests.
got := make(chan string, 1)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -73,7 +73,7 @@ func TestTransportFactory_TransportFor(t *testing.T) {
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, "/api/v2/aibridge/my-anthropic/v1/messages", <-got)
require.Equal(t, "/api/v2/ai-gateway/my-anthropic/v1/messages", <-got)
require.Equal(t, origPath, req.URL.Path,
"caller's request URL must not be mutated by RoundTrip")
})