Skip to content

Commit f35c609

Browse files
fix(openai-compat): handle normalized provider aliases
1 parent 7c44826 commit f35c609

6 files changed

Lines changed: 134 additions & 18 deletions

File tree

internal/util/provider.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ func IsOpenAICompatibleProvider(provider string) bool {
4141
return strings.HasPrefix(provider, openAICompatibleProviderPrefix)
4242
}
4343

44+
// OpenAICompatibleProviderName returns the config name encoded by a named
45+
// OpenAI-compatible provider key such as "openai-compatible-vast".
46+
func OpenAICompatibleProviderName(provider string) string {
47+
provider = strings.ToLower(strings.TrimSpace(provider))
48+
if !strings.HasPrefix(provider, openAICompatibleProviderPrefix) {
49+
return ""
50+
}
51+
return strings.TrimPrefix(provider, openAICompatibleProviderPrefix)
52+
}
53+
4454
// GetProviderName determines all AI service providers capable of serving a registered model.
4555
// It first queries the global model registry to retrieve the providers backing the supplied model name.
4656
// When the model has not been registered yet, it falls back to legacy string heuristics to infer

internal/util/provider_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package util
2+
3+
import "testing"
4+
5+
func TestIsOpenAICompatibleProvider(t *testing.T) {
6+
tests := []struct {
7+
name string
8+
provider string
9+
want bool
10+
}{
11+
{name: "empty", provider: "", want: false},
12+
{name: "whitespace", provider: " ", want: false},
13+
{name: "generic", provider: "openai-compatibility", want: true},
14+
{name: "generic mixed case", provider: " OpenAI-Compatibility ", want: true},
15+
{name: "named provider", provider: "openai-compatible-kimi", want: true},
16+
{name: "named provider mixed case", provider: " OpenAI-Compatible-Kimi ", want: true},
17+
{name: "unrelated provider", provider: "openai", want: false},
18+
{name: "prefix lookalike", provider: "openai-compat-kimi", want: false},
19+
}
20+
21+
for _, tt := range tests {
22+
t.Run(tt.name, func(t *testing.T) {
23+
if got := IsOpenAICompatibleProvider(tt.provider); got != tt.want {
24+
t.Fatalf("IsOpenAICompatibleProvider(%q) = %v, want %v", tt.provider, got, tt.want)
25+
}
26+
})
27+
}
28+
}
29+
30+
func TestOpenAICompatibleProviderName(t *testing.T) {
31+
tests := []struct {
32+
name string
33+
provider string
34+
want string
35+
}{
36+
{name: "empty", provider: "", want: ""},
37+
{name: "generic", provider: "openai-compatibility", want: ""},
38+
{name: "named provider", provider: "openai-compatible-kimi", want: "kimi"},
39+
{name: "named provider mixed case", provider: " OpenAI-Compatible-Kimi ", want: "kimi"},
40+
{name: "unrelated provider", provider: "openai", want: ""},
41+
}
42+
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
if got := OpenAICompatibleProviderName(tt.provider); got != tt.want {
46+
t.Fatalf("OpenAICompatibleProviderName(%q) = %q, want %q", tt.provider, got, tt.want)
47+
}
48+
})
49+
}
50+
}

sdk/cliproxy/auth/api_key_model_alias_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,28 @@ func TestResolveAPIKeyModelAliasWithResult_ForceMappingUsesConfigAliasNotRequest
267267
t.Fatalf("OriginalAlias = %q want claude-sonnet-4-5", result.OriginalAlias)
268268
}
269269
}
270+
271+
func TestResolveAPIKeyModelAliasWithResult_OpenAICompatProviderKeyName(t *testing.T) {
272+
cfg := &internalconfig.Config{
273+
OpenAICompatibility: []internalconfig.OpenAICompatibility{{
274+
Name: "vast",
275+
Models: []internalconfig.OpenAICompatibilityModel{{
276+
Name: "glm-5.2",
277+
Alias: "claude-opus-4-8",
278+
ForceMapping: true,
279+
}},
280+
}},
281+
}
282+
283+
mgr := NewManager(nil, nil, nil)
284+
mgr.SetConfig(cfg)
285+
286+
auth := &Auth{
287+
ID: "vast-header-auth",
288+
Provider: "openai-compatible-vast",
289+
}
290+
result := mgr.resolveAPIKeyModelAliasWithResult(auth, "claude-opus-4-8")
291+
if result.UpstreamModel != "glm-5.2" || !result.ForceMapping || result.OriginalAlias != "claude-opus-4-8" {
292+
t.Fatalf("resolveAPIKeyModelAliasWithResult() = %+v, want upstream glm-5.2 with alias rewrite", result)
293+
}
294+
}

sdk/cliproxy/auth/classification.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ func (a *Auth) AuthKind() string {
4040
if authAttribute(a, AttributeAPIKey) != "" {
4141
return AuthKindAPIKey
4242
}
43+
if authHasOAuthMetadata(a) {
44+
return AuthKindOAuth
45+
}
4346
// OpenAI-compatibility providers always use static credentials: either an API
4447
// key or custom headers (e.g. cookie/device headers) when no api-key is set.
4548
// Treat them as API-key auths so model-alias resolution and per-auth model
4649
// pools apply even for header-authenticated entries.
4750
if isOpenAICompatibilityAuth(a) {
4851
return AuthKindAPIKey
4952
}
50-
if authHasOAuthMetadata(a) {
51-
return AuthKindOAuth
52-
}
5353
return ""
5454
}
5555

