import { EngineRuntimeError } from './errors'; import type { EngineErrorPayload, EngineEvent, EngineMethod, EngineRequest, EngineResponse, EngineResponseMap, EngineWorkerMessage, } from './protocol'; import { BrowserEngineRuntime } from './runtime'; interface WorkerScope { onmessage: ((event: MessageEvent) => void) | null; postMessage(message: EngineWorkerMessage): void; } interface ActiveOperation { controller: AbortController; promise: Promise; } const scope = globalThis as unknown as WorkerScope; const runtime = new BrowserEngineRuntime(); const active = new Map(); let quiescing = false; function postEvent(event: EngineEvent): void { scope.postMessage(event); } function postSuccess( request: { requestId: string; method: Method }, result: EngineResponseMap[Method], ): void { scope.postMessage({ type: 'response', requestId: request.requestId, method: request.method, ok: true, result, } as EngineResponse); } function errorPayload(error: unknown): EngineErrorPayload { if (error instanceof EngineRuntimeError) { return { code: error.code, message: error.message, ...(error.details === undefined ? {} : { details: error.details }), }; } if (error instanceof DOMException && error.name === 'AbortError') { return { code: 'ABORTED', message: error.message }; } if (error instanceof Error) { return { code: error.name === 'AbortError' ? 'ABORTED' : 'ENGINE_ERROR', message: error.message, }; } return { code: 'ENGINE_ERROR', message: String(error) }; } function postFailure(request: { requestId: string; method: EngineMethod }, error: unknown): void { scope.postMessage({ type: 'response', requestId: request.requestId, method: request.method, ok: false, error: errorPayload(error), } as EngineResponse); } async function runExclusive( requestId: string, operation: (signal: AbortSignal) => Promise, ): Promise { if (quiescing || active.size > 0) { throw new EngineRuntimeError( 'ENGINE_BUSY', quiescing ? 'The engine is unloading or clearing storage.' : `Another mutable engine operation is active (${[...active.keys()].join(', ')}).`, ); } const controller = new AbortController(); const promise = operation(controller.signal); active.set(requestId, { controller, promise }); try { return await promise; } finally { active.delete(requestId); } } async function stopActiveOperations(): Promise { const operations = [...active.values()]; for (const operation of operations) { operation.controller.abort(); } await Promise.allSettled(operations.map((operation) => operation.promise)); } async function runQuiesced(operation: () => Promise): Promise { if (quiescing) { throw new EngineRuntimeError('ENGINE_BUSY', 'The engine is already unloading or clearing storage.'); } quiescing = true; try { await stopActiveOperations(); return await operation(); } finally { quiescing = false; } } async function handleRequest(request: EngineRequest): Promise { try { switch (request.method) { case 'capabilities': postSuccess(request, await runtime.capabilities()); return; case 'loadModel': postSuccess( request, await runExclusive(request.requestId, (signal) => ( runtime.loadModel(request.requestId, request.params, signal, postEvent) )), ); return; case 'generate': postSuccess( request, await runExclusive(request.requestId, (signal) => ( runtime.generate(request.requestId, request.params, signal, postEvent) )), ); return; case 'scoreSequence': postSuccess( request, await runExclusive(request.requestId, (signal) => ( runtime.scoreSequence(request.params, signal) )), ); return; case 'abort': { const operation = active.get(request.params.targetRequestId); const aborted = operation !== undefined && !operation.controller.signal.aborted; operation?.controller.abort(); postSuccess(request, { targetRequestId: request.params.targetRequestId, aborted }); return; } case 'unload': postSuccess(request, await runQuiesced(() => runtime.unload())); return; case 'backendReport': postSuccess(request, await runtime.backendReport()); return; case 'storageEstimate': postSuccess(request, await runtime.storageEstimate()); return; case 'storagePersist': postSuccess(request, await runtime.storagePersist()); return; case 'storageClear': postSuccess(request, await runQuiesced(() => runtime.storageClear())); return; } } catch (error) { postFailure(request, error); } } scope.onmessage = (event) => { const request = event.data; if (request?.type === 'request' && typeof request.requestId === 'string') { void handleRequest(request); } };