diff --git a/internal/registry/model_registry.go b/internal/registry/model_registry.go index edb1f124d9..36d1118af4 100644 --- a/internal/registry/model_registry.go +++ b/internal/registry/model_registry.go @@ -925,34 +925,37 @@ func (r *ModelRegistry) GetModelProviders(modelID string) []string { } type providerCount struct { - name string - count int + name string + nameLower string + count int } providers := make([]providerCount, 0, len(registration.Providers)) - // suspendedByProvider := make(map[string]int) - // if registration.SuspendedClients != nil { - // for clientID := range registration.SuspendedClients { - // if provider, ok := r.clientProviders[clientID]; ok && provider != "" { - // suspendedByProvider[provider]++ - // } - // } - // } for name, count := range registration.Providers { if count <= 0 { continue } - // adjusted := count - suspendedByProvider[name] - // if adjusted <= 0 { - // continue - // } - // providers = append(providers, providerCount{name: name, count: adjusted}) - providers = append(providers, providerCount{name: name, count: count}) + providers = append(providers, providerCount{ + name: name, + nameLower: strings.ToLower(name), + count: count, + }) } if len(providers) == 0 { return nil } + // Pre-calculate lowercase model ID for sorting (avoids O(N log N) repeated calls). + modelLower := strings.ToLower(modelID) + sort.Slice(providers, func(i, j int) bool { + // Native provider priority: provider name matching model prefix comes first. + // e.g., "claude-haiku-*" prefers "claude" provider over "antigravity". + iMatch := strings.HasPrefix(modelLower, providers[i].nameLower) + jMatch := strings.HasPrefix(modelLower, providers[j].nameLower) + if iMatch != jMatch { + return iMatch + } + // Fallback to original logic: count desc, then name asc. if providers[i].count == providers[j].count { return providers[i].name < providers[j].name } diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index 6662f9b9ec..fe03cf4755 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -1561,15 +1561,21 @@ func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cli } func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) { - providerSet := make(map[string]struct{}, len(providers)) + // Normalize providers while preserving order for priority-based selection. + normalizedProviders := make([]string, 0, len(providers)) + providerSeen := make(map[string]struct{}, len(providers)) for _, provider := range providers { p := strings.TrimSpace(strings.ToLower(provider)) if p == "" { continue } - providerSet[p] = struct{}{} + if _, seen := providerSeen[p]; seen { + continue + } + providerSeen[p] = struct{}{} + normalizedProviders = append(normalizedProviders, p) } - if len(providerSet) == 0 { + if len(normalizedProviders) == 0 { return nil, nil, "", &Error{Code: "provider_not_found", Message: "no provider supplied"} } @@ -1584,27 +1590,27 @@ func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model s } } registryRef := registry.GetGlobalRegistry() - for _, candidate := range m.auths { - if candidate == nil || candidate.Disabled { - continue - } - providerKey := strings.TrimSpace(strings.ToLower(candidate.Provider)) - if providerKey == "" { - continue - } - if _, ok := providerSet[providerKey]; !ok { - continue - } - if _, used := tried[candidate.ID]; used { - continue - } + // Iterate by provider order to preserve priority (e.g., native provider first). + for _, providerKey := range normalizedProviders { if _, ok := m.executors[providerKey]; !ok { continue } - if modelKey != "" && registryRef != nil && !registryRef.ClientSupportsModel(candidate.ID, modelKey) { - continue + for _, candidate := range m.auths { + if candidate == nil || candidate.Disabled { + continue + } + candidateProvider := strings.TrimSpace(strings.ToLower(candidate.Provider)) + if candidateProvider != providerKey { + continue + } + if _, used := tried[candidate.ID]; used { + continue + } + if modelKey != "" && registryRef != nil && !registryRef.ClientSupportsModel(candidate.ID, modelKey) { + continue + } + candidates = append(candidates, candidate) } - candidates = append(candidates, candidate) } if len(candidates) == 0 { m.mu.RUnlock()