readAIProvidersForPrefix parses provider env vars under a single indexed prefix (e.g. CODER_AI_GATEWAY_PROVIDER_) into a slice of AIProviderConfig. Per-field syntax errors and unknown keys are reported using the original env var name so the prefix stays visible to the operator.
(logger slog.Logger, environ []string, prefix string)
| 3176 | // reported using the original env var name so the prefix stays visible |
| 3177 | // to the operator. |
| 3178 | func readAIProvidersForPrefix(logger slog.Logger, environ []string, prefix string) ([]codersdk.AIProviderConfig, error) { |
| 3179 | parsed := serpent.ParseEnviron(environ, prefix) |
| 3180 | |
| 3181 | // Sort by numeric index so that PROVIDER_2 comes before PROVIDER_10. |
| 3182 | slices.SortFunc(parsed, func(a, b serpent.EnvVar) int { |
| 3183 | aIdx, _ := strconv.Atoi(strings.SplitN(a.Name, "_", 2)[0]) |
| 3184 | bIdx, _ := strconv.Atoi(strings.SplitN(b.Name, "_", 2)[0]) |
| 3185 | if aIdx != bIdx { |
| 3186 | return aIdx - bIdx |
| 3187 | } |
| 3188 | return strings.Compare(a.Name, b.Name) |
| 3189 | }) |
| 3190 | |
| 3191 | var providers []codersdk.AIProviderConfig |
| 3192 | for _, v := range parsed { |
| 3193 | fullName := prefix + v.Name |
| 3194 | tokens := strings.SplitN(v.Name, "_", 2) |
| 3195 | if len(tokens) != 2 { |
| 3196 | return nil, xerrors.Errorf("invalid env var: %s", fullName) |
| 3197 | } |
| 3198 | |
| 3199 | providerNum, err := strconv.Atoi(tokens[0]) |
| 3200 | if err != nil { |
| 3201 | return nil, xerrors.Errorf("parse number: %s", fullName) |
| 3202 | } |
| 3203 | |
| 3204 | var provider codersdk.AIProviderConfig |
| 3205 | switch { |
| 3206 | case len(providers) < providerNum: |
| 3207 | return nil, xerrors.Errorf( |
| 3208 | "provider num %v skipped: %s", |
| 3209 | len(providers), |
| 3210 | fullName, |
| 3211 | ) |
| 3212 | case len(providers) == providerNum: // First observation of this index, create a new provider. |
| 3213 | providers = append(providers, provider) |
| 3214 | case len(providers) == providerNum+1: // Provider already exists at this index, update it. |
| 3215 | provider = providers[providerNum] |
| 3216 | } |
| 3217 | |
| 3218 | key := tokens[1] |
| 3219 | switch key { |
| 3220 | case "TYPE": |
| 3221 | provider.Type = v.Value |
| 3222 | case "NAME": |
| 3223 | provider.Name = v.Value |
| 3224 | case "KEY", "KEYS": |
| 3225 | if len(provider.Keys) > 0 { |
| 3226 | return nil, xerrors.Errorf("provider %d: KEY and KEYS are mutually exclusive, use one or the other", providerNum) |
| 3227 | } |
| 3228 | if key == "KEYS" { |
| 3229 | provider.Keys = strings.Split(v.Value, ",") |
| 3230 | } else { |
| 3231 | provider.Keys = []string{v.Value} |
| 3232 | } |
| 3233 | case "BASE_URL": |
| 3234 | provider.BaseURL = v.Value |
| 3235 | case "BEDROCK_BASE_URL": |
no test coverage detected