Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions internal/registry/model_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
46 changes: 26 additions & 20 deletions sdk/cliproxy/auth/conductor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
}

Expand All @@ -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()
Expand Down