feat: add ai-gateway start command - #26605
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Docs preview📖 View docs preview for |
a66f1fe to
c768ef1
Compare
d2902a8 to
a0e84e0
Compare
c768ef1 to
8a51ad7
Compare
a0e84e0 to
0814404
Compare
8a51ad7 to
94f6f0d
Compare
0814404 to
06972b6
Compare
94f6f0d to
0d518d8
Compare
ce1531b to
cfecff6
Compare
0d518d8 to
3ca7c1b
Compare
3ca7c1b to
ea4601e
Compare
cfecff6 to
d54cd8d
Compare
ea4601e to
d1181b3
Compare
334315a to
9973338
Compare
4350b16 to
2138d0d
Compare
06af6f1 to
83efd04
Compare
2138d0d to
754b2bd
Compare
754b2bd to
1ae96fc
Compare
Documentation CheckThis PR adds Updates Needed
Automated review via Coder Agents |
| if errors.As(err, &sdkErr) { | ||
| switch sdkErr.StatusCode() { | ||
| // These statuses are returned by the /api/v2/ai-gateway/serve (wrong Gateway key or incompatible API versions) | ||
| // or FeatureAIBridge check. |
There was a problem hiding this comment.
So these status codes can only happen when calling the /api/v2/ai-gateway/serve endpoint (including the feature check), right?. What happens if the URL is valid, but incorrect, we would get a 404 here, that would be considered a transient error and retried forever, right? 🤔
There was a problem hiding this comment.
Yes, those sdkErr is only returned by /serve endpoint. Changed comment so it is clearer.
Yes, if URL is unreachable or returns 404 standalone will retry forever to connect.
| resp, err := client.IsAuthorized(ctx, authReq) | ||
| if err != nil { | ||
| logger.Warn(ctx, "key authorization check failed", slog.Error(err)) | ||
| logger.Warn(ctx, "key authorization check failed", slog.F("error", err.Error())) |
There was a problem hiding this comment.
I added this to shorten the logs emitted. Each time wrong coder token is used by client connecting to AI Gateway error + stack will be logged:
2026-07-07 12:57:15.230 [warn] aibridged: key authorization check failed method=POST path=/openai/v1/responses source="" auth_mode=centralized auth_delegated=false ...
error= invalid key
storj.io/drpc/drpcwire.UnmarshalError:26
storj.io/drpc/drpcstream.(*Stream).HandlePacket:230
storj.io/drpc/drpcmanager.(*Manager).manageReader:250
With this change only 1 line is logged:
2026-07-07 12:56:02.513 [warn] aibridged: key authorization check failed method=POST path=/openai/v1/responses source="" auth_mode=centralized auth_delegated=false error="invalid key"
Reverted to original to keep pattern used in other places but maybe some throttling would be nice here.
| } | ||
| } | ||
|
|
||
| shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), shutdownTimeout) |
There was a problem hiding this comment.
Is the shutdownTimeout=15s enough? 🤔 IIUC this means that if for instance, ai-gateway receives a SIGTERM, it stops accepting new requests, waits up to 15 seconds for in-flight requests to finish. Any request still running at 15s gets its connection cut and the client sees a broken stream mid-response.
15 seconds feels short for some LLM requests, would it make sense to increase this?
There was a problem hiding this comment.
Changed to 5 min. Maybe a bit much but for standalone maybe it is fine to have long timeout.
NewWebsocketDialer connects "coder ai-gateway start" to coderd's /api/v2/ai-gateway/serve endpoint over a yamux-multiplexed WebSocket. It lives with its sole consumer (aigatewaystart.go) on this branch.
The standalone gateway no longer builds providers from CODER_AI_GATEWAY_* env config; it fetches the provider set from coderd over DRPC (GetAIProviders). Update the command doc comment and --help text to match, and regenerate the help golden and CLI reference docs.
Both the standalone gateway dialer (coderd/aibridged/dialer.go) and the serve handler (enterprise/coderd/aibridgeserve.go) hardcoded the "version" query parameter. Extract it to aibridgedproto.VersionQueryParam so the two ends of the AI Gateway serve handshake share a single definition.
a76de94 to
2d2bb0c
Compare

This PR adds
coder ai-gateway startcommand that runs the AI Gateway as an independent process./api/v2/ai-gateway/servefor auth, recording and provider initialization./sessions) are only available thoughcoderd.Some wiring used by this new command is added.
NewWebsocketDialer- implements the standalone gateway's connection to coderd's/api/v2/ai-gateway/serveendpoint. It upgrades to a WebSocket, multiplexes with yamux, and wires all DRPC services.AIGatewayDataPlaneMiddleware- extracts the per-request middleware chain (concurrency limiting, rate limiting, BYOK gating) into a shared function used by both the embedded route and the standalone gateway.RootCmd.ResolveClientConnection- resolve the deployment URL and builds an HTTP transport without requiring a session token. Used inai-gateway startcommand as it authenticates using different credential type.