mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: add ai_gateway options that alias aibridge options (#25061)
Adds options matching new AI Gateway naming. New options are added as alias for old options. Old options are still working. Old options have deprecated message. No conflict detection was added. Updated documentation so it mentions only new options. Added note about old options still working. > Various AI tools where used to create this PR
This commit is contained in:
+74
-48
@@ -2926,11 +2926,78 @@ func parseExternalAuthProvidersFromEnv(prefix string, environ []string) ([]coder
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
// ReadAIProvidersFromEnv parses CODER_AIBRIDGE_PROVIDER_<N>_<KEY>
|
||||
// ReadAIProvidersFromEnv parses CODER_AI_GATEWAY_PROVIDER_<N>_<KEY>
|
||||
// environment variables into a slice of AIProviderConfig.
|
||||
// Deprecated alias env vars with the CODER_AIBRIDGE_PROVIDER_<N>_<KEY>
|
||||
// prefix are also accepted for compatibility. Prefixes are mutually exclusive.
|
||||
//
|
||||
// This follows the same indexed pattern as ReadExternalAuthProvidersFromEnv.
|
||||
func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AIProviderConfig, error) {
|
||||
parsed := serpent.ParseEnviron(environ, "CODER_AIBRIDGE_PROVIDER_")
|
||||
providers, err := readAIProvidersForPrefix(logger, environ, "CODER_AIBRIDGE_PROVIDER_")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gatewayProviders, err := readAIProvidersForPrefix(logger, environ, "CODER_AI_GATEWAY_PROVIDER_")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(providers) > 0 && len(gatewayProviders) > 0 {
|
||||
return nil, xerrors.New("cannot mix CODER_AIBRIDGE_PROVIDER_* and CODER_AI_GATEWAY_PROVIDER_* environment variables, please consolidate onto CODER_AI_GATEWAY_PROVIDER_*")
|
||||
}
|
||||
providers = append(providers, gatewayProviders...)
|
||||
|
||||
// Post-parse validation.
|
||||
names := make(map[string]int, len(providers))
|
||||
for i := range providers {
|
||||
p := &providers[i]
|
||||
if p.Type == "" {
|
||||
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)
|
||||
}
|
||||
|
||||
if p.Type != aibridge.ProviderAnthropic && hasBedrockFields(*p) {
|
||||
return nil, xerrors.Errorf("provider %d (%s): BEDROCK_* fields are only supported with TYPE %q",
|
||||
i, p.Type, aibridge.ProviderAnthropic)
|
||||
}
|
||||
|
||||
if p.Type == aibridge.ProviderCopilot && len(p.Keys) > 0 {
|
||||
return nil, xerrors.Errorf("provider %d (%s): KEY/KEYS are not supported for TYPE %q",
|
||||
i, p.Type, aibridge.ProviderCopilot)
|
||||
}
|
||||
|
||||
if err := validateProviderCredentialList(i, p.Type, p.Keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateBedrockCredentials(i, p.Type, p.BedrockAccessKeys, p.BedrockAccessKeySecrets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Name == "" {
|
||||
p.Name = p.Type
|
||||
}
|
||||
if other, exists := names[p.Name]; exists {
|
||||
return nil, xerrors.Errorf("providers %d and %d have duplicate NAME %q (multiple providers of the same type require unique NAME values)", other, i, p.Name)
|
||||
}
|
||||
names[p.Name] = i
|
||||
}
|
||||
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func readAIProvidersForPrefix(logger slog.Logger, environ []string, prefix string) ([]codersdk.AIProviderConfig, error) {
|
||||
parsed := serpent.ParseEnviron(environ, prefix)
|
||||
|
||||
// Sort by numeric index so that PROVIDER_2 comes before PROVIDER_10.
|
||||
slices.SortFunc(parsed, func(a, b serpent.EnvVar) int {
|
||||
@@ -2944,14 +3011,15 @@ func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AI
|
||||
|
||||
var providers []codersdk.AIProviderConfig
|
||||
for _, v := range parsed {
|
||||
fullName := prefix + v.Name
|
||||
tokens := strings.SplitN(v.Name, "_", 2)
|
||||
if len(tokens) != 2 {
|
||||
return nil, xerrors.Errorf("invalid env var: %s", v.Name)
|
||||
return nil, xerrors.Errorf("invalid env var: %s", fullName)
|
||||
}
|
||||
|
||||
providerNum, err := strconv.Atoi(tokens[0])
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse number: %s", v.Name)
|
||||
return nil, xerrors.Errorf("parse number: %s", fullName)
|
||||
}
|
||||
|
||||
var provider codersdk.AIProviderConfig
|
||||
@@ -2960,7 +3028,7 @@ func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AI
|
||||
return nil, xerrors.Errorf(
|
||||
"provider num %v skipped: %s",
|
||||
len(providers),
|
||||
v.Name,
|
||||
fullName,
|
||||
)
|
||||
case len(providers) == providerNum: // First observation of this index, create a new provider.
|
||||
providers = append(providers, provider)
|
||||
@@ -3015,54 +3083,12 @@ func ReadAIProvidersFromEnv(logger slog.Logger, environ []string) ([]codersdk.AI
|
||||
provider.BedrockSmallFastModel = v.Value
|
||||
default:
|
||||
logger.Warn(context.Background(), "ignoring unknown AI provider field (check for typos)",
|
||||
slog.F("env", fmt.Sprintf("CODER_AIBRIDGE_PROVIDER_%d_%s", providerNum, key)),
|
||||
slog.F("env", fullName),
|
||||
)
|
||||
}
|
||||
providers[providerNum] = provider
|
||||
}
|
||||
|
||||
// Post-parse validation.
|
||||
names := make(map[string]int, len(providers))
|
||||
for i := range providers {
|
||||
p := &providers[i]
|
||||
if p.Type == "" {
|
||||
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)
|
||||
}
|
||||
|
||||
if p.Type != aibridge.ProviderAnthropic && hasBedrockFields(*p) {
|
||||
return nil, xerrors.Errorf("provider %d (%s): BEDROCK_* fields are only supported with TYPE %q",
|
||||
i, p.Type, aibridge.ProviderAnthropic)
|
||||
}
|
||||
|
||||
if p.Type == aibridge.ProviderCopilot && len(p.Keys) > 0 {
|
||||
return nil, xerrors.Errorf("provider %d (%s): KEY/KEYS are not supported for TYPE %q",
|
||||
i, p.Type, aibridge.ProviderCopilot)
|
||||
}
|
||||
|
||||
if err := validateProviderCredentialList(i, p.Type, p.Keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateBedrockCredentials(i, p.Type, p.BedrockAccessKeys, p.BedrockAccessKeySecrets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Name == "" {
|
||||
p.Name = p.Type
|
||||
}
|
||||
if other, exists := names[p.Name]; exists {
|
||||
return nil, xerrors.Errorf("providers %d and %d have duplicate NAME %q (multiple providers of the same type require unique NAME values)", other, i, p.Name)
|
||||
}
|
||||
names[p.Name] = i
|
||||
}
|
||||
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user