diff --git a/internal/util/provider.go b/internal/util/provider.go index ae25a63148a..dc5d6f46b31 100644 --- a/internal/util/provider.go +++ b/internal/util/provider.go @@ -26,6 +26,31 @@ func OpenAICompatibleProviderKey(name string) string { return openAICompatibleProviderPrefix + name } +// IsOpenAICompatibleProvider reports whether the given provider key identifies an +// OpenAI-compatibility provider (the generic "openai-compatibility" key or a named +// "openai-compatible-" key). Such providers always use static credentials +// (an API key and/or custom headers) and are therefore treated as API-key auths. +func IsOpenAICompatibleProvider(provider string) bool { + provider = strings.ToLower(strings.TrimSpace(provider)) + if provider == "" { + return false + } + if provider == "openai-compatibility" { + return true + } + return strings.HasPrefix(provider, openAICompatibleProviderPrefix) +} + +// OpenAICompatibleProviderName returns the config name encoded by a named +// OpenAI-compatible provider key such as "openai-compatible-vast". +func OpenAICompatibleProviderName(provider string) string { + provider = strings.ToLower(strings.TrimSpace(provider)) + if !strings.HasPrefix(provider, openAICompatibleProviderPrefix) { + return "" + } + return strings.TrimPrefix(provider, openAICompatibleProviderPrefix) +} + // GetProviderName determines all AI service providers capable of serving a registered model. // It first queries the global model registry to retrieve the providers backing the supplied model name. // When the model has not been registered yet, it falls back to legacy string heuristics to infer diff --git a/sdk/cliproxy/auth/classification.go b/sdk/cliproxy/auth/classification.go index b8c7171844c..b3e60241728 100644 --- a/sdk/cliproxy/auth/classification.go +++ b/sdk/cliproxy/auth/classification.go @@ -1,6 +1,10 @@ package auth -import "strings" +import ( + "strings" + + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" +) const ( AuthKindAPIKey = "apikey" @@ -39,9 +43,32 @@ func (a *Auth) AuthKind() string { if authHasOAuthMetadata(a) { return AuthKindOAuth } + // OpenAI-compatibility providers always use static credentials: either an API + // key or custom headers (e.g. cookie/device headers) when no api-key is set. + // Treat them as API-key auths so model-alias resolution and per-auth model + // pools apply even for header-authenticated entries. + if isOpenAICompatibilityAuth(a) { + return AuthKindAPIKey + } return "" } +// isOpenAICompatibilityAuth reports whether the auth belongs to an +// OpenAI-compatibility provider. Such auths carry the provider key produced by +// util.OpenAICompatibleProviderKey and/or a "compat_name" attribute. +func isOpenAICompatibilityAuth(a *Auth) bool { + if a == nil { + return false + } + if util.IsOpenAICompatibleProvider(a.Provider) { + return true + } + if authAttribute(a, "compat_name") != "" { + return true + } + return false +} + // AuthSourceKind returns where the Auth entry came from at runtime. func (a *Auth) AuthSourceKind() string { if a == nil { diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index 1597efff02c..bda40d8bf2c 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -135,7 +135,7 @@ func providerCoolingDisabledForAuth(auth *Auth, cfg *internalconfig.Config) bool providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } - if providerKey == "" && compatName == "" && provider != "openai-compatibility" { + if providerKey == "" && compatName == "" && !util.IsOpenAICompatibleProvider(provider) { return false } if providerKey == "" { @@ -1011,13 +1011,13 @@ func isOpenAICompatAPIKeyAuth(auth *Auth) bool { if !isAPIKeyAuth(auth) { return false } - if strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { + if util.IsOpenAICompatibleProvider(auth.Provider) { return true } if auth.Attributes == nil { return false } - return strings.TrimSpace(auth.Attributes["compat_name"]) != "" + return authAttribute(auth, "compat_name") != "" } func openAICompatProviderKey(auth *Auth) string { @@ -1300,7 +1300,7 @@ func (m *Manager) resolveAPIKeyModelAliasWithResult(auth *Auth, requestedModel s providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } - if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { + if compatName != "" || util.IsOpenAICompatibleProvider(auth.Provider) { if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil { models = asModelAliasEntries(entry.Models) } @@ -1979,7 +1979,7 @@ func (m *Manager) rebuildAPIKeyModelAliasLocked(cfg *internalconfig.Config) { providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } - if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { + if compatName != "" || util.IsOpenAICompatibleProvider(auth.Provider) { if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } @@ -3201,7 +3201,7 @@ func resolveUpstreamModelForOpenAICompatAPIKey(cfg *internalconfig.Config, auth providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } - if compatName == "" && !strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { + if compatName == "" && !util.IsOpenAICompatibleProvider(auth.Provider) { return "" } entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider) @@ -3217,15 +3217,23 @@ func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatNa if cfg == nil { return nil } - candidates := make([]string, 0, 3) - if v := strings.TrimSpace(compatName); v != "" { - candidates = append(candidates, v) - } - if v := strings.TrimSpace(providerKey); v != "" { - candidates = append(candidates, v) + candidates := make([]string, 0, 6) + seen := make(map[string]struct{}, 6) + addCandidate := func(value string) { + value = strings.TrimSpace(value) + if value == "" { + return + } + key := strings.ToLower(value) + if _, ok := seen[key]; ok { + return + } + seen[key] = struct{}{} + candidates = append(candidates, value) } - if v := strings.TrimSpace(authProvider); v != "" { - candidates = append(candidates, v) + for _, value := range []string{compatName, providerKey, authProvider} { + addCandidate(value) + addCandidate(util.OpenAICompatibleProviderName(value)) } for i := range cfg.OpenAICompatibility { compat := &cfg.OpenAICompatibility[i]