From 2770bdc9d12fdfbdf4f8818d68476a81aeadddd5 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 27 May 2026 16:16:05 +0200 Subject: [PATCH] feat: route extra ai_provider_types through OpenAI and Anthropic providers (#25722) _Disclosure:_ _produced_ _with_ _Claude_ _Opus_ _4\.7_ AI Gateway only supports Anthropic (+Bedrock), OpenAI, and Copilot providers at present. All other types (Vercel, Gemini, etc) will be mapped to OpenAI since they support OpenAI-compatible endpoints. --- cli/aibridged.go | 26 +++++- cli/server.go | 37 +++++--- cli/server_aibridge_internal_test.go | 129 ++++++++++++++++++++++++++- coderd/ai_providers.go | 19 ++-- coderd/ai_providers_migrate.go | 21 ++--- coderd/ai_providers_migrate_test.go | 11 +-- coderd/apidoc/docs.go | 2 +- coderd/apidoc/swagger.json | 2 +- codersdk/aibridge.go | 18 ++-- codersdk/aiproviders.go | 18 +++- codersdk/deployment.go | 4 +- docs/reference/api/schemas.md | 16 ++-- site/src/api/typesGenerated.ts | 4 +- 13 files changed, 243 insertions(+), 64 deletions(-) diff --git a/cli/aibridged.go b/cli/aibridged.go index 50e2c35d5c..da28a74dee 100644 --- a/cli/aibridged.go +++ b/cli/aibridged.go @@ -184,17 +184,28 @@ func buildAIProviderFromRow( sendActorHeaders := cfg.SendActorHeaders.Value() dumpDir := cfg.APIDumpDir.Value() + // aibridge currently has native support for OpenAI and Anthropic + // only. The other ai_provider_type values (azure, google, + // openai-compat, openrouter, vercel) route through the OpenAI + // provider because chatd configures them against their + // OpenAI-compatible endpoints. Bedrock routes through the Anthropic + // provider with a Bedrock discriminator in Settings. switch row.Type { - case database.AiProviderTypeOpenai: + case database.AiProviderTypeOpenai, + database.AiProviderTypeAzure, + database.AiProviderTypeGoogle, + database.AiProviderTypeOpenaiCompat, + database.AiProviderTypeOpenrouter, + database.AiProviderTypeVercel: if len(keys) == 0 && !cfg.AllowBYOK.Value() { - return nil, xerrors.New("openai provider has no api keys configured and BYOK is not enabled") + return nil, xerrors.Errorf("%s provider has no api keys configured and BYOK is not enabled", row.Type) } var pool *keypool.Pool if len(keys) > 0 { var err error pool, err = buildAIProviderKeyPool(keys) if err != nil { - return nil, xerrors.Errorf("openai key pool: %w", err) + return nil, xerrors.Errorf("%s key pool: %w", row.Type, err) } } return aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{ @@ -206,8 +217,15 @@ func buildAIProviderFromRow( SendActorHeaders: sendActorHeaders, }), nil - case database.AiProviderTypeAnthropic: + case database.AiProviderTypeAnthropic, database.AiProviderTypeBedrock: bedrock := bedrockConfigFromRow(row, settings) + // A row typed 'bedrock' authenticates exclusively via settings; + // without populated Bedrock credentials it cannot make upstream + // calls, so refuse rather than falling back to an unsigned + // Anthropic client. + if row.Type == database.AiProviderTypeBedrock && bedrock == nil { + return nil, xerrors.New("bedrock provider has no bedrock credentials configured") + } // Bedrock-backed Anthropic authenticates via AWS credentials in // the settings blob, not the api_keys table. A bearer-token // Anthropic without any key cannot make upstream calls. diff --git a/cli/server.go b/cli/server.go index 3e3dd0c643..0a15645e50 100644 --- a/cli/server.go +++ b/cli/server.go @@ -56,7 +56,6 @@ import ( "cdr.dev/slog/v3" "cdr.dev/slog/v3/sloggers/sloghuman" - "github.com/coder/coder/v2/aibridge" "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/cli/clilog" "github.com/coder/coder/v2/cli/cliui" @@ -3008,11 +3007,10 @@ func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AI return nil, xerrors.Errorf("provider %d: TYPE is required", i) } - switch p.Type { - case aibridge.ProviderOpenAI, aibridge.ProviderAnthropic, aibridge.ProviderCopilot: - default: - return nil, xerrors.Errorf("provider %d: unknown TYPE %q (must be %s, %s, or %s)", - i, p.Type, aibridge.ProviderOpenAI, aibridge.ProviderAnthropic, aibridge.ProviderCopilot) + providerType := database.AIProviderType(p.Type) + if !providerType.Valid() { + return nil, xerrors.Errorf("provider %d: unknown TYPE %q (must be one of: %v)", + i, p.Type, database.AllAIProviderTypeValues()) } var bedrockKey, bedrockSecret string @@ -3028,21 +3026,36 @@ func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AI ) isBedrock := codersdk.IsBedrockConfigured(p.BedrockBaseURL, settings) - if p.Type != aibridge.ProviderAnthropic && isBedrock { - return nil, xerrors.Errorf("provider %d (%s): BEDROCK_* fields are only supported with TYPE %q", - i, p.Type, aibridge.ProviderAnthropic) + // BEDROCK_* fields are accepted on anthropic (mutually exclusive + // with KEYS) and required on bedrock. Any other TYPE rejecting + // them prevents silently-ignored credentials. + isBedrockType := providerType == database.AiProviderTypeBedrock + isAnthropicType := providerType == database.AiProviderTypeAnthropic + if !isAnthropicType && !isBedrockType && isBedrock { + return nil, xerrors.Errorf("provider %d (%s): BEDROCK_* fields are only supported with TYPE %q or %q", + i, p.Type, database.AiProviderTypeAnthropic, database.AiProviderTypeBedrock) } - if p.Type == aibridge.ProviderCopilot && len(p.Keys) > 0 { + if isBedrockType && !isBedrock { + return nil, xerrors.Errorf("provider %d (%s): TYPE %q requires BEDROCK_* fields to be configured", + i, p.Type, database.AiProviderTypeBedrock) + } + + if isBedrockType && len(p.Keys) > 0 { + return nil, xerrors.Errorf("provider %d (%s): KEY/KEYS are not supported for TYPE %q (use BEDROCK_* fields)", + i, p.Type, database.AiProviderTypeBedrock) + } + + if providerType == database.AiProviderTypeCopilot && len(p.Keys) > 0 { return nil, xerrors.Errorf("provider %d (%s): KEY/KEYS are not supported for TYPE %q", - i, p.Type, aibridge.ProviderCopilot) + i, p.Type, database.AiProviderTypeCopilot) } // An Anthropic provider authenticates either via a bearer // token (KEYS) or via Bedrock (BEDROCK_*), not both. Surface // the conflict here so misconfigured deployments fail before // any DB work happens at server startup. - if p.Type == aibridge.ProviderAnthropic && len(p.Keys) > 0 && isBedrock { + if isAnthropicType && len(p.Keys) > 0 && isBedrock { return nil, xerrors.Errorf("provider %d (%s): KEY/KEYS and BEDROCK_* fields are mutually exclusive", i, p.Type) } diff --git a/cli/server_aibridge_internal_test.go b/cli/server_aibridge_internal_test.go index 1797f1c7ed..6b712a6352 100644 --- a/cli/server_aibridge_internal_test.go +++ b/cli/server_aibridge_internal_test.go @@ -1,6 +1,8 @@ package cli import ( + "database/sql" + "encoding/json" "fmt" "testing" @@ -11,6 +13,7 @@ import ( "cdr.dev/slog/v3/sloggers/slogtest" "github.com/coder/coder/v2/aibridge" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/testutil" "github.com/coder/serpent" @@ -362,6 +365,40 @@ func TestReadAIProvidersFromEnv(t *testing.T) { }, errContains: "cannot mix CODER_AIBRIDGE_PROVIDER_* and CODER_AI_GATEWAY_PROVIDER_* environment variables", }, + { + name: "BedrockTypeHappyPath", + env: []string{ + "CODER_AIBRIDGE_PROVIDER_0_TYPE=bedrock", + "CODER_AIBRIDGE_PROVIDER_0_NAME=bedrock-prod", + "CODER_AIBRIDGE_PROVIDER_0_BEDROCK_REGION=us-east-1", + "CODER_AIBRIDGE_PROVIDER_0_BEDROCK_ACCESS_KEY=AKID", + "CODER_AIBRIDGE_PROVIDER_0_BEDROCK_ACCESS_KEY_SECRET=secret", + }, + expected: []codersdk.AIProviderConfig{ + { + Type: string(database.AiProviderTypeBedrock), + Name: "bedrock-prod", + BedrockRegion: "us-east-1", + BedrockAccessKeys: []string{"AKID"}, + BedrockAccessKeySecrets: []string{"secret"}, + }, + }, + }, + { + name: "BedrockTypeWithoutBedrockFields", + env: []string{"CODER_AIBRIDGE_PROVIDER_0_TYPE=bedrock", "CODER_AIBRIDGE_PROVIDER_0_NAME=bedrock-prod"}, + errContains: "requires BEDROCK_* fields to be configured", + }, + { + name: "BedrockTypeRejectsAPIKeys", + env: []string{ + "CODER_AIBRIDGE_PROVIDER_0_TYPE=bedrock", + "CODER_AIBRIDGE_PROVIDER_0_NAME=bedrock-prod", + "CODER_AIBRIDGE_PROVIDER_0_BEDROCK_REGION=us-east-1", + "CODER_AIBRIDGE_PROVIDER_0_KEY=sk-should-fail", + }, + errContains: "KEY/KEYS are not supported for TYPE", + }, { name: "BedrockKeysTooMany", env: []string{ @@ -544,8 +581,9 @@ func TestBuildAIProviderFromRowSetsAPIDumpDir(t *testing.T) { const dumpDir = "/tmp/coder-aibridge-dumps" tests := []struct { - name string - row database.AIProvider + name string + row database.AIProvider + expectedType string }{ { name: "OpenAI", @@ -554,6 +592,7 @@ func TestBuildAIProviderFromRowSetsAPIDumpDir(t *testing.T) { Name: "openai", BaseUrl: "https://api.openai.com/", }, + expectedType: aibridge.ProviderOpenAI, }, { name: "Anthropic", @@ -562,6 +601,7 @@ func TestBuildAIProviderFromRowSetsAPIDumpDir(t *testing.T) { Name: "anthropic", BaseUrl: "https://api.anthropic.com/", }, + expectedType: aibridge.ProviderAnthropic, }, { name: "Copilot", @@ -570,6 +610,68 @@ func TestBuildAIProviderFromRowSetsAPIDumpDir(t *testing.T) { Name: "copilot", BaseUrl: "https://api.githubcopilot.com/", }, + expectedType: aibridge.ProviderCopilot, + }, + { + name: "Azure", + row: database.AIProvider{ + Type: database.AiProviderTypeAzure, + Name: "azure", + BaseUrl: "https://example.openai.azure.com/", + }, + expectedType: aibridge.ProviderOpenAI, + }, + { + name: "Google", + row: database.AIProvider{ + Type: database.AiProviderTypeGoogle, + Name: "google", + BaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai/", + }, + expectedType: aibridge.ProviderOpenAI, + }, + { + name: "OpenAICompat", + row: database.AIProvider{ + Type: database.AiProviderTypeOpenaiCompat, + Name: "openai-compat", + BaseUrl: "https://compat.example.com/v1/", + }, + expectedType: aibridge.ProviderOpenAI, + }, + { + name: "OpenRouter", + row: database.AIProvider{ + Type: database.AiProviderTypeOpenrouter, + Name: "openrouter", + BaseUrl: "https://openrouter.ai/api/v1/", + }, + expectedType: aibridge.ProviderOpenAI, + }, + { + name: "Vercel", + row: database.AIProvider{ + Type: database.AiProviderTypeVercel, + Name: "vercel", + BaseUrl: "https://api.v0.dev/v1/", + }, + expectedType: aibridge.ProviderOpenAI, + }, + { + name: "Bedrock", + row: database.AIProvider{ + Type: database.AiProviderTypeBedrock, + Name: "bedrock", + BaseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com/", + Settings: mustMarshalSettings(codersdk.AIProviderSettings{ + Bedrock: &codersdk.AIProviderBedrockSettings{ + Region: "us-east-1", + AccessKey: ptr.Ref("AKID"), + AccessKeySecret: ptr.Ref("secret"), + }, + }), + }, + expectedType: aibridge.ProviderAnthropic, }, } @@ -583,6 +685,29 @@ func TestBuildAIProviderFromRowSetsAPIDumpDir(t *testing.T) { }) require.NoError(t, err) assert.Equal(t, dumpDir, provider.APIDumpDir()) + assert.Equal(t, tt.expectedType, provider.Type()) }) } } + +func TestBuildAIProviderFromRowBedrockWithoutSettings(t *testing.T) { + t.Parallel() + + _, err := buildAIProviderFromRow(database.AIProvider{ + Type: database.AiProviderTypeBedrock, + Name: "bedrock-no-settings", + BaseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com/", + }, nil, codersdk.AIBridgeConfig{ + AllowBYOK: serpent.Bool(true), + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "bedrock provider has no bedrock credentials configured") +} + +func mustMarshalSettings(s codersdk.AIProviderSettings) sql.NullString { + data, err := json.Marshal(s) + if err != nil { + panic(err) + } + return sql.NullString{String: string(data), Valid: true} +} diff --git a/coderd/ai_providers.go b/coderd/ai_providers.go index dd8e4c00d3..78ca50ecc9 100644 --- a/coderd/ai_providers.go +++ b/coderd/ai_providers.go @@ -320,10 +320,13 @@ func (api *API) aiProvidersUpdate(rw http.ResponseWriter, r *http.Request) { if req.Settings != nil { existing = mergeAIProviderSettings(existing, *req.Settings) } - // Bedrock settings are only meaningful for anthropic-typed - // providers; rejecting the mismatch keeps a misconfiguration - // from sitting silently in the encrypted blob. - if existing.Bedrock != nil && old.Type != database.AiProviderTypeAnthropic { + // Bedrock settings are only meaningful for anthropic- or + // bedrock-typed providers; rejecting the mismatch keeps a + // misconfiguration from sitting silently in the encrypted + // blob. + if existing.Bedrock != nil && + old.Type != database.AiProviderTypeAnthropic && + old.Type != database.AiProviderTypeBedrock { return errAIProviderBedrockTypeMismatch } settings, err := encodeAIProviderSettings(existing) @@ -382,7 +385,7 @@ func (api *API) aiProvidersUpdate(rw http.ResponseWriter, r *http.Request) { } if errors.Is(err, errAIProviderBedrockTypeMismatch) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Bedrock settings are only valid for type=anthropic.", + Message: "Bedrock settings are only valid for type=anthropic or type=bedrock.", }) return } @@ -482,9 +485,9 @@ var errBedrockRejectsAPIKeys = xerrors.New("bedrock providers do not accept api_ // errAIProviderBedrockTypeMismatch is the sentinel returned from // inside the update transaction when the post-merge settings carry a -// Bedrock block but the provider is not anthropic-typed; the outer -// handler translates it into a 400. -var errAIProviderBedrockTypeMismatch = xerrors.New("bedrock settings are only valid for type=anthropic") +// Bedrock block but the provider is not anthropic- or bedrock-typed; +// the outer handler translates it into a 400. +var errAIProviderBedrockTypeMismatch = xerrors.New("bedrock settings are only valid for type=anthropic or type=bedrock") // errAIProviderInvalidName is returned from lookupAIProvider when the // idOrName parameter is neither a UUID nor a syntactically-valid name. diff --git a/coderd/ai_providers_migrate.go b/coderd/ai_providers_migrate.go index 98cfba2226..6bc99ad840 100644 --- a/coderd/ai_providers_migrate.go +++ b/coderd/ai_providers_migrate.go @@ -327,28 +327,23 @@ func providersFromEnv(ctx context.Context, cfg codersdk.AIBridgeConfig, logger s dp := desiredAIProvider{ Name: name, } - switch p.Type { - case aibridge.ProviderOpenAI: - dp.Type = database.AiProviderTypeOpenai - case aibridge.ProviderAnthropic: - dp.Type = database.AiProviderTypeAnthropic - case aibridge.ProviderCopilot: - dp.Type = database.AiProviderTypeCopilot - default: + providerType := database.AIProviderType(p.Type) + if !providerType.Valid() { logger.Warn(ctx, "skipping indexed AI provider with unsupported type", slog.F("name", name), slog.F("type", p.Type), ) continue } + dp.Type = providerType dp.BaseURL = p.BaseURL - // Bedrock fields only apply to Anthropic. Detection goes - // through AIProviderBedrockSettings.IsConfigured() so the - // legacy and indexed paths agree on what counts as a Bedrock - // provider. + // Bedrock fields apply to Anthropic and the dedicated Bedrock + // type. Detection goes through + // AIProviderBedrockSettings.IsConfigured() so the legacy and + // indexed paths agree on what counts as a Bedrock provider. isBedrock := false - if dp.Type == database.AiProviderTypeAnthropic { + if dp.Type == database.AiProviderTypeAnthropic || dp.Type == database.AiProviderTypeBedrock { var accessKey, accessKeySecret string if len(p.BedrockAccessKeys) > 0 { accessKey = p.BedrockAccessKeys[0] diff --git a/coderd/ai_providers_migrate_test.go b/coderd/ai_providers_migrate_test.go index 87f5dd0764..d4a07bfdc2 100644 --- a/coderd/ai_providers_migrate_test.go +++ b/coderd/ai_providers_migrate_test.go @@ -371,14 +371,15 @@ func TestSeedAIProvidersFromEnv(t *testing.T) { db, _ := dbtestutil.NewDB(t) ctx := testutil.Context(t, testutil.WaitShort) - // vercel is a valid ai_provider_type DB value but the aibridge - // runtime has no constructor for it, so the seed switch falls - // into the default branch and skips the row. + // A TYPE that isn't part of the ai_provider_type enum falls + // into the default branch and the row is skipped rather than + // rejected, so deployments don't fail to start over a single + // typo'd provider. cfg := codersdk.AIBridgeConfig{ Providers: []codersdk.AIProviderConfig{ { - Type: "vercel", - Name: "vercel-instance", + Type: "not-a-real-provider", + Name: "ghost", BaseURL: "https://example.com", }, { diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index ae5715bd78..65f2c1927c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -15071,7 +15071,7 @@ const docTemplate = `{ "type": "string" }, "type": { - "description": "Type is the provider type: \"openai\", \"anthropic\", or \"copilot\".", + "description": "Type is the provider type. Valid values are: \"openai\",\n\"anthropic\", \"azure\", \"bedrock\", \"google\", \"openai-compat\",\n\"openrouter\", \"vercel\", \"copilot\".", "type": "string" } } diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 21ee879158..33ffe3e4b4 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -13475,7 +13475,7 @@ "type": "string" }, "type": { - "description": "Type is the provider type: \"openai\", \"anthropic\", or \"copilot\".", + "description": "Type is the provider type. Valid values are: \"openai\",\n\"anthropic\", \"azure\", \"bedrock\", \"google\", \"openai-compat\",\n\"openrouter\", \"vercel\", \"copilot\".", "type": "string" } } diff --git a/codersdk/aibridge.go b/codersdk/aibridge.go index 4e49176171..9bb7df0aac 100644 --- a/codersdk/aibridge.go +++ b/codersdk/aibridge.go @@ -175,9 +175,12 @@ type AIBridgeListSessionsFilter struct { Initiator string `json:"initiator,omitempty"` StartedBefore time.Time `json:"started_before,omitempty" format:"date-time"` StartedAfter time.Time `json:"started_after,omitempty" format:"date-time"` - // Provider matches the provider type column (openai, anthropic, - // copilot). Retained for backward compatibility; new clients should - // prefer ProviderName, which scopes to a specific configured row. + // Provider matches the runtime provider type column (openai, + // anthropic, copilot). The runtime type collapses the configured + // ai_provider_type: azure, google, openai-compat, openrouter, and + // vercel route through openai; bedrock routes through anthropic. + // Retained for backward compatibility; new clients should prefer + // ProviderName, which scopes to a specific configured row. Provider string `json:"provider,omitempty"` ProviderName string `json:"provider_name,omitempty"` Model string `json:"model,omitempty"` @@ -202,9 +205,12 @@ type AIBridgeListInterceptionsFilter struct { Initiator string `json:"initiator,omitempty"` StartedBefore time.Time `json:"started_before,omitempty" format:"date-time"` StartedAfter time.Time `json:"started_after,omitempty" format:"date-time"` - // Provider matches the provider type column (openai, anthropic, - // copilot). Retained for backward compatibility; new clients should - // prefer ProviderName, which scopes to a specific configured row. + // Provider matches the runtime provider type column (openai, + // anthropic, copilot). The runtime type collapses the configured + // ai_provider_type: azure, google, openai-compat, openrouter, and + // vercel route through openai; bedrock routes through anthropic. + // Retained for backward compatibility; new clients should prefer + // ProviderName, which scopes to a specific configured row. Provider string `json:"provider,omitempty"` ProviderName string `json:"provider_name,omitempty"` Model string `json:"model,omitempty"` diff --git a/codersdk/aiproviders.go b/codersdk/aiproviders.go index 3b47598118..fe34b9c03c 100644 --- a/codersdk/aiproviders.go +++ b/codersdk/aiproviders.go @@ -224,10 +224,24 @@ func (req CreateAIProviderRequest) Validate() []ValidationError { validations = append(validations, validateAIProviderName(req.Name)...) validations = append(validations, validateRequiredAIProviderBaseURL(req.BaseURL)...) validations = append(validations, validateAIProviderAPIKeys(req.APIKeys)...) - if req.Settings.Bedrock != nil && req.Type != AIProviderTypeAnthropic { + if req.Settings.Bedrock != nil && + req.Type != AIProviderTypeAnthropic && + req.Type != AIProviderTypeBedrock { validations = append(validations, ValidationError{ Field: "settings", - Detail: "bedrock settings are only valid for type=anthropic", + Detail: "bedrock settings are only valid for type=anthropic or type=bedrock", + }) + } + if req.Type == AIProviderTypeBedrock && (req.Settings.Bedrock == nil || !req.Settings.Bedrock.IsConfigured()) { + validations = append(validations, ValidationError{ + Field: "settings", + Detail: "type=bedrock requires bedrock settings", + }) + } + if req.Type == AIProviderTypeBedrock && len(req.APIKeys) > 0 { + validations = append(validations, ValidationError{ + Field: "api_keys", + Detail: "type=bedrock does not accept api_keys", }) } return validations diff --git a/codersdk/deployment.go b/codersdk/deployment.go index f9cdb8a8fc..b4939ec022 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -4684,7 +4684,9 @@ type AIBridgeBedrockConfig struct { // CODER_AIBRIDGE_PROVIDER__ is also accepted as a deprecated alias. // This follows the same indexed pattern as ExternalAuthConfig. type AIProviderConfig struct { - // Type is the provider type: "openai", "anthropic", or "copilot". + // Type is the provider type. Valid values are: "openai", + // "anthropic", "azure", "bedrock", "google", "openai-compat", + // "openrouter", "vercel", "copilot". Type string `json:"type"` // Name is the unique instance identifier used for routing. // Defaults to Type if not provided. diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index aaee58512c..912d53f8ce 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1352,14 +1352,14 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -|----------------------------|--------|----------|--------------|--------------------------------------------------------------------------------------------| -| `base_url` | string | false | | Base URL is the base URL of the upstream provider API. | -| `bedrock_model` | string | false | | | -| `bedrock_region` | string | false | | | -| `bedrock_small_fast_model` | string | false | | | -| `name` | string | false | | Name is the unique instance identifier used for routing. Defaults to Type if not provided. | -| `type` | string | false | | Type is the provider type: "openai", "anthropic", or "copilot". | +| Name | Type | Required | Restrictions | Description | +|----------------------------|--------|----------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `base_url` | string | false | | Base URL is the base URL of the upstream provider API. | +| `bedrock_model` | string | false | | | +| `bedrock_region` | string | false | | | +| `bedrock_small_fast_model` | string | false | | | +| `name` | string | false | | Name is the unique instance identifier used for routing. Defaults to Type if not provided. | +| `type` | string | false | | Type is the provider type. Valid values are: "openai", "anthropic", "azure", "bedrock", "google", "openai-compat", "openrouter", "vercel", "copilot". | ## codersdk.AIProviderKey diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 56fbbf6000..2c0df043ff 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -378,7 +378,9 @@ export const AIProviderBedrockSettingsVersion = 1; */ export interface AIProviderConfig { /** - * Type is the provider type: "openai", "anthropic", or "copilot". + * Type is the provider type. Valid values are: "openai", + * "anthropic", "azure", "bedrock", "google", "openai-compat", + * "openrouter", "vercel", "copilot". */ readonly type: string; /**