/** * 显式错误类型 — 上层调用方(B4 reviewer / B5 evolution / B6 mutator) * **必须**显式 catch 以下类型,不允许 generic try/catch 后默默走 NEUTRAL/0.5 * fallback。这是反欺骗的硬约束:LLM 离线、超预算、输出违约 都必须让 * 上层"看见",由上层 自行决定 是否走 heuristic fallback、是否登记 * lifecycle 事件。 * * 不要在调用 `llmRoleClient` 的地方写: * ```ts * try { ... } catch { return NEUTRAL; } // ❌ 静默,违反反欺骗 * ``` * * 应当写: * ```ts * try { * const r = await llmRoleClient(...); * ... * } catch (e) { * if (e instanceof LlmRoleDisabled) { ... 走 heuristic 并 logAlert ... } * if (e instanceof LlmRoleBudgetExceeded) { ... 同上 ... } * throw e; // 其他错误必须冒泡 * } * ``` */ abstract class LlmRoleErrorBase extends Error { abstract readonly status: string; constructor(message: string) { super(message); this.name = new.target.name; } } /** `CAPABILITY_LLM_ENABLED=false` 时所有角色调用都抛此错。 */ export class LlmRoleDisabled extends LlmRoleErrorBase { readonly status = "disabled"; constructor(reason = "CAPABILITY_LLM_ENABLED is false") { super(reason); } } /** 该 (capability, role) 当日花销已达 / 超过 daily_limit_usd。 */ export class LlmRoleBudgetExceeded extends LlmRoleErrorBase { readonly status = "budget_exceeded"; constructor( public readonly capabilityId: string | null, public readonly role: string, public readonly spent: number, public readonly limit: number, ) { super( `Daily LLM budget exceeded for capability=${capabilityId ?? "global"} role=${role}: spent=$${spent.toFixed(4)} limit=$${limit.toFixed(4)}`, ); } } /** Zod validate 输出失败,1 次重试后仍失败。 */ export class LlmRoleResponseInvalid extends LlmRoleErrorBase { readonly status = "invalid_response"; constructor( public readonly role: string, public readonly zodIssues: unknown, public readonly rawText: string, ) { super(`LLM role=${role} response did not validate after retry`); } } /** 单次调用 timeout(role-specific timeout 触发)。 */ export class LlmRoleTimeout extends LlmRoleErrorBase { readonly status = "timeout"; constructor( public readonly role: string, public readonly timeoutMs: number, ) { super(`LLM role=${role} timed out after ${timeoutMs}ms`); } } /** Gateway 熔断中或没 provider — 上层应走 heuristic fallback。 */ export class LlmRoleProviderUnavailable extends LlmRoleErrorBase { readonly status = "provider_unavailable"; constructor( public readonly role: string, public readonly providerErrorCode: string, detail = "", ) { super( `LLM role=${role} provider unavailable: ${providerErrorCode}${detail ? " — " + detail : ""}`, ); } } /** 共用类型守卫 — 让上层一行判断"这是 plumbing 层抛的可处理错误"。 */ export function isLlmRoleError(err: unknown): err is LlmRoleErrorBase { return err instanceof LlmRoleErrorBase; } export type LlmRoleErrorStatus = | "disabled" | "budget_exceeded" | "invalid_response" | "timeout" | "provider_unavailable";