@@ -63,7 +63,7 @@ func isOpenAICompatibilityAuth(a *Auth) bool {
6363
if util.IsOpenAICompatibleProvider(a.Provider) {
6464
return true
6565
}
66-
if a.Attributes != nil && strings.TrimSpace(a.Attributes["compat_name"]) != "" {
66+
if authAttribute(a, "compat_name") != "" {
6767
return true
6868
}
6969
return false

sdk/cliproxy/auth/classification_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,29 @@ func TestAuthKind(t *testing.T) {
3333
auth: &Auth{Metadata: map[string]any{"access_token": "token"}},
3434
want: AuthKindOAuth,
3535
},
36+
{
37+
name: "openai compatibility provider without api key",
38+
auth: &Auth{Provider: "openai-compatibility"},
39+
want: AuthKindAPIKey,
40+
},
41+
{
42+
name: "named openai compatibility provider without api key",
43+
auth: &Auth{Provider: "openai-compatible-foo"},
44+
want: AuthKindAPIKey,
45+
},
46+
{
47+
name: "compat name attribute without api key",
48+
auth: &Auth{Attributes: map[string]string{"compat_name": "foo"}},
49+
want: AuthKindAPIKey,
50+
},
51+
{
52+
name: "oauth metadata wins over openai compatibility fallback",
53+
auth: &Auth{
54+
Provider: "openai-compatible-foo",
55+
Metadata: map[string]any{"access_token": "token"},
56+
},
57+
want: AuthKindOAuth,
58+
},
3659
{
3760
name: "unknown metadata shape",
3861
auth: &Auth{Metadata: map[string]any{"type": "test"}},

sdk/cliproxy/auth/conductor.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func providerCoolingDisabledForAuth(auth *Auth, cfg *internalconfig.Config) bool
135135
providerKey = strings.TrimSpace(auth.Attributes["provider_key"])
136136
compatName = strings.TrimSpace(auth.Attributes["compat_name"])
137137
}
138-
if providerKey == "" && compatName == "" && provider != "openai-compatibility" {
138+
if providerKey == "" && compatName == "" && !util.IsOpenAICompatibleProvider(provider) {
139139
return false
140140
}
141141
if providerKey == "" {
@@ -1011,13 +1011,13 @@ func isOpenAICompatAPIKeyAuth(auth *Auth) bool {
10111011
if !isAPIKeyAuth(auth) {
10121012
return false
10131013
}
1014-
if strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") {
1014+
if util.IsOpenAICompatibleProvider(auth.Provider) {
10151015
return true
10161016
}
10171017
if auth.Attributes == nil {
10181018
return false
10191019
}
1020-
return strings.TrimSpace(auth.Attributes["compat_name"]) != ""
1020+
return authAttribute(auth, "compat_name") != ""
10211021
}
10221022

10231023
func openAICompatProviderKey(auth *Auth) string {
@@ -1300,7 +1300,7 @@ func (m *Manager) resolveAPIKeyModelAliasWithResult(auth *Auth, requestedModel s
13001300
providerKey = strings.TrimSpace(auth.Attributes["provider_key"])
13011301
compatName = strings.TrimSpace(auth.Attributes["compat_name"])
13021302
}
1303-
if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") {
1303+
if compatName != "" || util.IsOpenAICompatibleProvider(auth.Provider) {
13041304
if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil {
13051305
models = asModelAliasEntries(entry.Models)
13061306
}
@@ -1979,7 +1979,7 @@ func (m *Manager) rebuildAPIKeyModelAliasLocked(cfg *internalconfig.Config) {
19791979
providerKey = strings.TrimSpace(auth.Attributes["provider_key"])
19801980
compatName = strings.TrimSpace(auth.Attributes["compat_name"])
19811981
}
1982-
if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") {
1982+
if compatName != "" || util.IsOpenAICompatibleProvider(auth.Provider) {
19831983
if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil {
19841984
compileAPIKeyModelAliasForModels(byAlias, entry.Models)
19851985
}
@@ -3201,7 +3201,7 @@ func resolveUpstreamModelForOpenAICompatAPIKey(cfg *internalconfig.Config, auth
32013201
providerKey = strings.TrimSpace(auth.Attributes["provider_key"])
32023202
compatName = strings.TrimSpace(auth.Attributes["compat_name"])
32033203
}
3204-
if compatName == "" && !strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") {
3204+
if compatName == "" && !util.IsOpenAICompatibleProvider(auth.Provider) {
32053205
return ""
32063206
}
32073207
entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider)
@@ -3217,15 +3217,23 @@ func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatNa
32173217
if cfg == nil {
32183218
return nil
32193219
}
3220-
candidates := make([]string, 0, 3)
3221-
if v := strings.TrimSpace(compatName); v != "" {
3222-
candidates = append(candidates, v)
3223-
}
3224-
if v := strings.TrimSpace(providerKey); v != "" {
3225-
candidates = append(candidates, v)
3220+
candidates := make([]string, 0, 6)
3221+
seen := make(map[string]struct{}, 6)
3222+
addCandidate := func(value string) {
3223+
value = strings.TrimSpace(value)
3224+
if value == "" {
3225+
return
3226+
}
3227+
key := strings.ToLower(value)
3228+
if _, ok := seen[key]; ok {
3229+
return
3230+
}
3231+
seen[key] = struct{}{}
3232+
candidates = append(candidates, value)
32263233
}
3227-
if v := strings.TrimSpace(authProvider); v != "" {
3228-
candidates = append(candidates, v)
3234+
for _, value := range []string{compatName, providerKey, authProvider} {
3235+
addCandidate(value)
3236+
addCandidate(util.OpenAICompatibleProviderName(value))
32293237
}
32303238
for i := range cfg.OpenAICompatibility {
32313239
compat := &cfg.OpenAICompatibility[i]

0 commit comments

Comments
 (0)