mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
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.
This commit is contained in:
+22
-4
@@ -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.
|
||||
|
||||
+25
-12
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
+11
-8
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
Generated
+1
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+12
-6
@@ -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"`
|
||||
|
||||
+16
-2
@@ -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
|
||||
|
||||
@@ -4684,7 +4684,9 @@ type AIBridgeBedrockConfig struct {
|
||||
// CODER_AIBRIDGE_PROVIDER_<N>_<KEY> 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.
|
||||
|
||||
Generated
+8
-8
@@ -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
|
||||
|
||||
|
||||
Generated
+3
-1
@@ -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;
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user