History

fix antigravity credits stream fallback

sususu98 committed 6 months ago

Commit 5f5d5931 parent f1ba6152 files · +109 −9Browse files

Unified · Split

2 files
  1. sdk/cliproxy/auth/antigravity_credits_test.go +92 −0
  2. sdk/cliproxy/auth/conductor.go +17 −9
sdk/cliproxy/auth/antigravity_credits_test.go+92−0
@@ -1,10 +1,102 @@
1package auth1package auth
2 2
3import (3import (
4 "context"
5 "fmt"
6 "net/http"
4 "testing"7 "testing"
5 "time"8 "time"
9
10 internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
11 "github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
12 cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
6)13)
7 14
15type antigravityCreditsFallbackExecutor struct {
16 streamCreditsRequested []bool
17}
18
19func (e *antigravityCreditsFallbackExecutor) Identifier() string { return "antigravity" }
20
21func (e *antigravityCreditsFallbackExecutor) Execute(context.Context, *Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
22 return cliproxyexecutor.Response{}, &Error{HTTPStatus: http.StatusNotImplemented, Message: "Execute not implemented"}
23}
24
25func (e *antigravityCreditsFallbackExecutor) ExecuteStream(ctx context.Context, _ *Auth, req cliproxyexecutor.Request, _ cliproxyexecutor.Options) (*cliproxyexecutor.StreamResult, error) {
26 creditsRequested := AntigravityCreditsRequested(ctx)
27 e.streamCreditsRequested = append(e.streamCreditsRequested, creditsRequested)
28 ch := make(chan cliproxyexecutor.StreamChunk, 1)
29 if !creditsRequested {
30 ch <- cliproxyexecutor.StreamChunk{Err: &Error{HTTPStatus: http.StatusTooManyRequests, Message: "quota exhausted"}}
31 close(ch)
32 return &cliproxyexecutor.StreamResult{Headers: http.Header{"X-Initial": {req.Model}}, Chunks: ch}, nil
33 }
34 ch <- cliproxyexecutor.StreamChunk{Payload: []byte("credits fallback")}
35 close(ch)
36 return &cliproxyexecutor.StreamResult{Headers: http.Header{"X-Credits": {req.Model}}, Chunks: ch}, nil
37}
38
39func (e *antigravityCreditsFallbackExecutor) Refresh(_ context.Context, auth *Auth) (*Auth, error) {
40 return auth, nil
41}
42
43func (e *antigravityCreditsFallbackExecutor) CountTokens(context.Context, *Auth, cliproxyexecutor.Request, cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
44 return cliproxyexecutor.Response{}, &Error{HTTPStatus: http.StatusNotImplemented, Message: "CountTokens not implemented"}
45}
46
47func (e *antigravityCreditsFallbackExecutor) HttpRequest(context.Context, *Auth, *http.Request) (*http.Response, error) {
48 return nil, &Error{HTTPStatus: http.StatusNotImplemented, Message: "HttpRequest not implemented"}
49}
50
51func TestManagerExecuteStream_AntigravityCreditsFallbackAfterBootstrap429(t *testing.T) {
52 const model = "claude-opus-4-6-thinking"
53 executor := &antigravityCreditsFallbackExecutor{}
54 manager := NewManager(nil, nil, nil)
55 manager.SetConfig(&internalconfig.Config{
56 QuotaExceeded: internalconfig.QuotaExceeded{AntigravityCredits: true},
57 })
58 manager.RegisterExecutor(executor)
59 registry.GetGlobalRegistry().RegisterClient("ag-credits", "antigravity", []*registry.ModelInfo{{ID: model}})
60 t.Cleanup(func() { registry.GetGlobalRegistry().UnregisterClient("ag-credits") })
61 if _, errRegister := manager.Register(context.Background(), &Auth{ID: "ag-credits", Provider: "antigravity"}); errRegister != nil {
62 t.Fatalf("register auth: %v", errRegister)
63 }
64
65 streamResult, errExecute := manager.ExecuteStream(context.Background(), []string{"antigravity"}, cliproxyexecutor.Request{Model: model}, cliproxyexecutor.Options{})
66 if errExecute != nil {
67 t.Fatalf("execute stream: %v", errExecute)
68 }
69
70 var payload []byte
71 for chunk := range streamResult.Chunks {
72 if chunk.Err != nil {
73 t.Fatalf("unexpected stream error: %v", chunk.Err)
74 }
75 payload = append(payload, chunk.Payload...)
76 }
77 if string(payload) != "credits fallback" {
78 t.Fatalf("payload = %q, want %q", string(payload), "credits fallback")
79 }
80 if got := streamResult.Headers.Get("X-Credits"); got != model {
81 t.Fatalf("X-Credits header = %q, want routed model", got)
82 }
83 if len(executor.streamCreditsRequested) != 2 {
84 t.Fatalf("stream calls = %d, want 2", len(executor.streamCreditsRequested))
85 }
86 if executor.streamCreditsRequested[0] || !executor.streamCreditsRequested[1] {
87 t.Fatalf("credits flags = %v, want [false true]", executor.streamCreditsRequested)
88 }
89}
90
91func TestStatusCodeFromError_UnwrapsStreamBootstrap429(t *testing.T) {
92 bootstrapErr := newStreamBootstrapError(&Error{HTTPStatus: http.StatusTooManyRequests, Message: "quota exhausted"}, nil)
93 wrappedErr := fmt.Errorf("conductor stream failed: %w", bootstrapErr)
94
95 if status := statusCodeFromError(wrappedErr); status != http.StatusTooManyRequests {
96 t.Fatalf("statusCodeFromError() = %d, want %d", status, http.StatusTooManyRequests)
97 }
98}
99
8func TestIsAuthBlockedForModel_ClaudeWithCreditsStillBlockedDuringCooldown(t *testing.T) {100func TestIsAuthBlockedForModel_ClaudeWithCreditsStillBlockedDuringCooldown(t *testing.T) {
9 auth := &Auth{101 auth := &Auth{
10 ID: "ag-1",102 ID: "ag-1",
sdk/cliproxy/auth/conductor.go+17−9
@@ -1273,6 +1273,10 @@ func (m *Manager) ExecuteStream(ctx context.Context, providers []string, req cli · 1272 unmodified lines
1273 return result, nil1273 return result, nil
1274 }1274 }
1275 }1275 }
1276 var bootstrapErr *streamBootstrapError
1277 if errors.As(lastErr, &bootstrapErr) && bootstrapErr != nil {
1278 return streamErrorResult(bootstrapErr.Headers(), bootstrapErr.cause), nil
1279 }
1276 return nil, lastErr1280 return nil, lastErr
1277 }1281 }
1278 return nil, &Error{Code: "auth_not_found", Message: "no auth available"}1282 return nil, &Error{Code: "auth_not_found", Message: "no auth available"}
@@ -1446,10 +1450,6 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string · 167 unmodified lines
1446 for {1450 for {
1447 if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {1451 if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
1448 if lastErr != nil {1452 if lastErr != nil {
1449 var bootstrapErr *streamBootstrapError
1450 if errors.As(lastErr, &bootstrapErr) && bootstrapErr != nil {
1451 return streamErrorResult(bootstrapErr.Headers(), bootstrapErr.cause), nil
1452 }
1453 return nil, lastErr1453 return nil, lastErr
1454 }1454 }
1455 return nil, &Error{Code: "auth_not_found", Message: "no auth available"}1455 return nil, &Error{Code: "auth_not_found", Message: "no auth available"}
@@ -1457,10 +1457,6 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string · 1 unmodified line
1457 auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried)1457 auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried)
1458 if errPick != nil {1458 if errPick != nil {
1459 if lastErr != nil {1459 if lastErr != nil {
1460 var bootstrapErr *streamBootstrapError
1461 if errors.As(lastErr, &bootstrapErr) && bootstrapErr != nil {
1462 return streamErrorResult(bootstrapErr.Headers(), bootstrapErr.cause), nil
1463 }
1464 return nil, lastErr1460 return nil, lastErr
1465 }1461 }
1466 return nil, errPick1462 return nil, errPick
@@ -2299,6 +2295,13 @@ func cloneError(err *Error) *Error { · 832 unmodified lines
2299 }2295 }
2300}2296}
2301 2297
2298func errorString(err error) string {
2299 if err == nil {
2300 return ""
2301 }
2302 return err.Error()
2303}
2304
2302func statusCodeFromError(err error) int {2305func statusCodeFromError(err error) int {
2303 if err == nil {2306 if err == nil {
2304 return 02307 return 0
@@ -2965,6 +2968,12 @@ type creditsCandidateEntry struct { · 660 unmodified lines
2965}2968}
2966 2969
2967func shouldAttemptAntigravityCreditsFallback(m *Manager, lastErr error, providers []string) bool {2970func shouldAttemptAntigravityCreditsFallback(m *Manager, lastErr error, providers []string) bool {
2971 status := statusCodeFromError(lastErr)
2972 log.WithFields(log.Fields{
2973 "lastErr": errorString(lastErr),
2974 "status": status,
2975 "providers": providers,
2976 }).Debug("shouldAttemptAntigravityCreditsFallback")
2968 if m == nil || lastErr == nil {2977 if m == nil || lastErr == nil {
2969 return false2978 return false
2970 }2979 }
@@ -2984,7 +2993,6 @@ func shouldAttemptAntigravityCreditsFallback(m *Manager, lastErr error, provider · 13 unmodified lines
2984 if cfg == nil || !cfg.QuotaExceeded.AntigravityCredits {2993 if cfg == nil || !cfg.QuotaExceeded.AntigravityCredits {
2985 return false2994 return false
2986 }2995 }
2987 status := statusCodeFromError(lastErr)
2988 switch status {2996 switch status {
2989 case http.StatusTooManyRequests, http.StatusServiceUnavailable:2997 case http.StatusTooManyRequests, http.StatusServiceUnavailable:
2990 return true2998 return true

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t