Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions internal/util/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-<name>" 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
}
Comment on lines +33 to +37
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
Expand Down
29 changes: 28 additions & 1 deletion sdk/cliproxy/auth/classification.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package auth

import "strings"
import (
"strings"

"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
)

const (
AuthKindAPIKey = "apikey"
Expand Down Expand Up @@ -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) {
Comment on lines +46 to +50
return AuthKindAPIKey
Comment on lines +50 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prefer OpenAI-compatible classification before OAuth metadata

For OpenAI-compatible auth files that include an email field only as a label/account identifier, authHasOAuthMetadata returns OAuth before this new provider check can run, so a header-only openai-compatible-* auth is still excluded from the API-key alias/pool path this change is meant to enable. The file synthesizer preserves arbitrary email metadata for any provider, so these header-auth entries continue forwarding client aliases upstream unless the OpenAI-compatible/provider check runs before the legacy OAuth metadata fallback (while still letting explicit auth_kind: oauth win).

Useful? React with 👍 / 👎.

}
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
Comment on lines +63 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize named OpenAI-compatible providers for aliases

When an auth is identified only by its normalized provider key (for example Provider: "openai-compatible-vast") and has no compat_name/provider_key attributes, this branch now classifies it as an API-key auth, but the API-key alias/pool path still looks up OpenAI-compat config via those attributes or by comparing the raw provider string in resolveOpenAICompatConfig. Since that lookup never strips the openai-compatible- prefix to match a config named vast, these header-only provider-key auths still forward client aliases upstream while also skipping the OAuth alias fallback. Please derive the compat name/provider key from the prefix here or update the resolver to handle the normalized provider key.

Useful? React with 👍 / 👎.

}
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 {
Expand Down
36 changes: 22 additions & 14 deletions sdk/cliproxy/auth/conductor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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]
Expand Down
Loading