diff --git a/sdk/cliproxy/auth/home_concurrency.go b/sdk/cliproxy/auth/home_concurrency.go index c9e17fe9..1fd48674 100644 --- a/sdk/cliproxy/auth/home_concurrency.go +++ b/sdk/cliproxy/auth/home_concurrency.go @@ -253,6 +253,10 @@ func decodeHomeDispatchError(raw []byte) error { case "credential_concurrency_exceeded", "credential_model_concurrency_exceeded": result.HTTPStatus = http.StatusTooManyRequests return newHomeConcurrencyBusyError(result, time.Duration(detail.RetryAfterMS)*time.Millisecond) + case "user_credits_insufficient": + result.HTTPStatus = http.StatusPaymentRequired + case "user_period_limit_exceeded": + result.HTTPStatus = http.StatusTooManyRequests case "auth_not_found", "auth_unavailable", "refresh_temporarily_unavailable", "home_unavailable", "concurrency_protocol_required", "concurrency_tracker_unavailable", "concurrency_node_unavailable": result.HTTPStatus = http.StatusServiceUnavailable diff --git a/sdk/cliproxy/auth/home_concurrency_test.go b/sdk/cliproxy/auth/home_concurrency_test.go index 7408fe6b..da26ab18 100644 --- a/sdk/cliproxy/auth/home_concurrency_test.go +++ b/sdk/cliproxy/auth/home_concurrency_test.go @@ -396,6 +396,25 @@ func TestHomeNoCandidateErrorsMapToServiceUnavailable(t *testing.T) { } } +func TestHomeUserBillingAndPeriodLimitErrors(t *testing.T) { + tests := []struct { + code string + wantStatus int + }{ + {code: "user_credits_insufficient", wantStatus: http.StatusPaymentRequired}, + {code: "user_period_limit_exceeded", wantStatus: http.StatusTooManyRequests}, + } + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + errDispatch := decodeHomeDispatchError([]byte(fmt.Sprintf(`{"error":{"type":%q,"message":"limit hit"}}`, tt.code))) + var authErr *Error + if !errors.As(errDispatch, &authErr) || authErr.Code != tt.code || authErr.HTTPStatus != tt.wantStatus { + t.Fatalf("decodeHomeDispatchError(%s) = %#v, want %d", tt.code, errDispatch, tt.wantStatus) + } + }) + } +} + func TestHomeConcurrencyTupleAuthMismatchEndsScope(t *testing.T) { dispatcher := &fixtureHomeDispatcher{payload: []byte(`{"concurrency":{"accounted":true,"credential_id":"cred-1","model":"gpt"},"auth_index":"other","auth":{"id":"cred-1","provider":"codex"}}`)} manager := newHomeSelectionTestManager(t, dispatcher)