refactor: introduce AgentTriggerReason type union as SSOT for error reason strings
Replace scattered reason string literals with a centralized type hierarchy: - AgentTriggerReason: all possible trigger reasons - ClaudeRotationReason, CodexRotationReason, NoFallbackCooldownReason: derived subtypes for specific contexts - AgentErrorClassification, FallbackTriggerResult, CodexRotationTriggerResult: discriminated unions for compile-time narrowing Remove dead export isUsageExhausted() (superseded by isPrimaryNoFallbackCooldownActive). Replace NO_FALLBACK_COOLDOWN_REASONS Set with isNoFallbackCooldownReason() type guard. Add tests for agent-error-detection and provider-retry (423 total passing). Known limitation: 6 `as CodexRotationReason` casts in Codex rotation paths where streamed trigger reason is AgentTriggerReason but runtime value is always CodexRotationReason-compatible. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
57
src/agent-error-detection.test.ts
Normal file
57
src/agent-error-detection.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
classifyClaudeAuthError,
|
||||
isClaudeOrgAccessDeniedMessage,
|
||||
isNoFallbackCooldownReason,
|
||||
shouldRotateClaudeToken,
|
||||
} from './agent-error-detection.js';
|
||||
|
||||
describe('agent-error-detection', () => {
|
||||
it('detects Claude org access denied banners', () => {
|
||||
expect(
|
||||
isClaudeOrgAccessDeniedMessage(
|
||||
'Your organization does not have access to Claude. Please login again or contact your administrator.',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies org access denied banners as org-access-denied', () => {
|
||||
expect(
|
||||
classifyClaudeAuthError(
|
||||
'Your organization does not have access to Claude. Please login again or contact your administrator.',
|
||||
),
|
||||
).toEqual({
|
||||
category: 'org-access-denied',
|
||||
reason: 'org-access-denied',
|
||||
});
|
||||
});
|
||||
|
||||
it('classifies terminated 403 auth failures as org-access-denied', () => {
|
||||
expect(
|
||||
classifyClaudeAuthError(
|
||||
'Failed to authenticate. API Error: 403 terminated',
|
||||
),
|
||||
).toEqual({
|
||||
category: 'org-access-denied',
|
||||
reason: 'org-access-denied',
|
||||
});
|
||||
});
|
||||
|
||||
it('marks only Claude quota/auth reasons as Claude rotation reasons', () => {
|
||||
expect(shouldRotateClaudeToken('429')).toBe(true);
|
||||
expect(shouldRotateClaudeToken('usage-exhausted')).toBe(true);
|
||||
expect(shouldRotateClaudeToken('auth-expired')).toBe(true);
|
||||
expect(shouldRotateClaudeToken('org-access-denied')).toBe(true);
|
||||
expect(shouldRotateClaudeToken('overloaded')).toBe(false);
|
||||
expect(shouldRotateClaudeToken('success-null-result')).toBe(false);
|
||||
});
|
||||
|
||||
it('marks only no-fallback cooldown reasons as skip-worthy', () => {
|
||||
expect(isNoFallbackCooldownReason('usage-exhausted')).toBe(true);
|
||||
expect(isNoFallbackCooldownReason('auth-expired')).toBe(true);
|
||||
expect(isNoFallbackCooldownReason('org-access-denied')).toBe(true);
|
||||
expect(isNoFallbackCooldownReason('429')).toBe(false);
|
||||
expect(isNoFallbackCooldownReason('success-null-result')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user