import { M as MainToWorkerMessage, W as WorkerToMainMessage, C as CapabilityReport, S as SerializedError, a as ModelState, b as ModelEntry, D as Device, c as ScheduledTask, T as TaskPriority, P as PoolConfig, d as ModelLoadConfig, e as ModelHandle, L as LoadProgressEvent } from './types-CLRSaArX.cjs'; export { I as InferenceOptions, f as LoadedModel, g as ModelAdapter, h as ModelAdapterFactory, i as WasmCapability, j as WebGpuCapability, k as WorkerPoolInterface } from './types-CLRSaArX.cjs'; type RoleChangeCallback = (role: 'leader' | 'follower') => void; /** * Leader election via Web Locks API. * * @remarks * One tab holds the lock and acts as the "leader" — it owns the worker pool. * All other tabs are "followers" and proxy their requests through BroadcastChannel. * * When the leader tab closes, the lock is released automatically by the browser, * and the next tab in the lock queue is promoted to leader. * * Web Locks + BroadcastChannel combined coverage: ~96% of modern browsers. * * iOS Safari and older Android Chrome do not support SharedWorker but DO support * Web Locks, making this tier 2 a reliable cross-mobile fallback. */ declare class LeaderElection { private role; private readonly listeners; private abortController; /** * Start leader election. Resolves once the role is determined. * The lock is held for the lifetime of the tab. */ start(): Promise<'leader' | 'follower'>; private holdLock; /** * Release the lock and stop the election. Used for cleanup. */ stop(): void; /** * Subscribe to role changes. * @returns unsubscribe function */ onRoleChange(callback: RoleChangeCallback): () => void; get currentRole(): 'leader' | 'follower' | 'unknown'; get isLeader(): boolean; private setRole; /** * Check if the Web Locks API is available. */ static isSupported(): boolean; isSupported(): boolean; } type MessageListener = (msg: WorkerToMainMessage) => void; /** * Bridge to a SharedWorker. * * @remarks * SharedWorker allows all tabs from the same origin to share one worker process. * One worker holds all model instances, eliminating per-tab model duplication. * * Tier 1 coordination (~58% browser coverage): * - Chrome 4+, Edge 79+, Firefox 29+, Safari 16+ * - NOT supported on iOS Safari < 16 or Android Chrome * * If SharedWorker is unavailable, fall back to LeaderElection (tier 2) * or per-tab dedicated workers (tier 3). */ declare class SharedWorkerBridge { private readonly port; private readonly listeners; constructor(workerUrl: URL | string); /** * Send a message to the shared worker. */ postMessage(msg: MainToWorkerMessage, transfer?: Transferable[]): void; /** * Subscribe to incoming messages from the shared worker. * @returns unsubscribe function */ on(listener: MessageListener): () => void; /** * Disconnect from the shared worker. The worker itself stays alive * as long as other tabs are connected. */ disconnect(): void; /** * Check if SharedWorker is available in the current environment. */ static isSupported(): boolean; } type TabChannelMessage = { type: 'leader-elected'; tabId: string; } | { type: 'leader-gone'; tabId: string; } | { type: 'request'; tabId: string; reqId: string; payload: unknown; } | { type: 'response'; reqId: string; payload: unknown; error?: { message: string; name: string; }; } | { type: 'stream-chunk'; reqId: string; chunk: unknown; } | { type: 'stream-end'; reqId: string; } | { type: 'stream-error'; reqId: string; error: { message: string; name: string; }; }; type TabChannelListener = (msg: TabChannelMessage) => void; /** * Thin wrapper over BroadcastChannel for cross-tab coordination. * * @remarks * BroadcastChannel has ~96% browser coverage and works on both desktop * and mobile. Messages are NOT delivered to the sender tab. */ declare class TabChannel { private readonly channel; private readonly listeners; constructor(); /** * Broadcast a message to all other tabs. */ send(msg: TabChannelMessage): void; /** * Subscribe to incoming messages. * @returns unsubscribe function */ on(listener: TabChannelListener): () => void; /** * Close the channel and remove all listeners. */ close(): void; /** * Check if BroadcastChannel is available in the current environment. */ static isSupported(): boolean; } /** * LRU-based memory budget tracker. * * Tracks approximate memory usage across loaded models. * When a new model load would exceed the configured budget, * evicts least-recently-used models to make room. */ declare class MemoryBudget { private readonly maxMB; private usedMB; private readonly lruOrder; private readonly modelSizes; constructor(maxMB: number); /** Total memory budget in MB. */ get totalMB(): number; /** Currently allocated memory in MB. */ get allocatedMB(): number; /** Remaining available memory in MB. */ get availableMB(): number; /** * Determine which model IDs must be evicted to fit `requiredMB`. * Does NOT perform the eviction itself — caller is responsible for * actually unloading the returned models before calling `allocate()`. * * @returns Array of model IDs to evict (LRU order), or null if * `requiredMB` exceeds the total budget (impossible to fit). */ planEviction(requiredMB: number): string[] | null; /** * Allocate memory for a model. Call after the model is loaded. * Updates LRU order. */ allocate(modelId: string, memoryMB: number): void; /** * Release memory for a model. Call when model is unloaded. */ release(modelId: string): void; /** * Mark a model as recently used, moving it to the back of the LRU queue. */ touch(modelId: string): void; /** * Check whether allocating `requiredMB` would stay within budget * (i.e., requires no evictions). */ fits(requiredMB: number): boolean; /** Return LRU-ordered list of tracked model IDs (oldest first). */ get lruList(): readonly string[]; } /** * Detect browser capabilities for AI inference. * Result is cached after first call. * * @example * const caps = await detectCapabilities(); * if (caps.webgpu.supported) { * console.log('GPU vendor:', caps.webgpu.adapter?.vendor); * } */ declare function detectCapabilities(): Promise; /** Clear the cached capability report. Useful for testing. */ declare function clearCapabilitiesCache(): void; declare class InferisError extends Error { readonly code: string; constructor(message: string, code: string); static fromSerialized(err: SerializedError): InferisError; serialize(): SerializedError; } declare class ModelLoadError extends InferisError { readonly modelId: string; constructor(modelId: string, message: string); } declare class ModelNotReadyError extends InferisError { readonly modelId: string; constructor(modelId: string, state: string); } declare class ModelDisposedError extends InferisError { readonly modelId: string; constructor(modelId: string); } declare class InferenceError extends InferisError { readonly modelId: string; constructor(modelId: string, message: string); } declare class BudgetExceededError extends InferisError { readonly requestedMB: number; readonly budgetMB: number; constructor(requestedMB: number, budgetMB: number); } declare class TaskTimeoutError extends InferisError { readonly reqId: string; constructor(reqId: string, timeoutMs: number); } declare class WorkerError extends InferisError { readonly workerId: number; constructor(workerId: number, message: string); } declare class DeviceLostError extends InferisError { readonly modelId: string; readonly reason: string; constructor(modelId: string, reason: string); } declare class EnvironmentError extends InferisError { constructor(message: string); } declare class InvalidStateTransitionError extends InferisError { constructor(from: string, to: string); } /** * Validate and apply a lifecycle state transition. * * @throws {InvalidStateTransitionError} if the transition is not allowed. */ declare function transition(from: ModelState, to: ModelState): ModelState; /** * Check if a state transition is valid without throwing. */ declare function canTransition(from: ModelState, to: ModelState): boolean; /** * Check if a model in the given state can accept inference tasks. */ declare function isAcceptingInference(state: ModelState): boolean; /** * Check if a model in the given state is considered terminal (no further transitions possible). */ declare function isTerminal(state: ModelState): boolean; /** * ModelRegistry tracks all model entries across workers. * * A model ID is composed of `task:modelName` to allow the same underlying * model to be used for different tasks without collision. */ declare class ModelRegistry { private readonly models; /** * Compose a canonical model ID from task and config. */ static makeId(task: string, modelName: string): string; /** * Register a new model entry with IDLE state. */ register(id: string, task: string, config: Record): ModelEntry; /** * Get a model entry by ID. */ get(id: string): ModelEntry | undefined; /** * Check if a model entry exists. */ has(id: string): boolean; /** * Update the state of a model entry, notifying all subscribers. */ setState(id: string, state: ModelState): void; /** * Update the device, memory, and worker assignment after a successful load. */ setLoaded(id: string, device: Device, memoryMB: number, workerId: number): void; /** * Clear the worker assignment on unload. */ setUnloaded(id: string): void; /** * Add a state change listener to a model entry. * @returns unsubscribe function */ subscribe(id: string, listener: (state: ModelState) => void): () => void; /** * Remove a model entry completely. */ delete(id: string): void; /** * Return all model entries in a given state. */ byState(state: ModelState): ModelEntry[]; /** * Return all model entries assigned to a specific worker. */ byWorker(workerId: number): ModelEntry[]; /** Number of registered model entries. */ get size(): number; /** All model entries (read-only view for scheduling). */ get entries(): ReadonlyMap; } /** * Task scheduler with priority queue and model affinity. * * Model affinity: when dispatching a task, the scheduler prefers workers * that already have the required model loaded, avoiding redundant re-loads. */ declare class Scheduler { private readonly queues; private readonly workerLoad; private readonly workerModels; /** * Register a worker with the scheduler. */ addWorker(workerId: number): void; /** * Remove a worker from the scheduler. * Rejects any queued tasks targeting only this worker (affinity-pinned). */ removeWorker(workerId: number): void; /** * Notify scheduler that a worker has loaded a model. * Used for affinity tracking. */ notifyModelLoaded(workerId: number, modelId: string): void; /** * Notify scheduler that a worker has unloaded a model. */ notifyModelUnloaded(workerId: number, modelId: string): void; /** * Enqueue a task. It will be dispatched to the best available worker. * If all workers are busy, the task waits in the priority queue. */ enqueue(task: ScheduledTask, _models?: ReadonlyMap): void; /** * Notify scheduler that a worker has completed a task. * Dispatches the next queued task to the now-free worker, if any. */ notifyTaskComplete(workerId: number, _models?: ReadonlyMap): void; /** * Attempt to dispatch the highest-priority queued task to a specific worker. */ private drainNext; /** * Pick the best available worker for a given model. * Prefers workers with the model already loaded (affinity). * Falls back to the least-loaded worker. * Returns null if all workers are saturated (load >= concurrencyLimit). */ pickWorker(modelId: string, concurrencyPerWorker?: number): number | null; private dispatch; private findAffinityTask; /** Return current queue depth across all priorities. */ get queueDepth(): number; /** Return current load for a worker. */ workerLoadFor(workerId: number): number; /** Return all registered worker IDs. */ get workerIds(): number[]; /** Clear all worker state (called on pool termination). */ reset(): void; /** Return the effective priority weight for sorting. */ static priorityWeight(p: TaskPriority): number; } /** * WorkerPool — main orchestrator. * * Manages a pool of dedicated Web Workers, routes inference tasks, * tracks model lifecycle, and enforces memory budgets. * * @example * ```ts * const pool = await createPool({ adapter: transformersAdapter() }); * const model = await pool.load('feature-extraction', { model: 'Xenova/...' }); * const result = await model.run(['Hello world']); * await model.dispose(); * await pool.terminate(); * ``` */ declare class WorkerPool { private readonly workers; private readonly registry; private readonly scheduler; private readonly budget; private readonly inferenceWaiters; private readonly pending; private readonly config; private readonly caps; private readonly resolvedDevice; private nextWorkerId; private nextReqId; private terminated; private constructor(); /** * Create and initialize a WorkerPool. * Spawns `maxWorkers` dedicated workers and detects browser capabilities. */ static create(config: PoolConfig): Promise; private spawnWorkers; private spawnWorker; private handleWorkerMessage; private readonly reqIdToModelId; /** * Load a model. If already loaded with the same config, returns existing handle. * Performs memory budget check and eviction before loading. */ load(task: string, config: ModelLoadConfig): Promise>; private disposeModel; private makeHandle; /** * Waits until the model transitions to `ready`, or rejects if it reaches a * terminal/non-recoverable state (error, disposed, unloading). * Respects the provided AbortSignal. * * When the model is `inferring`, uses a priority queue so that higher-priority * callers are unblocked before lower-priority ones. * When the model is `loading`, falls back to a plain state subscription. */ private waitForReady; /** * Unblocks the highest-priority waiter queued for the given model. * Called after a model transitions back to `ready`. */ private drainInferenceWaiter; private runInference; private streamInference; private setRequestTimeout; private clearPendingTimeout; private handleWorkerCrash; /** * Gracefully terminate all workers and dispose all models. */ terminate(): Promise; /** * Return snapshot of detected browser capabilities. */ capabilities(): CapabilityReport; private resolveDevice; getMemoryBudget(): { totalMB: number; allocatedMB: number; availableMB: number; }; /** @internal */ get _registry(): ModelRegistry; /** @internal */ get _budget(): MemoryBudget; /** @internal */ get _scheduler(): Scheduler; } /** * Create and initialize a WorkerPool. * * @example * ```ts * import { createPool } from 'inferis-ml'; * import { transformersAdapter } from 'inferis-ml/adapters/transformers'; * * const pool = await createPool({ adapter: transformersAdapter() }); * ``` */ declare function createPool(config: PoolConfig): Promise; type ProgressListener = (event: LoadProgressEvent) => void; /** * Simple progress event emitter for model load operations. * Wraps the raw progress callback into a subscribable interface. */ declare class ProgressEmitter { private readonly listeners; /** * Subscribe to progress events. * @returns unsubscribe function */ on(listener: ProgressListener): () => void; /** * Emit a progress event to all subscribers. */ emit(event: LoadProgressEvent): void; /** Remove all listeners. */ clear(): void; /** Number of active listeners. */ get listenerCount(): number; } /** * Async iterator adapter for ReadableStream. * * Allows `for await (const token of stream)` syntax when the runtime * does not natively support ReadableStream async iteration. * * @example * ```ts * const stream = model.stream({ messages: [...] }); * for await (const token of readableToAsyncIter(stream)) { * process.stdout.write(token); * } * ``` */ declare function readableToAsyncIter(stream: ReadableStream): AsyncIterable; /** * Collect all chunks from a ReadableStream into an array. * Useful for non-streaming consumers. */ declare function collectStream(stream: ReadableStream): Promise; /** * Collect all chunks from a ReadableStream and join them as a string. */ declare function collectStreamText(stream: ReadableStream): Promise; export { BudgetExceededError, CapabilityReport, Device, DeviceLostError, EnvironmentError, InferenceError, InferisError, InvalidStateTransitionError, LeaderElection, LoadProgressEvent, MemoryBudget, ModelDisposedError, ModelHandle, ModelLoadConfig, ModelLoadError, ModelNotReadyError, ModelRegistry, ModelState, PoolConfig, ProgressEmitter, Scheduler, SharedWorkerBridge, TabChannel, TaskTimeoutError, WorkerError, WorkerPool, canTransition, clearCapabilitiesCache, collectStream, collectStreamText, createPool, detectCapabilities, isAcceptingInference, isTerminal, readableToAsyncIter, transition };