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:
Eyejoker
2026-03-25 17:46:43 +09:00
parent b86fd1ad89
commit 94eeaa5e3e
10 changed files with 290 additions and 53 deletions

View File

@@ -35,6 +35,7 @@ import {
getCodexAccountCount,
markCodexTokenHealthy,
} from './codex-token-rotation.js';
import type { AgentTriggerReason, CodexRotationReason } from './agent-error-detection.js';
import { getTokenCount } from './token-rotation.js';
import type { RegisteredGroup } from './types.js';
@@ -124,7 +125,7 @@ export async function runAgentForGroup(
sawOutput: boolean;
sawSuccessNullResultWithoutOutput: boolean;
streamedTriggerReason?: {
reason: string;
reason: AgentTriggerReason;
retryAfterMs?: number;
};
}> => {
@@ -293,7 +294,7 @@ export async function runAgentForGroup(
};
const runFallbackAttempt = async (
reason: string,
reason: AgentTriggerReason,
retryAfterMs?: number,
): Promise<'success' | 'error'> => {
const fallbackName = getFallbackProviderName();
@@ -347,7 +348,7 @@ export async function runAgentForGroup(
};
const retryCodexWithRotation = async (
initialTrigger: { reason: string },
initialTrigger: { reason: CodexRotationReason },
rotationMessage?: string,
): Promise<'success' | 'error'> => {
let trigger = initialTrigger;
@@ -407,7 +408,9 @@ export async function runAgentForGroup(
retryAttempt.streamedTriggerReason &&
retryOutput.status !== 'error'
) {
trigger = { reason: retryAttempt.streamedTriggerReason.reason };
trigger = {
reason: retryAttempt.streamedTriggerReason.reason as CodexRotationReason,
};
lastRotationMessage =
typeof retryOutput.result === 'string'
? retryOutput.result
@@ -419,7 +422,8 @@ export async function runAgentForGroup(
const retryTrigger = retryAttempt.streamedTriggerReason
? {
shouldRotate: true,
reason: retryAttempt.streamedTriggerReason.reason,
reason:
retryAttempt.streamedTriggerReason.reason as CodexRotationReason,
}
: detectCodexRotationTrigger(retryOutput.error);
@@ -451,7 +455,7 @@ export async function runAgentForGroup(
const retryClaudeWithRotation = async (
initialTrigger: {
reason: string;
reason: AgentTriggerReason;
retryAfterMs?: number;
},
rotationMessage?: string,
@@ -667,7 +671,9 @@ export async function runAgentForGroup(
getCodexAccountCount() > 1
) {
return retryCodexWithRotation(
{ reason: primaryAttempt.streamedTriggerReason.reason },
{
reason: primaryAttempt.streamedTriggerReason.reason as CodexRotationReason,
},
output.error ?? output.result ?? undefined,
);
}