From 8b42bc309b073fc42e24e49424170555cb77fd50 Mon Sep 17 00:00:00 2001 From: daiaji Date: Tue, 7 Jul 2026 13:39:40 +0800 Subject: [PATCH 1/6] fix(cliproxy): keep original model ids routable with aliases --- sdk/cliproxy/service.go | 70 ++++++++++++--- sdk/cliproxy/service_excluded_models_test.go | 93 ++++++++++++++++++++ 2 files changed, 150 insertions(+), 13 deletions(-) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index d1040c960e0..e79db054b6b 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -2479,12 +2479,15 @@ func buildOpenAICompatibilityConfigModels(compat *config.OpenAICompatibility) [] return nil } now := time.Now().Unix() - models := make([]*ModelInfo, 0, len(compat.Models)) + models := make([]*ModelInfo, 0, len(compat.Models)*2) + seen := make(map[string]struct{}, len(compat.Models)*2) for i := range compat.Models { model := compat.Models[i] - modelID := strings.TrimSpace(model.Alias) + alias := strings.TrimSpace(model.Alias) + name := strings.TrimSpace(model.Name) + modelID := alias if modelID == "" { - modelID = strings.TrimSpace(model.Name) + modelID = name } if modelID == "" { continue @@ -2497,7 +2500,7 @@ func buildOpenAICompatibilityConfigModels(compat *config.OpenAICompatibility) [] if thinking == nil && !model.Image { thinking = ®istry.ThinkingSupport{Levels: []string{"low", "medium", "high"}} } - models = append(models, &ModelInfo{ + info := &ModelInfo{ ID: modelID, Object: "model", Created: now, @@ -2506,18 +2509,49 @@ func buildOpenAICompatibilityConfigModels(compat *config.OpenAICompatibility) [] DisplayName: modelID, UserDefined: false, Thinking: thinking, - }) + } + models = appendModelInfoIfUnique(models, info, seen) + if alias != "" && name != "" && !strings.EqualFold(alias, name) { + models = appendModelInfoIfUnique(models, &ModelInfo{ + ID: name, + Object: "model", + Created: now, + OwnedBy: compat.Name, + Type: modelType, + DisplayName: name, + UserDefined: false, + Thinking: thinking, + }, seen) + } } return models } +func appendModelInfoIfUnique(models []*ModelInfo, info *ModelInfo, seen map[string]struct{}) []*ModelInfo { + if info == nil { + return models + } + modelID := strings.TrimSpace(info.ID) + if modelID == "" { + return models + } + key := strings.ToLower(modelID) + if _, exists := seen[key]; exists { + return models + } + seen[key] = struct{}{} + clone := *info + clone.ID = modelID + return append(models, &clone) +} + func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*ModelInfo { if len(models) == 0 { return nil } now := time.Now().Unix() - out := make([]*ModelInfo, 0, len(models)) - seen := make(map[string]struct{}, len(models)) + out := make([]*ModelInfo, 0, len(models)*2) + seen := make(map[string]struct{}, len(models)*2) for i := range models { model := models[i] name := strings.TrimSpace(model.GetName()) @@ -2528,11 +2562,6 @@ func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*M if alias == "" { continue } - key := strings.ToLower(alias) - if _, exists := seen[key]; exists { - continue - } - seen[key] = struct{}{} display := name if display == "" { display = alias @@ -2551,7 +2580,22 @@ func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*M info.Thinking = upstream.Thinking } } - out = append(out, info) + out = appendModelInfoIfUnique(out, info, seen) + if name != "" && !strings.EqualFold(alias, name) { + nameInfo := &ModelInfo{ + ID: name, + Object: "model", + Created: now, + OwnedBy: ownedBy, + Type: modelType, + DisplayName: name, + UserDefined: true, + } + if upstream := registry.LookupStaticModelInfo(name); upstream != nil && upstream.Thinking != nil { + nameInfo.Thinking = upstream.Thinking + } + out = appendModelInfoIfUnique(out, nameInfo, seen) + } } return out } diff --git a/sdk/cliproxy/service_excluded_models_test.go b/sdk/cliproxy/service_excluded_models_test.go index 96490743b11..9a0831b0e64 100644 --- a/sdk/cliproxy/service_excluded_models_test.go +++ b/sdk/cliproxy/service_excluded_models_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" internalregistry "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" @@ -136,6 +137,98 @@ func TestRegisterModelsForAuth_OpenAICompatibilityImageModelType(t *testing.T) { } } +func TestRegisterModelsForAuth_ConfigAliasKeepsOriginalModelRoutable(t *testing.T) { + testCases := []struct { + name string + service *Service + auth *coreauth.Auth + provider string + originalModel string + aliasModel string + }{ + { + name: "codex api key", + service: &Service{cfg: &config.Config{ + CodexKey: []internalconfig.CodexKey{{ + APIKey: "codex-key", + BaseURL: "https://example.com", + Models: []internalconfig.CodexModel{{ + Name: "gpt-5.4-mini", + Alias: "GPT-5.4 Mini", + }}, + }}, + }}, + auth: &coreauth.Auth{ + ID: "auth-codex-alias-route", + Provider: "codex", + Status: coreauth.StatusActive, + Attributes: map[string]string{ + "auth_kind": "api_key", + "api_key": "codex-key", + "base_url": "https://example.com", + }, + }, + provider: "codex", + originalModel: "gpt-5.4-mini", + aliasModel: "GPT-5.4 Mini", + }, + { + name: "openai compatibility", + service: &Service{cfg: &config.Config{ + OpenAICompatibility: []config.OpenAICompatibility{{ + Name: "compat", + BaseURL: "https://example.com/v1", + Models: []config.OpenAICompatibilityModel{{ + Name: "gpt-5.4-mini", + Alias: "GPT-5.4 Mini", + }}, + }}, + }}, + auth: &coreauth.Auth{ + ID: "auth-openai-compat-alias-route", + Provider: "openai-compatibility", + Status: coreauth.StatusActive, + Attributes: map[string]string{ + "auth_kind": "api_key", + "compat_name": "compat", + "provider_key": "compat", + }, + }, + provider: "compat", + originalModel: "gpt-5.4-mini", + aliasModel: "GPT-5.4 Mini", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + modelRegistry := internalregistry.GetGlobalRegistry() + modelRegistry.UnregisterClient(tt.auth.ID) + t.Cleanup(func() { + modelRegistry.UnregisterClient(tt.auth.ID) + }) + + tt.service.registerModelsForAuth(context.Background(), tt.auth) + + if !providersContain(modelRegistry.GetModelProviders(tt.aliasModel), tt.provider) { + t.Fatalf("alias model %q providers = %v, want %q", tt.aliasModel, modelRegistry.GetModelProviders(tt.aliasModel), tt.provider) + } + if !providersContain(modelRegistry.GetModelProviders(tt.originalModel), tt.provider) { + t.Fatalf("original model %q providers = %v, want %q", tt.originalModel, modelRegistry.GetModelProviders(tt.originalModel), tt.provider) + } + }) + } +} + +func providersContain(providers []string, want string) bool { + for _, provider := range providers { + if strings.EqualFold(strings.TrimSpace(provider), want) { + return true + } + } + return false +} + func TestRegisterModelsForAuth_AntigravityFetchesWebSearchCapability(t *testing.T) { var sawFetch bool server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { From adc389da4b462d1c21be28c00e86ab8945ce2259 Mon Sep 17 00:00:00 2001 From: daiaji Date: Tue, 7 Jul 2026 13:49:47 +0800 Subject: [PATCH 2/6] refactor(cliproxy): avoid cloning model info during registration appendModelInfoIfUnique receives freshly allocated ModelInfo values from its callers, so cloning the struct before appending is unnecessary. Trim and assign the normalized ID directly on the provided value, then append it to the model list. Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- sdk/cliproxy/service.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index e79db054b6b..0c50122240f 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -2540,9 +2540,8 @@ func appendModelInfoIfUnique(models []*ModelInfo, info *ModelInfo, seen map[stri return models } seen[key] = struct{}{} - clone := *info - clone.ID = modelID - return append(models, &clone) + info.ID = modelID + return append(models, info) } func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*ModelInfo { From 28f8f0acda696b0b8d599ae18c798ff1cf9a3b46 Mon Sep 17 00:00:00 2001 From: daiaji Date: Tue, 7 Jul 2026 13:53:16 +0800 Subject: [PATCH 3/6] fix(cliproxy): preserve case-distinct model aliases --- sdk/cliproxy/service.go | 6 +-- sdk/cliproxy/service_excluded_models_test.go | 52 ++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 0c50122240f..e392cd8b4aa 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -2511,7 +2511,7 @@ func buildOpenAICompatibilityConfigModels(compat *config.OpenAICompatibility) [] Thinking: thinking, } models = appendModelInfoIfUnique(models, info, seen) - if alias != "" && name != "" && !strings.EqualFold(alias, name) { + if alias != "" && name != "" && alias != name { models = appendModelInfoIfUnique(models, &ModelInfo{ ID: name, Object: "model", @@ -2535,7 +2535,7 @@ func appendModelInfoIfUnique(models []*ModelInfo, info *ModelInfo, seen map[stri if modelID == "" { return models } - key := strings.ToLower(modelID) + key := modelID if _, exists := seen[key]; exists { return models } @@ -2580,7 +2580,7 @@ func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*M } } out = appendModelInfoIfUnique(out, info, seen) - if name != "" && !strings.EqualFold(alias, name) { + if name != "" && alias != name { nameInfo := &ModelInfo{ ID: name, Object: "model", diff --git a/sdk/cliproxy/service_excluded_models_test.go b/sdk/cliproxy/service_excluded_models_test.go index 9a0831b0e64..711cfb1ccb0 100644 --- a/sdk/cliproxy/service_excluded_models_test.go +++ b/sdk/cliproxy/service_excluded_models_test.go @@ -198,6 +198,58 @@ func TestRegisterModelsForAuth_ConfigAliasKeepsOriginalModelRoutable(t *testing. originalModel: "gpt-5.4-mini", aliasModel: "GPT-5.4 Mini", }, + { + name: "codex case-distinct alias", + service: &Service{cfg: &config.Config{ + CodexKey: []internalconfig.CodexKey{{ + APIKey: "codex-case-key", + BaseURL: "https://example.com", + Models: []internalconfig.CodexModel{{ + Name: "gpt-5", + Alias: "GPT-5", + }}, + }}, + }}, + auth: &coreauth.Auth{ + ID: "auth-codex-case-alias-route", + Provider: "codex", + Status: coreauth.StatusActive, + Attributes: map[string]string{ + "auth_kind": "api_key", + "api_key": "codex-case-key", + "base_url": "https://example.com", + }, + }, + provider: "codex", + originalModel: "gpt-5", + aliasModel: "GPT-5", + }, + { + name: "openai compatibility case-distinct alias", + service: &Service{cfg: &config.Config{ + OpenAICompatibility: []config.OpenAICompatibility{{ + Name: "compat-case", + BaseURL: "https://example.com/v1", + Models: []config.OpenAICompatibilityModel{{ + Name: "gpt-5", + Alias: "GPT-5", + }}, + }}, + }}, + auth: &coreauth.Auth{ + ID: "auth-openai-compat-case-alias-route", + Provider: "openai-compatibility", + Status: coreauth.StatusActive, + Attributes: map[string]string{ + "auth_kind": "api_key", + "compat_name": "compat-case", + "provider_key": "compat-case", + }, + }, + provider: "compat-case", + originalModel: "gpt-5", + aliasModel: "GPT-5", + }, } for _, tt := range testCases { From a2a2cb295d2e716bf310dc3fe2247080c968cf51 Mon Sep 17 00:00:00 2001 From: daiaji Date: Tue, 7 Jul 2026 14:45:21 +0800 Subject: [PATCH 4/6] fix(cliproxy): exclude alias model pairs together --- sdk/cliproxy/service.go | 62 ++++++++++++++++++-- sdk/cliproxy/service_excluded_models_test.go | 38 ++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index e392cd8b4aa..6eb227fb72b 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -1951,7 +1951,7 @@ func (s *Service) registerModelsForAuthWithCache(ctx context.Context, a *coreaut models = buildGeminiConfigModels(entry) } if authKind == "apikey" { - excluded = entry.ExcludedModels + excluded = expandExcludedModelsForConfigAliases(entry.ExcludedModels, entry.Models) } } models = applyExcludedModels(models, excluded) @@ -1962,7 +1962,7 @@ func (s *Service) registerModelsForAuthWithCache(ctx context.Context, a *coreaut models = buildGeminiConfigModels(entry) } if authKind == "apikey" { - excluded = entry.ExcludedModels + excluded = expandExcludedModelsForConfigAliases(entry.ExcludedModels, entry.Models) } } models = applyExcludedModels(models, excluded) @@ -1974,7 +1974,7 @@ func (s *Service) registerModelsForAuthWithCache(ctx context.Context, a *coreaut models = buildVertexCompatConfigModels(entry) } if authKind == "apikey" { - excluded = entry.ExcludedModels + excluded = expandExcludedModelsForConfigAliases(entry.ExcludedModels, entry.Models) } } models = applyExcludedModels(models, excluded) @@ -1992,7 +1992,7 @@ func (s *Service) registerModelsForAuthWithCache(ctx context.Context, a *coreaut models = buildClaudeConfigModels(entry) } if authKind == "apikey" { - excluded = entry.ExcludedModels + excluded = expandExcludedModelsForConfigAliases(entry.ExcludedModels, entry.Models) } } models = applyExcludedModels(models, excluded) @@ -2018,7 +2018,7 @@ func (s *Service) registerModelsForAuthWithCache(ctx context.Context, a *coreaut models = buildCodexConfigModels(entry) } if authKind == "apikey" { - excluded = entry.ExcludedModels + excluded = expandExcludedModelsForConfigAliases(entry.ExcludedModels, entry.Models) } } models = applyExcludedModels(models, excluded) @@ -2383,6 +2383,58 @@ func applyExcludedModels(models []*ModelInfo, excluded []string) []*ModelInfo { return filtered } +func expandExcludedModelsForConfigAliases[T modelEntry](excluded []string, models []T) []string { + if len(excluded) == 0 || len(models) == 0 { + return excluded + } + patterns := make([]string, 0, len(excluded)) + seen := make(map[string]struct{}, len(excluded)+len(models)*2) + out := make([]string, 0, len(excluded)+len(models)*2) + add := func(model string) { + model = strings.ToLower(strings.TrimSpace(model)) + if model == "" { + return + } + if _, exists := seen[model]; exists { + return + } + seen[model] = struct{}{} + out = append(out, model) + } + for _, item := range excluded { + trimmed := strings.ToLower(strings.TrimSpace(item)) + if trimmed == "" { + continue + } + patterns = append(patterns, trimmed) + add(trimmed) + } + if len(patterns) == 0 { + return excluded + } + for i := range models { + name := strings.TrimSpace(models[i].GetName()) + alias := strings.TrimSpace(models[i].GetAlias()) + if name == "" || alias == "" || name == alias { + continue + } + nameKey := strings.ToLower(name) + aliasKey := strings.ToLower(alias) + matched := false + for _, pattern := range patterns { + if matchWildcard(pattern, nameKey) || matchWildcard(pattern, aliasKey) { + matched = true + break + } + } + if matched { + add(name) + add(alias) + } + } + return out +} + func applyModelPrefixes(models []*ModelInfo, prefix string, forceModelPrefix bool) []*ModelInfo { trimmedPrefix := strings.TrimSpace(prefix) if trimmedPrefix == "" || len(models) == 0 { diff --git a/sdk/cliproxy/service_excluded_models_test.go b/sdk/cliproxy/service_excluded_models_test.go index 711cfb1ccb0..c521f1ebacc 100644 --- a/sdk/cliproxy/service_excluded_models_test.go +++ b/sdk/cliproxy/service_excluded_models_test.go @@ -281,6 +281,44 @@ func providersContain(providers []string, want string) bool { return false } +func TestRegisterModelsForAuth_ConfigAliasExclusionBlocksOriginalModelPair(t *testing.T) { + service := &Service{cfg: &config.Config{ + CodexKey: []internalconfig.CodexKey{{ + APIKey: "codex-excluded-key", + BaseURL: "https://example.com", + ExcludedModels: []string{"my-gpt"}, + Models: []internalconfig.CodexModel{{ + Name: "gpt-5", + Alias: "my-gpt", + }}, + }}, + }} + auth := &coreauth.Auth{ + ID: "auth-codex-alias-excluded-route", + Provider: "codex", + Status: coreauth.StatusActive, + Attributes: map[string]string{ + "auth_kind": "api_key", + "api_key": "codex-excluded-key", + "base_url": "https://example.com", + }, + } + modelRegistry := internalregistry.GetGlobalRegistry() + modelRegistry.UnregisterClient(auth.ID) + t.Cleanup(func() { + modelRegistry.UnregisterClient(auth.ID) + }) + + service.registerModelsForAuth(context.Background(), auth) + + if providersContain(modelRegistry.GetModelProviders("my-gpt"), "codex") { + t.Fatalf("alias model remained routable after exclusion") + } + if providersContain(modelRegistry.GetModelProviders("gpt-5"), "codex") { + t.Fatalf("original model remained routable after alias exclusion") + } +} + func TestRegisterModelsForAuth_AntigravityFetchesWebSearchCapability(t *testing.T) { var sawFetch bool server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { From e9df43e97081038744437ef95392a79fed9f75e6 Mon Sep 17 00:00:00 2001 From: daiaji Date: Tue, 7 Jul 2026 14:59:10 +0800 Subject: [PATCH 5/6] fix(cliproxy): preserve static metadata for aliased originals --- sdk/cliproxy/service.go | 58 ++++++++++++-------- sdk/cliproxy/service_excluded_models_test.go | 21 +++++++ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 6eb227fb72b..9612e54ee38 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -2564,16 +2564,7 @@ func buildOpenAICompatibilityConfigModels(compat *config.OpenAICompatibility) [] } models = appendModelInfoIfUnique(models, info, seen) if alias != "" && name != "" && alias != name { - models = appendModelInfoIfUnique(models, &ModelInfo{ - ID: name, - Object: "model", - Created: now, - OwnedBy: compat.Name, - Type: modelType, - DisplayName: name, - UserDefined: false, - Thinking: thinking, - }, seen) + models = appendModelInfoIfUnique(models, configOriginalModelInfo(name, compat.Name, modelType, now, false, thinking), seen) } } return models @@ -2596,6 +2587,39 @@ func appendModelInfoIfUnique(models []*ModelInfo, info *ModelInfo, seen map[stri return append(models, info) } +func configOriginalModelInfo(name, ownedBy, modelType string, created int64, userDefined bool, thinking *registry.ThinkingSupport) *ModelInfo { + name = strings.TrimSpace(name) + if name == "" { + return nil + } + if info := registry.LookupStaticModelInfo(name); info != nil { + info.ID = name + if info.Object == "" { + info.Object = "model" + } + if info.Created == 0 { + info.Created = created + } + info.OwnedBy = ownedBy + info.Type = modelType + if info.DisplayName == "" { + info.DisplayName = name + } + info.UserDefined = false + return info + } + return &ModelInfo{ + ID: name, + Object: "model", + Created: created, + OwnedBy: ownedBy, + Type: modelType, + DisplayName: name, + UserDefined: userDefined, + Thinking: thinking, + } +} + func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*ModelInfo { if len(models) == 0 { return nil @@ -2633,19 +2657,7 @@ func buildConfigModels[T modelEntry](models []T, ownedBy, modelType string) []*M } out = appendModelInfoIfUnique(out, info, seen) if name != "" && alias != name { - nameInfo := &ModelInfo{ - ID: name, - Object: "model", - Created: now, - OwnedBy: ownedBy, - Type: modelType, - DisplayName: name, - UserDefined: true, - } - if upstream := registry.LookupStaticModelInfo(name); upstream != nil && upstream.Thinking != nil { - nameInfo.Thinking = upstream.Thinking - } - out = appendModelInfoIfUnique(out, nameInfo, seen) + out = appendModelInfoIfUnique(out, configOriginalModelInfo(name, ownedBy, modelType, now, true, info.Thinking), seen) } } return out diff --git a/sdk/cliproxy/service_excluded_models_test.go b/sdk/cliproxy/service_excluded_models_test.go index c521f1ebacc..6c9c52777b0 100644 --- a/sdk/cliproxy/service_excluded_models_test.go +++ b/sdk/cliproxy/service_excluded_models_test.go @@ -268,10 +268,31 @@ func TestRegisterModelsForAuth_ConfigAliasKeepsOriginalModelRoutable(t *testing. if !providersContain(modelRegistry.GetModelProviders(tt.originalModel), tt.provider) { t.Fatalf("original model %q providers = %v, want %q", tt.originalModel, modelRegistry.GetModelProviders(tt.originalModel), tt.provider) } + if static := internalregistry.LookupStaticModelInfo(tt.originalModel); static != nil && static.ContextLength > 0 { + registered := clientModelByID(modelRegistry.GetModelsForClient(tt.auth.ID), tt.originalModel) + if registered == nil { + t.Fatalf("registered original model %q not found", tt.originalModel) + } + if registered.ContextLength != static.ContextLength { + t.Fatalf("original model %q context length = %d, want static %d", tt.originalModel, registered.ContextLength, static.ContextLength) + } + if registered.UserDefined { + t.Fatalf("original model %q should preserve static metadata instead of being marked user-defined", tt.originalModel) + } + } }) } } +func clientModelByID(models []*internalregistry.ModelInfo, id string) *internalregistry.ModelInfo { + for _, model := range models { + if model != nil && model.ID == id { + return model + } + } + return nil +} + func providersContain(providers []string, want string) bool { for _, provider := range providers { if strings.EqualFold(strings.TrimSpace(provider), want) { From cc21639ae4b408596d771b22bd6fb3ba39651043 Mon Sep 17 00:00:00 2001 From: daiaji Date: Tue, 7 Jul 2026 15:09:40 +0800 Subject: [PATCH 6/6] fix(cliproxy): preserve compat thinking for original ids --- sdk/cliproxy/service.go | 3 ++ sdk/cliproxy/service_excluded_models_test.go | 46 ++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 9612e54ee38..f56289d77c8 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -2605,6 +2605,9 @@ func configOriginalModelInfo(name, ownedBy, modelType string, created int64, use if info.DisplayName == "" { info.DisplayName = name } + if thinking != nil { + info.Thinking = thinking + } info.UserDefined = false return info } diff --git a/sdk/cliproxy/service_excluded_models_test.go b/sdk/cliproxy/service_excluded_models_test.go index 6c9c52777b0..5901de32208 100644 --- a/sdk/cliproxy/service_excluded_models_test.go +++ b/sdk/cliproxy/service_excluded_models_test.go @@ -293,6 +293,52 @@ func clientModelByID(models []*internalregistry.ModelInfo, id string) *internalr return nil } +func TestRegisterModelsForAuth_OpenAICompatibilityOriginalModelKeepsCompatThinking(t *testing.T) { + static := internalregistry.LookupStaticModelInfo("gemini-2.5-pro") + if static == nil { + t.Fatal("expected static gemini-2.5-pro metadata") + } + service := &Service{cfg: &config.Config{ + OpenAICompatibility: []config.OpenAICompatibility{{ + Name: "compat-thinking", + BaseURL: "https://example.com/v1", + Models: []config.OpenAICompatibilityModel{{ + Name: "gemini-2.5-pro", + Alias: "gemini-pro", + Thinking: &internalregistry.ThinkingSupport{Levels: []string{"low", "high"}}, + }}, + }}, + }} + auth := &coreauth.Auth{ + ID: "auth-openai-compat-thinking-route", + Provider: "openai-compatibility", + Status: coreauth.StatusActive, + Attributes: map[string]string{ + "auth_kind": "api_key", + "compat_name": "compat-thinking", + "provider_key": "compat-thinking", + }, + } + modelRegistry := internalregistry.GetGlobalRegistry() + modelRegistry.UnregisterClient(auth.ID) + t.Cleanup(func() { + modelRegistry.UnregisterClient(auth.ID) + }) + + service.registerModelsForAuth(context.Background(), auth) + + registered := clientModelByID(modelRegistry.GetModelsForClient(auth.ID), "gemini-2.5-pro") + if registered == nil { + t.Fatal("expected original model to be registered") + } + if static.ContextLength > 0 && registered.ContextLength != static.ContextLength { + t.Fatalf("context length = %d, want static %d", registered.ContextLength, static.ContextLength) + } + if registered.Thinking == nil || len(registered.Thinking.Levels) != 2 || registered.Thinking.Levels[0] != "low" || registered.Thinking.Levels[1] != "high" { + t.Fatalf("thinking = %+v, want compat levels [low high]", registered.Thinking) + } +} + func providersContain(providers []string, want string) bool { for _, provider := range providers { if strings.EqualFold(strings.TrimSpace(provider), want) {