buildAIProviderFromRow decodes the settings blob and constructs the appropriate [aibridge.Provider] for a single ai_providers row. Disabled rows return a Provider stub carrying only Name and Disabled: true; settings decode, key loading, and credential checks are skipped because the provider will nev
( row database.AIProvider, keys []database.AIProviderKey, cfg codersdk.AIBridgeConfig, )
| 207 | // Disabled: true; settings decode, key loading, and credential checks |
| 208 | // are skipped because the provider will never call upstream. |
| 209 | func buildAIProviderFromRow( |
| 210 | row database.AIProvider, |
| 211 | keys []database.AIProviderKey, |
| 212 | cfg codersdk.AIBridgeConfig, |
| 213 | ) (aibridge.Provider, error) { |
| 214 | if !row.Enabled { |
| 215 | return disabledProviderFromRow(row) |
| 216 | } |
| 217 | |
| 218 | settings, err := db2sdk.AIProviderSettings(row.Settings) |
| 219 | if err != nil { |
| 220 | return nil, xerrors.Errorf("decode settings: %w", err) |
| 221 | } |
| 222 | |
| 223 | cbCfg := circuitBreakerConfig(cfg) |
| 224 | sendActorHeaders := cfg.SendActorHeaders.Value() |
| 225 | dumpDir := cfg.APIDumpDir.Value() |
| 226 | |
| 227 | // aibridge currently has native support for OpenAI and Anthropic |
| 228 | // only. The other ai_provider_type values (azure, google, |
| 229 | // openai-compat, openrouter, vercel) route through the OpenAI |
| 230 | // provider because chatd configures them against their |
| 231 | // OpenAI-compatible endpoints. Bedrock routes through the Anthropic |
| 232 | // provider with a Bedrock discriminator in Settings. |
| 233 | switch row.Type { |
| 234 | case database.AiProviderTypeOpenai, |
| 235 | database.AiProviderTypeAzure, |
| 236 | database.AiProviderTypeGoogle, |
| 237 | database.AiProviderTypeOpenaiCompat, |
| 238 | database.AiProviderTypeOpenrouter, |
| 239 | database.AiProviderTypeVercel: |
| 240 | if len(keys) == 0 && !cfg.AllowBYOK.Value() { |
| 241 | return nil, xerrors.Errorf("%s provider has no api keys configured and BYOK is not enabled", row.Type) |
| 242 | } |
| 243 | var pool *keypool.Pool |
| 244 | if len(keys) > 0 { |
| 245 | var err error |
| 246 | pool, err = buildAIProviderKeyPool(keys) |
| 247 | if err != nil { |
| 248 | return nil, xerrors.Errorf("%s key pool: %w", row.Type, err) |
| 249 | } |
| 250 | } |
| 251 | return aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{ |
| 252 | Name: row.Name, |
| 253 | BaseURL: row.BaseUrl, |
| 254 | KeyPool: pool, |
| 255 | APIDumpDir: dumpDir, |
| 256 | CircuitBreaker: cbCfg, |
| 257 | SendActorHeaders: sendActorHeaders, |
| 258 | }), nil |
| 259 | |
| 260 | case database.AiProviderTypeAnthropic, database.AiProviderTypeBedrock: |
| 261 | bedrock := bedrockConfigFromRow(row, settings) |
| 262 | // A row typed 'bedrock' authenticates exclusively via settings; |
| 263 | // without populated Bedrock credentials it cannot make upstream |
| 264 | // calls, so refuse rather than falling back to an unsigned |
| 265 | // Anthropic client. |
| 266 | if row.Type == database.AiProviderTypeBedrock && bedrock == nil { |