fix(auth): break credits cold-start deadlock by keeping unknown-hint auths as fallback candidates
Replace antigravityCreditsAvailableForModel with inline known/unknown split. Auths whose credit hints are not yet populated are kept as lower-priority candidates instead of being rejected, breaking the chicken-and-egg deadlock at cold start.
This commit is contained in:
@@ -2910,7 +2910,8 @@ func (m *Manager) findAllAntigravityCreditsCandidateAuths(routeModel string, opt
|
|||||||
pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata)
|
pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata)
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
var candidates []creditsCandidateEntry
|
var known []creditsCandidateEntry
|
||||||
|
var unknown []creditsCandidateEntry
|
||||||
for _, auth := range m.auths {
|
for _, auth := range m.auths {
|
||||||
if auth == nil || auth.Disabled || auth.Status == StatusDisabled {
|
if auth == nil || auth.Disabled || auth.Status == StatusDisabled {
|
||||||
continue
|
continue
|
||||||
@@ -2918,7 +2919,10 @@ func (m *Manager) findAllAntigravityCreditsCandidateAuths(routeModel string, opt
|
|||||||
if pinnedAuthID != "" && auth.ID != pinnedAuthID {
|
if pinnedAuthID != "" && auth.ID != pinnedAuthID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !antigravityCreditsAvailableForModel(auth, routeModel) {
|
if !strings.EqualFold(strings.TrimSpace(auth.Provider), "antigravity") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(strings.ToLower(strings.TrimSpace(routeModel)), "claude") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
providerKey := strings.TrimSpace(strings.ToLower(auth.Provider))
|
providerKey := strings.TrimSpace(strings.ToLower(auth.Provider))
|
||||||
@@ -2926,16 +2930,32 @@ func (m *Manager) findAllAntigravityCreditsCandidateAuths(routeModel string, opt
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
candidates = append(candidates, creditsCandidateEntry{
|
|
||||||
|
hint, okHint := GetAntigravityCreditsHint(auth.ID)
|
||||||
|
if okHint && hint.Known {
|
||||||
|
if !hint.Available {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known = append(known, creditsCandidateEntry{
|
||||||
|
auth: auth.Clone(),
|
||||||
|
executor: executor,
|
||||||
|
provider: providerKey,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unknown = append(unknown, creditsCandidateEntry{
|
||||||
auth: auth.Clone(),
|
auth: auth.Clone(),
|
||||||
executor: executor,
|
executor: executor,
|
||||||
provider: providerKey,
|
provider: providerKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Slice(candidates, func(i, j int) bool {
|
sort.Slice(known, func(i, j int) bool {
|
||||||
return candidates[i].auth.ID < candidates[j].auth.ID
|
return known[i].auth.ID < known[j].auth.ID
|
||||||
})
|
})
|
||||||
return candidates
|
sort.Slice(unknown, func(i, j int) bool {
|
||||||
|
return unknown[i].auth.ID < unknown[j].auth.ID
|
||||||
|
})
|
||||||
|
return append(known, unknown...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type creditsCandidateEntry struct {
|
type creditsCandidateEntry struct {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindAllAntigravityCreditsCandidateAuths_PrefersKnownCreditsThenUnknown(t *testing.T) {
|
||||||
|
m := &Manager{
|
||||||
|
auths: map[string]*Auth{
|
||||||
|
"zz-credits": {ID: "zz-credits", Provider: "antigravity"},
|
||||||
|
"aa-unknown": {ID: "aa-unknown", Provider: "antigravity"},
|
||||||
|
"mm-no": {ID: "mm-no", Provider: "antigravity"},
|
||||||
|
},
|
||||||
|
executors: map[string]ProviderExecutor{
|
||||||
|
"antigravity": schedulerTestExecutor{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAntigravityCreditsHint("zz-credits", AntigravityCreditsHint{
|
||||||
|
Known: true,
|
||||||
|
Available: true,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
SetAntigravityCreditsHint("mm-no", AntigravityCreditsHint{
|
||||||
|
Known: true,
|
||||||
|
Available: false,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
opts := cliproxyexecutor.Options{}
|
||||||
|
|
||||||
|
candidates := m.findAllAntigravityCreditsCandidateAuths("claude-sonnet-4-6", opts)
|
||||||
|
if len(candidates) != 2 {
|
||||||
|
t.Fatalf("candidates len = %d, want 2", len(candidates))
|
||||||
|
}
|
||||||
|
if candidates[0].auth.ID != "zz-credits" {
|
||||||
|
t.Fatalf("candidates[0].auth.ID = %q, want %q", candidates[0].auth.ID, "zz-credits")
|
||||||
|
}
|
||||||
|
if candidates[1].auth.ID != "aa-unknown" {
|
||||||
|
t.Fatalf("candidates[1].auth.ID = %q, want %q", candidates[1].auth.ID, "aa-unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
nonClaude := m.findAllAntigravityCreditsCandidateAuths("gemini-3-flash", opts)
|
||||||
|
if len(nonClaude) != 0 {
|
||||||
|
t.Fatalf("nonClaude len = %d, want 0", len(nonClaude))
|
||||||
|
}
|
||||||
|
|
||||||
|
pinnedOpts := cliproxyexecutor.Options{
|
||||||
|
Metadata: map[string]any{cliproxyexecutor.PinnedAuthMetadataKey: "aa-unknown"},
|
||||||
|
}
|
||||||
|
pinned := m.findAllAntigravityCreditsCandidateAuths("claude-sonnet-4-6", pinnedOpts)
|
||||||
|
if len(pinned) != 1 {
|
||||||
|
t.Fatalf("pinned len = %d, want 1", len(pinned))
|
||||||
|
}
|
||||||
|
if pinned[0].auth.ID != "aa-unknown" {
|
||||||
|
t.Fatalf("pinned[0].auth.ID = %q, want %q", pinned[0].auth.ID, "aa-unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user