From 11ea5b9224dbf068fe98a6796872cdf340618363 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Tue, 16 Dec 2025 20:01:17 +0000 Subject: [PATCH] feat: add core AI MITM proxy daemon --- cli/testdata/coder_server_--help.golden | 14 ++ cli/testdata/server-config.yaml.golden | 13 ++ coderd/apidoc/docs.go | 20 +++ coderd/apidoc/swagger.json | 20 +++ codersdk/deployment.go | 55 +++++++ docs/reference/api/general.md | 6 + docs/reference/api/schemas.md | 39 +++++ docs/reference/cli/server.md | 42 +++++ enterprise/aiproxyd/aiproxyd.go | 113 +++++++++++++ enterprise/aiproxyd/aiproxyd_test.go | 152 ++++++++++++++++++ enterprise/cli/aiproxyd.go | 28 ++++ enterprise/cli/server.go | 10 ++ .../cli/testdata/coder_server_--help.golden | 14 ++ go.mod | 1 + go.sum | 2 + site/src/api/typesGenerated.ts | 9 ++ 16 files changed, 538 insertions(+) create mode 100644 enterprise/aiproxyd/aiproxyd.go create mode 100644 enterprise/aiproxyd/aiproxyd_test.go create mode 100644 enterprise/cli/aiproxyd.go diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 43ed943601..13012c650f 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -139,6 +139,20 @@ AI BRIDGE OPTIONS: Maximum number of AI Bridge requests per second per replica. Set to 0 to disable (unlimited). +AI PROXY OPTIONS: + --aiproxy-cert-file string, $CODER_AIPROXY_CERT_FILE + Path to the CA certificate file for MITM. + + --aiproxy-enabled bool, $CODER_AIPROXY_ENABLED (default: false) + Enable the AI MITM proxy for intercepting and decrypting AI provider + requests. + + --aiproxy-key-file string, $CODER_AIPROXY_KEY_FILE + Path to the CA private key file for MITM. + + --aiproxy-listen-addr string, $CODER_AIPROXY_LISTEN_ADDR (default: :8888) + The address the AI proxy will listen on. + CLIENT OPTIONS: These options change the behavior of how clients interact with the Coder. Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI. diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 14ea079f4c..70492148b3 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -765,6 +765,19 @@ aibridge: # (unlimited). # (default: 0, type: int) rateLimit: 0 +aiproxy: + # Enable the AI MITM proxy for intercepting and decrypting AI provider requests. + # (default: false, type: bool) + enabled: false + # The address the AI proxy will listen on. + # (default: :8888, type: string) + listen_addr: :8888 + # Path to the CA certificate file for MITM. + # (default: , type: string) + cert_file: "" + # Path to the CA private key file for MITM. + # (default: , type: string) + key_file: "" # Configure data retention policies for various database tables. Retention # policies automatically purge old data to reduce database size and improve # performance. Setting a retention duration to 0 disables automatic purging for diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index e48f97a138..6ef1163d46 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12110,6 +12110,26 @@ const docTemplate = `{ "properties": { "bridge": { "$ref": "#/definitions/codersdk.AIBridgeConfig" + }, + "proxy": { + "$ref": "#/definitions/codersdk.AIProxyConfig" + } + } + }, + "codersdk.AIProxyConfig": { + "type": "object", + "properties": { + "cert_file": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "key_file": { + "type": "string" + }, + "listen_addr": { + "type": "string" } } }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 577914a491..3ea6eb9f7c 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10774,6 +10774,26 @@ "properties": { "bridge": { "$ref": "#/definitions/codersdk.AIBridgeConfig" + }, + "proxy": { + "$ref": "#/definitions/codersdk.AIProxyConfig" + } + } + }, + "codersdk.AIProxyConfig": { + "type": "object", + "properties": { + "cert_file": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "key_file": { + "type": "string" + }, + "listen_addr": { + "type": "string" } } }, diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 58725eea84..2cb4eeed28 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -1217,6 +1217,10 @@ func (c *DeploymentValues) Options() serpent.OptionSet { Name: "AI Bridge", YAML: "aibridge", } + deploymentGroupAIProxy = serpent.Group{ + Name: "AI Proxy", + YAML: "aiproxy", + } deploymentGroupRetention = serpent.Group{ Name: "Retention", Description: "Configure data retention policies for various database tables. Retention policies automatically purge old data to reduce database size and improve performance. Setting a retention duration to 0 disables automatic purging for that data type.", @@ -3443,6 +3447,49 @@ Write out the current server config as YAML to stdout.`, Group: &deploymentGroupAIBridge, YAML: "rateLimit", }, + + // AI Proxy Options + { + Name: "AI Proxy Enabled", + Description: "Enable the AI MITM proxy for intercepting and decrypting AI provider requests.", + Flag: "aiproxy-enabled", + Env: "CODER_AIPROXY_ENABLED", + Value: &c.AI.ProxyConfig.Enabled, + Default: "false", + Group: &deploymentGroupAIProxy, + YAML: "enabled", + }, + { + Name: "AI Proxy Listen Address", + Description: "The address the AI proxy will listen on.", + Flag: "aiproxy-listen-addr", + Env: "CODER_AIPROXY_LISTEN_ADDR", + Value: &c.AI.ProxyConfig.ListenAddr, + Default: ":8888", + Group: &deploymentGroupAIProxy, + YAML: "listen_addr", + }, + { + Name: "AI Proxy Certificate File", + Description: "Path to the CA certificate file for MITM.", + Flag: "aiproxy-cert-file", + Env: "CODER_AIPROXY_CERT_FILE", + Value: &c.AI.ProxyConfig.CertFile, + Default: "", + Group: &deploymentGroupAIProxy, + YAML: "cert_file", + }, + { + Name: "AI Proxy Key File", + Description: "Path to the CA private key file for MITM.", + Flag: "aiproxy-key-file", + Env: "CODER_AIPROXY_KEY_FILE", + Value: &c.AI.ProxyConfig.KeyFile, + Default: "", + Group: &deploymentGroupAIProxy, + YAML: "key_file", + }, + // Retention settings { Name: "Audit Logs Retention", @@ -3535,8 +3582,16 @@ type AIBridgeBedrockConfig struct { SmallFastModel serpent.String `json:"small_fast_model" typescript:",notnull"` } +type AIProxyConfig struct { + Enabled serpent.Bool `json:"enabled" typescript:",notnull"` + ListenAddr serpent.String `json:"listen_addr" typescript:",notnull"` + CertFile serpent.String `json:"cert_file" typescript:",notnull"` + KeyFile serpent.String `json:"key_file" typescript:",notnull"` +} + type AIConfig struct { BridgeConfig AIBridgeConfig `json:"bridge,omitempty"` + ProxyConfig AIProxyConfig `json:"proxy,omitempty"` } type SupportConfig struct { diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md index e9a12a196f..da40a73801 100644 --- a/docs/reference/api/general.md +++ b/docs/reference/api/general.md @@ -183,6 +183,12 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ }, "rate_limit": 0, "retention": 0 + }, + "proxy": { + "cert_file": "string", + "enabled": true, + "key_file": "string", + "listen_addr": "string" } }, "allow_workspace_renames": true, diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 6929f4bdb6..442b8e164a 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -711,6 +711,12 @@ }, "rate_limit": 0, "retention": 0 + }, + "proxy": { + "cert_file": "string", + "enabled": true, + "key_file": "string", + "listen_addr": "string" } } ``` @@ -720,6 +726,27 @@ | Name | Type | Required | Restrictions | Description | |----------|----------------------------------------------------|----------|--------------|-------------| | `bridge` | [codersdk.AIBridgeConfig](#codersdkaibridgeconfig) | false | | | +| `proxy` | [codersdk.AIProxyConfig](#codersdkaiproxyconfig) | false | | | + +## codersdk.AIProxyConfig + +```json +{ + "cert_file": "string", + "enabled": true, + "key_file": "string", + "listen_addr": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|---------------|---------|----------|--------------|-------------| +| `cert_file` | string | false | | | +| `enabled` | boolean | false | | | +| `key_file` | string | false | | | +| `listen_addr` | string | false | | | ## codersdk.APIAllowListTarget @@ -2616,6 +2643,12 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o }, "rate_limit": 0, "retention": 0 + }, + "proxy": { + "cert_file": "string", + "enabled": true, + "key_file": "string", + "listen_addr": "string" } }, "allow_workspace_renames": true, @@ -3147,6 +3180,12 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o }, "rate_limit": 0, "retention": 0 + }, + "proxy": { + "cert_file": "string", + "enabled": true, + "key_file": "string", + "listen_addr": "string" } }, "allow_workspace_renames": true, diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index 6f7aa705a9..a1d4e732f7 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1814,6 +1814,48 @@ Maximum number of concurrent AI Bridge requests per replica. Set to 0 to disable Maximum number of AI Bridge requests per second per replica. Set to 0 to disable (unlimited). +### --aiproxy-enabled + +| | | +|-------------|-------------------------------------| +| Type | bool | +| Environment | $CODER_AIPROXY_ENABLED | +| YAML | aiproxy.enabled | +| Default | false | + +Enable the AI MITM proxy for intercepting and decrypting AI provider requests. + +### --aiproxy-listen-addr + +| | | +|-------------|-----------------------------------------| +| Type | string | +| Environment | $CODER_AIPROXY_LISTEN_ADDR | +| YAML | aiproxy.listen_addr | +| Default | :8888 | + +The address the AI proxy will listen on. + +### --aiproxy-cert-file + +| | | +|-------------|---------------------------------------| +| Type | string | +| Environment | $CODER_AIPROXY_CERT_FILE | +| YAML | aiproxy.cert_file | + +Path to the CA certificate file for MITM. + +### --aiproxy-key-file + +| | | +|-------------|--------------------------------------| +| Type | string | +| Environment | $CODER_AIPROXY_KEY_FILE | +| YAML | aiproxy.key_file | + +Path to the CA private key file for MITM. + ### --audit-logs-retention | | | diff --git a/enterprise/aiproxyd/aiproxyd.go b/enterprise/aiproxyd/aiproxyd.go new file mode 100644 index 0000000000..2ea856ba68 --- /dev/null +++ b/enterprise/aiproxyd/aiproxyd.go @@ -0,0 +1,113 @@ +package aiproxyd + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "net/http" + "time" + + "github.com/elazarl/goproxy" + "golang.org/x/xerrors" + + "cdr.dev/slog" +) + +// Server is the AI MITM (Man-in-the-Middle) proxy server. +// It is responsible for: +// - intercepting HTTPS requests to AI providers +// - decrypting requests using the configured CA certificate +// - forwarding requests to aibridge for processing +type Server struct { + logger slog.Logger + proxy *goproxy.ProxyHttpServer + httpServer *http.Server +} + +// Options configures the AI proxy server. +type Options struct { + // ListenAddr is the address the proxy server will listen on. + ListenAddr string + // CertFile is the path to the CA certificate file used for MITM. + CertFile string + // KeyFile is the path to the CA private key file used for MITM. + KeyFile string +} + +func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error) { + logger.Info(ctx, "initializing AI proxy server") + + if opts.ListenAddr == "" { + return nil, xerrors.New("listen address is required") + } + + if opts.CertFile == "" || opts.KeyFile == "" { + return nil, xerrors.New("cert file and key file are required") + } + + // Load CA certificate for MITM + if err := loadMitmCertificate(opts.CertFile, opts.KeyFile); err != nil { + return nil, xerrors.Errorf("failed to load MITM certificate: %w", err) + } + + proxy := goproxy.NewProxyHttpServer() + + // Decrypt all HTTPS requests via MITM. Requests are forwarded to + // the original destination without modification for now. + // TODO(ssncferreira): Route requests to aibridged + // See https://github.com/coder/internal/issues/1181 + proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) + + srv := &Server{ + logger: logger, + proxy: proxy, + } + + // Start HTTP server in background + srv.httpServer = &http.Server{ + Addr: opts.ListenAddr, + Handler: proxy, + ReadHeaderTimeout: 10 * time.Second, + } + + go func() { + logger.Info(ctx, "starting AI proxy", slog.F("addr", opts.ListenAddr)) + if err := srv.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Error(ctx, "aiproxyd server error", slog.Error(err)) + } + }() + + return srv, nil +} + +// loadMitmCertificate loads the CA certificate and key for MITM into goproxy. +func loadMitmCertificate(certFile, keyFile string) error { + tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return xerrors.Errorf("load x509 keypair: %w", err) + } + + x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + return xerrors.Errorf("parse certificate: %w", err) + } + + goproxy.GoproxyCa = tls.Certificate{ + Certificate: tlsCert.Certificate, + PrivateKey: tlsCert.PrivateKey, + Leaf: x509Cert, + } + + return nil +} + +// Close gracefully shuts down the proxy server. +func (s *Server) Close() error { + if s.httpServer == nil { + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return s.httpServer.Shutdown(ctx) +} diff --git a/enterprise/aiproxyd/aiproxyd_test.go b/enterprise/aiproxyd/aiproxyd_test.go new file mode 100644 index 0000000000..005518f418 --- /dev/null +++ b/enterprise/aiproxyd/aiproxyd_test.go @@ -0,0 +1,152 @@ +package aiproxyd_test + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "cdr.dev/slog/sloggers/slogtest" + + "github.com/coder/coder/v2/enterprise/aiproxyd" +) + +// generateTestCA creates a temporary CA certificate and key for testing. +func generateTestCA(t *testing.T) (certFile, keyFile string) { + t.Helper() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "Test CA", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey) + require.NoError(t, err) + + tempDir := t.TempDir() + certFile = filepath.Join(tempDir, "ca.crt") + keyFile = filepath.Join(tempDir, "ca.key") + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + err = os.WriteFile(certFile, certPEM, 0o600) + require.NoError(t, err) + + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) + err = os.WriteFile(keyFile, keyPEM, 0o600) + require.NoError(t, err) + + return certFile, keyFile +} + +func TestNew(t *testing.T) { + t.Parallel() + + t.Run("MissingListenAddr", func(t *testing.T) { + t.Parallel() + + certFile, keyFile := generateTestCA(t) + logger := slogtest.Make(t, nil) + + _, err := aiproxyd.New(t.Context(), logger, aiproxyd.Options{ + ListenAddr: "", + CertFile: certFile, + KeyFile: keyFile, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "listen address is required") + }) + + t.Run("MissingCertFile", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, nil) + + _, err := aiproxyd.New(t.Context(), logger, aiproxyd.Options{ + ListenAddr: ":0", + KeyFile: "key.pem", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "cert file and key file are required") + }) + + t.Run("MissingKeyFile", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, nil) + + _, err := aiproxyd.New(t.Context(), logger, aiproxyd.Options{ + ListenAddr: ":0", + CertFile: "cert.pem", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "cert file and key file are required") + }) + + t.Run("InvalidCertFile", func(t *testing.T) { + t.Parallel() + logger := slogtest.Make(t, nil) + + _, err := aiproxyd.New(t.Context(), logger, aiproxyd.Options{ + ListenAddr: ":0", + CertFile: "/nonexistent/cert.pem", + KeyFile: "/nonexistent/key.pem", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to load MITM certificate") + }) + + t.Run("Success", func(t *testing.T) { + t.Parallel() + + certFile, keyFile := generateTestCA(t) + logger := slogtest.Make(t, nil) + + srv, err := aiproxyd.New(t.Context(), logger, aiproxyd.Options{ + ListenAddr: "127.0.0.1:0", + CertFile: certFile, + KeyFile: keyFile, + }) + require.NoError(t, err) + require.NotNil(t, srv) + + err = srv.Close() + require.NoError(t, err) + }) +} + +func TestClose(t *testing.T) { + t.Parallel() + + certFile, keyFile := generateTestCA(t) + logger := slogtest.Make(t, nil) + + srv, err := aiproxyd.New(t.Context(), logger, aiproxyd.Options{ + ListenAddr: "127.0.0.1:0", + CertFile: certFile, + KeyFile: keyFile, + }) + require.NoError(t, err) + + err = srv.Close() + require.NoError(t, err) + + // Calling Close again should not error + err = srv.Close() + require.NoError(t, err) +} diff --git a/enterprise/cli/aiproxyd.go b/enterprise/cli/aiproxyd.go new file mode 100644 index 0000000000..719148b4ef --- /dev/null +++ b/enterprise/cli/aiproxyd.go @@ -0,0 +1,28 @@ +//go:build !slim + +package cli + +import ( + "context" + + "github.com/coder/coder/v2/enterprise/aiproxyd" + "github.com/coder/coder/v2/enterprise/coderd" +) + +func newAIProxyDaemon(coderAPI *coderd.API) (*aiproxyd.Server, error) { + ctx := context.Background() + coderAPI.Logger.Debug(ctx, "starting in-memory aiproxy daemon") + + logger := coderAPI.Logger.Named("aiproxyd") + + srv, err := aiproxyd.New(ctx, logger, aiproxyd.Options{ + ListenAddr: coderAPI.DeploymentValues.AI.ProxyConfig.ListenAddr.String(), + CertFile: coderAPI.DeploymentValues.AI.ProxyConfig.CertFile.String(), + KeyFile: coderAPI.DeploymentValues.AI.ProxyConfig.KeyFile.String(), + }) + if err != nil { + return nil, err + } + + return srv, nil +} diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index bc77bc54ba..c4686bab52 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -165,6 +165,16 @@ func (r *RootCmd) Server(_ func()) *serpent.Command { closers.Add(aibridgeDaemon) } + // In-memory AI proxy daemon + if options.DeploymentValues.AI.ProxyConfig.Enabled.Value() { + aiProxyServer, err := newAIProxyDaemon(api) + if err != nil { + _ = closers.Close() + return nil, nil, xerrors.Errorf("create aiproxyd: %w", err) + } + closers.Add(aiProxyServer) + } + return api.AGPL, closers, nil }) diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 20c3c2609e..7aa7866085 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -140,6 +140,20 @@ AI BRIDGE OPTIONS: Maximum number of AI Bridge requests per second per replica. Set to 0 to disable (unlimited). +AI PROXY OPTIONS: + --aiproxy-cert-file string, $CODER_AIPROXY_CERT_FILE + Path to the CA certificate file for MITM. + + --aiproxy-enabled bool, $CODER_AIPROXY_ENABLED (default: false) + Enable the AI MITM proxy for intercepting and decrypting AI provider + requests. + + --aiproxy-key-file string, $CODER_AIPROXY_KEY_FILE + Path to the CA private key file for MITM. + + --aiproxy-listen-addr string, $CODER_AIPROXY_LISTEN_ADDR (default: :8888) + The address the AI proxy will listen on. + CLIENT OPTIONS: These options change the behavior of how clients interact with the Coder. Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI. diff --git a/go.mod b/go.mod index 63101df460..3980d9da21 100644 --- a/go.mod +++ b/go.mod @@ -482,6 +482,7 @@ require ( github.com/coder/preview v1.0.4 github.com/danieljoos/wincred v1.2.3 github.com/dgraph-io/ristretto/v2 v2.3.0 + github.com/elazarl/goproxy v1.7.2 github.com/fsnotify/fsnotify v1.9.0 github.com/go-git/go-git/v5 v5.16.2 github.com/icholy/replace v0.6.0 diff --git a/go.sum b/go.sum index 0275e5112c..ea30265378 100644 --- a/go.sum +++ b/go.sum @@ -1049,6 +1049,8 @@ github.com/elastic/go-sysinfo v1.15.1 h1:zBmTnFEXxIQ3iwcQuk7MzaUotmKRp3OabbbWM8T github.com/elastic/go-sysinfo v1.15.1/go.mod h1:jPSuTgXG+dhhh0GKIyI2Cso+w5lPJ5PvVqKlL8LV/Hk= github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-smtp v0.21.2 h1:OLDgvZKuofk4em9fT5tFG5j4jE1/hXnX75UMvcrL4AA= diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f9cd42e2c3..4ee2d600d5 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -106,6 +106,15 @@ export interface AIBridgeUserPrompt { // From codersdk/deployment.go export interface AIConfig { readonly bridge?: AIBridgeConfig; + readonly proxy?: AIProxyConfig; +} + +// From codersdk/deployment.go +export interface AIProxyConfig { + readonly enabled: boolean; + readonly listen_addr: string; + readonly cert_file: string; + readonly key_file: string; } // From codersdk/allowlist.go