From 4e4ac9aae4631059291c2717b1253a3a1993976e Mon Sep 17 00:00:00 2001 From: jiang1997 Date: Fri, 4 Sep 2026 17:57:54 +0800 Subject: [PATCH] fix(runtime): preserve billing classification in durable diagnostics Generated-by: Codex --- .../src/__tests__/provider-error-classification.test.ts | 5 +++++ packages/runtime/src/provider-error-classification.ts | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/__tests__/provider-error-classification.test.ts b/packages/runtime/src/__tests__/provider-error-classification.test.ts index bf0507a18c..eb61291230 100644 --- a/packages/runtime/src/__tests__/provider-error-classification.test.ts +++ b/packages/runtime/src/__tests__/provider-error-classification.test.ts @@ -69,6 +69,7 @@ describe('Provider error classification', () => { data: { error: { code: 'insufficient_quota' } }, }); assert.equal(classifyError(quotaOn401), 'ProviderBilling'); + assert.equal(providerFailureDiagnostic(quotaOn401).errorClass, 'ProviderBilling'); const balanceOn403 = Object.assign(new Error('request failed'), { name: 'AI_APICallError', @@ -76,6 +77,7 @@ describe('Provider error classification', () => { data: { error: { code: 'insufficient_balance' } }, }); assert.equal(classifyError(balanceOn403), 'ProviderBilling'); + assert.equal(providerFailureDiagnostic(balanceOn403).errorClass, 'ProviderBilling'); // Explicit provider evidence outranks the numeric HTTP fallback: an // exhausted quota is a closed window, not a transient throttle to retry. @@ -86,6 +88,7 @@ describe('Provider error classification', () => { }); assert.equal(classifyError(quotaOn429), 'ProviderBilling'); assert.equal(providerRetryMetadata(quotaOn429).retryable, false); + assert.equal(providerFailureDiagnostic(quotaOn429).errorClass, 'ProviderBilling'); }); test('plan-window wording on a credential-shaped status projects to billing', () => { @@ -100,6 +103,7 @@ describe('Provider error classification', () => { }); assert.equal(classifyError(planWindow), 'ProviderBilling'); assert.equal(providerRetryMetadata(planWindow).retryable, false); + assert.equal(providerFailureDiagnostic(planWindow).errorClass, 'ProviderBilling'); const exhaustedCredits = Object.assign(new Error('Request failed with status code 403'), { name: 'AI_APICallError', @@ -109,6 +113,7 @@ describe('Provider error classification', () => { }), }); assert.equal(classifyError(exhaustedCredits), 'ProviderBilling'); + assert.equal(providerFailureDiagnostic(exhaustedCredits).errorClass, 'ProviderBilling'); }); test('genuine credential and permission failures stay auth on 401/403', () => { diff --git a/packages/runtime/src/provider-error-classification.ts b/packages/runtime/src/provider-error-classification.ts index aaa73bbe9b..bc1904f048 100644 --- a/packages/runtime/src/provider-error-classification.ts +++ b/packages/runtime/src/provider-error-classification.ts @@ -455,11 +455,12 @@ function durableProviderErrorClass( classified: string, httpStatus: number | undefined, ): string { - // Structured context-overflow and capacity evidence can legitimately arrive + // Structured context-overflow, capacity, and billing evidence can legitimately arrive // behind a generic 4xx/5xx proxy response and remains stronger than the wrapper code. if ( classified === 'ContextLength' || classified === 'ProviderCapacity' || + classified === 'ProviderBilling' || (classified === 'ProviderUnavailable' && isTrustedCodexEdgeRejection(facts)) ) { return classified;