export interface WebGpuDeviceLostDetails { recoverable: true; nextAction: 'reload-model'; stage: 'load' | 'generate' | 'score-sequence' | 'backend-report'; modelId: string; cpuFallbackAvailable: boolean; cause: 'memory-pressure' | 'device-loss'; signal: { line: string; reason: number | null; message: string | null; }; } export type ResourceFailureKind = 'context-capacity' | 'memory-pressure'; export function classifyResourceFailure( error: unknown, nativeLog: readonly string[] = [], ): ResourceFailureKind | null { const errorRecord = typeof error === 'object' && error !== null ? error as { message?: unknown; name?: unknown; type?: unknown } : null; const diagnostic = [ typeof error === 'string' ? error : '', typeof errorRecord?.name === 'string' ? errorRecord.name : '', typeof errorRecord?.message === 'string' ? errorRecord.message : '', typeof errorRecord?.type === 'string' ? errorRecord.type : '', ...nativeLog, ].join('\n'); if ( /\bkv[_\s-]?cache(?:[_\s-]+is)?[_\s-]+full\b|running out of context capacity|context (?:window|capacity).*(?:full|exhausted)|(?:prompt|tokens?).*(?:exceed|too (?:long|large)).*(?:context|n_ctx)/iu.test(diagnostic) ) { return 'context-capacity'; } if ( /\bout[ -]of[ -](?:device[ -])?memory\b|\boom\b|GPUOutOfMemoryError|memory access out of bounds|cannot (?:enlarge|allocate).*memory|failed to (?:allocate|create).*(?:buffer|memory)|(?:buffer|memory) allocation failed/iu.test(diagnostic) ) { return 'memory-pressure'; } return null; } export class EngineRuntimeError extends Error { readonly code: string; readonly details?: unknown; constructor(code: string, message: string, details?: unknown) { super(message); this.name = 'EngineRuntimeError'; this.code = code; this.details = details; } } export function abortError(): DOMException { return new DOMException('The engine operation was aborted.', 'AbortError'); } export function throwIfAborted(signal: AbortSignal): void { if (signal.aborted) { throw abortError(); } }