diff --git a/coderd/aibridge/aibridge.go b/coderd/aibridge/aibridge.go index 4a9adee62e..716e7d5da7 100644 --- a/coderd/aibridge/aibridge.go +++ b/coderd/aibridge/aibridge.go @@ -6,18 +6,28 @@ import ( "strings" ) -// HeaderCoderAuth is an internal header used to pass the Coder token -// from AI Proxy to AI Bridge for authentication. This header is stripped -// by AI Bridge before forwarding requests to upstream providers. -const HeaderCoderAuth = "X-Coder-Token" +// 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. -// ExtractAuthToken extracts an authorization token from HTTP headers. -// It checks X-Coder-Token first (set by AI Proxy), then falls back -// to Authorization header (Bearer token) and X-Api-Key header, which represent -// the different ways clients authenticate against AI providers. -// If none are present, an empty string is returned. +// 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(HeaderCoderAuth)); token != "" { + if token := strings.TrimSpace(header.Get(HeaderCoderToken)); token != "" { return token } if auth := strings.TrimSpace(header.Get("Authorization")); auth != "" { diff --git a/enterprise/aibridged/aibridged_test.go b/enterprise/aibridged/aibridged_test.go index dbba210091..e12a6e1e0a 100644 --- a/enterprise/aibridged/aibridged_test.go +++ b/enterprise/aibridged/aibridged_test.go @@ -174,44 +174,107 @@ func TestServeHTTP_FailureModes(t *testing.T) { } } -func TestServeHTTP_CoderTokenRemoved(t *testing.T) { +func TestServeHTTP_StripCoderToken(t *testing.T) { t.Parallel() - mockHandler := &mockHandler{} + cases := []struct { + name string + reqHeaders map[string]string + expectPresent map[string]string // header → expected value + expectAbsent []string // headers that must be gone + }{ + { + // Centralized: the client sets Authorization and X-Api-Key, + // but does not include HeaderCoderToken. + // All auth headers are stripped. + name: "centralized", + reqHeaders: map[string]string{ + "Authorization": "Bearer coder-token", + "X-Api-Key": "sk-ant-api03-user-key", + }, + expectAbsent: []string{ + "Authorization", + "X-Api-Key", + agplaibridge.HeaderCoderToken, + }, + }, + { + // BYOK with access token: Coder token in BYOK header, + // user's access token in Authorization. Only the + // BYOK header is stripped. + name: "byok bearer token", + reqHeaders: map[string]string{ + agplaibridge.HeaderCoderToken: "coder-token", + "Authorization": "Bearer sk-ant-oat01-user-oauth-token", + }, + expectPresent: map[string]string{ + "Authorization": "Bearer sk-ant-oat01-user-oauth-token", + }, + expectAbsent: []string{ + agplaibridge.HeaderCoderToken, + }, + }, + { + // BYOK with personal API key: Coder token in BYOK header, + // user's API key in X-Api-Key. Only the BYOK header is + // stripped. + name: "byok api key", + reqHeaders: map[string]string{ + agplaibridge.HeaderCoderToken: "coder-token", + "X-Api-Key": "sk-ant-api03-user-key", + }, + expectPresent: map[string]string{ + "X-Api-Key": "sk-ant-api03-user-key", + }, + expectAbsent: []string{ + agplaibridge.HeaderCoderToken, + }, + }, + } - srv, client, pool := newTestServer(t) - conn := &mockDRPCConn{} - client.EXPECT().DRPCConn().AnyTimes().Return(conn) - client.EXPECT().IsAuthorized(gomock.Any(), gomock.Any()).AnyTimes().Return(&proto.IsAuthorizedResponse{OwnerId: uuid.NewString()}, nil) - pool.EXPECT().Acquire(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(mockHandler, nil) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() - httpSrv := httptest.NewServer(srv) - t.Cleanup(httpSrv.Close) + mockH := &mockHandler{} - ctx := testutil.Context(t, testutil.WaitShort) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, httpSrv.URL+"/openai/v1/chat/completions", nil) - require.NoError(t, err) + srv, client, pool := newTestServer(t) + conn := &mockDRPCConn{} + client.EXPECT().DRPCConn().AnyTimes().Return(conn) + client.EXPECT().IsAuthorized(gomock.Any(), gomock.Any()).AnyTimes().Return(&proto.IsAuthorizedResponse{OwnerId: uuid.NewString()}, nil) + pool.EXPECT().Acquire(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(mockH, nil) - // X-Coder-Token is used for authentication and should be stripped. - // Other authorization headers should be preserved. - req.Header.Set(agplaibridge.HeaderCoderAuth, "coder-token") - req.Header.Set("Authorization", "Bearer some-token") - req.Header.Set("X-Api-Key", "some-api-key") + httpSrv := httptest.NewServer(srv) + t.Cleanup(httpSrv.Close) - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer resp.Body.Close() + ctx := testutil.Context(t, testutil.WaitShort) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, httpSrv.URL+"/openai/v1/chat/completions", nil) + require.NoError(t, err) - require.Equal(t, http.StatusOK, resp.StatusCode) + for k, v := range tc.reqHeaders { + req.Header.Set(k, v) + } - // Verify X-Coder-Token was removed before forwarding to handler. - require.NotNil(t, mockHandler.headersReceived) - require.Empty(t, mockHandler.headersReceived.Get(agplaibridge.HeaderCoderAuth), - "X-Coder-Token should be removed before forwarding to handler") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() - // Verify other headers were preserved. - require.Equal(t, "Bearer some-token", mockHandler.headersReceived.Get("Authorization")) - require.Equal(t, "some-api-key", mockHandler.headersReceived.Get("X-Api-Key")) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.NotNil(t, mockH.headersReceived) + + for header, expected := range tc.expectPresent { + require.Equal(t, expected, mockH.headersReceived.Get(header), + "header %q should be preserved with value %q", header, expected) + } + for _, header := range tc.expectAbsent { + require.Empty(t, mockH.headersReceived.Get(header), + "header %q should be stripped", header) + } + // HeaderCoderToken should always be stripped + require.Empty(t, mockH.headersReceived.Get(agplaibridge.HeaderCoderToken), + "header %q should be stripped", agplaibridge.HeaderCoderToken) + }) + } } func TestExtractAuthToken(t *testing.T) { @@ -225,31 +288,6 @@ func TestExtractAuthToken(t *testing.T) { { name: "none", }, - { - name: "x-coder-token/empty", - headers: map[string]string{agplaibridge.HeaderCoderAuth: ""}, - }, - { - name: "x-coder-token/ok", - headers: map[string]string{agplaibridge.HeaderCoderAuth: "coder-token"}, - expectedKey: "coder-token", - }, - { - name: "x-coder-token/priority over authorization", - headers: map[string]string{ - agplaibridge.HeaderCoderAuth: "coder-token", - "Authorization": "Bearer other-token", - }, - expectedKey: "coder-token", - }, - { - name: "x-coder-token/priority over x-api-key", - headers: map[string]string{ - agplaibridge.HeaderCoderAuth: "coder-token", - "X-Api-Key": "api-key", - }, - expectedKey: "coder-token", - }, { name: "authorization/invalid", headers: map[string]string{"authorization": "invalid"}, @@ -285,6 +323,27 @@ func TestExtractAuthToken(t *testing.T) { headers: map[string]string{"X-Api-Key": "key"}, expectedKey: "key", }, + + // BYOK: X-Coder-AI-Governance-Token carries the Coder + // token and has the highest priority. + { + name: "byok/empty", + headers: map[string]string{agplaibridge.HeaderCoderToken: ""}, + }, + { + name: "byok/ok", + headers: map[string]string{agplaibridge.HeaderCoderToken: "coder-token"}, + expectedKey: "coder-token", + }, + { + name: "byok/priority over all", + headers: map[string]string{ + agplaibridge.HeaderCoderToken: "coder-token", + "Authorization": "Bearer oauth-token", + "X-Api-Key": "api-key", + }, + expectedKey: "coder-token", + }, } for _, tc := range cases { diff --git a/enterprise/aibridged/http.go b/enterprise/aibridged/http.go index 5693a7c413..007ec6325d 100644 --- a/enterprise/aibridged/http.go +++ b/enterprise/aibridged/http.go @@ -37,6 +37,12 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { logger := s.logger.With(slog.F("path", r.URL.Path)) + byok := agplaibridge.IsBYOK(r.Header) + authMode := "centralized" + if byok { + authMode = "byok" + } + key := strings.TrimSpace(agplaibridge.ExtractAuthToken(r.Header)) if key == "" { logger.Warn(ctx, "no auth key provided") @@ -44,8 +50,23 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { return } - // Remove the Coder token header so it's not forwarded to upstream providers. - r.Header.Del(agplaibridge.HeaderCoderAuth) + // Strip every header that may carry the Coder token so it is + // never forwarded to upstream providers. After stripping, the + // aibridge library can treat the request as a normal LLM API call + // with no Coder-specific information. + if byok { + // In BYOK mode the token is in X-Coder-AI-Governance-Token; + // Authorization and X-Api-Key carry the user's own LLM credentials + // and must be preserved. + r.Header.Del(agplaibridge.HeaderCoderToken) + } else { + // In centralized mode the token may be in Authorization (the + // documented path) or X-Api-Key (legacy clients that set + // ANTHROPIC_API_KEY to their Coder token). Both are + // stripped. + r.Header.Del("Authorization") + r.Header.Del("X-Api-Key") + } client, err := s.Client() if err != nil { @@ -56,7 +77,7 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { resp, err := client.IsAuthorized(ctx, &proto.IsAuthorizedRequest{Key: key}) if err != nil { - logger.Warn(ctx, "key authorization check failed", slog.Error(err)) + logger.Warn(ctx, "key authorization check failed", slog.Error(err), slog.F("auth_mode", authMode)) http.Error(rw, ErrUnauthorized.Error(), http.StatusForbidden) return } diff --git a/enterprise/aibridgeproxyd/aibridgeproxyd.go b/enterprise/aibridgeproxyd/aibridgeproxyd.go index e659050eaf..5d831d13a7 100644 --- a/enterprise/aibridgeproxyd/aibridgeproxyd.go +++ b/enterprise/aibridgeproxyd/aibridgeproxyd.go @@ -714,6 +714,17 @@ func extractCoderTokenFromProxyAuth(proxyAuth string) string { return credentials[1] } +// extractCoderTokenFromBearerAuth extracts the bearer token from an +// Authorization header. Returns empty string if the header is not a +// valid "Bearer " value. +func extractCoderTokenFromBearerAuth(auth string) string { + parts := strings.Fields(auth) + if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { + return "" + } + return parts[1] +} + // newProxyAuthRequiredResponse creates a 407 Proxy Authentication Required // response with the appropriate challenge header. This is used both during // CONNECT handling and for decrypted requests missing authentication. @@ -866,8 +877,10 @@ func (s *Server) checkBlockedIPAndDial(ctx context.Context, network, addr string } // handleRequest intercepts HTTP requests after MITM decryption. -// - Requests to known AI providers are rewritten to aibridged, with the Coder token -// (from ctx.UserData, set during CONNECT) set in the X-Coder-Token header. +// - Requests to known AI providers are rewritten to point at aibridged. +// In centralized mode the Coder token is already in the +// Authorization header. For BYOK clients that cannot set custom +// headers, the proxy injects the BYOK header. // - Unknown hosts are passed through to the original upstream. func (s *Server) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { originalPath := req.URL.Path @@ -945,10 +958,7 @@ func (s *Server) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http. req.URL = aiBridgeParsedURL req.Host = aiBridgeParsedURL.Host - // Set X-Coder-Token header for aibridged authentication. - // Using a separate header preserves the original request headers, - // which are forwarded to upstream providers. - req.Header.Set(agplaibridge.HeaderCoderAuth, reqCtx.CoderToken) + injectBYOKHeaderIfNeeded(req.Header, reqCtx.CoderToken) // Set custom header for cross-service log correlation. // This allows correlating aibridgeproxyd logs with aibridged logs. @@ -967,6 +977,27 @@ func (s *Server) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http. return req, nil } +// injectBYOKHeaderIfNeeded sets HeaderCoderToken when the +// Authorization header carries a bearer token that differs from the +// Coder token, indicating the client is using its own LLM +// credentials. Clients that can set custom headers +// do this themselves; this handles clients that cannot. +// +// In centralized mode, Authorization carries the Coder token +// itself, so aibridged discovers it via ExtractAuthToken +// without any extra header. +func injectBYOKHeaderIfNeeded(header http.Header, coderToken string) { + // Don’t overwrite the header if it’s already set. + if header.Get(agplaibridge.HeaderCoderToken) != "" { + return + } + + bearer := extractCoderTokenFromBearerAuth(header.Get("Authorization")) + if bearer != "" && bearer != coderToken { + header.Set(agplaibridge.HeaderCoderToken, coderToken) + } +} + // handleResponse handles responses received from aibridged. // This is only called for MITM'd requests (allowlisted domains routed through aibridged). // Tunneled requests (non-allowlisted domains) bypass this handler entirely. diff --git a/enterprise/aibridgeproxyd/aibridgeproxyd_test.go b/enterprise/aibridgeproxyd/aibridgeproxyd_test.go index 12800e457b..d5fd2e8de5 100644 --- a/enterprise/aibridgeproxyd/aibridgeproxyd_test.go +++ b/enterprise/aibridgeproxyd/aibridgeproxyd_test.go @@ -1370,12 +1370,13 @@ func TestProxy_MITM(t *testing.T) { metrics := aibridgeproxyd.NewMetrics(reg) // Track what aibridged receives. - var receivedPath, receivedCoderToken, receivedRequestID string + var receivedPath, receivedAuthz, receivedBYOK, receivedRequestID string // Create a mock aibridged server that captures requests. aibridgedServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedPath = r.URL.Path - receivedCoderToken = r.Header.Get(agplaibridge.HeaderCoderAuth) + receivedAuthz = r.Header.Get("Authorization") + receivedBYOK = r.Header.Get(agplaibridge.HeaderCoderToken) receivedRequestID = r.Header.Get(aibridgeproxyd.HeaderAIBridgeRequestID) w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("hello from aibridged")) @@ -1427,11 +1428,14 @@ func TestProxy_MITM(t *testing.T) { certPool = getProxyCertPool(t) } - // Make a request through the proxy to the target URL. - client := newProxyClient(t, srv, makeProxyAuthHeader("test-token"), certPool, false) + // Simulate the primary proxy use case: the Coder + // token is in Proxy-Authorization, and the user's + // own LLM token is in Authorization. + client := newProxyClient(t, srv, makeProxyAuthHeader("coder-token"), certPool, false) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, targetURL, strings.NewReader(`{}`)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer user-llm-token") resp, err := client.Do(req) require.NoError(t, err) @@ -1449,7 +1453,7 @@ func TestProxy_MITM(t *testing.T) { // Verify request went to target server, not aibridged. require.Equal(t, "hello from tunneled", string(body)) require.Empty(t, receivedPath, "aibridged should not receive tunneled requests") - require.Empty(t, receivedCoderToken, "tunneled requests are not authenticated by the proxy") + require.Empty(t, receivedAuthz, "tunneled requests should not reach aibridged") require.Empty(t, receivedRequestID, "tunneled requests should not have request ID header") // Verify metrics for tunneled requests. @@ -1464,7 +1468,8 @@ func TestProxy_MITM(t *testing.T) { // Verify the request was routed to aibridged correctly. require.Equal(t, "hello from aibridged", string(body)) require.Equal(t, tt.expectedPath, receivedPath) - require.Equal(t, "test-token", receivedCoderToken, "MITM'd requests must include Coder token") + require.Equal(t, "Bearer user-llm-token", receivedAuthz, "user's LLM credentials must be forwarded") + require.Equal(t, "coder-token", receivedBYOK, "proxy must inject BYOK header with Coder token") require.NotEmpty(t, receivedRequestID, "MITM'd requests must include request ID header") _, err := uuid.Parse(receivedRequestID) require.NoError(t, err, "request ID must be a valid UUID") @@ -1482,6 +1487,95 @@ func TestProxy_MITM(t *testing.T) { } } +// TestProxy_MITM_BYOKInjection verifies that the proxy sets the BYOK header +// when Authorization carries a bearer token different from the Coder +// token. This handles clients that send per-user LLM credentials +// but cannot set custom headers. +func TestProxy_MITM_BYOKInjection(t *testing.T) { + t.Parallel() + + coderToken := "coder-token" + + tests := []struct { + name string + authzHeader string + byokHeader string // pre-set by client; empty means not set + expectBYOK bool + expectBYOKVal string + }{ + { + // Centralized: Authorization carries the Coder token (same + // value as Proxy-Authorization). No BYOK header is set. + name: "Authorization matches Coder token", + authzHeader: "Bearer " + coderToken, + expectBYOK: false, + }, + { + // BYOK: Authorization carries the user's token, + // which differs from the Coder token. The proxy injects + // the BYOK header. + name: "Authorization differs from Coder token", + authzHeader: "Bearer client-access-token", + expectBYOK: true, + expectBYOKVal: coderToken, + }, + { + // Client already set the BYOK header (Claude Code, Codex). + // The proxy must not overwrite it. + name: "BYOK header already set by client — not overwritten", + authzHeader: "Bearer client-access-token", + byokHeader: "client-set-coder-token", + expectBYOK: true, + expectBYOKVal: "client-set-coder-token", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var receivedBYOKHeader, receivedAuthz string + + aibridgedServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedAuthz = r.Header.Get("Authorization") + receivedBYOKHeader = r.Header.Get(agplaibridge.HeaderCoderToken) + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(aibridgedServer.Close) + + srv := newTestProxy(t, + withCoderAccessURL(aibridgedServer.URL), + withDomainAllowlist(aibridgeproxyd.HostCopilot), + withAIBridgeProviderFromHost(nil), + ) + + certPool := getProxyCertPool(t) + client := newProxyClient(t, srv, makeProxyAuthHeader(coderToken), certPool, false) + + req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "https://"+aibridgeproxyd.HostCopilot+"/chat/completions", strings.NewReader(`{}`)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", tt.authzHeader) + if tt.byokHeader != "" { + req.Header.Set(agplaibridge.HeaderCoderToken, tt.byokHeader) + } + + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, tt.authzHeader, receivedAuthz, "Authorization must be forwarded to aibridged") + + if tt.expectBYOK { + require.Equal(t, tt.expectBYOKVal, receivedBYOKHeader, "BYOK header must be set when Authorization differs from Coder token") + } else { + require.Empty(t, receivedBYOKHeader, "BYOK header must not be set") + } + }) + } +} + // TestListenerTLS verifies that the proxy works correctly when its listener is wrapped in TLS. // It tests both tunneled and MITM'd requests through an HTTPS proxy listener. func TestListenerTLS(t *testing.T) { @@ -1785,7 +1879,8 @@ func TestUpstreamProxy(t *testing.T) { finalDestinationBody string aibridgeReceived bool aibridgePath string - aibridgeCoderToken string + aibridgeAuthz string + aibridgeBYOK string aibridgeBody string ) @@ -1882,7 +1977,8 @@ func TestUpstreamProxy(t *testing.T) { aibridgeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { aibridgeReceived = true aibridgePath = r.URL.Path - aibridgeCoderToken = r.Header.Get(agplaibridge.HeaderCoderAuth) + aibridgeAuthz = r.Header.Get("Authorization") + aibridgeBYOK = r.Header.Get(agplaibridge.HeaderCoderToken) body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -1937,7 +2033,8 @@ func TestUpstreamProxy(t *testing.T) { certPool = getProxyCertPool(t) } - // Create HTTP client configured to use aiproxy. + // Create HTTP client configured to use aiproxy. Coder token + // in Proxy-Authorization, user's LLM token in Authorization. client := newProxyClient(t, srv, makeProxyAuthHeader("test-coder-token"), certPool, false) // Make request through aiproxy. @@ -1945,6 +2042,7 @@ func TestUpstreamProxy(t *testing.T) { req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, targetURL, strings.NewReader(requestBody)) require.NoError(t, err) req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer user-llm-token") resp, err := client.Do(req) require.NoError(t, err) @@ -1966,8 +2064,8 @@ func TestUpstreamProxy(t *testing.T) { "final destination should receive the exact request body") require.False(t, aibridgeReceived, "aibridge should NOT receive request for non-allowlisted domain") - require.Empty(t, aibridgeCoderToken, - "tunneled requests should not have Coder token") + require.Empty(t, aibridgeAuthz, + "tunneled requests should not reach aibridge") } else { require.False(t, upstreamProxyCONNECTReceived, "upstream proxy should NOT receive CONNECT for allowlisted domain") @@ -1975,8 +2073,10 @@ func TestUpstreamProxy(t *testing.T) { "aibridge should receive the MITM'd request") require.Equal(t, tt.expectedAIBridgePath, aibridgePath, "aibridge should receive rewritten path") - require.Equal(t, "test-coder-token", aibridgeCoderToken, - "aibridge should receive Coder token header") + require.Equal(t, "Bearer user-llm-token", aibridgeAuthz, + "user's LLM credentials must be forwarded") + require.Equal(t, "test-coder-token", aibridgeBYOK, + "proxy must inject BYOK header with Coder token") require.Equal(t, requestBody, aibridgeBody, "aibridge should receive the exact request body") require.False(t, finalDestinationReceived, diff --git a/go.mod b/go.mod index 88129e2f41..e2a888e7e0 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.20260316151612-5c071a7db41b + github.com/coder/aibridge v1.0.8-0.20260324203533-dd8c239e5566 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 0f7d2e9b92..0a2a2dbc7a 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.20260316151612-5c071a7db41b h1:O470JUI+D8cuCSsPVSI6JMUq1JlKDdsPtk7feDCaLqQ= -github.com/coder/aibridge v1.0.8-0.20260316151612-5c071a7db41b/go.mod h1:u6WvGLMQQbk3ByeOw+LBdVgDNc/v/ujAtUc6MfvzQb4= +github.com/coder/aibridge v1.0.8-0.20260324203533-dd8c239e5566 h1:DK+a7Q9bPpTyq7ePaz81Ihauyp1ilXNhF8MI+7rmZpA= +github.com/coder/aibridge v1.0.8-0.20260324203533-dd8c239e5566/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=