ReadAIProvidersFromEnv parses CODER_AI_GATEWAY_PROVIDER_ _ environment variables into a slice of AIProviderConfig. Deprecated alias env vars with the CODER_AIBRIDGE_PROVIDER_ _ prefix are also accepted for compatibility. Prefixes are mutually exclusive. This follows the same indexed p
(logger slog.Logger, environ []string)
| 3052 | // |
| 3053 | // This follows the same indexed pattern as ReadExternalAuthProvidersFromEnv. |
| 3054 | func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AIProviderConfig, error) { |
| 3055 | providers, err := readAIProvidersForPrefix(logger, environ, aiBridgeProviderEnvPrefix) |
| 3056 | if err != nil { |
| 3057 | return nil, err |
| 3058 | } |
| 3059 | gatewayProviders, err := readAIProvidersForPrefix(logger, environ, aiGatewayProviderEnvPrefix) |
| 3060 | if err != nil { |
| 3061 | return nil, err |
| 3062 | } |
| 3063 | if len(providers) > 0 && len(gatewayProviders) > 0 { |
| 3064 | return nil, xerrors.Errorf("cannot mix %s* and %s* environment variables, please consolidate onto %s*", aiBridgeProviderEnvPrefix, aiGatewayProviderEnvPrefix, aiGatewayProviderEnvPrefix) |
| 3065 | } |
| 3066 | var activePrefix string |
| 3067 | if len(providers) > 0 { |
| 3068 | activePrefix = aiBridgeProviderEnvPrefix |
| 3069 | } else if len(gatewayProviders) > 0 { |
| 3070 | activePrefix = aiGatewayProviderEnvPrefix |
| 3071 | } |
| 3072 | providers = append(providers, gatewayProviders...) |
| 3073 | |
| 3074 | // Post-parse validation. |
| 3075 | names := make(map[string]int, len(providers)) |
| 3076 | for i := range providers { |
| 3077 | p := &providers[i] |
| 3078 | if p.Type == "" { |
| 3079 | return nil, xerrors.Errorf("provider %d: TYPE is required", i) |
| 3080 | } |
| 3081 | |
| 3082 | providerType := database.AIProviderType(p.Type) |
| 3083 | if !providerType.Valid() { |
| 3084 | return nil, xerrors.Errorf("provider %d: unknown TYPE %q (must be one of: %v)", |
| 3085 | i, p.Type, database.AllAIProviderTypeValues()) |
| 3086 | } |
| 3087 | |
| 3088 | var bedrockKey, bedrockSecret string |
| 3089 | if len(p.BedrockAccessKeys) > 0 { |
| 3090 | bedrockKey = p.BedrockAccessKeys[0] |
| 3091 | } |
| 3092 | if len(p.BedrockAccessKeySecrets) > 0 { |
| 3093 | bedrockSecret = p.BedrockAccessKeySecrets[0] |
| 3094 | } |
| 3095 | settings := codersdk.NewAIProviderBedrockSettings( |
| 3096 | p.BedrockRegion, bedrockKey, bedrockSecret, |
| 3097 | p.BedrockModel, p.BedrockSmallFastModel, |
| 3098 | ) |
| 3099 | isBedrock := codersdk.IsBedrockConfigured(p.BedrockBaseURL, settings) |
| 3100 | |
| 3101 | // BEDROCK_* fields are accepted on anthropic (mutually exclusive |
| 3102 | // with KEYS) and required on bedrock. Any other TYPE rejecting |
| 3103 | // them prevents silently-ignored credentials. |
| 3104 | isBedrockType := providerType == database.AiProviderTypeBedrock |
| 3105 | isAnthropicType := providerType == database.AiProviderTypeAnthropic |
| 3106 | if !isAnthropicType && !isBedrockType && isBedrock { |
| 3107 | return nil, xerrors.Errorf("provider %d (%s): BEDROCK_* fields are only supported with TYPE %q or %q", |
| 3108 | i, p.Type, database.AiProviderTypeAnthropic, database.AiProviderTypeBedrock) |
| 3109 | } |
| 3110 | |
| 3111 | if isBedrockType && !isBedrock { |