diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 5909dffc..e29dc29f 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -26,6 +26,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/qwen" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" @@ -266,6 +267,54 @@ func (h *Handler) ListAuthFiles(c *gin.Context) { c.JSON(200, gin.H{"files": files}) } +// GetAuthFileModels returns the models supported by a specific auth file +func (h *Handler) GetAuthFileModels(c *gin.Context) { + name := c.Query("name") + if name == "" { + c.JSON(400, gin.H{"error": "name is required"}) + return + } + + // Try to find auth ID via authManager + var authID string + if h.authManager != nil { + auths := h.authManager.List() + for _, auth := range auths { + if auth.FileName == name || auth.ID == name { + authID = auth.ID + break + } + } + } + + if authID == "" { + authID = name // fallback to filename as ID + } + + // Get models from registry + reg := registry.GetGlobalRegistry() + models := reg.GetModelsForClient(authID) + + result := make([]gin.H, 0, len(models)) + for _, m := range models { + entry := gin.H{ + "id": m.ID, + } + if m.DisplayName != "" { + entry["display_name"] = m.DisplayName + } + if m.Type != "" { + entry["type"] = m.Type + } + if m.OwnedBy != "" { + entry["owned_by"] = m.OwnedBy + } + result = append(result, entry) + } + + c.JSON(200, gin.H{"models": result}) +} + // List auth files from disk when the auth manager is unavailable. func (h *Handler) listAuthFilesFromDisk(c *gin.Context) { entries, err := os.ReadDir(h.cfg.AuthDir) diff --git a/internal/api/server.go b/internal/api/server.go index 79dcf12a..ae8abccc 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -568,6 +568,7 @@ func (s *Server) registerManagementRoutes() { mgmt.DELETE("/oauth-excluded-models", s.mgmt.DeleteOAuthExcludedModels) mgmt.GET("/auth-files", s.mgmt.ListAuthFiles) + mgmt.GET("/auth-files/models", s.mgmt.GetAuthFileModels) mgmt.GET("/auth-files/download", s.mgmt.DownloadAuthFile) mgmt.POST("/auth-files", s.mgmt.UploadAuthFile) mgmt.DELETE("/auth-files", s.mgmt.DeleteAuthFile) diff --git a/internal/registry/model_registry.go b/internal/registry/model_registry.go index f3517bde..888db60c 100644 --- a/internal/registry/model_registry.go +++ b/internal/registry/model_registry.go @@ -871,3 +871,32 @@ func (r *ModelRegistry) GetFirstAvailableModel(handlerType string) (string, erro return "", fmt.Errorf("no available clients for any model in handler type: %s", handlerType) } + +// GetModelsForClient returns the models registered for a specific client. +// Parameters: +// - clientID: The client identifier (typically auth file name or auth ID) +// +// Returns: +// - []*ModelInfo: List of models registered for this client, nil if client not found +func (r *ModelRegistry) GetModelsForClient(clientID string) []*ModelInfo { + r.mutex.RLock() + defer r.mutex.RUnlock() + + modelIDs, exists := r.clientModels[clientID] + if !exists || len(modelIDs) == 0 { + return nil + } + + seen := make(map[string]struct{}) + result := make([]*ModelInfo, 0, len(modelIDs)) + for _, modelID := range modelIDs { + if _, dup := seen[modelID]; dup { + continue + } + seen[modelID] = struct{}{} + if reg, ok := r.models[modelID]; ok && reg.Info != nil { + result = append(result, reg.Info) + } + } + return result +}