@@ -190,6 +190,93 @@ func ptrString(v string) *string {
190190 return & v
191191}
192192
193+ func (s * Server ) resolveAPIAccount (ctx context.Context , selector string ) (store.Account , error ) {
194+ account , _ , err := s .svc .ResolveForRequest (ctx , selector )
195+ if err != nil {
196+ return store.Account {}, err
197+ }
198+
199+ usage , usageErr := s .loadOrRefreshUsage (ctx , account .ID )
200+ if usageErr != nil {
201+ // If usage check fails, keep current behavior and proceed with active account.
202+ return account , nil
203+ }
204+ if usageAvailable (usage ) {
205+ return account , nil
206+ }
207+
208+ // Explicit selector should stay strict and not auto-switch.
209+ if strings .TrimSpace (selector ) != "" {
210+ return store.Account {}, fmt .Errorf ("target account quota exhausted" )
211+ }
212+
213+ best , ok := s .findBestUsageAccount (ctx , account .ID )
214+ if ! ok {
215+ return store.Account {}, fmt .Errorf ("all API accounts are exhausted" )
216+ }
217+ switched , err := s .svc .UseAccountAPI (ctx , best .ID )
218+ if err != nil {
219+ return store.Account {}, err
220+ }
221+ return switched , nil
222+ }
223+
224+ func (s * Server ) loadOrRefreshUsage (ctx context.Context , accountID string ) (store.UsageSnapshot , error ) {
225+ usage , err := s .svc .Store .GetUsage (ctx , accountID )
226+ if err == nil {
227+ return usage , nil
228+ }
229+ return s .svc .RefreshUsage (ctx , accountID )
230+ }
231+
232+ func usageAvailable (u store.UsageSnapshot ) bool {
233+ return u .HourlyPct > 0 && u .WeeklyPct > 0
234+ }
235+
236+ func usageScore (u store.UsageSnapshot ) int {
237+ if u .HourlyPct < u .WeeklyPct {
238+ return u .HourlyPct
239+ }
240+ return u .WeeklyPct
241+ }
242+
243+ func (s * Server ) findBestUsageAccount (ctx context.Context , skipID string ) (store.Account , bool ) {
244+ accounts , err := s .svc .ListAccounts (ctx )
245+ if err != nil || len (accounts ) == 0 {
246+ return store.Account {}, false
247+ }
248+
249+ usageMap , _ := s .svc .Store .ListUsageSnapshots (ctx )
250+ var best store.Account
251+ bestScore := - 1
252+ found := false
253+
254+ for _ , a := range accounts {
255+ if strings .TrimSpace (a .ID ) == "" || a .ID == skipID {
256+ continue
257+ }
258+ u , ok := usageMap [a .ID ]
259+ if ! ok || strings .TrimSpace (u .LastError ) != "" {
260+ refreshed , err := s .svc .RefreshUsage (ctx , a .ID )
261+ if err != nil {
262+ continue
263+ }
264+ u = refreshed
265+ }
266+ if ! usageAvailable (u ) {
267+ continue
268+ }
269+ score := usageScore (u )
270+ if score > bestScore {
271+ best = a
272+ bestScore = score
273+ found = true
274+ }
275+ }
276+
277+ return best , found
278+ }
279+
193280type accessLogRecorder struct {
194281 http.ResponseWriter
195282 status int
@@ -932,25 +1019,20 @@ func (s *Server) handleCodeReview(w http.ResponseWriter, r *http.Request) {
9321019 return
9331020 }
9341021
935- account , _ , err := s .svc . ResolveForRequest (r .Context (), "" )
1022+ account , err := s .resolveAPIAccount (r .Context (), "" )
9361023 if err != nil {
937- respondErr (w , 404 , "account_not_found" , err .Error ())
938- return
939- }
940- setResolvedAccountHeaders (w , account )
941- usage , usageErr := s .svc .Store .GetUsage (r .Context (), account .ID )
942- if usageErr != nil {
943- if snap , err := s .svc .RefreshUsage (r .Context (), account .ID ); err == nil {
944- usage = snap
945- usageErr = nil
946- }
947- }
948- if usageErr == nil {
949- if usage .HourlyPct <= 0 || usage .WeeklyPct <= 0 {
1024+ msg := strings .ToLower (strings .TrimSpace (err .Error ()))
1025+ switch {
1026+ case strings .Contains (msg , "not found" ):
1027+ respondErr (w , 404 , "account_not_found" , err .Error ())
1028+ case strings .Contains (msg , "exhausted" ):
9501029 respondErr (w , 429 , "quota_exhausted" , "target account quota exhausted" )
951- return
1030+ default :
1031+ respondErr (w , 500 , "internal_error" , err .Error ())
9521032 }
1033+ return
9531034 }
1035+ setResolvedAccountHeaders (w , account )
9541036
9551037 status := 200
9561038 defer func () {
@@ -1055,25 +1137,20 @@ func (s *Server) handleChatCompletions(w http.ResponseWriter, r *http.Request) {
10551137 }
10561138 req .Model = s .resolveMappedModel (req .Model )
10571139 prompt := promptFromMessagesWithTools (req .Messages , req .Tools , req .ToolChoice )
1058- account , _ , err := s .svc . ResolveForRequest (r .Context (), selector )
1140+ account , err := s .resolveAPIAccount (r .Context (), selector )
10591141 if err != nil {
1060- respondErr (w , 404 , "account_not_found" , err .Error ())
1061- return
1062- }
1063- setResolvedAccountHeaders (w , account )
1064- usage , usageErr := s .svc .Store .GetUsage (r .Context (), account .ID )
1065- if usageErr != nil {
1066- if snap , err := s .svc .RefreshUsage (r .Context (), account .ID ); err == nil {
1067- usage = snap
1068- usageErr = nil
1069- }
1070- }
1071- if usageErr == nil {
1072- if usage .HourlyPct <= 0 || usage .WeeklyPct <= 0 {
1142+ msg := strings .ToLower (strings .TrimSpace (err .Error ()))
1143+ switch {
1144+ case strings .Contains (msg , "not found" ):
1145+ respondErr (w , 404 , "account_not_found" , err .Error ())
1146+ case strings .Contains (msg , "exhausted" ):
10731147 respondErr (w , 429 , "quota_exhausted" , "target account quota exhausted" )
1074- return
1148+ default :
1149+ respondErr (w , 500 , "internal_error" , err .Error ())
10751150 }
1151+ return
10761152 }
1153+ setResolvedAccountHeaders (w , account )
10771154 status := 200
10781155 defer func () {
10791156 _ = s .svc .Store .InsertAudit (r .Context (), store.AuditRecord {
@@ -1188,25 +1265,20 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) {
11881265 respondErr (w , 400 , "bad_request" , "input is required" )
11891266 return
11901267 }
1191- account , _ , err := s .svc . ResolveForRequest (r .Context (), selector )
1268+ account , err := s .resolveAPIAccount (r .Context (), selector )
11921269 if err != nil {
1193- respondErr (w , 404 , "account_not_found" , err .Error ())
1194- return
1195- }
1196- setResolvedAccountHeaders (w , account )
1197- usage , usageErr := s .svc .Store .GetUsage (r .Context (), account .ID )
1198- if usageErr != nil {
1199- if snap , err := s .svc .RefreshUsage (r .Context (), account .ID ); err == nil {
1200- usage = snap
1201- usageErr = nil
1202- }
1203- }
1204- if usageErr == nil {
1205- if usage .HourlyPct <= 0 || usage .WeeklyPct <= 0 {
1270+ msg := strings .ToLower (strings .TrimSpace (err .Error ()))
1271+ switch {
1272+ case strings .Contains (msg , "not found" ):
1273+ respondErr (w , 404 , "account_not_found" , err .Error ())
1274+ case strings .Contains (msg , "exhausted" ):
12061275 respondErr (w , 429 , "quota_exhausted" , "target account quota exhausted" )
1207- return
1276+ default :
1277+ respondErr (w , 500 , "internal_error" , err .Error ())
12081278 }
1279+ return
12091280 }
1281+ setResolvedAccountHeaders (w , account )
12101282 status := 200
12111283 defer func () {
12121284 _ = s .svc .Store .InsertAudit (r .Context (), store.AuditRecord {
@@ -1351,25 +1423,20 @@ func (s *Server) handleClaudeMessages(w http.ResponseWriter, r *http.Request) {
13511423 respondErr (w , 400 , "bad_request" , "messages are required" )
13521424 return
13531425 }
1354- account , _ , err := s .svc . ResolveForRequest (r .Context (), selector )
1426+ account , err := s .resolveAPIAccount (r .Context (), selector )
13551427 if err != nil {
1356- respondErr (w , 404 , "account_not_found" , err .Error ())
1357- return
1358- }
1359- setResolvedAccountHeaders (w , account )
1360- usage , usageErr := s .svc .Store .GetUsage (r .Context (), account .ID )
1361- if usageErr != nil {
1362- if snap , err := s .svc .RefreshUsage (r .Context (), account .ID ); err == nil {
1363- usage = snap
1364- usageErr = nil
1365- }
1366- }
1367- if usageErr == nil {
1368- if usage .HourlyPct <= 0 || usage .WeeklyPct <= 0 {
1428+ msg := strings .ToLower (strings .TrimSpace (err .Error ()))
1429+ switch {
1430+ case strings .Contains (msg , "not found" ):
1431+ respondErr (w , 404 , "account_not_found" , err .Error ())
1432+ case strings .Contains (msg , "exhausted" ):
13691433 respondErr (w , 429 , "quota_exhausted" , "target account quota exhausted" )
1370- return
1434+ default :
1435+ respondErr (w , 500 , "internal_error" , err .Error ())
13711436 }
1437+ return
13721438 }
1439+ setResolvedAccountHeaders (w , account )
13731440 status := 200
13741441 defer func () {
13751442 _ = s .svc .Store .InsertAudit (r .Context (), store.AuditRecord {
0 commit comments