mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: fetch providers over DRPC (#26650)
Closes [AIGOV-455](https://linear.app/codercom/issue/AIGOV-455/extend-drpc-with-buildproviders). ## Why The AI Gateway (`aibridged`) is being split into a standalone process that must not touch the database. `coderd` stays the source of truth and seeds the `ai_providers` / `ai_provider_keys` tables from the environment. This PR adds a DRPC call so the gateway fetches provider config from `coderd` instead of reading the DB, for both the embedded and standalone daemons. ## What - **Proto:** new `ProviderConfigurator` service with a unary `GetAIProviders` RPC, plus `AIProvider` / `AIProviderBedrock` messages. `CurrentMinor` bumped to 1 (additive). - **Server (`coderd/aibridgedserver`):** `GetAIProviders` runs a read-only `InTx` under `LockIDAIProvidersEnvSeed` so it never returns a mid-seed snapshot, reads providers (incl. disabled) plus keys for enabled ones, and maps to proto under `dbauthz.AsAIBridged`. Unmappable rows are skipped and logged; plaintext keys and Bedrock secrets are never logged. - **Client:** `DRPCProviderConfiguratorClient` wired into the client union, `dialer.go`, and `CreateInMemoryAIBridgeServer`. - **cli:** `BuildProvidersFromProto` maps the response through the existing DB-neutral `buildProvider`. A shared `poolRPCReloader` does the fetch/build/replace for both daemons: the embedded daemon reloads on every `ai_providers` change and fails startup if it cannot subscribe; the standalone gateway drives the same reloader once at startup, retrying until success and staying interruptible. - **Dead code removed:** `BuildProvidersFromConfig`, `ProvidersFromConfig`, `AIProviderFromConfig`, and the DB-read `BuildProviders` path.
This commit is contained in:
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"github.com/coder/coder/v2/aibridge/intercept/apidump"
|
||||
"github.com/coder/coder/v2/coderd/aibridged"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
@@ -78,8 +77,10 @@ func newAIBridgeProxyDaemon(coderAPI *coderd.API) (io.Closer, error) {
|
||||
|
||||
unsubscribe, err := aibridged.SubscribeProviderReload(ctx, coderAPI.Pubsub, srv, logger.Named("provider-reload"))
|
||||
if err != nil {
|
||||
logger.Warn(ctx, "subscribe aibridgeproxyd to ai providers change channel", slog.Error(err))
|
||||
unsubscribe = func() {}
|
||||
// Without the subscription the proxy can never track provider changes,
|
||||
// so fail startup rather than serve a permanently stale snapshot.
|
||||
_ = srv.Close()
|
||||
return nil, xerrors.Errorf("subscribe aibridgeproxyd to ai providers change channel: %w", err)
|
||||
}
|
||||
|
||||
// Register the handler so coderd can serve the proxy endpoints.
|
||||
|
||||
@@ -30,11 +30,11 @@ import (
|
||||
// last_heartbeat_at for its authenticating key.
|
||||
const aiGatewayKeyHeartbeatInterval = 60 * time.Second
|
||||
|
||||
// aiGatewayServe upgrades the connection to a WebSocket and serves the DRPC
|
||||
// services (Recorder, MCPConfigurator, Authorizer) to a remote standalone AI
|
||||
// Gateway replica, mirroring the embedded case. AI Gateway key authentication is
|
||||
// enforced before the WebSocket upgrade. License entitlement is enforced by
|
||||
// middleware on the route.
|
||||
// aiGatewayServe upgrades the connection to a WebSocket and serves the aibridged
|
||||
// DRPC services (Recorder, MCPConfigurator, Authorizer, ProviderConfigurator) to a remote standalone
|
||||
// AI Gateway replica, mirroring the embedded case. AI Gateway key
|
||||
// authentication is enforced before the WebSocket upgrade. License entitlement
|
||||
// is enforced by middleware on the route.
|
||||
//
|
||||
// @Summary AI Gateway serve
|
||||
// @ID ai-gateway-serve
|
||||
|
||||
Reference in New Issue
Block a user