From a288bab73ace3ec789dd3af26ca8e98f3277c766 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Thu, 13 Aug 2026 12:40:06 +0800 Subject: [PATCH] fix(gateway): align passthrough model discovery --- .../gateway_hotpath_optimization_test.go | 91 +++++++++++++++++++ backend/internal/service/gateway_service.go | 11 +++ 2 files changed, 102 insertions(+) diff --git a/backend/internal/service/gateway_hotpath_optimization_test.go b/backend/internal/service/gateway_hotpath_optimization_test.go index 48cfc358a6..d9a00710a3 100644 --- a/backend/internal/service/gateway_hotpath_optimization_test.go +++ b/backend/internal/service/gateway_hotpath_optimization_test.go @@ -602,6 +602,97 @@ func TestGetAvailableModels_ErrorAndGlobalListBranches(t *testing.T) { require.Equal(t, int64(1), okRepo.listAllCalls.Load()) } +func TestGetAvailableModels_OpenAIPassthroughUsesDefaultFallback(t *testing.T) { + groupID := int64(10) + + tests := []struct { + name string + accounts []Account + want []string + }{ + { + name: "passthrough only ignores stale mapping", + accounts: []Account{ + { + ID: 1, + Platform: PlatformOpenAI, + Credentials: map[string]any{"model_mapping": map[string]any{"stale-model": "upstream-model"}}, + Extra: map[string]any{"openai_passthrough": true}, + }, + }, + want: nil, + }, + { + name: "passthrough wins over ordinary account mapping", + accounts: []Account{ + { + ID: 2, + Platform: PlatformOpenAI, + Credentials: map[string]any{"model_mapping": map[string]any{"configured-model": "configured-upstream"}}, + }, + { + ID: 3, + Platform: PlatformOpenAI, + Credentials: map[string]any{"model_mapping": map[string]any{"stale-model": "upstream-model"}}, + Extra: map[string]any{"openai_passthrough": true}, + }, + }, + want: nil, + }, + { + name: "ordinary accounts preserve mapped whitelist", + accounts: []Account{ + { + ID: 4, + Platform: PlatformOpenAI, + Credentials: map[string]any{"model_mapping": map[string]any{"configured-model": "configured-upstream"}}, + }, + }, + want: []string{"configured-model"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repo := &modelsListAccountRepoStub{byGroup: map[int64][]Account{groupID: tt.accounts}} + svc := &GatewayService{ + accountRepo: repo, + modelsListCache: gocache.New(time.Minute, time.Minute), + modelsListCacheTTL: time.Minute, + } + + require.Equal(t, tt.want, svc.GetAvailableModels(context.Background(), &groupID, PlatformOpenAI)) + }) + } +} + +func TestGetAvailableModels_GlobalListPreservesMappedModelsWithOpenAIPassthrough(t *testing.T) { + groupID := int64(11) + repo := &modelsListAccountRepoStub{ + byGroup: map[int64][]Account{ + groupID: { + { + ID: 1, + Platform: PlatformOpenAI, + Extra: map[string]any{"openai_passthrough": true}, + }, + { + ID: 2, + Platform: PlatformAnthropic, + Credentials: map[string]any{"model_mapping": map[string]any{"claude-mapped": "claude-upstream"}}, + }, + }, + }, + } + svc := &GatewayService{ + accountRepo: repo, + modelsListCache: gocache.New(time.Minute, time.Minute), + modelsListCacheTTL: time.Minute, + } + + require.Equal(t, []string{"claude-mapped"}, svc.GetAvailableModels(context.Background(), &groupID, "")) +} + func TestGatewayHotpathHelpers_CacheTTLAndStickyContext(t *testing.T) { t.Run("resolve_user_group_rate_cache_ttl", func(t *testing.T) { require.Equal(t, defaultUserGroupRateCacheTTL, resolveUserGroupRateCacheTTL(nil)) diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 0f3415fab5..7e42a74cbd 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -1379,6 +1379,17 @@ func (s *GatewayService) GetAvailableModels(ctx context.Context, groupID *int64, hasAnyMapping := false for _, acc := range accounts { + // Passthrough routing accepts models independently of model_mapping. A stale + // mapping on any eligible passthrough account therefore cannot define the + // public whitelist; return nil so the handler uses its default model set. + if platform == PlatformOpenAI && acc.IsOpenAIPassthroughEnabled() { + if s.modelsListCache != nil { + s.modelsListCache.Set(cacheKey, []string(nil), s.modelsListCacheTTL) + modelsListCacheStoreTotal.Add(1) + } + return nil + } + mapping := acc.GetModelMapping() if len(mapping) > 0 { hasAnyMapping = true