diff --git a/apps/gateway/internal/adapters/metering/client.go b/apps/gateway/internal/adapters/metering/client.go index 283a270..f95852e 100644 --- a/apps/gateway/internal/adapters/metering/client.go +++ b/apps/gateway/internal/adapters/metering/client.go @@ -295,6 +295,9 @@ func userFacingMeteringError(method, path string, status int, envelope errorResp if envelope.Error.Code == domain.ErrCodeInsufficientBalance { wantStatus = http.StatusPaymentRequired wantType = domain.ErrTypePermission + } else if envelope.Error.Code == domain.ErrCodeDailyTokenLimit { + wantStatus = http.StatusTooManyRequests + wantType = domain.ErrTypeRateLimit } } diff --git a/apps/gateway/internal/adapters/metering/client_test.go b/apps/gateway/internal/adapters/metering/client_test.go index cabf6ca..c7c30fa 100644 --- a/apps/gateway/internal/adapters/metering/client_test.go +++ b/apps/gateway/internal/adapters/metering/client_test.go @@ -263,3 +263,22 @@ func assertInternalMeteringError(t *testing.T, err error) { t.Fatalf("metadata = %#v, want metering dependency", gatewayErr.Metadata) } } + +func TestClientReserve_MapsDailyTokenLimitWithoutRetry(t *testing.T) { + var attempts atomic.Int32 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attempts.Add(1) + w.WriteHeader(http.StatusTooManyRequests) + _, _ = w.Write([]byte(`{"error":{"type":"rate_limit_error","code":"daily_token_limit_exceeded","message":"Daily token quota reached"}}`)) + })) + defer server.Close() + client := NewClient(server.URL, "token", time.Second) + _, err := client.Reserve(context.Background(), testAuth(), domain.EndpointChatCompletions, domain.GenerateRequest{PublicModelID: "model"}, testModel(), "gw-limit") + var gatewayErr *domain.GatewayError + if !errors.As(err, &gatewayErr) || gatewayErr.HTTPStatus != 429 || gatewayErr.Type != domain.ErrTypeRateLimit || gatewayErr.Code != domain.ErrCodeDailyTokenLimit { + t.Fatalf("got %#v, want daily quota 429", err) + } + if attempts.Load() != 1 { + t.Fatalf("quota error retried %d times", attempts.Load()) + } +} diff --git a/pkg/domain/error.go b/pkg/domain/error.go index 34a1a2d..dbea434 100644 --- a/pkg/domain/error.go +++ b/pkg/domain/error.go @@ -11,6 +11,7 @@ const ( ErrTypePermission = "permission_error" ErrTypeProvider = "provider_error" ErrTypeInternal = "internal_error" + ErrTypeRateLimit = "rate_limit_error" ) // Error codes as defined in the spec. @@ -33,6 +34,7 @@ const ( ErrCodeInvalidField = "invalid_field" ErrCodeUnknownField = "unknown_field" ErrCodeInsufficientBalance = "insufficient_balance" + ErrCodeDailyTokenLimit = "daily_token_limit_exceeded" ErrCodeClientCanceled = "client_canceled" ErrCodeInternalError = "internal_error" )