/** * @license * Copyright 2025 Steven Roussey * SPDX-License-Identifier: Apache-2.0 */ /** Service token for the platform-specific WorkerServer instance. */ export declare const WORKER_SERVER: import("../di").ServiceToken; /** * Construction-time options for {@link WorkerServerBase}. * * All fields are optional and safe to omit — defaults match the previous * hard-coded values. * * The platform-specific `WorkerServer` subclasses (`Worker.bun.ts`, * `Worker.node.ts`, `Worker.browser.ts`) forward an optional * {@link WorkerServerBaseOptions} argument to this base constructor, so * `new WorkerServer({ pendingAbortHardCap: ... })` is configurable in * production as well as in tests. */ export interface WorkerServerBaseOptions { /** * Maximum number of pending-abort markers retained in memory. Once the * `pendingAborts` map exceeds this cap, the oldest-by-timestamp half is * evicted in one pass — a memory safety-net for pathological abort bursts * that outrun the per-id TTL timers. Defaults to `10_000`. * * Configurable on the platform `WorkerServer` subclasses as well; it also * serves as a test seam, letting tests exercise the overflow eviction path * with a tiny cap (e.g. `100`) and ~110 entries instead of pushing 10,010 * entries through vitest's fake-timer heap. */ readonly pendingAbortHardCap?: number; } /** * WorkerServerBase is a class that handles messages from the main thread to the worker. * It is used to register functions that can be called from the main thread. * It also handles the transfer of transferables to the main thread. */ export declare class WorkerServerBase { constructor(options?: WorkerServerBaseOptions); private static readonly PENDING_ABORT_TTL_MS; private static readonly PENDING_ABORT_HARD_CAP; /** * Per-instance hard cap on `pendingAborts` map size. Initialised from the * constructor option (defaults to {@link PENDING_ABORT_HARD_CAP}). */ private readonly pendingAbortHardCap; private functions; private streamFunctions; private runFunctions; private previewFunctions; private requestControllers; private completedRequests; private pendingAborts; /** * Per-id TTL-cleanup timer handles, keyed by request id. The contract: * - Exactly one timer per live entry in `pendingAborts`. `recordPendingAbort` * clears any prior timer for the same id before scheduling a new one, so * a re-recorded abort always gets a full fresh TTL (previously the stale * timer would still fire and delete the renewed entry early). * - `consumePendingAbort` clears and removes the timer when it consumes * (or treats as expired) a pending marker, so a consumed id never has * a stale timer queued. * - The hard-cap eviction path in `recordPendingAbort` also clears the * timer for each evicted id, bounding timer-handle memory together * with `pendingAborts` itself. * - The TTL-fire callback removes its own entry by id. */ private pendingAbortTimers; private postResult; private postError; private postStreamChunk; /** * Send the ready message to the main thread, advertising which functions are * registered in each category. Call this after all functions have been registered * so WorkerManager can skip unnecessary roundtrips for unregistered calls. */ sendReady(): void; /** * Register a callable worker function. * * Cancellation is cooperative: an `abort` only fires the `AbortSignal` passed * to `fn` (the 4th argument). The worker does NOT interrupt or terminate a * running function, so `fn` MUST poll `signal.aborted` (or listen for the * `abort` event) and return early to stop consuming CPU/GPU. A function that * ignores the signal runs to completion even after the caller has aborted. * * @param name - The function name (e.g., task type identifier) * @param fn - Async function: (input, model, postProgress, signal) => Promise */ registerFunction(name: string, fn: (...args: any[]) => Promise): void; /** * Register a preview function for lightweight preview execution. * Preview functions receive (input, model) and return a fast preview * without progress tracking or abort signals. * * @param name - The function name (e.g., task type identifier) * @param fn - Async function: (input, model) => Promise */ registerPreviewFunction(name: string, fn: (input: any, model: any) => Promise): void; /** * Register an async generator function for streaming execution. * When called via the worker protocol with `stream: true`, the server * iterates the generator and sends each yielded value as a `stream_chunk` * message, followed by a `complete` message when the generator finishes. * * Cancellation is cooperative: an `abort` fires the `signal` but does not * terminate the generator. The generator MUST check `signal.aborted` and * return promptly; otherwise the underlying compute keeps running even though * the server stops forwarding chunks once the request is marked complete. * * @param name - The function name (e.g., task type identifier) * @param fn - Async generator function: (input, model, signal) => AsyncIterable */ registerStreamFunction(name: string, fn: (...args: any[]) => AsyncIterable): void; /** * Register a Promise+emit run function for the new run-fn shape. The function * receives an `emit` callback that posts events as `stream_chunk` messages on * the worker port and resolves with no value when complete. Use this in place * of {@link registerStreamFunction} for newly-ported provider run-fns. * * Cancellation is cooperative: an `abort` fires the supplied `signal` but the * worker neither interrupts nor terminates the run-fn. The run-fn MUST honor * `signal.aborted` (e.g. stop generating tokens / abort the network request) * to actually release CPU/GPU; ignoring it lets the work run to completion * after the caller has moved on. */ registerRunFunction(name: string, fn: (input: unknown, model: unknown, signal: AbortSignal, emit: (event: unknown) => void, outputSchema?: unknown, sessionId?: string) => Promise): void; handleMessage(event: { type: string; data: any; }): Promise; /** * Handle an `abort` message for a request id. This only fires the request's * {@link AbortController} (so a cooperative fn can observe `signal.aborted`) * and posts the terminal "Operation aborted" error to the caller. It does NOT * interrupt or terminate an in-flight function: non-cooperative fns that * ignore the signal continue running to completion. If the abort beats the * call to the worker, the id is recorded as a pending abort so the imminent * handler aborts its controller as soon as it is constructed. */ handleAbort(id: string): Promise; /** * If a prior `abort` arrived for `id` before the call started, and that * abort is still within the TTL window, abort the supplied controller * immediately and drop the pending marker. Returns `true` when an abort * was consumed so callers may exit fast. Expired markers are silently * dropped (returns `false`). * * Always clears the per-id TTL timer from {@link pendingAbortTimers} * before returning, regardless of whether the marker was live or * expired — once a marker is consumed (or proven stale) its timer is * stale too and should not fire later. */ private consumePendingAbort; /** * Records `id` as having received an abort that has not yet been matched * to a controller. Stores a wall-clock timestamp so entries are evicted * by TTL (see {@link PENDING_ABORT_TTL_MS}) rather than by insertion * order — a noisy/malicious client spamming aborts can no longer evict * a legitimate pending abort before its matching `call` arrives. * * Cleanup strategy (amortised O(1) per call, no inline sweep): * - A per-id `setTimeout` (tracked in {@link pendingAbortTimers}, * keyed by id) drops this entry once TTL elapses. If `id` already * had a queued timer from a prior `recordPendingAbort`, that timer * is `clearTimeout`'d first so the renewed marker gets a full * fresh TTL rather than inheriting the stale +TTL of the original. * - {@link consumePendingAbort} re-checks the timestamp at consume * time and also clears the per-id timer so it can't fire later * against an unrelated id. * - If the map exceeds {@link pendingAbortHardCap}, the oldest half * is evicted in one pass; each evicted id also has its timer * cleared, so timer-handle memory is bounded together with the * data map. */ private recordPendingAbort; /** * Handle a preview call. Returns undefined (non-error) if the preview * function is not registered, since not all task types expose a preview fn. */ handlePreviewCall(id: string, functionName: string, [input, model]: [any, any]): Promise; handleCall(id: string, functionName: string, [input, model]: [any, any]): Promise; /** * Handle a streaming call. If a stream function is registered for the given name, * iterate it and send each yielded event as a `stream_chunk` message. If only a * regular function is registered, run it and wrap the result as a single `finish` * stream event (graceful fallback for providers that don't implement streaming). */ handleStreamCall(id: string, functionName: string, [input, model]: [any, any]): Promise; /** * Handle a Promise+emit run call. Resolves to `undefined` when the run-fn * settles; emitted events are forwarded as `stream_chunk` messages. This is * the new run-fn shape that replaces async-generator stream functions. */ handleRunCall(id: string, functionName: string, [input, model, outputSchema, sessionId]: [any, any, any, any]): Promise; /** * Schedule cleanup of a completed request ID. The delay matches * {@link PENDING_ABORT_TTL_MS} so a completed/aborted id stays in * {@link completedRequests} at least as long as a pending-abort marker can * live. Otherwise a `call` that arrives between the old 5s cleanup and the * 30s abort TTL would be aborted by `consumePendingAbort` yet still post a * SECOND terminal response (the dedupe guard in {@link postResult} / * {@link postError} had already forgotten the id) — violating the * "at most one terminal message per requestId" wire invariant. * * Caps the completed set size at {@link COMPLETED_REQUESTS_HARD_CAP} entries * to prevent unbounded growth. As in {@link recordPendingAbort}, the eviction * list is snapshotted via `Array.from` before deletion to avoid * iterating-while-deleting. */ private scheduleCompletedRequestCleanup; } //# sourceMappingURL=WorkerServerBase.d.ts.map