From f416175fcd29d0c0dbfb84ab97a21f285d86f175 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Thu, 3 Sep 2026 02:58:14 +0800 Subject: [PATCH] fix(home): map user_credits_insufficient to 402 and user_period_limit_exceeded to 429 - Map `user_credits_insufficient` to `402 Payment Required` in `decodeHomeDispatchError`. - Map `user_period_limit_exceeded` to `429 Too Many Requests` in `decodeHomeDispatchError`. - Add unit test coverage for billing and period limit error mappings. Closes: #5170 --- sdk/cliproxy/auth/home_concurrency.go | 4 ++++ sdk/cliproxy/auth/home_concurrency_test.go | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) 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) -- 2.51.2