diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index fac8a1a3b3..6d0d2d3c90 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -2656,6 +2656,14 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) { // Handle OpenAI accounts if account.IsOpenAI() { + // Prefer the shared, account-keyed upstream catalog. If discovery fails, + // retain the legacy local catalog below so the test dialog remains usable. + if h.accountTestService != nil { + if models, fetchErr := h.accountTestService.FetchOpenAIAccountModels(c.Request.Context(), account); fetchErr == nil { + response.Success(c, models) + return + } + } // OpenAI 自动透传会绕过常规模型改写,测试/模型列表也应回落到默认模型集。 if account.IsOpenAIPassthroughEnabled() { response.Success(c, openai.DefaultModels) diff --git a/backend/internal/service/account_test_service.go b/backend/internal/service/account_test_service.go index 4e304eca10..ffae4ce8b7 100644 --- a/backend/internal/service/account_test_service.go +++ b/backend/internal/service/account_test_service.go @@ -151,6 +151,7 @@ type AccountTestService struct { modelMetadataRegistry map[string]modelsDevProvider modelMetadataRegistryAt time.Time pluginManager *PluginManager + openaiGatewayService *OpenAIGatewayService agentIdentityTaskMu sync.Mutex agentIdentityWS agentIdentityWSConnectionInvalidator // grokWSDialer is optional; realtime account tests use the default OpenAI-style @@ -170,6 +171,30 @@ func (s *AccountTestService) SetPluginManager(pluginManager *PluginManager) { } } +func (s *AccountTestService) SetOpenAIGatewayService(gateway *OpenAIGatewayService) { + if s != nil { + s.openaiGatewayService = gateway + } +} + +// FetchOpenAIAccountModels uses the shared cached discovery path for the test picker. +func (s *AccountTestService) FetchOpenAIAccountModels(ctx context.Context, account *Account) ([]openai.Model, error) { + if s == nil || s.openaiGatewayService == nil { + return nil, errors.New("OpenAI model discovery service is unavailable") + } + response, err := s.openaiGatewayService.FetchOpenAIModelsList(ctx, account) + if err != nil { + return nil, err + } + var payload struct { + Data []openai.Model `json:"data"` + } + if err := json.Unmarshal(response.Body, &payload); err != nil { + return nil, fmt.Errorf("decode OpenAI account models: %w", err) + } + return payload.Data, nil +} + // NewAccountTestService creates a new AccountTestService func NewAccountTestService( accountRepo AccountRepository, diff --git a/backend/internal/service/openai_models_list.go b/backend/internal/service/openai_models_list.go index 18ff11aa53..58ce3d0777 100644 --- a/backend/internal/service/openai_models_list.go +++ b/backend/internal/service/openai_models_list.go @@ -24,7 +24,11 @@ func (s *OpenAIGatewayService) FetchOpenAIModelsList(ctx context.Context, accoun return nil, fmt.Errorf("resolve model list credentials: %w", err) } if credentialAccount.IsOpenAIOAuth() { - response, err := s.FetchCodexModelsManifest(ctx, account, CodexCanonicalClientVersion(), "") + clientVersion := CodexCanonicalClientVersion() + if s.settingService != nil { + clientVersion = s.settingService.GetOpenAICodexClientVersion(ctx) + } + response, err := s.FetchCodexModelsManifest(ctx, account, clientVersion, "") if err != nil { return nil, err } diff --git a/backend/internal/service/wire.go b/backend/internal/service/wire.go index 6e708de1d9..e7ff2dc4d3 100644 --- a/backend/internal/service/wire.go +++ b/backend/internal/service/wire.go @@ -267,6 +267,7 @@ func ProvideAccountTestService( tlsFPProfileService, ) service.agentIdentityWS = openAIGatewayService + service.SetOpenAIGatewayService(openAIGatewayService) service.SetSettingService(settingService) service.SetPluginManager(pluginManager) return service