localm-web 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-05-10
11
+
12
+ ### Added
13
+
14
+ - **Structured output (v0.4)** — JSON mode and JSON Schema constrained
15
+ decoding via WebLLM's `response_format` / xgrammar.
16
+ - `GenerationOptions.json: boolean` — when `true`, the engine is forced
17
+ to emit a string parseable as JSON (free-form shape).
18
+ `GenerationOptions.jsonSchema?: object` — when set, takes priority
19
+ over `json` and constrains decoding so the output matches the schema.
20
+ - `ChatReply.json<T>()` and `CompletionResult.json<T>()` parse the
21
+ generated text and return it cast to `T`. No runtime validation of
22
+ the schema is performed; pair with Ajv / Zod on the call site if you
23
+ need it.
24
+ - `StructuredOutputError` (extends `LocalmWebError`) wraps the
25
+ underlying `SyntaxError` from `JSON.parse`, so consumers can
26
+ distinguish SDK-issued failures from unrelated runtime exceptions.
27
+ - `src/structured/json-schema.ts` exposes `assertJsonSchema`,
28
+ `serializeJsonSchema`, and `parseStructuredOutput<T>` re-exported
29
+ from `localm-web`.
30
+ - `WebLLMEngine.generate` / `stream` / `complete` / `streamCompletion`
31
+ forward `response_format` to WebLLM. Worker engine inherits the
32
+ behavior without changes (the worker protocol already passes
33
+ `GenerationOptions` through `postMessage`; only `signal` is stripped).
34
+ - 15 unit tests in `test/structured-output.test.ts` covering schema
35
+ assertion (accept / reject paths), schema serialization, JSON parsing
36
+ of objects / arrays / primitives / invalid input, error chaining via
37
+ `cause`, and the `.json()` helpers on `ChatReply` and
38
+ `CompletionResult`.
39
+
40
+ ## [0.3.0] - 2026-05-10
41
+
10
42
  ### Changed
11
43
 
12
44
  - **`LMTaskCreateOptions.inWorker` default flipped from `false` to `true`.**
package/README.md CHANGED
@@ -134,10 +134,22 @@ const vectors = await emb.embed(["hello world", "another sentence"]);
134
134
  const rerank = await Reranker.create("bge-reranker-base");
135
135
  const scores = await rerank.score("query", ["doc1", "doc2", "doc3"]);
136
136
 
137
- // Structured output (JSON Schema → constrained decoding)
138
- const json = await chat.send("Extract user info from: ...", {
139
- jsonSchema: { type: "object", properties: { name: { type: "string" } } },
137
+ // Structured output free-form JSON
138
+ const jsonReply = await chat.send("List three pros and cons of WebGPU as JSON.", { json: true });
139
+ const data = jsonReply.json<{ pros: string[]; cons: string[] }>();
140
+
141
+ // Structured output — JSON Schema constrained decoding (xgrammar via WebLLM)
142
+ const userReply = await chat.send("Extract user info from: 'Ada, 36, …'", {
143
+ jsonSchema: {
144
+ type: "object",
145
+ required: ["name", "age"],
146
+ properties: {
147
+ name: { type: "string" },
148
+ age: { type: "integer", minimum: 0 },
149
+ },
150
+ },
140
151
  });
152
+ const user = userReply.json<{ name: string; age: number }>();
141
153
  ```
142
154
 
143
155
  The shape mirrors `ort-vision-sdk-web`: `await Class.create(model)` then `predict()` / `send()` / `embed()` / `score()`.
@@ -24,6 +24,33 @@ class ModelNotLoadedError extends LocalmWebError {
24
24
  }
25
25
  class GenerationAbortedError extends LocalmWebError {
26
26
  }
27
+ class StructuredOutputError extends LocalmWebError {
28
+ }
29
+ function assertJsonSchema(schema) {
30
+ if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
31
+ throw new StructuredOutputError("jsonSchema must be a plain object describing a JSON Schema.");
32
+ }
33
+ const keys = Object.keys(schema);
34
+ const recognized = [
35
+ "type",
36
+ "$ref",
37
+ "oneOf",
38
+ "anyOf",
39
+ "allOf",
40
+ "enum",
41
+ "const",
42
+ "properties"
43
+ ];
44
+ if (!keys.some((key) => recognized.includes(key))) {
45
+ throw new StructuredOutputError(
46
+ "jsonSchema does not look like a JSON Schema (missing type/$ref/oneOf/anyOf/allOf/enum/const/properties)."
47
+ );
48
+ }
49
+ }
50
+ function serializeJsonSchema(schema) {
51
+ assertJsonSchema(schema);
52
+ return JSON.stringify(schema);
53
+ }
27
54
  let webllmModulePromise = null;
28
55
  async function loadWebLLM() {
29
56
  if (!webllmModulePromise) {
@@ -41,6 +68,15 @@ function buildSamplingParams(options) {
41
68
  if (options.topP !== void 0) params.top_p = options.topP;
42
69
  return params;
43
70
  }
71
+ function buildResponseFormat(options) {
72
+ if (options.jsonSchema !== void 0) {
73
+ return { type: "json_object", schema: serializeJsonSchema(options.jsonSchema) };
74
+ }
75
+ if (options.json) {
76
+ return { type: "json_object" };
77
+ }
78
+ return void 0;
79
+ }
44
80
  function toChatMessages(messages) {
45
81
  return messages.map((m) => {
46
82
  switch (m.role) {
@@ -95,10 +131,12 @@ class WebLLMEngine {
95
131
  if (options.signal?.aborted) {
96
132
  throw new GenerationAbortedError("Generation aborted before start.");
97
133
  }
134
+ const responseFormat = buildResponseFormat(options);
98
135
  const completion = await engine2.chat.completions.create({
99
136
  ...buildSamplingParams(options),
100
137
  messages: toChatMessages(messages),
101
- stream: false
138
+ stream: false,
139
+ ...responseFormat ? { response_format: responseFormat } : {}
102
140
  });
103
141
  return completion.choices[0]?.message?.content ?? "";
104
142
  }
@@ -107,10 +145,12 @@ class WebLLMEngine {
107
145
  if (options.signal?.aborted) {
108
146
  throw new GenerationAbortedError("Generation aborted before start.");
109
147
  }
148
+ const responseFormat = buildResponseFormat(options);
110
149
  const completion = await engine2.chat.completions.create({
111
150
  ...buildSamplingParams(options),
112
151
  messages: toChatMessages(messages),
113
- stream: true
152
+ stream: true,
153
+ ...responseFormat ? { response_format: responseFormat } : {}
114
154
  });
115
155
  let index = 0;
116
156
  let finished = false;
@@ -144,10 +184,12 @@ class WebLLMEngine {
144
184
  if (options.signal?.aborted) {
145
185
  throw new GenerationAbortedError("Generation aborted before start.");
146
186
  }
187
+ const responseFormat = buildResponseFormat(options);
147
188
  const completion = await engine2.completions.create({
148
189
  ...buildSamplingParams(options),
149
190
  prompt,
150
- stream: false
191
+ stream: false,
192
+ ...responseFormat ? { response_format: responseFormat } : {}
151
193
  });
152
194
  return completion.choices[0]?.text ?? "";
153
195
  }
@@ -156,10 +198,12 @@ class WebLLMEngine {
156
198
  if (options.signal?.aborted) {
157
199
  throw new GenerationAbortedError("Generation aborted before start.");
158
200
  }
201
+ const responseFormat = buildResponseFormat(options);
159
202
  const completion = await engine2.completions.create({
160
203
  ...buildSamplingParams(options),
161
204
  prompt,
162
- stream: true
205
+ stream: true,
206
+ ...responseFormat ? { response_format: responseFormat } : {}
163
207
  });
164
208
  let index = 0;
165
209
  let finished = false;
@@ -327,4 +371,4 @@ self.addEventListener("message", (event) => {
327
371
  return;
328
372
  }
329
373
  });
330
- //# sourceMappingURL=inference.worker-CwvQtobb.js.map
374
+ //# sourceMappingURL=inference.worker-DZbXKJZY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inference.worker-DZbXKJZY.js","sources":["../src/core/load-phase.ts","../src/core/exceptions.ts","../src/structured/json-schema.ts","../src/core/webllm-engine.ts","../src/worker/inference.worker.ts"],"sourcesContent":["import type { ModelLoadPhase } from \"../types\";\n\nconst DOWNLOAD_PATTERN: RegExp = /\\b(fetch|download|loading from cache|cache hit|param)/i;\nconst COMPILE_PATTERN: RegExp = /\\b(compil|shader|kernel|tensor|init|allocat|warm)/i;\n\n/**\n * Classify a runtime status text into a {@link ModelLoadPhase}.\n *\n * Heuristic: match download-related verbs first (network or cache hits are\n * treated as `downloading`), then compile-related verbs. Anything else falls\n * back to the generic `loading` bucket. The `ready` phase is never returned\n * here — callers emit it explicitly when the load resolves.\n *\n * @param text - The raw status string from the runtime.\n * @returns The classified phase.\n */\nexport function classifyLoadPhase(text: string): ModelLoadPhase {\n if (DOWNLOAD_PATTERN.test(text)) return \"downloading\";\n if (COMPILE_PATTERN.test(text)) return \"compiling\";\n return \"loading\";\n}\n","/**\n * Error hierarchy for localm-web.\n *\n * All errors thrown by the SDK extend `LocalmWebError` so consumers can\n * distinguish SDK errors from unrelated runtime errors with a single\n * `instanceof` check.\n */\n\n/** Base class for every error raised by localm-web. */\nexport class LocalmWebError extends Error {\n /**\n * @param message - Human-readable description of the error.\n * @param cause - Underlying error, if any.\n */\n constructor(\n message: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/** Thrown when WebGPU is required but not available in the host browser. */\nexport class WebGPUUnavailableError extends LocalmWebError {}\n\n/** Thrown when a model fails to load (network, parsing, runtime init). */\nexport class ModelLoadError extends LocalmWebError {}\n\n/** Thrown when an inference call is made before a model has loaded. */\nexport class ModelNotLoadedError extends LocalmWebError {}\n\n/** Thrown when a model id is not present in the curated registry. */\nexport class UnknownModelError extends LocalmWebError {}\n\n/** Thrown when generation is aborted via an `AbortSignal`. */\nexport class GenerationAbortedError extends LocalmWebError {}\n\n/** Thrown when the browser denies storage quota for the model cache. */\nexport class QuotaExceededError extends LocalmWebError {}\n\n/** Thrown when no usable backend is available on the current platform. */\nexport class BackendNotAvailableError extends LocalmWebError {}\n\n/**\n * Thrown when structured output (JSON mode or JSON Schema constrained\n * decoding) fails to parse as valid JSON.\n *\n * Wraps the underlying `SyntaxError` from `JSON.parse` so consumers can\n * distinguish SDK-issued failures from unrelated runtime exceptions.\n */\nexport class StructuredOutputError extends LocalmWebError {}\n","/**\n * JSON Schema helpers for structured output.\n *\n * The SDK delegates the actual constrained decoding to the underlying\n * runtime (xgrammar inside WebLLM today, ORT-Web equivalent later). These\n * helpers normalize user input — turning a JS object schema into the\n * JSON-string shape that WebLLM's `response_format.schema` expects — and\n * parse the runtime's textual output back into typed JSON.\n */\n\nimport { StructuredOutputError } from \"../core/exceptions\";\n\n/**\n * Minimal structural sanity check for a JSON Schema.\n *\n * Does not validate the schema against the JSON Schema meta-schema. The goal\n * is to fail fast on obvious mistakes (passing a string, an array, `null`)\n * before handing the value off to the runtime, where errors surface much\n * later and with much worse messages.\n *\n * @param schema - Candidate JSON Schema object.\n * @throws StructuredOutputError when `schema` is not a plain object or has\n * no recognizable schema shape (`type`, `$ref`, `oneOf`, `anyOf`, `allOf`,\n * `enum`).\n */\nexport function assertJsonSchema(schema: unknown): asserts schema is object {\n if (schema === null || typeof schema !== \"object\" || Array.isArray(schema)) {\n throw new StructuredOutputError(\"jsonSchema must be a plain object describing a JSON Schema.\");\n }\n const keys: string[] = Object.keys(schema);\n const recognized: readonly string[] = [\n \"type\",\n \"$ref\",\n \"oneOf\",\n \"anyOf\",\n \"allOf\",\n \"enum\",\n \"const\",\n \"properties\",\n ];\n if (!keys.some((key) => recognized.includes(key))) {\n throw new StructuredOutputError(\n \"jsonSchema does not look like a JSON Schema (missing type/$ref/oneOf/anyOf/allOf/enum/const/properties).\"\n );\n }\n}\n\n/**\n * Serialize a JSON Schema object for the WebLLM `response_format.schema`\n * field.\n *\n * WebLLM expects the schema as a JSON-encoded string (xgrammar parses it\n * server-side). Validates the shape via {@link assertJsonSchema} first.\n *\n * @param schema - JSON Schema object.\n * @returns The schema serialized as a JSON string.\n * @throws StructuredOutputError when `schema` is not a recognizable JSON\n * Schema shape.\n */\nexport function serializeJsonSchema(schema: unknown): string {\n assertJsonSchema(schema);\n return JSON.stringify(schema);\n}\n\n/**\n * Parse the textual output of a structured-decoding generation as JSON.\n *\n * @typeParam T - The expected parsed shape. The function does not validate\n * the parsed value against `T`; that is the caller's responsibility.\n * @param text - Raw text returned by the engine.\n * @returns The parsed JSON value cast to `T`.\n * @throws StructuredOutputError when the text is not valid JSON.\n */\nexport function parseStructuredOutput<T = unknown>(text: string): T {\n try {\n return JSON.parse(text) as T;\n } catch (err) {\n throw new StructuredOutputError(\n \"Engine output is not valid JSON. The model may have ignored the constrained decoding directive.\",\n err\n );\n }\n}\n","import type { Engine } from \"./engine\";\nimport { classifyLoadPhase } from \"./load-phase\";\nimport type { GenerationOptions, Message, ProgressCallback, TokenChunk } from \"../types\";\nimport {\n GenerationAbortedError,\n ModelLoadError,\n ModelNotLoadedError,\n WebGPUUnavailableError,\n} from \"./exceptions\";\nimport { serializeJsonSchema } from \"../structured/json-schema\";\n\ntype WebLLMModule = typeof import(\"@mlc-ai/web-llm\");\ntype MLCEngine = import(\"@mlc-ai/web-llm\").MLCEngineInterface;\ntype ChatCompletionMessageParam = import(\"@mlc-ai/web-llm\").ChatCompletionMessageParam;\ntype ResponseFormat = import(\"@mlc-ai/web-llm\").ResponseFormat;\n\nlet webllmModulePromise: Promise<WebLLMModule> | null = null;\n\nasync function loadWebLLM(): Promise<WebLLMModule> {\n if (!webllmModulePromise) {\n webllmModulePromise = import(\"@mlc-ai/web-llm\");\n }\n return webllmModulePromise;\n}\n\nfunction isWebGPUAvailable(): boolean {\n return typeof navigator !== \"undefined\" && \"gpu\" in navigator;\n}\n\ninterface SamplingParams {\n max_tokens?: number;\n temperature?: number;\n top_p?: number;\n}\n\nfunction buildSamplingParams(options: GenerationOptions): SamplingParams {\n const params: SamplingParams = {};\n if (options.maxTokens !== undefined) params.max_tokens = options.maxTokens;\n if (options.temperature !== undefined) params.temperature = options.temperature;\n if (options.topP !== undefined) params.top_p = options.topP;\n return params;\n}\n\n/**\n * Build the WebLLM `response_format` payload from generation options.\n *\n * Returns `undefined` when the caller has not requested structured output —\n * letting WebLLM use its default free-text decoding path. When `jsonSchema`\n * is set it takes priority and is serialized into the `schema` field\n * (xgrammar parses it server-side). When only `json` is set the payload\n * carries `{ type: \"json_object\" }` for unconstrained-but-valid JSON.\n */\nfunction buildResponseFormat(options: GenerationOptions): ResponseFormat | undefined {\n if (options.jsonSchema !== undefined) {\n return { type: \"json_object\", schema: serializeJsonSchema(options.jsonSchema) };\n }\n if (options.json) {\n return { type: \"json_object\" };\n }\n return undefined;\n}\n\nfunction toChatMessages(messages: Message[]): ChatCompletionMessageParam[] {\n return messages.map((m): ChatCompletionMessageParam => {\n switch (m.role) {\n case \"system\":\n return { role: \"system\", content: m.content };\n case \"user\":\n return { role: \"user\", content: m.content };\n case \"assistant\":\n return { role: \"assistant\", content: m.content };\n case \"tool\":\n return { role: \"tool\", content: m.content, tool_call_id: m.name ?? \"\" };\n }\n });\n}\n\n/**\n * Inference engine backed by [WebLLM (MLC)](https://github.com/mlc-ai/web-llm).\n *\n * Requires WebGPU. The fallback path planned for v0.5 will route to ORT-Web\n * when WebGPU is missing.\n */\nexport class WebLLMEngine implements Engine {\n private engine: MLCEngine | null = null;\n\n isLoaded(): boolean {\n return this.engine !== null;\n }\n\n async load(modelId: string, onProgress?: ProgressCallback): Promise<void> {\n if (!isWebGPUAvailable()) {\n throw new WebGPUUnavailableError(\n \"WebGPU is not available in this browser. The ORT-Web fallback is planned for v0.5.\"\n );\n }\n const webllm = await loadWebLLM();\n try {\n this.engine = await webllm.CreateMLCEngine(modelId, {\n initProgressCallback: (report): void => {\n onProgress?.({\n progress: report.progress,\n text: report.text,\n loaded: 0,\n total: 0,\n phase: classifyLoadPhase(report.text),\n });\n },\n });\n onProgress?.({\n progress: 1,\n text: \"Model ready.\",\n loaded: 0,\n total: 0,\n phase: \"ready\",\n });\n } catch (err) {\n throw new ModelLoadError(`Failed to load model \"${modelId}\".`, err);\n }\n }\n\n async generate(messages: Message[], options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: false,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n return completion.choices[0]?.message?.content ?? \"\";\n }\n\n async *stream(messages: Message[], options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: true,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.delta?.content ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming generation failed.\", err);\n }\n }\n\n async complete(prompt: string, options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: false,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n return completion.choices[0]?.text ?? \"\";\n }\n\n async *streamCompletion(\n prompt: string,\n options: GenerationOptions = {}\n ): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: true,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.text ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming completion failed.\", err);\n }\n }\n\n async unload(): Promise<void> {\n if (this.engine) {\n await this.engine.unload();\n this.engine = null;\n }\n }\n\n private requireEngine(): MLCEngine {\n if (!this.engine) {\n throw new ModelNotLoadedError(\"Engine not loaded. Call load() before generation.\");\n }\n return this.engine;\n }\n}\n","/// <reference lib=\"webworker\" />\n\nimport { WebLLMEngine } from \"../core/webllm-engine\";\nimport type { WorkerRequest, WorkerResponse } from \"./protocol\";\n\ndeclare const self: DedicatedWorkerGlobalScope;\n\nconst engine: WebLLMEngine = new WebLLMEngine();\nconst aborts: Map<number, AbortController> = new Map();\n\nfunction reply(message: WorkerResponse): void {\n self.postMessage(message);\n}\n\nfunction fail(id: number, err: unknown): void {\n const error = err instanceof Error ? err : new Error(String(err));\n reply({ op: \"error\", id, name: error.name, message: error.message });\n}\n\nasync function handleLoad(req: Extract<WorkerRequest, { op: \"load\" }>): Promise<void> {\n try {\n await engine.load(req.modelId, (payload) => {\n reply({ op: \"progress\", id: req.id, payload });\n });\n reply({ op: \"loaded\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n }\n}\n\nasync function handleGenerate(req: Extract<WorkerRequest, { op: \"generate\" }>): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n const text: string = await engine.generate(req.messages, {\n ...req.options,\n signal: controller.signal,\n });\n reply({ op: \"generated\", id: req.id, text });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleComplete(req: Extract<WorkerRequest, { op: \"complete\" }>): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n const text: string = await engine.complete(req.prompt, {\n ...req.options,\n signal: controller.signal,\n });\n reply({ op: \"generated\", id: req.id, text });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleStreamCompletion(\n req: Extract<WorkerRequest, { op: \"stream-completion\" }>\n): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n for await (const chunk of engine.streamCompletion(req.prompt, {\n ...req.options,\n signal: controller.signal,\n })) {\n reply({ op: \"token\", id: req.id, chunk });\n }\n reply({ op: \"stream-end\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleStream(req: Extract<WorkerRequest, { op: \"stream\" }>): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n for await (const chunk of engine.stream(req.messages, {\n ...req.options,\n signal: controller.signal,\n })) {\n reply({ op: \"token\", id: req.id, chunk });\n }\n reply({ op: \"stream-end\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleUnload(req: Extract<WorkerRequest, { op: \"unload\" }>): Promise<void> {\n try {\n await engine.unload();\n reply({ op: \"unloaded\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n }\n}\n\nfunction handleIsLoaded(req: Extract<WorkerRequest, { op: \"isLoaded\" }>): void {\n reply({ op: \"is-loaded\", id: req.id, value: engine.isLoaded() });\n}\n\nfunction handleAbort(req: Extract<WorkerRequest, { op: \"abort\" }>): void {\n aborts.get(req.id)?.abort();\n}\n\nself.addEventListener(\"message\", (event: MessageEvent<WorkerRequest>): void => {\n const req = event.data;\n switch (req.op) {\n case \"load\":\n void handleLoad(req);\n return;\n case \"generate\":\n void handleGenerate(req);\n return;\n case \"stream\":\n void handleStream(req);\n return;\n case \"complete\":\n void handleComplete(req);\n return;\n case \"stream-completion\":\n void handleStreamCompletion(req);\n return;\n case \"unload\":\n void handleUnload(req);\n return;\n case \"isLoaded\":\n handleIsLoaded(req);\n return;\n case \"abort\":\n handleAbort(req);\n return;\n }\n});\n"],"names":["engine"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,30 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
 
9
+ /**
10
+ * JSON Schema helpers for structured output.
11
+ *
12
+ * The SDK delegates the actual constrained decoding to the underlying
13
+ * runtime (xgrammar inside WebLLM today, ORT-Web equivalent later). These
14
+ * helpers normalize user input — turning a JS object schema into the
15
+ * JSON-string shape that WebLLM's `response_format.schema` expects — and
16
+ * parse the runtime's textual output back into typed JSON.
17
+ */
18
+ /**
19
+ * Minimal structural sanity check for a JSON Schema.
20
+ *
21
+ * Does not validate the schema against the JSON Schema meta-schema. The goal
22
+ * is to fail fast on obvious mistakes (passing a string, an array, `null`)
23
+ * before handing the value off to the runtime, where errors surface much
24
+ * later and with much worse messages.
25
+ *
26
+ * @param schema - Candidate JSON Schema object.
27
+ * @throws StructuredOutputError when `schema` is not a plain object or has
28
+ * no recognizable schema shape (`type`, `$ref`, `oneOf`, `anyOf`, `allOf`,
29
+ * `enum`).
30
+ */
31
+ export declare function assertJsonSchema(schema: unknown): asserts schema is object;
32
+
9
33
  /** Thrown when no usable backend is available on the current platform. */
10
34
  export declare class BackendNotAvailableError extends LocalmWebError {
11
35
  }
@@ -118,6 +142,18 @@ export declare class ChatReply {
118
142
  tokensGenerated: number,
119
143
  /** Why the generation loop stopped. */
120
144
  finishReason: FinishReason);
145
+ /**
146
+ * Parse {@link ChatReply.text} as JSON.
147
+ *
148
+ * Intended for replies generated with `json: true` or `jsonSchema`.
149
+ * The result is cast to `T` without runtime validation; pair with Zod /
150
+ * Ajv on the call site if you need to verify the schema.
151
+ *
152
+ * @typeParam T - Expected parsed shape.
153
+ * @returns The parsed JSON value.
154
+ * @throws StructuredOutputError if the text is not valid JSON.
155
+ */
156
+ json<T = unknown>(): T;
121
157
  }
122
158
 
123
159
  /**
@@ -208,6 +244,17 @@ export declare class CompletionResult {
208
244
  tokensGenerated: number,
209
245
  /** Why the generation loop stopped. */
210
246
  finishReason: FinishReason);
247
+ /**
248
+ * Parse {@link CompletionResult.text} as JSON.
249
+ *
250
+ * Intended for completions generated with `json: true` or `jsonSchema`.
251
+ * The result is cast to `T` without runtime validation.
252
+ *
253
+ * @typeParam T - Expected parsed shape.
254
+ * @returns The parsed JSON value.
255
+ * @throws StructuredOutputError if the text is not valid JSON.
256
+ */
257
+ json<T = unknown>(): T;
211
258
  }
212
259
 
213
260
  /**
@@ -423,8 +470,23 @@ export declare interface GenerationOptions {
423
470
  /** Cancellation signal. When triggered, the engine stops generation. */
424
471
  signal?: AbortSignal;
425
472
  /**
426
- * JSON Schema for structured output. The engine constrains decoding to
427
- * produce a string parseable as JSON matching the schema. Planned for v0.4.
473
+ * Force the engine to emit a string parseable as JSON.
474
+ *
475
+ * When `true` (and `jsonSchema` is not also set), the engine maps to
476
+ * WebLLM's `response_format: { type: "json_object" }` — the model is free
477
+ * to choose any JSON shape, but the output is guaranteed to parse.
478
+ *
479
+ * Ignored when {@link GenerationOptions.jsonSchema} is set.
480
+ */
481
+ json?: boolean;
482
+ /**
483
+ * JSON Schema for structured output. When set, the engine constrains
484
+ * decoding (xgrammar inside WebLLM) so the output parses as JSON matching
485
+ * the schema. Takes priority over {@link GenerationOptions.json}.
486
+ *
487
+ * The schema is passed verbatim to the runtime — the SDK does not validate
488
+ * the parsed value against it. Use Ajv/Zod on the consumer side if you
489
+ * need runtime validation in addition to constrained decoding.
428
490
  */
429
491
  jsonSchema?: object;
430
492
  }
@@ -677,6 +739,17 @@ export declare interface ModelPreset {
677
739
  description: string;
678
740
  }
679
741
 
742
+ /**
743
+ * Parse the textual output of a structured-decoding generation as JSON.
744
+ *
745
+ * @typeParam T - The expected parsed shape. The function does not validate
746
+ * the parsed value against `T`; that is the caller's responsibility.
747
+ * @param text - Raw text returned by the engine.
748
+ * @returns The parsed JSON value cast to `T`.
749
+ * @throws StructuredOutputError when the text is not valid JSON.
750
+ */
751
+ export declare function parseStructuredOutput<T = unknown>(text: string): T;
752
+
680
753
  /** Callback signature for model load progress. */
681
754
  export declare type ProgressCallback = (progress: ModelLoadProgress) => void;
682
755
 
@@ -856,6 +929,30 @@ export declare type Role = "system" | "user" | "assistant" | "tool";
856
929
  */
857
930
  declare type SerializableGenerationOptions = Omit<GenerationOptions, "signal">;
858
931
 
932
+ /**
933
+ * Serialize a JSON Schema object for the WebLLM `response_format.schema`
934
+ * field.
935
+ *
936
+ * WebLLM expects the schema as a JSON-encoded string (xgrammar parses it
937
+ * server-side). Validates the shape via {@link assertJsonSchema} first.
938
+ *
939
+ * @param schema - JSON Schema object.
940
+ * @returns The schema serialized as a JSON string.
941
+ * @throws StructuredOutputError when `schema` is not a recognizable JSON
942
+ * Schema shape.
943
+ */
944
+ export declare function serializeJsonSchema(schema: unknown): string;
945
+
946
+ /**
947
+ * Thrown when structured output (JSON mode or JSON Schema constrained
948
+ * decoding) fails to parse as valid JSON.
949
+ *
950
+ * Wraps the underlying `SyntaxError` from `JSON.parse` so consumers can
951
+ * distinguish SDK-issued failures from unrelated runtime exceptions.
952
+ */
953
+ export declare class StructuredOutputError extends LocalmWebError {
954
+ }
955
+
859
956
  /**
860
957
  * Wrap an async iterable so that each `TokenChunk` is also passed to a
861
958
  * caller-supplied side-effect callback before being yielded downstream.
package/dist/index.js CHANGED
@@ -30,6 +30,43 @@ class QuotaExceededError extends LocalmWebError {
30
30
  }
31
31
  class BackendNotAvailableError extends LocalmWebError {
32
32
  }
33
+ class StructuredOutputError extends LocalmWebError {
34
+ }
35
+ function assertJsonSchema(schema) {
36
+ if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
37
+ throw new StructuredOutputError("jsonSchema must be a plain object describing a JSON Schema.");
38
+ }
39
+ const keys = Object.keys(schema);
40
+ const recognized = [
41
+ "type",
42
+ "$ref",
43
+ "oneOf",
44
+ "anyOf",
45
+ "allOf",
46
+ "enum",
47
+ "const",
48
+ "properties"
49
+ ];
50
+ if (!keys.some((key) => recognized.includes(key))) {
51
+ throw new StructuredOutputError(
52
+ "jsonSchema does not look like a JSON Schema (missing type/$ref/oneOf/anyOf/allOf/enum/const/properties)."
53
+ );
54
+ }
55
+ }
56
+ function serializeJsonSchema(schema) {
57
+ assertJsonSchema(schema);
58
+ return JSON.stringify(schema);
59
+ }
60
+ function parseStructuredOutput(text) {
61
+ try {
62
+ return JSON.parse(text);
63
+ } catch (err) {
64
+ throw new StructuredOutputError(
65
+ "Engine output is not valid JSON. The model may have ignored the constrained decoding directive.",
66
+ err
67
+ );
68
+ }
69
+ }
33
70
  let webllmModulePromise = null;
34
71
  async function loadWebLLM() {
35
72
  if (!webllmModulePromise) {
@@ -47,6 +84,15 @@ function buildSamplingParams(options) {
47
84
  if (options.topP !== void 0) params.top_p = options.topP;
48
85
  return params;
49
86
  }
87
+ function buildResponseFormat(options) {
88
+ if (options.jsonSchema !== void 0) {
89
+ return { type: "json_object", schema: serializeJsonSchema(options.jsonSchema) };
90
+ }
91
+ if (options.json) {
92
+ return { type: "json_object" };
93
+ }
94
+ return void 0;
95
+ }
50
96
  function toChatMessages(messages) {
51
97
  return messages.map((m) => {
52
98
  switch (m.role) {
@@ -101,10 +147,12 @@ class WebLLMEngine {
101
147
  if (options.signal?.aborted) {
102
148
  throw new GenerationAbortedError("Generation aborted before start.");
103
149
  }
150
+ const responseFormat = buildResponseFormat(options);
104
151
  const completion = await engine.chat.completions.create({
105
152
  ...buildSamplingParams(options),
106
153
  messages: toChatMessages(messages),
107
- stream: false
154
+ stream: false,
155
+ ...responseFormat ? { response_format: responseFormat } : {}
108
156
  });
109
157
  return completion.choices[0]?.message?.content ?? "";
110
158
  }
@@ -113,10 +161,12 @@ class WebLLMEngine {
113
161
  if (options.signal?.aborted) {
114
162
  throw new GenerationAbortedError("Generation aborted before start.");
115
163
  }
164
+ const responseFormat = buildResponseFormat(options);
116
165
  const completion = await engine.chat.completions.create({
117
166
  ...buildSamplingParams(options),
118
167
  messages: toChatMessages(messages),
119
- stream: true
168
+ stream: true,
169
+ ...responseFormat ? { response_format: responseFormat } : {}
120
170
  });
121
171
  let index = 0;
122
172
  let finished = false;
@@ -150,10 +200,12 @@ class WebLLMEngine {
150
200
  if (options.signal?.aborted) {
151
201
  throw new GenerationAbortedError("Generation aborted before start.");
152
202
  }
203
+ const responseFormat = buildResponseFormat(options);
153
204
  const completion = await engine.completions.create({
154
205
  ...buildSamplingParams(options),
155
206
  prompt,
156
- stream: false
207
+ stream: false,
208
+ ...responseFormat ? { response_format: responseFormat } : {}
157
209
  });
158
210
  return completion.choices[0]?.text ?? "";
159
211
  }
@@ -162,10 +214,12 @@ class WebLLMEngine {
162
214
  if (options.signal?.aborted) {
163
215
  throw new GenerationAbortedError("Generation aborted before start.");
164
216
  }
217
+ const responseFormat = buildResponseFormat(options);
165
218
  const completion = await engine.completions.create({
166
219
  ...buildSamplingParams(options),
167
220
  prompt,
168
- stream: true
221
+ stream: true,
222
+ ...responseFormat ? { response_format: responseFormat } : {}
169
223
  });
170
224
  let index = 0;
171
225
  let finished = false;
@@ -583,7 +637,7 @@ function listSupportedRerankerModels() {
583
637
  function createInferenceWorker() {
584
638
  return new Worker(new URL(
585
639
  /* @vite-ignore */
586
- "/assets/inference.worker-CwvQtobb.js",
640
+ "/assets/inference.worker-DZbXKJZY.js",
587
641
  import.meta.url
588
642
  ), {
589
643
  type: "module"
@@ -633,6 +687,20 @@ class ChatReply {
633
687
  this.tokensGenerated = tokensGenerated;
634
688
  this.finishReason = finishReason;
635
689
  }
690
+ /**
691
+ * Parse {@link ChatReply.text} as JSON.
692
+ *
693
+ * Intended for replies generated with `json: true` or `jsonSchema`.
694
+ * The result is cast to `T` without runtime validation; pair with Zod /
695
+ * Ajv on the call site if you need to verify the schema.
696
+ *
697
+ * @typeParam T - Expected parsed shape.
698
+ * @returns The parsed JSON value.
699
+ * @throws StructuredOutputError if the text is not valid JSON.
700
+ */
701
+ json() {
702
+ return parseStructuredOutput(this.text);
703
+ }
636
704
  }
637
705
  class CompletionResult {
638
706
  constructor(text, prompt, tokensGenerated, finishReason) {
@@ -641,6 +709,19 @@ class CompletionResult {
641
709
  this.tokensGenerated = tokensGenerated;
642
710
  this.finishReason = finishReason;
643
711
  }
712
+ /**
713
+ * Parse {@link CompletionResult.text} as JSON.
714
+ *
715
+ * Intended for completions generated with `json: true` or `jsonSchema`.
716
+ * The result is cast to `T` without runtime validation.
717
+ *
718
+ * @typeParam T - Expected parsed shape.
719
+ * @returns The parsed JSON value.
720
+ * @throws StructuredOutputError if the text is not valid JSON.
721
+ */
722
+ json() {
723
+ return parseStructuredOutput(this.text);
724
+ }
644
725
  }
645
726
  class Chat extends LMTask {
646
727
  history = [];
@@ -1104,7 +1185,7 @@ async function* tap(stream, onChunk) {
1104
1185
  yield chunk;
1105
1186
  }
1106
1187
  }
1107
- const VERSION = "0.3.0";
1188
+ const VERSION = "0.4.0";
1108
1189
  export {
1109
1190
  BackendNotAvailableError,
1110
1191
  Chat,
@@ -1123,18 +1204,22 @@ export {
1123
1204
  QuotaExceededError,
1124
1205
  RERANKER_PRESETS,
1125
1206
  Reranker,
1207
+ StructuredOutputError,
1126
1208
  UnknownModelError,
1127
1209
  VERSION,
1128
1210
  WebGPUUnavailableError,
1129
1211
  WorkerEngine,
1212
+ assertJsonSchema,
1130
1213
  collectStream,
1131
1214
  createInferenceWorker,
1132
1215
  listSupportedEmbeddingModels,
1133
1216
  listSupportedModels,
1134
1217
  listSupportedRerankerModels,
1218
+ parseStructuredOutput,
1135
1219
  resolveEmbeddingPreset,
1136
1220
  resolveModelPreset,
1137
1221
  resolveRerankerPreset,
1222
+ serializeJsonSchema,
1138
1223
  tap
1139
1224
  };
1140
1225
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/core/load-phase.ts","../src/core/exceptions.ts","../src/core/webllm-engine.ts","../src/worker/protocol.ts","../src/core/worker-engine.ts","../src/presets/models.ts","../src/worker/create-worker.ts","../src/tasks/lm-task.ts","../src/results.ts","../src/tasks/chat.ts","../src/tasks/completion.ts","../src/tasks/embeddings.ts","../src/tasks/reranker.ts","../src/cache/model-cache.ts","../src/streaming/token-stream.ts","../src/index.ts"],"sourcesContent":["import type { ModelLoadPhase } from \"../types\";\n\nconst DOWNLOAD_PATTERN: RegExp = /\\b(fetch|download|loading from cache|cache hit|param)/i;\nconst COMPILE_PATTERN: RegExp = /\\b(compil|shader|kernel|tensor|init|allocat|warm)/i;\n\n/**\n * Classify a runtime status text into a {@link ModelLoadPhase}.\n *\n * Heuristic: match download-related verbs first (network or cache hits are\n * treated as `downloading`), then compile-related verbs. Anything else falls\n * back to the generic `loading` bucket. The `ready` phase is never returned\n * here — callers emit it explicitly when the load resolves.\n *\n * @param text - The raw status string from the runtime.\n * @returns The classified phase.\n */\nexport function classifyLoadPhase(text: string): ModelLoadPhase {\n if (DOWNLOAD_PATTERN.test(text)) return \"downloading\";\n if (COMPILE_PATTERN.test(text)) return \"compiling\";\n return \"loading\";\n}\n","/**\n * Error hierarchy for localm-web.\n *\n * All errors thrown by the SDK extend `LocalmWebError` so consumers can\n * distinguish SDK errors from unrelated runtime errors with a single\n * `instanceof` check.\n */\n\n/** Base class for every error raised by localm-web. */\nexport class LocalmWebError extends Error {\n /**\n * @param message - Human-readable description of the error.\n * @param cause - Underlying error, if any.\n */\n constructor(\n message: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/** Thrown when WebGPU is required but not available in the host browser. */\nexport class WebGPUUnavailableError extends LocalmWebError {}\n\n/** Thrown when a model fails to load (network, parsing, runtime init). */\nexport class ModelLoadError extends LocalmWebError {}\n\n/** Thrown when an inference call is made before a model has loaded. */\nexport class ModelNotLoadedError extends LocalmWebError {}\n\n/** Thrown when a model id is not present in the curated registry. */\nexport class UnknownModelError extends LocalmWebError {}\n\n/** Thrown when generation is aborted via an `AbortSignal`. */\nexport class GenerationAbortedError extends LocalmWebError {}\n\n/** Thrown when the browser denies storage quota for the model cache. */\nexport class QuotaExceededError extends LocalmWebError {}\n\n/** Thrown when no usable backend is available on the current platform. */\nexport class BackendNotAvailableError extends LocalmWebError {}\n","import type { Engine } from \"./engine\";\nimport { classifyLoadPhase } from \"./load-phase\";\nimport type { GenerationOptions, Message, ProgressCallback, TokenChunk } from \"../types\";\nimport {\n GenerationAbortedError,\n ModelLoadError,\n ModelNotLoadedError,\n WebGPUUnavailableError,\n} from \"./exceptions\";\n\ntype WebLLMModule = typeof import(\"@mlc-ai/web-llm\");\ntype MLCEngine = import(\"@mlc-ai/web-llm\").MLCEngineInterface;\ntype ChatCompletionMessageParam = import(\"@mlc-ai/web-llm\").ChatCompletionMessageParam;\n\nlet webllmModulePromise: Promise<WebLLMModule> | null = null;\n\nasync function loadWebLLM(): Promise<WebLLMModule> {\n if (!webllmModulePromise) {\n webllmModulePromise = import(\"@mlc-ai/web-llm\");\n }\n return webllmModulePromise;\n}\n\nfunction isWebGPUAvailable(): boolean {\n return typeof navigator !== \"undefined\" && \"gpu\" in navigator;\n}\n\ninterface SamplingParams {\n max_tokens?: number;\n temperature?: number;\n top_p?: number;\n}\n\nfunction buildSamplingParams(options: GenerationOptions): SamplingParams {\n const params: SamplingParams = {};\n if (options.maxTokens !== undefined) params.max_tokens = options.maxTokens;\n if (options.temperature !== undefined) params.temperature = options.temperature;\n if (options.topP !== undefined) params.top_p = options.topP;\n return params;\n}\n\nfunction toChatMessages(messages: Message[]): ChatCompletionMessageParam[] {\n return messages.map((m): ChatCompletionMessageParam => {\n switch (m.role) {\n case \"system\":\n return { role: \"system\", content: m.content };\n case \"user\":\n return { role: \"user\", content: m.content };\n case \"assistant\":\n return { role: \"assistant\", content: m.content };\n case \"tool\":\n return { role: \"tool\", content: m.content, tool_call_id: m.name ?? \"\" };\n }\n });\n}\n\n/**\n * Inference engine backed by [WebLLM (MLC)](https://github.com/mlc-ai/web-llm).\n *\n * Requires WebGPU. The fallback path planned for v0.5 will route to ORT-Web\n * when WebGPU is missing.\n */\nexport class WebLLMEngine implements Engine {\n private engine: MLCEngine | null = null;\n\n isLoaded(): boolean {\n return this.engine !== null;\n }\n\n async load(modelId: string, onProgress?: ProgressCallback): Promise<void> {\n if (!isWebGPUAvailable()) {\n throw new WebGPUUnavailableError(\n \"WebGPU is not available in this browser. The ORT-Web fallback is planned for v0.5.\"\n );\n }\n const webllm = await loadWebLLM();\n try {\n this.engine = await webllm.CreateMLCEngine(modelId, {\n initProgressCallback: (report): void => {\n onProgress?.({\n progress: report.progress,\n text: report.text,\n loaded: 0,\n total: 0,\n phase: classifyLoadPhase(report.text),\n });\n },\n });\n onProgress?.({\n progress: 1,\n text: \"Model ready.\",\n loaded: 0,\n total: 0,\n phase: \"ready\",\n });\n } catch (err) {\n throw new ModelLoadError(`Failed to load model \"${modelId}\".`, err);\n }\n }\n\n async generate(messages: Message[], options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: false,\n });\n return completion.choices[0]?.message?.content ?? \"\";\n }\n\n async *stream(messages: Message[], options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: true,\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.delta?.content ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming generation failed.\", err);\n }\n }\n\n async complete(prompt: string, options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: false,\n });\n return completion.choices[0]?.text ?? \"\";\n }\n\n async *streamCompletion(\n prompt: string,\n options: GenerationOptions = {}\n ): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: true,\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.text ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming completion failed.\", err);\n }\n }\n\n async unload(): Promise<void> {\n if (this.engine) {\n await this.engine.unload();\n this.engine = null;\n }\n }\n\n private requireEngine(): MLCEngine {\n if (!this.engine) {\n throw new ModelNotLoadedError(\"Engine not loaded. Call load() before generation.\");\n }\n return this.engine;\n }\n}\n","import type { GenerationOptions, Message, ModelLoadProgress, TokenChunk } from \"../types\";\n\n/**\n * Subset of {@link GenerationOptions} that survives `postMessage`.\n *\n * `AbortSignal` cannot be cloned across the worker boundary, so it is replaced\n * by a separate {@link AbortRequest} message keyed on the same operation id.\n */\nexport type SerializableGenerationOptions = Omit<GenerationOptions, \"signal\">;\n\n/** Strip `signal` from a {@link GenerationOptions} before posting it. */\nexport function toSerializableOptions(\n options: GenerationOptions = {}\n): SerializableGenerationOptions {\n const { signal: _signal, ...rest } = options;\n void _signal;\n return rest;\n}\n\n/** Operation request sent from the main thread to the worker. */\nexport type WorkerRequest =\n | { op: \"load\"; id: number; modelId: string }\n | {\n op: \"generate\";\n id: number;\n messages: Message[];\n options: SerializableGenerationOptions;\n }\n | {\n op: \"stream\";\n id: number;\n messages: Message[];\n options: SerializableGenerationOptions;\n }\n | {\n op: \"complete\";\n id: number;\n prompt: string;\n options: SerializableGenerationOptions;\n }\n | {\n op: \"stream-completion\";\n id: number;\n prompt: string;\n options: SerializableGenerationOptions;\n }\n | { op: \"abort\"; id: number }\n | { op: \"unload\"; id: number }\n | { op: \"isLoaded\"; id: number };\n\n/** Operation response sent from the worker back to the main thread. */\nexport type WorkerResponse =\n | { op: \"loaded\"; id: number }\n | { op: \"generated\"; id: number; text: string }\n | { op: \"progress\"; id: number; payload: ModelLoadProgress }\n | { op: \"token\"; id: number; chunk: TokenChunk }\n | { op: \"stream-end\"; id: number }\n | { op: \"error\"; id: number; name: string; message: string }\n | { op: \"unloaded\"; id: number }\n | { op: \"is-loaded\"; id: number; value: boolean };\n\n/** Subset of `Worker` we depend on. Lets tests inject a mock. */\nexport interface WorkerLike {\n postMessage(message: WorkerRequest): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent<WorkerResponse>) => void): void;\n removeEventListener(\n type: \"message\",\n listener: (event: MessageEvent<WorkerResponse>) => void\n ): void;\n terminate(): void;\n}\n\n/** Internal alias used when the message direction is irrelevant (logging, debug). */\nexport type AbortRequest = Extract<WorkerRequest, { op: \"abort\" }>;\n","import { GenerationAbortedError, ModelLoadError, ModelNotLoadedError } from \"./exceptions\";\nimport type { Engine } from \"./engine\";\nimport type { GenerationOptions, Message, ProgressCallback, TokenChunk } from \"../types\";\nimport {\n toSerializableOptions,\n type WorkerLike,\n type WorkerRequest,\n type WorkerResponse,\n} from \"../worker/protocol\";\n\ninterface PendingGenerate {\n resolve: (text: string) => void;\n reject: (err: Error) => void;\n}\n\ninterface PendingStream {\n push: (chunk: TokenChunk) => void;\n end: () => void;\n fail: (err: Error) => void;\n}\n\n/**\n * Engine implementation that proxies all calls to a Web Worker.\n *\n * The worker holds the actual {@link WebLLMEngine}; this class is a thin RPC\n * shell that serializes requests, tracks pending operations by a numeric id,\n * and turns worker responses back into Promises and async iterables.\n *\n * Use {@link createInferenceWorker} to obtain a real worker. Tests can pass a\n * {@link WorkerLike} mock implementing the same `postMessage` /\n * `addEventListener` surface.\n */\nexport class WorkerEngine implements Engine {\n private nextId: number = 1;\n private loaded: boolean = false;\n private currentLoad: { resolve: () => void; reject: (e: Error) => void } | null = null;\n private currentLoadId: number = 0;\n private currentLoadProgress: ProgressCallback | undefined = undefined;\n private currentUnload: { resolve: () => void; reject: (e: Error) => void } | null = null;\n private currentUnloadId: number = 0;\n private pendingGenerates: Map<number, PendingGenerate> = new Map();\n private pendingStreams: Map<number, PendingStream> = new Map();\n\n private readonly listener: (event: MessageEvent<WorkerResponse>) => void;\n\n constructor(private readonly worker: WorkerLike) {\n this.listener = (event): void => this.handleMessage(event.data);\n this.worker.addEventListener(\"message\", this.listener);\n }\n\n isLoaded(): boolean {\n return this.loaded;\n }\n\n async load(modelId: string, onProgress?: ProgressCallback): Promise<void> {\n if (this.currentLoad) {\n throw new ModelLoadError(\"Another load is already in progress.\");\n }\n const id: number = this.allocateId();\n this.currentLoadId = id;\n this.currentLoadProgress = onProgress;\n return new Promise<void>((resolve, reject) => {\n this.currentLoad = { resolve, reject };\n this.send({ op: \"load\", id, modelId });\n });\n }\n\n async generate(messages: Message[], options: GenerationOptions = {}): Promise<string> {\n const id: number = this.allocateId();\n return new Promise<string>((resolve, reject) => {\n this.pendingGenerates.set(id, { resolve, reject });\n this.send({\n op: \"generate\",\n id,\n messages,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n });\n }\n\n async *stream(messages: Message[], options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const id: number = this.allocateId();\n const queue: TokenChunk[] = [];\n let done: boolean = false;\n let error: Error | null = null;\n let notify: (() => void) | null = null;\n\n const wakeup = (): void => {\n if (notify) {\n const fn = notify;\n notify = null;\n fn();\n }\n };\n\n this.pendingStreams.set(id, {\n push: (chunk): void => {\n queue.push(chunk);\n wakeup();\n },\n end: (): void => {\n done = true;\n wakeup();\n },\n fail: (err): void => {\n error = err;\n done = true;\n wakeup();\n },\n });\n\n this.send({\n op: \"stream\",\n id,\n messages,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n\n try {\n while (true) {\n if (queue.length > 0) {\n const chunk = queue.shift();\n if (chunk) yield chunk;\n continue;\n }\n if (error) throw error;\n if (done) return;\n await new Promise<void>((r) => {\n notify = r;\n });\n }\n } finally {\n this.pendingStreams.delete(id);\n }\n }\n\n async complete(prompt: string, options: GenerationOptions = {}): Promise<string> {\n const id: number = this.allocateId();\n return new Promise<string>((resolve, reject) => {\n this.pendingGenerates.set(id, { resolve, reject });\n this.send({\n op: \"complete\",\n id,\n prompt,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n });\n }\n\n async *streamCompletion(\n prompt: string,\n options: GenerationOptions = {}\n ): AsyncIterable<TokenChunk> {\n const id: number = this.allocateId();\n const queue: TokenChunk[] = [];\n let done: boolean = false;\n let error: Error | null = null;\n let notify: (() => void) | null = null;\n\n const wakeup = (): void => {\n if (notify) {\n const fn = notify;\n notify = null;\n fn();\n }\n };\n\n this.pendingStreams.set(id, {\n push: (chunk): void => {\n queue.push(chunk);\n wakeup();\n },\n end: (): void => {\n done = true;\n wakeup();\n },\n fail: (err): void => {\n error = err;\n done = true;\n wakeup();\n },\n });\n\n this.send({\n op: \"stream-completion\",\n id,\n prompt,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n\n try {\n while (true) {\n if (queue.length > 0) {\n const chunk = queue.shift();\n if (chunk) yield chunk;\n continue;\n }\n if (error) throw error;\n if (done) return;\n await new Promise<void>((r) => {\n notify = r;\n });\n }\n } finally {\n this.pendingStreams.delete(id);\n }\n }\n\n async unload(): Promise<void> {\n if (!this.loaded) return;\n if (this.currentUnload) {\n throw new ModelLoadError(\"Another unload is already in progress.\");\n }\n const id: number = this.allocateId();\n this.currentUnloadId = id;\n return new Promise<void>((resolve, reject) => {\n this.currentUnload = { resolve, reject };\n this.send({ op: \"unload\", id });\n });\n }\n\n /** Tear down the underlying worker. The engine is unusable after this. */\n terminate(): void {\n this.worker.removeEventListener(\"message\", this.listener);\n this.worker.terminate();\n this.loaded = false;\n }\n\n private allocateId(): number {\n const id = this.nextId;\n this.nextId += 1;\n return id;\n }\n\n private send(req: WorkerRequest): void {\n this.worker.postMessage(req);\n }\n\n private handleMessage(msg: WorkerResponse): void {\n switch (msg.op) {\n case \"loaded\":\n if (this.currentLoad && msg.id === this.currentLoadId) {\n this.loaded = true;\n this.currentLoad.resolve();\n this.currentLoad = null;\n this.currentLoadProgress = undefined;\n }\n return;\n case \"progress\":\n if (msg.id === this.currentLoadId) {\n this.currentLoadProgress?.(msg.payload);\n }\n return;\n case \"generated\": {\n const pending = this.pendingGenerates.get(msg.id);\n if (pending) {\n pending.resolve(msg.text);\n this.pendingGenerates.delete(msg.id);\n }\n return;\n }\n case \"token\": {\n const stream = this.pendingStreams.get(msg.id);\n stream?.push(msg.chunk);\n return;\n }\n case \"stream-end\": {\n const stream = this.pendingStreams.get(msg.id);\n stream?.end();\n return;\n }\n case \"unloaded\":\n if (this.currentUnload && msg.id === this.currentUnloadId) {\n this.loaded = false;\n this.currentUnload.resolve();\n this.currentUnload = null;\n }\n return;\n case \"is-loaded\":\n return;\n case \"error\": {\n const err = mapError(msg.name, msg.message);\n if (this.currentLoad && msg.id === this.currentLoadId) {\n this.currentLoad.reject(err);\n this.currentLoad = null;\n this.currentLoadProgress = undefined;\n return;\n }\n if (this.currentUnload && msg.id === this.currentUnloadId) {\n this.currentUnload.reject(err);\n this.currentUnload = null;\n return;\n }\n const generate = this.pendingGenerates.get(msg.id);\n if (generate) {\n generate.reject(err);\n this.pendingGenerates.delete(msg.id);\n return;\n }\n const stream = this.pendingStreams.get(msg.id);\n if (stream) {\n stream.fail(err);\n return;\n }\n return;\n }\n }\n }\n}\n\nfunction mapError(name: string, message: string): Error {\n switch (name) {\n case \"ModelLoadError\":\n return new ModelLoadError(message);\n case \"ModelNotLoadedError\":\n return new ModelNotLoadedError(message);\n case \"GenerationAbortedError\":\n return new GenerationAbortedError(message);\n default: {\n const err = new Error(message);\n err.name = name;\n return err;\n }\n }\n}\n","import type { ModelPreset } from \"../types\";\nimport { UnknownModelError } from \"../core/exceptions\";\n\n/**\n * Curated registry of supported models for v0.1.\n *\n * Each entry maps a friendly id (e.g. `\"phi-3.5-mini-int4\"`) to the underlying\n * runtime identifier and metadata. Friendly ids are stable; backend ids may\n * change as upstream MLC packages evolve.\n *\n * Only models that have been validated to load in browsers with WebGPU and\n * that fit the SLM target (≤ 4B parameters at INT4) are included.\n */\nexport const MODEL_PRESETS: Readonly<Record<string, ModelPreset>> = Object.freeze({\n \"phi-3.5-mini-int4\": {\n id: \"phi-3.5-mini-int4\",\n family: \"Phi-3.5\",\n parameters: \"3.8B\",\n quantization: \"q4f16_1\",\n webllmId: \"Phi-3.5-mini-instruct-q4f16_1-MLC\",\n contextWindow: 4096,\n description: \"Microsoft Phi-3.5 mini, INT4 quantized for browser inference.\",\n },\n \"llama-3.2-1b-int4\": {\n id: \"llama-3.2-1b-int4\",\n family: \"Llama-3.2\",\n parameters: \"1B\",\n quantization: \"q4f16_1\",\n webllmId: \"Llama-3.2-1B-Instruct-q4f16_1-MLC\",\n contextWindow: 4096,\n description: \"Meta Llama 3.2 1B Instruct, INT4 quantized.\",\n },\n \"qwen2.5-1.5b-int4\": {\n id: \"qwen2.5-1.5b-int4\",\n family: \"Qwen2.5\",\n parameters: \"1.5B\",\n quantization: \"q4f16_1\",\n webllmId: \"Qwen2.5-1.5B-Instruct-q4f16_1-MLC\",\n contextWindow: 4096,\n description: \"Alibaba Qwen 2.5 1.5B Instruct, INT4 quantized.\",\n },\n});\n\n/**\n * Resolve a friendly model id to its full preset metadata.\n *\n * @param modelId - Friendly id (e.g. `\"phi-3.5-mini-int4\"`).\n * @returns The matching preset.\n * @throws UnknownModelError if no preset matches.\n */\nexport function resolveModelPreset(modelId: string): ModelPreset {\n const preset = MODEL_PRESETS[modelId];\n if (!preset) {\n const available = Object.keys(MODEL_PRESETS).join(\", \");\n throw new UnknownModelError(`Unknown model \"${modelId}\". Available models: ${available}.`);\n }\n return preset;\n}\n\n/** Return the list of supported friendly model ids. */\nexport function listSupportedModels(): string[] {\n return Object.keys(MODEL_PRESETS);\n}\n\n/** Curated metadata for a supported embedding model. */\nexport interface EmbeddingPreset {\n /** Friendly identifier (e.g. `\"bge-small-en-v1.5\"`). */\n id: string;\n /** Family name (e.g. `\"BGE\"`). */\n family: string;\n /** Embedding dimension. */\n dimension: number;\n /** Maximum input length in tokens. */\n maxTokens: number;\n /** Identifier passed to `@huggingface/transformers`. */\n transformersId: string;\n /** Approximate quantization scheme (e.g. `\"fp32\"`, `\"int8\"`). */\n quantization: string;\n /** Short human description. */\n description: string;\n}\n\n/**\n * Curated registry of supported embedding models for v0.3.\n *\n * Each entry maps a friendly id to the underlying transformers.js model id.\n */\nexport const EMBEDDING_PRESETS: Readonly<Record<string, EmbeddingPreset>> = Object.freeze({\n \"bge-small-en-v1.5\": {\n id: \"bge-small-en-v1.5\",\n family: \"BGE\",\n dimension: 384,\n maxTokens: 512,\n transformersId: \"Xenova/bge-small-en-v1.5\",\n quantization: \"fp32\",\n description: \"BAAI BGE small English v1.5, 384-dim sentence embeddings.\",\n },\n \"bge-base-en-v1.5\": {\n id: \"bge-base-en-v1.5\",\n family: \"BGE\",\n dimension: 768,\n maxTokens: 512,\n transformersId: \"Xenova/bge-base-en-v1.5\",\n quantization: \"fp32\",\n description: \"BAAI BGE base English v1.5, 768-dim sentence embeddings.\",\n },\n});\n\n/**\n * Resolve a friendly embedding model id to its full preset metadata.\n *\n * @param modelId - Friendly id (e.g. `\"bge-small-en-v1.5\"`).\n * @returns The matching preset.\n * @throws UnknownModelError if no preset matches.\n */\nexport function resolveEmbeddingPreset(modelId: string): EmbeddingPreset {\n const preset = EMBEDDING_PRESETS[modelId];\n if (!preset) {\n const available = Object.keys(EMBEDDING_PRESETS).join(\", \");\n throw new UnknownModelError(\n `Unknown embedding model \"${modelId}\". Available models: ${available}.`\n );\n }\n return preset;\n}\n\n/** Return the list of supported embedding model ids. */\nexport function listSupportedEmbeddingModels(): string[] {\n return Object.keys(EMBEDDING_PRESETS);\n}\n\n/** Curated metadata for a supported reranker (cross-encoder) model. */\nexport interface RerankerPreset {\n /** Friendly identifier (e.g. `\"bge-reranker-base\"`). */\n id: string;\n /** Family name (e.g. `\"BGE Reranker\"`). */\n family: string;\n /** Maximum input length in tokens (combined query + document). */\n maxTokens: number;\n /** Identifier passed to `@huggingface/transformers`. */\n transformersId: string;\n /** Approximate quantization (e.g. `\"fp32\"`). */\n quantization: string;\n /** Short human description. */\n description: string;\n}\n\n/**\n * Curated registry of supported reranker models for v0.3.\n */\nexport const RERANKER_PRESETS: Readonly<Record<string, RerankerPreset>> = Object.freeze({\n \"bge-reranker-base\": {\n id: \"bge-reranker-base\",\n family: \"BGE Reranker\",\n maxTokens: 512,\n transformersId: \"Xenova/bge-reranker-base\",\n quantization: \"fp32\",\n description: \"BAAI BGE reranker base — multilingual cross-encoder.\",\n },\n});\n\n/**\n * Resolve a friendly reranker model id to its full preset metadata.\n *\n * @param modelId - Friendly id (e.g. `\"bge-reranker-base\"`).\n * @throws UnknownModelError if no preset matches.\n */\nexport function resolveRerankerPreset(modelId: string): RerankerPreset {\n const preset = RERANKER_PRESETS[modelId];\n if (!preset) {\n const available = Object.keys(RERANKER_PRESETS).join(\", \");\n throw new UnknownModelError(\n `Unknown reranker model \"${modelId}\". Available models: ${available}.`\n );\n }\n return preset;\n}\n\n/** Return the list of supported reranker model ids. */\nexport function listSupportedRerankerModels(): string[] {\n return Object.keys(RERANKER_PRESETS);\n}\n","import type { WorkerLike } from \"./protocol\";\n\n/**\n * Spawn a new inference Web Worker.\n *\n * Uses Vite/webpack-friendly `new Worker(new URL(...), { type: \"module\" })`\n * syntax. The bundler emits the worker as a separate ES module chunk.\n *\n * Consumers normally do not call this directly — `LMTask.create()` invokes it\n * when `inWorker: true` is set. It is exported for advanced scenarios (custom\n * worker management, pooling, lifecycle integration with a host app).\n *\n * @returns A {@link WorkerLike}-compatible Worker instance.\n */\nexport function createInferenceWorker(): WorkerLike {\n return new Worker(new URL(\"./inference.worker.ts\", import.meta.url), {\n type: \"module\",\n }) as unknown as WorkerLike;\n}\n","import type { Engine } from \"../core/engine\";\nimport { WebLLMEngine } from \"../core/webllm-engine\";\nimport { WorkerEngine } from \"../core/worker-engine\";\nimport { resolveModelPreset } from \"../presets/models\";\nimport { createInferenceWorker } from \"../worker/create-worker\";\nimport type { ModelPreset, ProgressCallback } from \"../types\";\n\n/** Common options accepted by every task's `create()` factory. */\nexport interface LMTaskCreateOptions {\n /** Optional callback for model load progress updates. */\n onProgress?: ProgressCallback;\n /**\n * Override the engine used for inference. Intended for testing.\n * Production callers should let the SDK pick a backend automatically.\n */\n engine?: Engine;\n /**\n * Run inference inside a Web Worker, isolating the UI thread from\n * tokenization and generation. **Default `true` from v0.3** — the\n * `WorkerEngine` is the recommended path. Pass `false` to keep\n * inference on the main thread (useful for environments without\n * `Worker` support or when debugging the runtime directly).\n *\n * Ignored when {@link engine} is provided.\n */\n inWorker?: boolean;\n}\n\n/** Internal payload returned by {@link LMTask.createEngine}. */\nexport interface ResolvedEngine {\n engine: Engine;\n preset: ModelPreset;\n}\n\n/**\n * Base class shared by all language-model tasks (`Chat` for v0.1; `Completion`,\n * `Embeddings` and `Reranker` planned for later versions).\n *\n * The base owns:\n * - resolving a friendly model id to a {@link ModelPreset};\n * - selecting and loading an {@link Engine} (defaulting to WebLLM);\n * - exposing `unload()` for cleanup.\n *\n * Subclasses add task-specific public methods (`send`, `stream`, etc.).\n */\nexport abstract class LMTask {\n protected constructor(\n /** Engine used for inference. */\n protected readonly engine: Engine,\n /** Resolved metadata for the loaded model. */\n public readonly preset: ModelPreset\n ) {}\n\n /**\n * Load a model into a backend and return the wired-up engine + preset.\n *\n * Subclasses call this from their static `create()` factories.\n *\n * @param modelId - Friendly model id from the registry.\n * @param options - Task creation options.\n */\n protected static async createEngine(\n modelId: string,\n options: LMTaskCreateOptions = {}\n ): Promise<ResolvedEngine> {\n const preset = resolveModelPreset(modelId);\n const engine = options.engine ?? LMTask.defaultEngine(options);\n if (!engine.isLoaded()) {\n await engine.load(preset.webllmId, options.onProgress);\n }\n return { engine, preset };\n }\n\n private static defaultEngine(options: LMTaskCreateOptions): Engine {\n const useWorker: boolean = options.inWorker ?? true;\n if (useWorker) {\n return new WorkerEngine(createInferenceWorker());\n }\n return new WebLLMEngine();\n }\n\n /** Release engine resources. Safe to call multiple times. */\n async unload(): Promise<void> {\n await this.engine.unload();\n }\n\n /** Whether the underlying engine has a loaded model. */\n isLoaded(): boolean {\n return this.engine.isLoaded();\n }\n}\n","import type { FinishReason, Message } from \"./types\";\n\n/**\n * Result returned by `Chat.send()`.\n *\n * Holds the assistant's textual reply, the structured assistant message\n * (already appended to the chat history), and metadata about the generation.\n */\nexport class ChatReply {\n constructor(\n /** The assistant's reply text. */\n public readonly text: string,\n /** The structured assistant message (already appended to chat history). */\n public readonly message: Message,\n /** Number of tokens generated. 0 when the engine does not report it. */\n public readonly tokensGenerated: number,\n /** Why the generation loop stopped. */\n public readonly finishReason: FinishReason\n ) {}\n}\n\n/**\n * Result returned by `Completion.predict()`.\n *\n * Holds the generated continuation text (the prompt itself is not included)\n * plus metadata about the generation loop.\n */\nexport class CompletionResult {\n constructor(\n /** The generated text (continuation only, prompt excluded). */\n public readonly text: string,\n /** The original prompt that was fed to the model. */\n public readonly prompt: string,\n /** Number of tokens generated. 0 when the engine does not report it. */\n public readonly tokensGenerated: number,\n /** Why the generation loop stopped. */\n public readonly finishReason: FinishReason\n ) {}\n}\n","import { LMTask, type LMTaskCreateOptions } from \"./lm-task\";\nimport type { Engine } from \"../core/engine\";\nimport { ChatReply } from \"../results\";\nimport type { GenerationOptions, Message, ModelPreset, TokenChunk } from \"../types\";\n\n/**\n * Multi-turn chat task.\n *\n * Maintains an in-memory conversation history and applies the chat template\n * configured for the loaded model. Use {@link Chat.create} to construct an\n * instance — the constructor is private.\n *\n * @example\n * ```ts\n * const chat = await Chat.create(\"phi-3.5-mini-int4\");\n * const reply = await chat.send(\"Explain ONNX in one sentence.\");\n * console.log(reply.text);\n * ```\n *\n * @example Streaming\n * ```ts\n * const controller = new AbortController();\n * for await (const token of chat.stream(\"Explain ONNX.\", { signal: controller.signal })) {\n * process.stdout.write(token.text);\n * }\n * ```\n */\nexport class Chat extends LMTask {\n private readonly history: Message[] = [];\n private systemPrompt: string | null = null;\n\n private constructor(engine: Engine, preset: ModelPreset) {\n super(engine, preset);\n }\n\n /**\n * Create and load a `Chat` task for the given model.\n *\n * @param modelId - Friendly model id from the registry (e.g. `\"phi-3.5-mini-int4\"`).\n * @param options - Optional creation options (progress callback, engine override).\n */\n static async create(modelId: string, options: LMTaskCreateOptions = {}): Promise<Chat> {\n const { engine, preset } = await LMTask.createEngine(modelId, options);\n return new Chat(engine, preset);\n }\n\n /** Set or replace the system prompt prepended to every conversation. */\n setSystemPrompt(prompt: string): void {\n this.systemPrompt = prompt;\n }\n\n /** Clear the system prompt. */\n clearSystemPrompt(): void {\n this.systemPrompt = null;\n }\n\n /** Reset the conversation history. The system prompt is preserved. */\n resetHistory(): void {\n this.history.length = 0;\n }\n\n /** A read-only snapshot of the conversation history. */\n getHistory(): readonly Message[] {\n return this.history.slice();\n }\n\n /**\n * Send a user message and await the full assistant reply.\n *\n * The user message and the assistant reply are appended to the history.\n *\n * @param message - The user-facing message text.\n * @param options - Generation options.\n * @returns A {@link ChatReply} with the assistant's reply.\n */\n async send(message: string, options: GenerationOptions = {}): Promise<ChatReply> {\n const messages = this.buildMessages(message);\n const text = await this.engine.generate(messages, options);\n const userMsg: Message = { role: \"user\", content: message };\n const assistantMsg: Message = { role: \"assistant\", content: text };\n this.history.push(userMsg, assistantMsg);\n return new ChatReply(text, assistantMsg, 0, \"stop\");\n }\n\n /**\n * Stream the assistant reply token-by-token as an async iterable.\n *\n * The full reply is appended to the history when the stream completes\n * normally. If the stream is aborted, neither message is appended.\n *\n * @param message - The user-facing message text.\n * @param options - Generation options including an optional `signal`.\n */\n async *stream(message: string, options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const messages = this.buildMessages(message);\n const userMsg: Message = { role: \"user\", content: message };\n let acc: string = \"\";\n for await (const chunk of this.engine.stream(messages, options)) {\n acc += chunk.text;\n yield chunk;\n }\n const assistantMsg: Message = { role: \"assistant\", content: acc };\n this.history.push(userMsg, assistantMsg);\n }\n\n private buildMessages(userMessage: string): Message[] {\n const messages: Message[] = [];\n if (this.systemPrompt) {\n messages.push({ role: \"system\", content: this.systemPrompt });\n }\n messages.push(...this.history);\n messages.push({ role: \"user\", content: userMessage });\n return messages;\n }\n}\n","import { LMTask, type LMTaskCreateOptions } from \"./lm-task\";\nimport type { Engine } from \"../core/engine\";\nimport { CompletionResult } from \"../results\";\nimport type { GenerationOptions, ModelPreset, TokenChunk } from \"../types\";\n\n/**\n * Raw text-completion task.\n *\n * Unlike {@link Chat}, `Completion` does not maintain a conversation history\n * and does not apply a chat template. The prompt is fed to the model verbatim\n * and the model continues it. Useful for \"Once upon a time…\" style generation,\n * code completion, or any scenario where chat formatting would interfere.\n *\n * Use {@link Completion.create} to construct an instance — the constructor is\n * private.\n *\n * @example\n * ```ts\n * const comp = await Completion.create(\"qwen2.5-1.5b-int4\");\n * const result = await comp.predict(\"Once upon a time\", { maxTokens: 50 });\n * console.log(result.text);\n * ```\n *\n * @example Streaming\n * ```ts\n * const controller = new AbortController();\n * for await (const token of comp.stream(\"def fibonacci(n):\", { signal: controller.signal })) {\n * process.stdout.write(token.text);\n * }\n * ```\n */\nexport class Completion extends LMTask {\n private constructor(engine: Engine, preset: ModelPreset) {\n super(engine, preset);\n }\n\n /**\n * Create and load a `Completion` task for the given model.\n *\n * @param modelId - Friendly model id from the registry (e.g. `\"qwen2.5-1.5b-int4\"`).\n * @param options - Optional creation options (progress callback, engine override).\n */\n static async create(modelId: string, options: LMTaskCreateOptions = {}): Promise<Completion> {\n const { engine, preset } = await LMTask.createEngine(modelId, options);\n return new Completion(engine, preset);\n }\n\n /**\n * Generate a continuation for the given prompt.\n *\n * @param prompt - Raw text fed to the model.\n * @param options - Generation options.\n * @returns A {@link CompletionResult} with the generated continuation.\n */\n async predict(prompt: string, options: GenerationOptions = {}): Promise<CompletionResult> {\n const text = await this.engine.complete(prompt, options);\n return new CompletionResult(text, prompt, 0, \"stop\");\n }\n\n /**\n * Stream a continuation for the given prompt as an async iterable of token\n * chunks.\n *\n * @param prompt - Raw text fed to the model.\n * @param options - Generation options including an optional `signal`.\n */\n async *stream(prompt: string, options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n for await (const chunk of this.engine.streamCompletion(prompt, options)) {\n yield chunk;\n }\n }\n}\n","import { ModelLoadError, ModelNotLoadedError } from \"../core/exceptions\";\nimport { resolveEmbeddingPreset, type EmbeddingPreset } from \"../presets/models\";\nimport type { ProgressCallback } from \"../types\";\n\n/** Options accepted by {@link Embeddings.create}. */\nexport interface EmbeddingsCreateOptions {\n /** Optional callback for model load progress updates. */\n onProgress?: ProgressCallback;\n /** Override the embedding pipeline. Intended for testing. */\n pipeline?: EmbedPipeline;\n}\n\n/** Options accepted by {@link Embeddings.embed}. */\nexport interface EmbedOptions {\n /** L2-normalize each vector. Recommended for cosine similarity downstream. Default `true`. */\n normalize?: boolean;\n /** Pooling strategy. BGE-style models use `\"cls\"`. Most sentence-transformers use `\"mean\"`. Default `\"mean\"`. */\n pooling?: \"mean\" | \"cls\";\n}\n\n/**\n * Minimal pipeline contract that {@link Embeddings} depends on.\n *\n * The default implementation wraps `@huggingface/transformers`. Tests inject\n * a fake satisfying the same shape — they never load the real runtime.\n */\nexport interface EmbedPipeline {\n /**\n * Run the encoder on a batch of inputs and return raw vectors.\n *\n * @param texts - Input strings.\n * @param options - Pooling + normalization passed to the underlying pipeline.\n */\n embed(texts: string[], options: Required<EmbedOptions>): Promise<number[][]>;\n /** Release pipeline resources. */\n unload?(): Promise<void>;\n}\n\ntype TransformersModule = typeof import(\"@huggingface/transformers\");\n\nlet transformersModulePromise: Promise<TransformersModule> | null = null;\n\nasync function loadTransformers(): Promise<TransformersModule> {\n if (!transformersModulePromise) {\n transformersModulePromise = import(\"@huggingface/transformers\");\n }\n return transformersModulePromise;\n}\n\nasync function buildDefaultPipeline(\n preset: EmbeddingPreset,\n onProgress?: ProgressCallback\n): Promise<EmbedPipeline> {\n const transformers = await loadTransformers();\n try {\n const pipe = await transformers.pipeline(\"feature-extraction\", preset.transformersId, {\n progress_callback: (report: unknown): void => {\n if (!onProgress) return;\n const r = report as { progress?: number; status?: string };\n onProgress({\n progress: typeof r.progress === \"number\" ? r.progress / 100 : 0,\n text: r.status ?? \"\",\n loaded: 0,\n total: 0,\n phase: \"downloading\",\n });\n },\n });\n return {\n async embed(texts, options): Promise<number[][]> {\n const output = await pipe(texts, {\n pooling: options.pooling,\n normalize: options.normalize,\n });\n return output.tolist();\n },\n async unload(): Promise<void> {\n if (typeof (pipe as { dispose?: () => Promise<void> }).dispose === \"function\") {\n await (pipe as unknown as { dispose: () => Promise<void> }).dispose();\n }\n },\n };\n } catch (err) {\n throw new ModelLoadError(`Failed to load embedding model \"${preset.id}\".`, err);\n }\n}\n\n/**\n * Sentence embedding task backed by `@huggingface/transformers`.\n *\n * Use {@link Embeddings.create} to construct an instance — the constructor is\n * private. The default backend lazy-loads the transformers.js runtime; tests\n * inject a {@link EmbedPipeline} mock instead.\n *\n * @example\n * ```ts\n * const emb = await Embeddings.create(\"bge-small-en-v1.5\");\n * const vectors = await emb.embed([\"hello world\", \"another sentence\"]);\n * console.log(vectors[0].length); // 384\n * ```\n */\nexport class Embeddings {\n private constructor(\n private readonly pipeline: EmbedPipeline,\n /** Resolved metadata for the loaded model. */\n public readonly preset: EmbeddingPreset\n ) {}\n\n /**\n * Create and load an `Embeddings` task for the given model.\n *\n * @param modelId - Friendly id from the embedding registry.\n * @param options - Optional creation options.\n * @throws UnknownModelError if `modelId` is not in the registry.\n * @throws ModelLoadError if the underlying pipeline fails to load.\n */\n static async create(modelId: string, options: EmbeddingsCreateOptions = {}): Promise<Embeddings> {\n const preset = resolveEmbeddingPreset(modelId);\n const pipeline = options.pipeline ?? (await buildDefaultPipeline(preset, options.onProgress));\n return new Embeddings(pipeline, preset);\n }\n\n /**\n * Encode an array of strings into dense vectors.\n *\n * Returns one vector per input, in the same order. Empty input array\n * returns an empty array (no error).\n *\n * @param texts - Input strings.\n * @param options - Pooling + normalization. Defaults: `pooling: \"mean\"`, `normalize: true`.\n */\n async embed(texts: string[], options: EmbedOptions = {}): Promise<number[][]> {\n if (texts.length === 0) return [];\n if (!this.pipeline) {\n throw new ModelNotLoadedError(\"Embeddings pipeline not initialized.\");\n }\n const merged: Required<EmbedOptions> = {\n normalize: options.normalize ?? true,\n pooling: options.pooling ?? \"mean\",\n };\n return this.pipeline.embed(texts, merged);\n }\n\n /**\n * Convenience: encode a single string and return its vector.\n *\n * @param text - Input string.\n * @param options - Forwarded to {@link Embeddings.embed}.\n */\n async embedSingle(text: string, options: EmbedOptions = {}): Promise<number[]> {\n const [vec] = await this.embed([text], options);\n if (!vec) {\n throw new ModelLoadError(\"Embedding pipeline returned no result.\");\n }\n return vec;\n }\n\n /** Embedding dimension exposed by the loaded model. */\n get dimension(): number {\n return this.preset.dimension;\n }\n\n /** Release pipeline resources. Safe to call multiple times. */\n async unload(): Promise<void> {\n await this.pipeline.unload?.();\n }\n}\n","import { ModelLoadError, ModelNotLoadedError } from \"../core/exceptions\";\nimport { resolveRerankerPreset, type RerankerPreset } from \"../presets/models\";\nimport type { ProgressCallback } from \"../types\";\n\n/** Options accepted by {@link Reranker.create}. */\nexport interface RerankerCreateOptions {\n /** Optional callback for model load progress updates. */\n onProgress?: ProgressCallback;\n /** Override the rerank pipeline. Intended for testing. */\n pipeline?: RerankPipeline;\n}\n\n/** Options accepted by {@link Reranker.score}. */\nexport interface RerankOptions {\n /**\n * Apply sigmoid to logits to map scores into `[0, 1]`. Recommended when the\n * downstream code uses scores as probabilities. Default `false` (raw logits).\n */\n sigmoid?: boolean;\n}\n\n/** A document paired with its score, for {@link Reranker.rank}. */\nexport interface RankedDocument {\n /** The document text. */\n text: string;\n /** Score from the cross-encoder. */\n score: number;\n /** Original index of the document in the input array. */\n index: number;\n}\n\n/**\n * Minimal pipeline contract that {@link Reranker} depends on.\n *\n * The default implementation wraps `@huggingface/transformers`. Tests inject\n * a fake satisfying the same shape — they never load the real runtime.\n */\nexport interface RerankPipeline {\n /**\n * Score `(query, doc)` pairs. One score per doc, in the same order.\n *\n * @param query - Single query string.\n * @param docs - Documents to score against the query.\n */\n score(query: string, docs: string[]): Promise<number[]>;\n /** Release pipeline resources. */\n unload?(): Promise<void>;\n}\n\ntype TransformersModule = typeof import(\"@huggingface/transformers\");\n\nlet transformersModulePromise: Promise<TransformersModule> | null = null;\n\nasync function loadTransformers(): Promise<TransformersModule> {\n if (!transformersModulePromise) {\n transformersModulePromise = import(\"@huggingface/transformers\");\n }\n return transformersModulePromise;\n}\n\nfunction sigmoidValue(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\nasync function buildDefaultPipeline(\n preset: RerankerPreset,\n onProgress?: ProgressCallback\n): Promise<RerankPipeline> {\n const transformers = await loadTransformers();\n try {\n const tokenizer = await transformers.AutoTokenizer.from_pretrained(preset.transformersId, {\n progress_callback: (report: unknown): void => {\n if (!onProgress) return;\n const r = report as { progress?: number; status?: string };\n onProgress({\n progress: typeof r.progress === \"number\" ? r.progress / 100 : 0,\n text: r.status ?? \"\",\n loaded: 0,\n total: 0,\n phase: \"downloading\",\n });\n },\n });\n const model = await transformers.AutoModelForSequenceClassification.from_pretrained(\n preset.transformersId,\n {\n progress_callback: (report: unknown): void => {\n if (!onProgress) return;\n const r = report as { progress?: number; status?: string };\n onProgress({\n progress: typeof r.progress === \"number\" ? r.progress / 100 : 0,\n text: r.status ?? \"\",\n loaded: 0,\n total: 0,\n phase: \"downloading\",\n });\n },\n }\n );\n return {\n async score(query, docs): Promise<number[]> {\n if (docs.length === 0) return [];\n const queries: string[] = docs.map(() => query);\n // `transformers.js` AutoTokenizer accepts `(text, options)` where\n // `options.text_pair` carries the second sequence; pair-input typing\n // isn't exported, so we cast through `unknown`.\n const tokenize = tokenizer as unknown as (\n text: string[],\n options: Record<string, unknown>\n ) => Record<string, unknown>;\n const inputs = tokenize(queries, {\n text_pair: docs,\n padding: true,\n truncation: true,\n max_length: preset.maxTokens,\n });\n const callModel = model as unknown as (\n inputs: Record<string, unknown>\n ) => Promise<{ logits: { tolist: () => number[][] } }>;\n const outputs = await callModel(inputs);\n const logits: number[][] = outputs.logits.tolist();\n return logits.map((row) => row[0] ?? 0);\n },\n async unload(): Promise<void> {\n const m = model as unknown as { dispose?: () => Promise<unknown> };\n if (typeof m.dispose === \"function\") await m.dispose();\n },\n };\n } catch (err) {\n throw new ModelLoadError(`Failed to load reranker model \"${preset.id}\".`, err);\n }\n}\n\n/**\n * Cross-encoder reranking task backed by `@huggingface/transformers`.\n *\n * Use {@link Reranker.create} to construct an instance — the constructor is\n * private. Useful as a second-stage step in a retrieve-then-rerank pipeline:\n * pull top-K candidates with a fast embedding similarity, then rerank with\n * a cross-encoder for higher precision.\n *\n * @example\n * ```ts\n * const rerank = await Reranker.create(\"bge-reranker-base\");\n * const scores = await rerank.score(\"what is webgpu?\", [\n * \"WebGPU is a modern graphics API\",\n * \"Bananas grow on trees\",\n * ]);\n * // scores[0] >> scores[1]\n * ```\n *\n * @example Ranked output sorted by score\n * ```ts\n * const ranked = await rerank.rank(\"what is webgpu?\", docs);\n * for (const r of ranked) console.log(r.score, r.text);\n * ```\n */\nexport class Reranker {\n private constructor(\n private readonly pipeline: RerankPipeline,\n /** Resolved metadata for the loaded model. */\n public readonly preset: RerankerPreset\n ) {}\n\n /**\n * Create and load a `Reranker` task for the given model.\n *\n * @param modelId - Friendly id from the reranker registry.\n * @param options - Optional creation options.\n * @throws UnknownModelError if `modelId` is not in the registry.\n * @throws ModelLoadError if the underlying pipeline fails to load.\n */\n static async create(modelId: string, options: RerankerCreateOptions = {}): Promise<Reranker> {\n const preset = resolveRerankerPreset(modelId);\n const pipeline = options.pipeline ?? (await buildDefaultPipeline(preset, options.onProgress));\n return new Reranker(pipeline, preset);\n }\n\n /**\n * Score each document against the query. Returns one score per doc, in\n * the same order. Empty `docs` returns `[]` (no error).\n *\n * @param query - Query string.\n * @param docs - Documents to score.\n * @param options - `sigmoid: true` maps logits into `[0, 1]`.\n */\n async score(query: string, docs: string[], options: RerankOptions = {}): Promise<number[]> {\n if (docs.length === 0) return [];\n if (!this.pipeline) {\n throw new ModelNotLoadedError(\"Reranker pipeline not initialized.\");\n }\n const raw = await this.pipeline.score(query, docs);\n return options.sigmoid ? raw.map(sigmoidValue) : raw;\n }\n\n /**\n * Score and sort documents by score in descending order. Returns a list of\n * {@link RankedDocument}s carrying the original index.\n *\n * @param query - Query string.\n * @param docs - Documents to rank.\n * @param options - Forwarded to {@link Reranker.score}.\n */\n async rank(\n query: string,\n docs: string[],\n options: RerankOptions = {}\n ): Promise<RankedDocument[]> {\n const scores = await this.score(query, docs, options);\n const ranked: RankedDocument[] = scores.map((score, index) => {\n const text: string = docs[index] ?? \"\";\n return { text, score, index };\n });\n ranked.sort((a, b) => b.score - a.score);\n return ranked;\n }\n\n /** Release pipeline resources. Safe to call multiple times. */\n async unload(): Promise<void> {\n await this.pipeline.unload?.();\n }\n}\n","import { MODEL_PRESETS, resolveModelPreset } from \"../presets/models\";\nimport { UnknownModelError } from \"../core/exceptions\";\n\n/** Snapshot of a single cached model's metadata. */\nexport interface CachedModelEntry {\n /** Friendly id from the registry (e.g. `\"llama-3.2-1b-int4\"`). */\n id: string;\n /** Backend-specific id (e.g. WebLLM `webllmId`). */\n backendId: string;\n /** Human-readable family name. */\n family: string;\n /** Approx parameter count, e.g. `\"1B\"`. */\n parameters: string;\n}\n\n/** Aggregate storage usage reported by the browser. */\nexport interface CacheUsage {\n /** Bytes used by the entire origin's storage (not just our cache). */\n usage: number;\n /** Bytes the browser is willing to give the origin. */\n quota: number;\n}\n\n/**\n * Hooks the {@link ModelCache} uses to talk to the underlying runtime and\n * the browser. Tests inject mocks; production code leaves them undefined,\n * letting `ModelCache` resolve the real `@mlc-ai/web-llm` helpers and\n * `navigator.storage.estimate()` lazily.\n */\nexport interface ModelCacheOptions {\n /** Override `hasModelInCache` from the runtime. */\n hasModel?: (backendId: string) => Promise<boolean>;\n /** Override `deleteModelInCache` from the runtime. */\n deleteModel?: (backendId: string) => Promise<void>;\n /** Override `navigator.storage.estimate()`. */\n estimate?: () => Promise<CacheUsage>;\n}\n\ntype WebLLMCacheModule = {\n hasModelInCache: (id: string) => Promise<boolean>;\n deleteModelInCache: (id: string) => Promise<void>;\n};\n\nlet webllmCachePromise: Promise<WebLLMCacheModule> | null = null;\n\nasync function loadWebLLMCacheHelpers(): Promise<WebLLMCacheModule> {\n if (!webllmCachePromise) {\n webllmCachePromise = import(\"@mlc-ai/web-llm\").then((m) => ({\n hasModelInCache: m.hasModelInCache,\n deleteModelInCache: m.deleteModelInCache,\n }));\n }\n return webllmCachePromise;\n}\n\nasync function defaultEstimate(): Promise<CacheUsage> {\n if (typeof navigator === \"undefined\" || !navigator.storage?.estimate) {\n return { usage: 0, quota: 0 };\n }\n const estimate = await navigator.storage.estimate();\n return {\n usage: estimate.usage ?? 0,\n quota: estimate.quota ?? 0,\n };\n}\n\n/**\n * Inspect and manage cached model weights.\n *\n * `localm-web` does not download or cache weights itself — that work is owned\n * by `@mlc-ai/web-llm`, which writes to the browser Cache API. `ModelCache`\n * is a thin wrapper that lets a consuming app surface cache state in its UI:\n * \"this model is downloaded\", \"you have 1.4 GB cached, free up space?\",\n * \"clear all models on logout\".\n *\n * @example\n * ```ts\n * const cache = new ModelCache();\n * if (await cache.has(\"llama-3.2-1b-int4\")) {\n * console.log(\"ready offline\");\n * }\n * const cached = await cache.list();\n * await cache.delete(\"phi-3.5-mini-int4\");\n * const usage = await cache.estimateUsage();\n * console.log(`${usage.usage} / ${usage.quota} bytes`);\n * ```\n */\nexport class ModelCache {\n private readonly hasModelHook: ((id: string) => Promise<boolean>) | undefined;\n private readonly deleteModelHook: ((id: string) => Promise<void>) | undefined;\n private readonly estimateHook: () => Promise<CacheUsage>;\n\n constructor(options: ModelCacheOptions = {}) {\n this.hasModelHook = options.hasModel;\n this.deleteModelHook = options.deleteModel;\n this.estimateHook = options.estimate ?? defaultEstimate;\n }\n\n /**\n * Whether the model's weights are present in the browser cache.\n *\n * @param modelId - Friendly id from the registry.\n * @throws UnknownModelError if `modelId` is not in the registry.\n */\n async has(modelId: string): Promise<boolean> {\n const backendId: string = resolveModelPreset(modelId).webllmId;\n const fn = this.hasModelHook ?? (await loadWebLLMCacheHelpers()).hasModelInCache;\n return fn(backendId);\n }\n\n /**\n * Delete a single model's weights from the browser cache. No-op when the\n * model is not cached.\n *\n * @param modelId - Friendly id from the registry.\n * @throws UnknownModelError if `modelId` is not in the registry.\n */\n async delete(modelId: string): Promise<void> {\n const backendId: string = resolveModelPreset(modelId).webllmId;\n const fn = this.deleteModelHook ?? (await loadWebLLMCacheHelpers()).deleteModelInCache;\n await fn(backendId);\n }\n\n /**\n * List the registry models that are currently cached.\n *\n * Iterates `MODEL_PRESETS` and probes each one. Only returns models known\n * to the SDK — models cached by external WebLLM calls outside our registry\n * are not included.\n *\n * @returns Empty list when nothing is cached.\n */\n async list(): Promise<CachedModelEntry[]> {\n const fn = this.hasModelHook ?? (await loadWebLLMCacheHelpers()).hasModelInCache;\n const probes = await Promise.all(\n Object.values(MODEL_PRESETS).map(async (preset) => {\n const cached: boolean = await fn(preset.webllmId);\n if (!cached) return null;\n const entry: CachedModelEntry = {\n id: preset.id,\n backendId: preset.webllmId,\n family: preset.family,\n parameters: preset.parameters,\n };\n return entry;\n })\n );\n return probes.filter((p): p is CachedModelEntry => p !== null);\n }\n\n /**\n * Delete every registry model from the cache. Useful for logout flows or\n * \"reset\" buttons. Models cached outside the registry are not touched.\n */\n async clear(): Promise<void> {\n const fn = this.deleteModelHook ?? (await loadWebLLMCacheHelpers()).deleteModelInCache;\n await Promise.all(Object.values(MODEL_PRESETS).map((p) => fn(p.webllmId)));\n }\n\n /**\n * Aggregate storage stats from the browser. Returned numbers cover the\n * entire origin (Cache API + IndexedDB + Service Workers + OPFS), not\n * just our model cache — use it for \"you have X of Y available\" hints.\n */\n async estimateUsage(): Promise<CacheUsage> {\n return this.estimateHook();\n }\n\n /**\n * Throw a descriptive error if the given id is not in the registry.\n * Exposed for code paths that want to validate before calling other\n * methods (those already throw on their own).\n *\n * @throws UnknownModelError\n */\n static assertKnown(modelId: string): void {\n if (!(modelId in MODEL_PRESETS)) {\n const available = Object.keys(MODEL_PRESETS).join(\", \");\n throw new UnknownModelError(`Unknown model \"${modelId}\". Available models: ${available}.`);\n }\n }\n}\n","import type { TokenChunk } from \"../types\";\n\n/**\n * Drain an async iterable of token chunks into a single string.\n *\n * Useful in tests, for non-streaming consumers, and as a one-line way to\n * reconstruct the final text from a `Chat.stream(...)` call.\n *\n * @param stream - The token-chunk async iterable to consume.\n * @returns The concatenation of every chunk's `text` field.\n */\nexport async function collectStream(stream: AsyncIterable<TokenChunk>): Promise<string> {\n let acc: string = \"\";\n for await (const chunk of stream) {\n acc += chunk.text;\n }\n return acc;\n}\n\n/**\n * Wrap an async iterable so that each `TokenChunk` is also passed to a\n * caller-supplied side-effect callback before being yielded downstream.\n *\n * This is intentionally a passthrough — it does not buffer.\n *\n * @param stream - The upstream token-chunk async iterable.\n * @param onChunk - Side-effect invoked for every chunk.\n * @returns A new async iterable yielding the same chunks.\n */\nexport async function* tap(\n stream: AsyncIterable<TokenChunk>,\n onChunk: (chunk: TokenChunk) => void\n): AsyncIterable<TokenChunk> {\n for await (const chunk of stream) {\n onChunk(chunk);\n yield chunk;\n }\n}\n","/**\n * localm-web — browser-only TypeScript SDK for running LLMs and SLMs locally.\n *\n * Public API surface for v0.1.\n *\n * @packageDocumentation\n */\n\nexport { Chat } from \"./tasks/chat\";\nexport { Completion } from \"./tasks/completion\";\nexport { Embeddings } from \"./tasks/embeddings\";\nexport type { EmbeddingsCreateOptions, EmbedOptions, EmbedPipeline } from \"./tasks/embeddings\";\nexport { Reranker } from \"./tasks/reranker\";\nexport type {\n RerankerCreateOptions,\n RerankOptions,\n RerankPipeline,\n RankedDocument,\n} from \"./tasks/reranker\";\nexport { LMTask } from \"./tasks/lm-task\";\nexport type { LMTaskCreateOptions } from \"./tasks/lm-task\";\n\nexport { ChatReply, CompletionResult } from \"./results\";\n\nexport {\n MODEL_PRESETS,\n resolveModelPreset,\n listSupportedModels,\n EMBEDDING_PRESETS,\n resolveEmbeddingPreset,\n listSupportedEmbeddingModels,\n RERANKER_PRESETS,\n resolveRerankerPreset,\n listSupportedRerankerModels,\n} from \"./presets/models\";\nexport type { EmbeddingPreset, RerankerPreset } from \"./presets/models\";\n\nexport {\n LocalmWebError,\n WebGPUUnavailableError,\n ModelLoadError,\n ModelNotLoadedError,\n UnknownModelError,\n GenerationAbortedError,\n QuotaExceededError,\n BackendNotAvailableError,\n} from \"./core/exceptions\";\n\nexport type { Engine } from \"./core/engine\";\nexport { WorkerEngine } from \"./core/worker-engine\";\nexport { createInferenceWorker } from \"./worker/create-worker\";\nexport type { WorkerLike } from \"./worker/protocol\";\n\nexport { ModelCache } from \"./cache\";\nexport type { CachedModelEntry, CacheUsage, ModelCacheOptions } from \"./cache\";\n\nexport { collectStream, tap } from \"./streaming/token-stream\";\n\nexport type {\n Role,\n FinishReason,\n Message,\n GenerationOptions,\n ModelLoadProgress,\n ModelLoadPhase,\n ProgressCallback,\n TokenChunk,\n ModelPreset,\n} from \"./types\";\n\n/** Current package version. Updated at release time. */\nexport const VERSION: string = \"0.3.0\";\n"],"names":["transformersModulePromise","loadTransformers","buildDefaultPipeline"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/core/load-phase.ts","../src/core/exceptions.ts","../src/structured/json-schema.ts","../src/core/webllm-engine.ts","../src/worker/protocol.ts","../src/core/worker-engine.ts","../src/presets/models.ts","../src/worker/create-worker.ts","../src/tasks/lm-task.ts","../src/results.ts","../src/tasks/chat.ts","../src/tasks/completion.ts","../src/tasks/embeddings.ts","../src/tasks/reranker.ts","../src/cache/model-cache.ts","../src/streaming/token-stream.ts","../src/index.ts"],"sourcesContent":["import type { ModelLoadPhase } from \"../types\";\n\nconst DOWNLOAD_PATTERN: RegExp = /\\b(fetch|download|loading from cache|cache hit|param)/i;\nconst COMPILE_PATTERN: RegExp = /\\b(compil|shader|kernel|tensor|init|allocat|warm)/i;\n\n/**\n * Classify a runtime status text into a {@link ModelLoadPhase}.\n *\n * Heuristic: match download-related verbs first (network or cache hits are\n * treated as `downloading`), then compile-related verbs. Anything else falls\n * back to the generic `loading` bucket. The `ready` phase is never returned\n * here — callers emit it explicitly when the load resolves.\n *\n * @param text - The raw status string from the runtime.\n * @returns The classified phase.\n */\nexport function classifyLoadPhase(text: string): ModelLoadPhase {\n if (DOWNLOAD_PATTERN.test(text)) return \"downloading\";\n if (COMPILE_PATTERN.test(text)) return \"compiling\";\n return \"loading\";\n}\n","/**\n * Error hierarchy for localm-web.\n *\n * All errors thrown by the SDK extend `LocalmWebError` so consumers can\n * distinguish SDK errors from unrelated runtime errors with a single\n * `instanceof` check.\n */\n\n/** Base class for every error raised by localm-web. */\nexport class LocalmWebError extends Error {\n /**\n * @param message - Human-readable description of the error.\n * @param cause - Underlying error, if any.\n */\n constructor(\n message: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/** Thrown when WebGPU is required but not available in the host browser. */\nexport class WebGPUUnavailableError extends LocalmWebError {}\n\n/** Thrown when a model fails to load (network, parsing, runtime init). */\nexport class ModelLoadError extends LocalmWebError {}\n\n/** Thrown when an inference call is made before a model has loaded. */\nexport class ModelNotLoadedError extends LocalmWebError {}\n\n/** Thrown when a model id is not present in the curated registry. */\nexport class UnknownModelError extends LocalmWebError {}\n\n/** Thrown when generation is aborted via an `AbortSignal`. */\nexport class GenerationAbortedError extends LocalmWebError {}\n\n/** Thrown when the browser denies storage quota for the model cache. */\nexport class QuotaExceededError extends LocalmWebError {}\n\n/** Thrown when no usable backend is available on the current platform. */\nexport class BackendNotAvailableError extends LocalmWebError {}\n\n/**\n * Thrown when structured output (JSON mode or JSON Schema constrained\n * decoding) fails to parse as valid JSON.\n *\n * Wraps the underlying `SyntaxError` from `JSON.parse` so consumers can\n * distinguish SDK-issued failures from unrelated runtime exceptions.\n */\nexport class StructuredOutputError extends LocalmWebError {}\n","/**\n * JSON Schema helpers for structured output.\n *\n * The SDK delegates the actual constrained decoding to the underlying\n * runtime (xgrammar inside WebLLM today, ORT-Web equivalent later). These\n * helpers normalize user input — turning a JS object schema into the\n * JSON-string shape that WebLLM's `response_format.schema` expects — and\n * parse the runtime's textual output back into typed JSON.\n */\n\nimport { StructuredOutputError } from \"../core/exceptions\";\n\n/**\n * Minimal structural sanity check for a JSON Schema.\n *\n * Does not validate the schema against the JSON Schema meta-schema. The goal\n * is to fail fast on obvious mistakes (passing a string, an array, `null`)\n * before handing the value off to the runtime, where errors surface much\n * later and with much worse messages.\n *\n * @param schema - Candidate JSON Schema object.\n * @throws StructuredOutputError when `schema` is not a plain object or has\n * no recognizable schema shape (`type`, `$ref`, `oneOf`, `anyOf`, `allOf`,\n * `enum`).\n */\nexport function assertJsonSchema(schema: unknown): asserts schema is object {\n if (schema === null || typeof schema !== \"object\" || Array.isArray(schema)) {\n throw new StructuredOutputError(\"jsonSchema must be a plain object describing a JSON Schema.\");\n }\n const keys: string[] = Object.keys(schema);\n const recognized: readonly string[] = [\n \"type\",\n \"$ref\",\n \"oneOf\",\n \"anyOf\",\n \"allOf\",\n \"enum\",\n \"const\",\n \"properties\",\n ];\n if (!keys.some((key) => recognized.includes(key))) {\n throw new StructuredOutputError(\n \"jsonSchema does not look like a JSON Schema (missing type/$ref/oneOf/anyOf/allOf/enum/const/properties).\"\n );\n }\n}\n\n/**\n * Serialize a JSON Schema object for the WebLLM `response_format.schema`\n * field.\n *\n * WebLLM expects the schema as a JSON-encoded string (xgrammar parses it\n * server-side). Validates the shape via {@link assertJsonSchema} first.\n *\n * @param schema - JSON Schema object.\n * @returns The schema serialized as a JSON string.\n * @throws StructuredOutputError when `schema` is not a recognizable JSON\n * Schema shape.\n */\nexport function serializeJsonSchema(schema: unknown): string {\n assertJsonSchema(schema);\n return JSON.stringify(schema);\n}\n\n/**\n * Parse the textual output of a structured-decoding generation as JSON.\n *\n * @typeParam T - The expected parsed shape. The function does not validate\n * the parsed value against `T`; that is the caller's responsibility.\n * @param text - Raw text returned by the engine.\n * @returns The parsed JSON value cast to `T`.\n * @throws StructuredOutputError when the text is not valid JSON.\n */\nexport function parseStructuredOutput<T = unknown>(text: string): T {\n try {\n return JSON.parse(text) as T;\n } catch (err) {\n throw new StructuredOutputError(\n \"Engine output is not valid JSON. The model may have ignored the constrained decoding directive.\",\n err\n );\n }\n}\n","import type { Engine } from \"./engine\";\nimport { classifyLoadPhase } from \"./load-phase\";\nimport type { GenerationOptions, Message, ProgressCallback, TokenChunk } from \"../types\";\nimport {\n GenerationAbortedError,\n ModelLoadError,\n ModelNotLoadedError,\n WebGPUUnavailableError,\n} from \"./exceptions\";\nimport { serializeJsonSchema } from \"../structured/json-schema\";\n\ntype WebLLMModule = typeof import(\"@mlc-ai/web-llm\");\ntype MLCEngine = import(\"@mlc-ai/web-llm\").MLCEngineInterface;\ntype ChatCompletionMessageParam = import(\"@mlc-ai/web-llm\").ChatCompletionMessageParam;\ntype ResponseFormat = import(\"@mlc-ai/web-llm\").ResponseFormat;\n\nlet webllmModulePromise: Promise<WebLLMModule> | null = null;\n\nasync function loadWebLLM(): Promise<WebLLMModule> {\n if (!webllmModulePromise) {\n webllmModulePromise = import(\"@mlc-ai/web-llm\");\n }\n return webllmModulePromise;\n}\n\nfunction isWebGPUAvailable(): boolean {\n return typeof navigator !== \"undefined\" && \"gpu\" in navigator;\n}\n\ninterface SamplingParams {\n max_tokens?: number;\n temperature?: number;\n top_p?: number;\n}\n\nfunction buildSamplingParams(options: GenerationOptions): SamplingParams {\n const params: SamplingParams = {};\n if (options.maxTokens !== undefined) params.max_tokens = options.maxTokens;\n if (options.temperature !== undefined) params.temperature = options.temperature;\n if (options.topP !== undefined) params.top_p = options.topP;\n return params;\n}\n\n/**\n * Build the WebLLM `response_format` payload from generation options.\n *\n * Returns `undefined` when the caller has not requested structured output —\n * letting WebLLM use its default free-text decoding path. When `jsonSchema`\n * is set it takes priority and is serialized into the `schema` field\n * (xgrammar parses it server-side). When only `json` is set the payload\n * carries `{ type: \"json_object\" }` for unconstrained-but-valid JSON.\n */\nfunction buildResponseFormat(options: GenerationOptions): ResponseFormat | undefined {\n if (options.jsonSchema !== undefined) {\n return { type: \"json_object\", schema: serializeJsonSchema(options.jsonSchema) };\n }\n if (options.json) {\n return { type: \"json_object\" };\n }\n return undefined;\n}\n\nfunction toChatMessages(messages: Message[]): ChatCompletionMessageParam[] {\n return messages.map((m): ChatCompletionMessageParam => {\n switch (m.role) {\n case \"system\":\n return { role: \"system\", content: m.content };\n case \"user\":\n return { role: \"user\", content: m.content };\n case \"assistant\":\n return { role: \"assistant\", content: m.content };\n case \"tool\":\n return { role: \"tool\", content: m.content, tool_call_id: m.name ?? \"\" };\n }\n });\n}\n\n/**\n * Inference engine backed by [WebLLM (MLC)](https://github.com/mlc-ai/web-llm).\n *\n * Requires WebGPU. The fallback path planned for v0.5 will route to ORT-Web\n * when WebGPU is missing.\n */\nexport class WebLLMEngine implements Engine {\n private engine: MLCEngine | null = null;\n\n isLoaded(): boolean {\n return this.engine !== null;\n }\n\n async load(modelId: string, onProgress?: ProgressCallback): Promise<void> {\n if (!isWebGPUAvailable()) {\n throw new WebGPUUnavailableError(\n \"WebGPU is not available in this browser. The ORT-Web fallback is planned for v0.5.\"\n );\n }\n const webllm = await loadWebLLM();\n try {\n this.engine = await webllm.CreateMLCEngine(modelId, {\n initProgressCallback: (report): void => {\n onProgress?.({\n progress: report.progress,\n text: report.text,\n loaded: 0,\n total: 0,\n phase: classifyLoadPhase(report.text),\n });\n },\n });\n onProgress?.({\n progress: 1,\n text: \"Model ready.\",\n loaded: 0,\n total: 0,\n phase: \"ready\",\n });\n } catch (err) {\n throw new ModelLoadError(`Failed to load model \"${modelId}\".`, err);\n }\n }\n\n async generate(messages: Message[], options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: false,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n return completion.choices[0]?.message?.content ?? \"\";\n }\n\n async *stream(messages: Message[], options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: true,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.delta?.content ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming generation failed.\", err);\n }\n }\n\n async complete(prompt: string, options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: false,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n return completion.choices[0]?.text ?? \"\";\n }\n\n async *streamCompletion(\n prompt: string,\n options: GenerationOptions = {}\n ): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const responseFormat = buildResponseFormat(options);\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: true,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.text ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming completion failed.\", err);\n }\n }\n\n async unload(): Promise<void> {\n if (this.engine) {\n await this.engine.unload();\n this.engine = null;\n }\n }\n\n private requireEngine(): MLCEngine {\n if (!this.engine) {\n throw new ModelNotLoadedError(\"Engine not loaded. Call load() before generation.\");\n }\n return this.engine;\n }\n}\n","import type { GenerationOptions, Message, ModelLoadProgress, TokenChunk } from \"../types\";\n\n/**\n * Subset of {@link GenerationOptions} that survives `postMessage`.\n *\n * `AbortSignal` cannot be cloned across the worker boundary, so it is replaced\n * by a separate {@link AbortRequest} message keyed on the same operation id.\n */\nexport type SerializableGenerationOptions = Omit<GenerationOptions, \"signal\">;\n\n/** Strip `signal` from a {@link GenerationOptions} before posting it. */\nexport function toSerializableOptions(\n options: GenerationOptions = {}\n): SerializableGenerationOptions {\n const { signal: _signal, ...rest } = options;\n void _signal;\n return rest;\n}\n\n/** Operation request sent from the main thread to the worker. */\nexport type WorkerRequest =\n | { op: \"load\"; id: number; modelId: string }\n | {\n op: \"generate\";\n id: number;\n messages: Message[];\n options: SerializableGenerationOptions;\n }\n | {\n op: \"stream\";\n id: number;\n messages: Message[];\n options: SerializableGenerationOptions;\n }\n | {\n op: \"complete\";\n id: number;\n prompt: string;\n options: SerializableGenerationOptions;\n }\n | {\n op: \"stream-completion\";\n id: number;\n prompt: string;\n options: SerializableGenerationOptions;\n }\n | { op: \"abort\"; id: number }\n | { op: \"unload\"; id: number }\n | { op: \"isLoaded\"; id: number };\n\n/** Operation response sent from the worker back to the main thread. */\nexport type WorkerResponse =\n | { op: \"loaded\"; id: number }\n | { op: \"generated\"; id: number; text: string }\n | { op: \"progress\"; id: number; payload: ModelLoadProgress }\n | { op: \"token\"; id: number; chunk: TokenChunk }\n | { op: \"stream-end\"; id: number }\n | { op: \"error\"; id: number; name: string; message: string }\n | { op: \"unloaded\"; id: number }\n | { op: \"is-loaded\"; id: number; value: boolean };\n\n/** Subset of `Worker` we depend on. Lets tests inject a mock. */\nexport interface WorkerLike {\n postMessage(message: WorkerRequest): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent<WorkerResponse>) => void): void;\n removeEventListener(\n type: \"message\",\n listener: (event: MessageEvent<WorkerResponse>) => void\n ): void;\n terminate(): void;\n}\n\n/** Internal alias used when the message direction is irrelevant (logging, debug). */\nexport type AbortRequest = Extract<WorkerRequest, { op: \"abort\" }>;\n","import { GenerationAbortedError, ModelLoadError, ModelNotLoadedError } from \"./exceptions\";\nimport type { Engine } from \"./engine\";\nimport type { GenerationOptions, Message, ProgressCallback, TokenChunk } from \"../types\";\nimport {\n toSerializableOptions,\n type WorkerLike,\n type WorkerRequest,\n type WorkerResponse,\n} from \"../worker/protocol\";\n\ninterface PendingGenerate {\n resolve: (text: string) => void;\n reject: (err: Error) => void;\n}\n\ninterface PendingStream {\n push: (chunk: TokenChunk) => void;\n end: () => void;\n fail: (err: Error) => void;\n}\n\n/**\n * Engine implementation that proxies all calls to a Web Worker.\n *\n * The worker holds the actual {@link WebLLMEngine}; this class is a thin RPC\n * shell that serializes requests, tracks pending operations by a numeric id,\n * and turns worker responses back into Promises and async iterables.\n *\n * Use {@link createInferenceWorker} to obtain a real worker. Tests can pass a\n * {@link WorkerLike} mock implementing the same `postMessage` /\n * `addEventListener` surface.\n */\nexport class WorkerEngine implements Engine {\n private nextId: number = 1;\n private loaded: boolean = false;\n private currentLoad: { resolve: () => void; reject: (e: Error) => void } | null = null;\n private currentLoadId: number = 0;\n private currentLoadProgress: ProgressCallback | undefined = undefined;\n private currentUnload: { resolve: () => void; reject: (e: Error) => void } | null = null;\n private currentUnloadId: number = 0;\n private pendingGenerates: Map<number, PendingGenerate> = new Map();\n private pendingStreams: Map<number, PendingStream> = new Map();\n\n private readonly listener: (event: MessageEvent<WorkerResponse>) => void;\n\n constructor(private readonly worker: WorkerLike) {\n this.listener = (event): void => this.handleMessage(event.data);\n this.worker.addEventListener(\"message\", this.listener);\n }\n\n isLoaded(): boolean {\n return this.loaded;\n }\n\n async load(modelId: string, onProgress?: ProgressCallback): Promise<void> {\n if (this.currentLoad) {\n throw new ModelLoadError(\"Another load is already in progress.\");\n }\n const id: number = this.allocateId();\n this.currentLoadId = id;\n this.currentLoadProgress = onProgress;\n return new Promise<void>((resolve, reject) => {\n this.currentLoad = { resolve, reject };\n this.send({ op: \"load\", id, modelId });\n });\n }\n\n async generate(messages: Message[], options: GenerationOptions = {}): Promise<string> {\n const id: number = this.allocateId();\n return new Promise<string>((resolve, reject) => {\n this.pendingGenerates.set(id, { resolve, reject });\n this.send({\n op: \"generate\",\n id,\n messages,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n });\n }\n\n async *stream(messages: Message[], options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const id: number = this.allocateId();\n const queue: TokenChunk[] = [];\n let done: boolean = false;\n let error: Error | null = null;\n let notify: (() => void) | null = null;\n\n const wakeup = (): void => {\n if (notify) {\n const fn = notify;\n notify = null;\n fn();\n }\n };\n\n this.pendingStreams.set(id, {\n push: (chunk): void => {\n queue.push(chunk);\n wakeup();\n },\n end: (): void => {\n done = true;\n wakeup();\n },\n fail: (err): void => {\n error = err;\n done = true;\n wakeup();\n },\n });\n\n this.send({\n op: \"stream\",\n id,\n messages,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n\n try {\n while (true) {\n if (queue.length > 0) {\n const chunk = queue.shift();\n if (chunk) yield chunk;\n continue;\n }\n if (error) throw error;\n if (done) return;\n await new Promise<void>((r) => {\n notify = r;\n });\n }\n } finally {\n this.pendingStreams.delete(id);\n }\n }\n\n async complete(prompt: string, options: GenerationOptions = {}): Promise<string> {\n const id: number = this.allocateId();\n return new Promise<string>((resolve, reject) => {\n this.pendingGenerates.set(id, { resolve, reject });\n this.send({\n op: \"complete\",\n id,\n prompt,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n });\n }\n\n async *streamCompletion(\n prompt: string,\n options: GenerationOptions = {}\n ): AsyncIterable<TokenChunk> {\n const id: number = this.allocateId();\n const queue: TokenChunk[] = [];\n let done: boolean = false;\n let error: Error | null = null;\n let notify: (() => void) | null = null;\n\n const wakeup = (): void => {\n if (notify) {\n const fn = notify;\n notify = null;\n fn();\n }\n };\n\n this.pendingStreams.set(id, {\n push: (chunk): void => {\n queue.push(chunk);\n wakeup();\n },\n end: (): void => {\n done = true;\n wakeup();\n },\n fail: (err): void => {\n error = err;\n done = true;\n wakeup();\n },\n });\n\n this.send({\n op: \"stream-completion\",\n id,\n prompt,\n options: toSerializableOptions(options),\n });\n options.signal?.addEventListener(\"abort\", () => this.send({ op: \"abort\", id }));\n\n try {\n while (true) {\n if (queue.length > 0) {\n const chunk = queue.shift();\n if (chunk) yield chunk;\n continue;\n }\n if (error) throw error;\n if (done) return;\n await new Promise<void>((r) => {\n notify = r;\n });\n }\n } finally {\n this.pendingStreams.delete(id);\n }\n }\n\n async unload(): Promise<void> {\n if (!this.loaded) return;\n if (this.currentUnload) {\n throw new ModelLoadError(\"Another unload is already in progress.\");\n }\n const id: number = this.allocateId();\n this.currentUnloadId = id;\n return new Promise<void>((resolve, reject) => {\n this.currentUnload = { resolve, reject };\n this.send({ op: \"unload\", id });\n });\n }\n\n /** Tear down the underlying worker. The engine is unusable after this. */\n terminate(): void {\n this.worker.removeEventListener(\"message\", this.listener);\n this.worker.terminate();\n this.loaded = false;\n }\n\n private allocateId(): number {\n const id = this.nextId;\n this.nextId += 1;\n return id;\n }\n\n private send(req: WorkerRequest): void {\n this.worker.postMessage(req);\n }\n\n private handleMessage(msg: WorkerResponse): void {\n switch (msg.op) {\n case \"loaded\":\n if (this.currentLoad && msg.id === this.currentLoadId) {\n this.loaded = true;\n this.currentLoad.resolve();\n this.currentLoad = null;\n this.currentLoadProgress = undefined;\n }\n return;\n case \"progress\":\n if (msg.id === this.currentLoadId) {\n this.currentLoadProgress?.(msg.payload);\n }\n return;\n case \"generated\": {\n const pending = this.pendingGenerates.get(msg.id);\n if (pending) {\n pending.resolve(msg.text);\n this.pendingGenerates.delete(msg.id);\n }\n return;\n }\n case \"token\": {\n const stream = this.pendingStreams.get(msg.id);\n stream?.push(msg.chunk);\n return;\n }\n case \"stream-end\": {\n const stream = this.pendingStreams.get(msg.id);\n stream?.end();\n return;\n }\n case \"unloaded\":\n if (this.currentUnload && msg.id === this.currentUnloadId) {\n this.loaded = false;\n this.currentUnload.resolve();\n this.currentUnload = null;\n }\n return;\n case \"is-loaded\":\n return;\n case \"error\": {\n const err = mapError(msg.name, msg.message);\n if (this.currentLoad && msg.id === this.currentLoadId) {\n this.currentLoad.reject(err);\n this.currentLoad = null;\n this.currentLoadProgress = undefined;\n return;\n }\n if (this.currentUnload && msg.id === this.currentUnloadId) {\n this.currentUnload.reject(err);\n this.currentUnload = null;\n return;\n }\n const generate = this.pendingGenerates.get(msg.id);\n if (generate) {\n generate.reject(err);\n this.pendingGenerates.delete(msg.id);\n return;\n }\n const stream = this.pendingStreams.get(msg.id);\n if (stream) {\n stream.fail(err);\n return;\n }\n return;\n }\n }\n }\n}\n\nfunction mapError(name: string, message: string): Error {\n switch (name) {\n case \"ModelLoadError\":\n return new ModelLoadError(message);\n case \"ModelNotLoadedError\":\n return new ModelNotLoadedError(message);\n case \"GenerationAbortedError\":\n return new GenerationAbortedError(message);\n default: {\n const err = new Error(message);\n err.name = name;\n return err;\n }\n }\n}\n","import type { ModelPreset } from \"../types\";\nimport { UnknownModelError } from \"../core/exceptions\";\n\n/**\n * Curated registry of supported models for v0.1.\n *\n * Each entry maps a friendly id (e.g. `\"phi-3.5-mini-int4\"`) to the underlying\n * runtime identifier and metadata. Friendly ids are stable; backend ids may\n * change as upstream MLC packages evolve.\n *\n * Only models that have been validated to load in browsers with WebGPU and\n * that fit the SLM target (≤ 4B parameters at INT4) are included.\n */\nexport const MODEL_PRESETS: Readonly<Record<string, ModelPreset>> = Object.freeze({\n \"phi-3.5-mini-int4\": {\n id: \"phi-3.5-mini-int4\",\n family: \"Phi-3.5\",\n parameters: \"3.8B\",\n quantization: \"q4f16_1\",\n webllmId: \"Phi-3.5-mini-instruct-q4f16_1-MLC\",\n contextWindow: 4096,\n description: \"Microsoft Phi-3.5 mini, INT4 quantized for browser inference.\",\n },\n \"llama-3.2-1b-int4\": {\n id: \"llama-3.2-1b-int4\",\n family: \"Llama-3.2\",\n parameters: \"1B\",\n quantization: \"q4f16_1\",\n webllmId: \"Llama-3.2-1B-Instruct-q4f16_1-MLC\",\n contextWindow: 4096,\n description: \"Meta Llama 3.2 1B Instruct, INT4 quantized.\",\n },\n \"qwen2.5-1.5b-int4\": {\n id: \"qwen2.5-1.5b-int4\",\n family: \"Qwen2.5\",\n parameters: \"1.5B\",\n quantization: \"q4f16_1\",\n webllmId: \"Qwen2.5-1.5B-Instruct-q4f16_1-MLC\",\n contextWindow: 4096,\n description: \"Alibaba Qwen 2.5 1.5B Instruct, INT4 quantized.\",\n },\n});\n\n/**\n * Resolve a friendly model id to its full preset metadata.\n *\n * @param modelId - Friendly id (e.g. `\"phi-3.5-mini-int4\"`).\n * @returns The matching preset.\n * @throws UnknownModelError if no preset matches.\n */\nexport function resolveModelPreset(modelId: string): ModelPreset {\n const preset = MODEL_PRESETS[modelId];\n if (!preset) {\n const available = Object.keys(MODEL_PRESETS).join(\", \");\n throw new UnknownModelError(`Unknown model \"${modelId}\". Available models: ${available}.`);\n }\n return preset;\n}\n\n/** Return the list of supported friendly model ids. */\nexport function listSupportedModels(): string[] {\n return Object.keys(MODEL_PRESETS);\n}\n\n/** Curated metadata for a supported embedding model. */\nexport interface EmbeddingPreset {\n /** Friendly identifier (e.g. `\"bge-small-en-v1.5\"`). */\n id: string;\n /** Family name (e.g. `\"BGE\"`). */\n family: string;\n /** Embedding dimension. */\n dimension: number;\n /** Maximum input length in tokens. */\n maxTokens: number;\n /** Identifier passed to `@huggingface/transformers`. */\n transformersId: string;\n /** Approximate quantization scheme (e.g. `\"fp32\"`, `\"int8\"`). */\n quantization: string;\n /** Short human description. */\n description: string;\n}\n\n/**\n * Curated registry of supported embedding models for v0.3.\n *\n * Each entry maps a friendly id to the underlying transformers.js model id.\n */\nexport const EMBEDDING_PRESETS: Readonly<Record<string, EmbeddingPreset>> = Object.freeze({\n \"bge-small-en-v1.5\": {\n id: \"bge-small-en-v1.5\",\n family: \"BGE\",\n dimension: 384,\n maxTokens: 512,\n transformersId: \"Xenova/bge-small-en-v1.5\",\n quantization: \"fp32\",\n description: \"BAAI BGE small English v1.5, 384-dim sentence embeddings.\",\n },\n \"bge-base-en-v1.5\": {\n id: \"bge-base-en-v1.5\",\n family: \"BGE\",\n dimension: 768,\n maxTokens: 512,\n transformersId: \"Xenova/bge-base-en-v1.5\",\n quantization: \"fp32\",\n description: \"BAAI BGE base English v1.5, 768-dim sentence embeddings.\",\n },\n});\n\n/**\n * Resolve a friendly embedding model id to its full preset metadata.\n *\n * @param modelId - Friendly id (e.g. `\"bge-small-en-v1.5\"`).\n * @returns The matching preset.\n * @throws UnknownModelError if no preset matches.\n */\nexport function resolveEmbeddingPreset(modelId: string): EmbeddingPreset {\n const preset = EMBEDDING_PRESETS[modelId];\n if (!preset) {\n const available = Object.keys(EMBEDDING_PRESETS).join(\", \");\n throw new UnknownModelError(\n `Unknown embedding model \"${modelId}\". Available models: ${available}.`\n );\n }\n return preset;\n}\n\n/** Return the list of supported embedding model ids. */\nexport function listSupportedEmbeddingModels(): string[] {\n return Object.keys(EMBEDDING_PRESETS);\n}\n\n/** Curated metadata for a supported reranker (cross-encoder) model. */\nexport interface RerankerPreset {\n /** Friendly identifier (e.g. `\"bge-reranker-base\"`). */\n id: string;\n /** Family name (e.g. `\"BGE Reranker\"`). */\n family: string;\n /** Maximum input length in tokens (combined query + document). */\n maxTokens: number;\n /** Identifier passed to `@huggingface/transformers`. */\n transformersId: string;\n /** Approximate quantization (e.g. `\"fp32\"`). */\n quantization: string;\n /** Short human description. */\n description: string;\n}\n\n/**\n * Curated registry of supported reranker models for v0.3.\n */\nexport const RERANKER_PRESETS: Readonly<Record<string, RerankerPreset>> = Object.freeze({\n \"bge-reranker-base\": {\n id: \"bge-reranker-base\",\n family: \"BGE Reranker\",\n maxTokens: 512,\n transformersId: \"Xenova/bge-reranker-base\",\n quantization: \"fp32\",\n description: \"BAAI BGE reranker base — multilingual cross-encoder.\",\n },\n});\n\n/**\n * Resolve a friendly reranker model id to its full preset metadata.\n *\n * @param modelId - Friendly id (e.g. `\"bge-reranker-base\"`).\n * @throws UnknownModelError if no preset matches.\n */\nexport function resolveRerankerPreset(modelId: string): RerankerPreset {\n const preset = RERANKER_PRESETS[modelId];\n if (!preset) {\n const available = Object.keys(RERANKER_PRESETS).join(\", \");\n throw new UnknownModelError(\n `Unknown reranker model \"${modelId}\". Available models: ${available}.`\n );\n }\n return preset;\n}\n\n/** Return the list of supported reranker model ids. */\nexport function listSupportedRerankerModels(): string[] {\n return Object.keys(RERANKER_PRESETS);\n}\n","import type { WorkerLike } from \"./protocol\";\n\n/**\n * Spawn a new inference Web Worker.\n *\n * Uses Vite/webpack-friendly `new Worker(new URL(...), { type: \"module\" })`\n * syntax. The bundler emits the worker as a separate ES module chunk.\n *\n * Consumers normally do not call this directly — `LMTask.create()` invokes it\n * when `inWorker: true` is set. It is exported for advanced scenarios (custom\n * worker management, pooling, lifecycle integration with a host app).\n *\n * @returns A {@link WorkerLike}-compatible Worker instance.\n */\nexport function createInferenceWorker(): WorkerLike {\n return new Worker(new URL(\"./inference.worker.ts\", import.meta.url), {\n type: \"module\",\n }) as unknown as WorkerLike;\n}\n","import type { Engine } from \"../core/engine\";\nimport { WebLLMEngine } from \"../core/webllm-engine\";\nimport { WorkerEngine } from \"../core/worker-engine\";\nimport { resolveModelPreset } from \"../presets/models\";\nimport { createInferenceWorker } from \"../worker/create-worker\";\nimport type { ModelPreset, ProgressCallback } from \"../types\";\n\n/** Common options accepted by every task's `create()` factory. */\nexport interface LMTaskCreateOptions {\n /** Optional callback for model load progress updates. */\n onProgress?: ProgressCallback;\n /**\n * Override the engine used for inference. Intended for testing.\n * Production callers should let the SDK pick a backend automatically.\n */\n engine?: Engine;\n /**\n * Run inference inside a Web Worker, isolating the UI thread from\n * tokenization and generation. **Default `true` from v0.3** — the\n * `WorkerEngine` is the recommended path. Pass `false` to keep\n * inference on the main thread (useful for environments without\n * `Worker` support or when debugging the runtime directly).\n *\n * Ignored when {@link engine} is provided.\n */\n inWorker?: boolean;\n}\n\n/** Internal payload returned by {@link LMTask.createEngine}. */\nexport interface ResolvedEngine {\n engine: Engine;\n preset: ModelPreset;\n}\n\n/**\n * Base class shared by all language-model tasks (`Chat` for v0.1; `Completion`,\n * `Embeddings` and `Reranker` planned for later versions).\n *\n * The base owns:\n * - resolving a friendly model id to a {@link ModelPreset};\n * - selecting and loading an {@link Engine} (defaulting to WebLLM);\n * - exposing `unload()` for cleanup.\n *\n * Subclasses add task-specific public methods (`send`, `stream`, etc.).\n */\nexport abstract class LMTask {\n protected constructor(\n /** Engine used for inference. */\n protected readonly engine: Engine,\n /** Resolved metadata for the loaded model. */\n public readonly preset: ModelPreset\n ) {}\n\n /**\n * Load a model into a backend and return the wired-up engine + preset.\n *\n * Subclasses call this from their static `create()` factories.\n *\n * @param modelId - Friendly model id from the registry.\n * @param options - Task creation options.\n */\n protected static async createEngine(\n modelId: string,\n options: LMTaskCreateOptions = {}\n ): Promise<ResolvedEngine> {\n const preset = resolveModelPreset(modelId);\n const engine = options.engine ?? LMTask.defaultEngine(options);\n if (!engine.isLoaded()) {\n await engine.load(preset.webllmId, options.onProgress);\n }\n return { engine, preset };\n }\n\n private static defaultEngine(options: LMTaskCreateOptions): Engine {\n const useWorker: boolean = options.inWorker ?? true;\n if (useWorker) {\n return new WorkerEngine(createInferenceWorker());\n }\n return new WebLLMEngine();\n }\n\n /** Release engine resources. Safe to call multiple times. */\n async unload(): Promise<void> {\n await this.engine.unload();\n }\n\n /** Whether the underlying engine has a loaded model. */\n isLoaded(): boolean {\n return this.engine.isLoaded();\n }\n}\n","import { parseStructuredOutput } from \"./structured/json-schema\";\nimport type { FinishReason, Message } from \"./types\";\n\n/**\n * Result returned by `Chat.send()`.\n *\n * Holds the assistant's textual reply, the structured assistant message\n * (already appended to the chat history), and metadata about the generation.\n */\nexport class ChatReply {\n constructor(\n /** The assistant's reply text. */\n public readonly text: string,\n /** The structured assistant message (already appended to chat history). */\n public readonly message: Message,\n /** Number of tokens generated. 0 when the engine does not report it. */\n public readonly tokensGenerated: number,\n /** Why the generation loop stopped. */\n public readonly finishReason: FinishReason\n ) {}\n\n /**\n * Parse {@link ChatReply.text} as JSON.\n *\n * Intended for replies generated with `json: true` or `jsonSchema`.\n * The result is cast to `T` without runtime validation; pair with Zod /\n * Ajv on the call site if you need to verify the schema.\n *\n * @typeParam T - Expected parsed shape.\n * @returns The parsed JSON value.\n * @throws StructuredOutputError if the text is not valid JSON.\n */\n json<T = unknown>(): T {\n return parseStructuredOutput<T>(this.text);\n }\n}\n\n/**\n * Result returned by `Completion.predict()`.\n *\n * Holds the generated continuation text (the prompt itself is not included)\n * plus metadata about the generation loop.\n */\nexport class CompletionResult {\n constructor(\n /** The generated text (continuation only, prompt excluded). */\n public readonly text: string,\n /** The original prompt that was fed to the model. */\n public readonly prompt: string,\n /** Number of tokens generated. 0 when the engine does not report it. */\n public readonly tokensGenerated: number,\n /** Why the generation loop stopped. */\n public readonly finishReason: FinishReason\n ) {}\n\n /**\n * Parse {@link CompletionResult.text} as JSON.\n *\n * Intended for completions generated with `json: true` or `jsonSchema`.\n * The result is cast to `T` without runtime validation.\n *\n * @typeParam T - Expected parsed shape.\n * @returns The parsed JSON value.\n * @throws StructuredOutputError if the text is not valid JSON.\n */\n json<T = unknown>(): T {\n return parseStructuredOutput<T>(this.text);\n }\n}\n","import { LMTask, type LMTaskCreateOptions } from \"./lm-task\";\nimport type { Engine } from \"../core/engine\";\nimport { ChatReply } from \"../results\";\nimport type { GenerationOptions, Message, ModelPreset, TokenChunk } from \"../types\";\n\n/**\n * Multi-turn chat task.\n *\n * Maintains an in-memory conversation history and applies the chat template\n * configured for the loaded model. Use {@link Chat.create} to construct an\n * instance — the constructor is private.\n *\n * @example\n * ```ts\n * const chat = await Chat.create(\"phi-3.5-mini-int4\");\n * const reply = await chat.send(\"Explain ONNX in one sentence.\");\n * console.log(reply.text);\n * ```\n *\n * @example Streaming\n * ```ts\n * const controller = new AbortController();\n * for await (const token of chat.stream(\"Explain ONNX.\", { signal: controller.signal })) {\n * process.stdout.write(token.text);\n * }\n * ```\n */\nexport class Chat extends LMTask {\n private readonly history: Message[] = [];\n private systemPrompt: string | null = null;\n\n private constructor(engine: Engine, preset: ModelPreset) {\n super(engine, preset);\n }\n\n /**\n * Create and load a `Chat` task for the given model.\n *\n * @param modelId - Friendly model id from the registry (e.g. `\"phi-3.5-mini-int4\"`).\n * @param options - Optional creation options (progress callback, engine override).\n */\n static async create(modelId: string, options: LMTaskCreateOptions = {}): Promise<Chat> {\n const { engine, preset } = await LMTask.createEngine(modelId, options);\n return new Chat(engine, preset);\n }\n\n /** Set or replace the system prompt prepended to every conversation. */\n setSystemPrompt(prompt: string): void {\n this.systemPrompt = prompt;\n }\n\n /** Clear the system prompt. */\n clearSystemPrompt(): void {\n this.systemPrompt = null;\n }\n\n /** Reset the conversation history. The system prompt is preserved. */\n resetHistory(): void {\n this.history.length = 0;\n }\n\n /** A read-only snapshot of the conversation history. */\n getHistory(): readonly Message[] {\n return this.history.slice();\n }\n\n /**\n * Send a user message and await the full assistant reply.\n *\n * The user message and the assistant reply are appended to the history.\n *\n * @param message - The user-facing message text.\n * @param options - Generation options.\n * @returns A {@link ChatReply} with the assistant's reply.\n */\n async send(message: string, options: GenerationOptions = {}): Promise<ChatReply> {\n const messages = this.buildMessages(message);\n const text = await this.engine.generate(messages, options);\n const userMsg: Message = { role: \"user\", content: message };\n const assistantMsg: Message = { role: \"assistant\", content: text };\n this.history.push(userMsg, assistantMsg);\n return new ChatReply(text, assistantMsg, 0, \"stop\");\n }\n\n /**\n * Stream the assistant reply token-by-token as an async iterable.\n *\n * The full reply is appended to the history when the stream completes\n * normally. If the stream is aborted, neither message is appended.\n *\n * @param message - The user-facing message text.\n * @param options - Generation options including an optional `signal`.\n */\n async *stream(message: string, options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const messages = this.buildMessages(message);\n const userMsg: Message = { role: \"user\", content: message };\n let acc: string = \"\";\n for await (const chunk of this.engine.stream(messages, options)) {\n acc += chunk.text;\n yield chunk;\n }\n const assistantMsg: Message = { role: \"assistant\", content: acc };\n this.history.push(userMsg, assistantMsg);\n }\n\n private buildMessages(userMessage: string): Message[] {\n const messages: Message[] = [];\n if (this.systemPrompt) {\n messages.push({ role: \"system\", content: this.systemPrompt });\n }\n messages.push(...this.history);\n messages.push({ role: \"user\", content: userMessage });\n return messages;\n }\n}\n","import { LMTask, type LMTaskCreateOptions } from \"./lm-task\";\nimport type { Engine } from \"../core/engine\";\nimport { CompletionResult } from \"../results\";\nimport type { GenerationOptions, ModelPreset, TokenChunk } from \"../types\";\n\n/**\n * Raw text-completion task.\n *\n * Unlike {@link Chat}, `Completion` does not maintain a conversation history\n * and does not apply a chat template. The prompt is fed to the model verbatim\n * and the model continues it. Useful for \"Once upon a time…\" style generation,\n * code completion, or any scenario where chat formatting would interfere.\n *\n * Use {@link Completion.create} to construct an instance — the constructor is\n * private.\n *\n * @example\n * ```ts\n * const comp = await Completion.create(\"qwen2.5-1.5b-int4\");\n * const result = await comp.predict(\"Once upon a time\", { maxTokens: 50 });\n * console.log(result.text);\n * ```\n *\n * @example Streaming\n * ```ts\n * const controller = new AbortController();\n * for await (const token of comp.stream(\"def fibonacci(n):\", { signal: controller.signal })) {\n * process.stdout.write(token.text);\n * }\n * ```\n */\nexport class Completion extends LMTask {\n private constructor(engine: Engine, preset: ModelPreset) {\n super(engine, preset);\n }\n\n /**\n * Create and load a `Completion` task for the given model.\n *\n * @param modelId - Friendly model id from the registry (e.g. `\"qwen2.5-1.5b-int4\"`).\n * @param options - Optional creation options (progress callback, engine override).\n */\n static async create(modelId: string, options: LMTaskCreateOptions = {}): Promise<Completion> {\n const { engine, preset } = await LMTask.createEngine(modelId, options);\n return new Completion(engine, preset);\n }\n\n /**\n * Generate a continuation for the given prompt.\n *\n * @param prompt - Raw text fed to the model.\n * @param options - Generation options.\n * @returns A {@link CompletionResult} with the generated continuation.\n */\n async predict(prompt: string, options: GenerationOptions = {}): Promise<CompletionResult> {\n const text = await this.engine.complete(prompt, options);\n return new CompletionResult(text, prompt, 0, \"stop\");\n }\n\n /**\n * Stream a continuation for the given prompt as an async iterable of token\n * chunks.\n *\n * @param prompt - Raw text fed to the model.\n * @param options - Generation options including an optional `signal`.\n */\n async *stream(prompt: string, options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n for await (const chunk of this.engine.streamCompletion(prompt, options)) {\n yield chunk;\n }\n }\n}\n","import { ModelLoadError, ModelNotLoadedError } from \"../core/exceptions\";\nimport { resolveEmbeddingPreset, type EmbeddingPreset } from \"../presets/models\";\nimport type { ProgressCallback } from \"../types\";\n\n/** Options accepted by {@link Embeddings.create}. */\nexport interface EmbeddingsCreateOptions {\n /** Optional callback for model load progress updates. */\n onProgress?: ProgressCallback;\n /** Override the embedding pipeline. Intended for testing. */\n pipeline?: EmbedPipeline;\n}\n\n/** Options accepted by {@link Embeddings.embed}. */\nexport interface EmbedOptions {\n /** L2-normalize each vector. Recommended for cosine similarity downstream. Default `true`. */\n normalize?: boolean;\n /** Pooling strategy. BGE-style models use `\"cls\"`. Most sentence-transformers use `\"mean\"`. Default `\"mean\"`. */\n pooling?: \"mean\" | \"cls\";\n}\n\n/**\n * Minimal pipeline contract that {@link Embeddings} depends on.\n *\n * The default implementation wraps `@huggingface/transformers`. Tests inject\n * a fake satisfying the same shape — they never load the real runtime.\n */\nexport interface EmbedPipeline {\n /**\n * Run the encoder on a batch of inputs and return raw vectors.\n *\n * @param texts - Input strings.\n * @param options - Pooling + normalization passed to the underlying pipeline.\n */\n embed(texts: string[], options: Required<EmbedOptions>): Promise<number[][]>;\n /** Release pipeline resources. */\n unload?(): Promise<void>;\n}\n\ntype TransformersModule = typeof import(\"@huggingface/transformers\");\n\nlet transformersModulePromise: Promise<TransformersModule> | null = null;\n\nasync function loadTransformers(): Promise<TransformersModule> {\n if (!transformersModulePromise) {\n transformersModulePromise = import(\"@huggingface/transformers\");\n }\n return transformersModulePromise;\n}\n\nasync function buildDefaultPipeline(\n preset: EmbeddingPreset,\n onProgress?: ProgressCallback\n): Promise<EmbedPipeline> {\n const transformers = await loadTransformers();\n try {\n const pipe = await transformers.pipeline(\"feature-extraction\", preset.transformersId, {\n progress_callback: (report: unknown): void => {\n if (!onProgress) return;\n const r = report as { progress?: number; status?: string };\n onProgress({\n progress: typeof r.progress === \"number\" ? r.progress / 100 : 0,\n text: r.status ?? \"\",\n loaded: 0,\n total: 0,\n phase: \"downloading\",\n });\n },\n });\n return {\n async embed(texts, options): Promise<number[][]> {\n const output = await pipe(texts, {\n pooling: options.pooling,\n normalize: options.normalize,\n });\n return output.tolist();\n },\n async unload(): Promise<void> {\n if (typeof (pipe as { dispose?: () => Promise<void> }).dispose === \"function\") {\n await (pipe as unknown as { dispose: () => Promise<void> }).dispose();\n }\n },\n };\n } catch (err) {\n throw new ModelLoadError(`Failed to load embedding model \"${preset.id}\".`, err);\n }\n}\n\n/**\n * Sentence embedding task backed by `@huggingface/transformers`.\n *\n * Use {@link Embeddings.create} to construct an instance — the constructor is\n * private. The default backend lazy-loads the transformers.js runtime; tests\n * inject a {@link EmbedPipeline} mock instead.\n *\n * @example\n * ```ts\n * const emb = await Embeddings.create(\"bge-small-en-v1.5\");\n * const vectors = await emb.embed([\"hello world\", \"another sentence\"]);\n * console.log(vectors[0].length); // 384\n * ```\n */\nexport class Embeddings {\n private constructor(\n private readonly pipeline: EmbedPipeline,\n /** Resolved metadata for the loaded model. */\n public readonly preset: EmbeddingPreset\n ) {}\n\n /**\n * Create and load an `Embeddings` task for the given model.\n *\n * @param modelId - Friendly id from the embedding registry.\n * @param options - Optional creation options.\n * @throws UnknownModelError if `modelId` is not in the registry.\n * @throws ModelLoadError if the underlying pipeline fails to load.\n */\n static async create(modelId: string, options: EmbeddingsCreateOptions = {}): Promise<Embeddings> {\n const preset = resolveEmbeddingPreset(modelId);\n const pipeline = options.pipeline ?? (await buildDefaultPipeline(preset, options.onProgress));\n return new Embeddings(pipeline, preset);\n }\n\n /**\n * Encode an array of strings into dense vectors.\n *\n * Returns one vector per input, in the same order. Empty input array\n * returns an empty array (no error).\n *\n * @param texts - Input strings.\n * @param options - Pooling + normalization. Defaults: `pooling: \"mean\"`, `normalize: true`.\n */\n async embed(texts: string[], options: EmbedOptions = {}): Promise<number[][]> {\n if (texts.length === 0) return [];\n if (!this.pipeline) {\n throw new ModelNotLoadedError(\"Embeddings pipeline not initialized.\");\n }\n const merged: Required<EmbedOptions> = {\n normalize: options.normalize ?? true,\n pooling: options.pooling ?? \"mean\",\n };\n return this.pipeline.embed(texts, merged);\n }\n\n /**\n * Convenience: encode a single string and return its vector.\n *\n * @param text - Input string.\n * @param options - Forwarded to {@link Embeddings.embed}.\n */\n async embedSingle(text: string, options: EmbedOptions = {}): Promise<number[]> {\n const [vec] = await this.embed([text], options);\n if (!vec) {\n throw new ModelLoadError(\"Embedding pipeline returned no result.\");\n }\n return vec;\n }\n\n /** Embedding dimension exposed by the loaded model. */\n get dimension(): number {\n return this.preset.dimension;\n }\n\n /** Release pipeline resources. Safe to call multiple times. */\n async unload(): Promise<void> {\n await this.pipeline.unload?.();\n }\n}\n","import { ModelLoadError, ModelNotLoadedError } from \"../core/exceptions\";\nimport { resolveRerankerPreset, type RerankerPreset } from \"../presets/models\";\nimport type { ProgressCallback } from \"../types\";\n\n/** Options accepted by {@link Reranker.create}. */\nexport interface RerankerCreateOptions {\n /** Optional callback for model load progress updates. */\n onProgress?: ProgressCallback;\n /** Override the rerank pipeline. Intended for testing. */\n pipeline?: RerankPipeline;\n}\n\n/** Options accepted by {@link Reranker.score}. */\nexport interface RerankOptions {\n /**\n * Apply sigmoid to logits to map scores into `[0, 1]`. Recommended when the\n * downstream code uses scores as probabilities. Default `false` (raw logits).\n */\n sigmoid?: boolean;\n}\n\n/** A document paired with its score, for {@link Reranker.rank}. */\nexport interface RankedDocument {\n /** The document text. */\n text: string;\n /** Score from the cross-encoder. */\n score: number;\n /** Original index of the document in the input array. */\n index: number;\n}\n\n/**\n * Minimal pipeline contract that {@link Reranker} depends on.\n *\n * The default implementation wraps `@huggingface/transformers`. Tests inject\n * a fake satisfying the same shape — they never load the real runtime.\n */\nexport interface RerankPipeline {\n /**\n * Score `(query, doc)` pairs. One score per doc, in the same order.\n *\n * @param query - Single query string.\n * @param docs - Documents to score against the query.\n */\n score(query: string, docs: string[]): Promise<number[]>;\n /** Release pipeline resources. */\n unload?(): Promise<void>;\n}\n\ntype TransformersModule = typeof import(\"@huggingface/transformers\");\n\nlet transformersModulePromise: Promise<TransformersModule> | null = null;\n\nasync function loadTransformers(): Promise<TransformersModule> {\n if (!transformersModulePromise) {\n transformersModulePromise = import(\"@huggingface/transformers\");\n }\n return transformersModulePromise;\n}\n\nfunction sigmoidValue(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\nasync function buildDefaultPipeline(\n preset: RerankerPreset,\n onProgress?: ProgressCallback\n): Promise<RerankPipeline> {\n const transformers = await loadTransformers();\n try {\n const tokenizer = await transformers.AutoTokenizer.from_pretrained(preset.transformersId, {\n progress_callback: (report: unknown): void => {\n if (!onProgress) return;\n const r = report as { progress?: number; status?: string };\n onProgress({\n progress: typeof r.progress === \"number\" ? r.progress / 100 : 0,\n text: r.status ?? \"\",\n loaded: 0,\n total: 0,\n phase: \"downloading\",\n });\n },\n });\n const model = await transformers.AutoModelForSequenceClassification.from_pretrained(\n preset.transformersId,\n {\n progress_callback: (report: unknown): void => {\n if (!onProgress) return;\n const r = report as { progress?: number; status?: string };\n onProgress({\n progress: typeof r.progress === \"number\" ? r.progress / 100 : 0,\n text: r.status ?? \"\",\n loaded: 0,\n total: 0,\n phase: \"downloading\",\n });\n },\n }\n );\n return {\n async score(query, docs): Promise<number[]> {\n if (docs.length === 0) return [];\n const queries: string[] = docs.map(() => query);\n // `transformers.js` AutoTokenizer accepts `(text, options)` where\n // `options.text_pair` carries the second sequence; pair-input typing\n // isn't exported, so we cast through `unknown`.\n const tokenize = tokenizer as unknown as (\n text: string[],\n options: Record<string, unknown>\n ) => Record<string, unknown>;\n const inputs = tokenize(queries, {\n text_pair: docs,\n padding: true,\n truncation: true,\n max_length: preset.maxTokens,\n });\n const callModel = model as unknown as (\n inputs: Record<string, unknown>\n ) => Promise<{ logits: { tolist: () => number[][] } }>;\n const outputs = await callModel(inputs);\n const logits: number[][] = outputs.logits.tolist();\n return logits.map((row) => row[0] ?? 0);\n },\n async unload(): Promise<void> {\n const m = model as unknown as { dispose?: () => Promise<unknown> };\n if (typeof m.dispose === \"function\") await m.dispose();\n },\n };\n } catch (err) {\n throw new ModelLoadError(`Failed to load reranker model \"${preset.id}\".`, err);\n }\n}\n\n/**\n * Cross-encoder reranking task backed by `@huggingface/transformers`.\n *\n * Use {@link Reranker.create} to construct an instance — the constructor is\n * private. Useful as a second-stage step in a retrieve-then-rerank pipeline:\n * pull top-K candidates with a fast embedding similarity, then rerank with\n * a cross-encoder for higher precision.\n *\n * @example\n * ```ts\n * const rerank = await Reranker.create(\"bge-reranker-base\");\n * const scores = await rerank.score(\"what is webgpu?\", [\n * \"WebGPU is a modern graphics API\",\n * \"Bananas grow on trees\",\n * ]);\n * // scores[0] >> scores[1]\n * ```\n *\n * @example Ranked output sorted by score\n * ```ts\n * const ranked = await rerank.rank(\"what is webgpu?\", docs);\n * for (const r of ranked) console.log(r.score, r.text);\n * ```\n */\nexport class Reranker {\n private constructor(\n private readonly pipeline: RerankPipeline,\n /** Resolved metadata for the loaded model. */\n public readonly preset: RerankerPreset\n ) {}\n\n /**\n * Create and load a `Reranker` task for the given model.\n *\n * @param modelId - Friendly id from the reranker registry.\n * @param options - Optional creation options.\n * @throws UnknownModelError if `modelId` is not in the registry.\n * @throws ModelLoadError if the underlying pipeline fails to load.\n */\n static async create(modelId: string, options: RerankerCreateOptions = {}): Promise<Reranker> {\n const preset = resolveRerankerPreset(modelId);\n const pipeline = options.pipeline ?? (await buildDefaultPipeline(preset, options.onProgress));\n return new Reranker(pipeline, preset);\n }\n\n /**\n * Score each document against the query. Returns one score per doc, in\n * the same order. Empty `docs` returns `[]` (no error).\n *\n * @param query - Query string.\n * @param docs - Documents to score.\n * @param options - `sigmoid: true` maps logits into `[0, 1]`.\n */\n async score(query: string, docs: string[], options: RerankOptions = {}): Promise<number[]> {\n if (docs.length === 0) return [];\n if (!this.pipeline) {\n throw new ModelNotLoadedError(\"Reranker pipeline not initialized.\");\n }\n const raw = await this.pipeline.score(query, docs);\n return options.sigmoid ? raw.map(sigmoidValue) : raw;\n }\n\n /**\n * Score and sort documents by score in descending order. Returns a list of\n * {@link RankedDocument}s carrying the original index.\n *\n * @param query - Query string.\n * @param docs - Documents to rank.\n * @param options - Forwarded to {@link Reranker.score}.\n */\n async rank(\n query: string,\n docs: string[],\n options: RerankOptions = {}\n ): Promise<RankedDocument[]> {\n const scores = await this.score(query, docs, options);\n const ranked: RankedDocument[] = scores.map((score, index) => {\n const text: string = docs[index] ?? \"\";\n return { text, score, index };\n });\n ranked.sort((a, b) => b.score - a.score);\n return ranked;\n }\n\n /** Release pipeline resources. Safe to call multiple times. */\n async unload(): Promise<void> {\n await this.pipeline.unload?.();\n }\n}\n","import { MODEL_PRESETS, resolveModelPreset } from \"../presets/models\";\nimport { UnknownModelError } from \"../core/exceptions\";\n\n/** Snapshot of a single cached model's metadata. */\nexport interface CachedModelEntry {\n /** Friendly id from the registry (e.g. `\"llama-3.2-1b-int4\"`). */\n id: string;\n /** Backend-specific id (e.g. WebLLM `webllmId`). */\n backendId: string;\n /** Human-readable family name. */\n family: string;\n /** Approx parameter count, e.g. `\"1B\"`. */\n parameters: string;\n}\n\n/** Aggregate storage usage reported by the browser. */\nexport interface CacheUsage {\n /** Bytes used by the entire origin's storage (not just our cache). */\n usage: number;\n /** Bytes the browser is willing to give the origin. */\n quota: number;\n}\n\n/**\n * Hooks the {@link ModelCache} uses to talk to the underlying runtime and\n * the browser. Tests inject mocks; production code leaves them undefined,\n * letting `ModelCache` resolve the real `@mlc-ai/web-llm` helpers and\n * `navigator.storage.estimate()` lazily.\n */\nexport interface ModelCacheOptions {\n /** Override `hasModelInCache` from the runtime. */\n hasModel?: (backendId: string) => Promise<boolean>;\n /** Override `deleteModelInCache` from the runtime. */\n deleteModel?: (backendId: string) => Promise<void>;\n /** Override `navigator.storage.estimate()`. */\n estimate?: () => Promise<CacheUsage>;\n}\n\ntype WebLLMCacheModule = {\n hasModelInCache: (id: string) => Promise<boolean>;\n deleteModelInCache: (id: string) => Promise<void>;\n};\n\nlet webllmCachePromise: Promise<WebLLMCacheModule> | null = null;\n\nasync function loadWebLLMCacheHelpers(): Promise<WebLLMCacheModule> {\n if (!webllmCachePromise) {\n webllmCachePromise = import(\"@mlc-ai/web-llm\").then((m) => ({\n hasModelInCache: m.hasModelInCache,\n deleteModelInCache: m.deleteModelInCache,\n }));\n }\n return webllmCachePromise;\n}\n\nasync function defaultEstimate(): Promise<CacheUsage> {\n if (typeof navigator === \"undefined\" || !navigator.storage?.estimate) {\n return { usage: 0, quota: 0 };\n }\n const estimate = await navigator.storage.estimate();\n return {\n usage: estimate.usage ?? 0,\n quota: estimate.quota ?? 0,\n };\n}\n\n/**\n * Inspect and manage cached model weights.\n *\n * `localm-web` does not download or cache weights itself — that work is owned\n * by `@mlc-ai/web-llm`, which writes to the browser Cache API. `ModelCache`\n * is a thin wrapper that lets a consuming app surface cache state in its UI:\n * \"this model is downloaded\", \"you have 1.4 GB cached, free up space?\",\n * \"clear all models on logout\".\n *\n * @example\n * ```ts\n * const cache = new ModelCache();\n * if (await cache.has(\"llama-3.2-1b-int4\")) {\n * console.log(\"ready offline\");\n * }\n * const cached = await cache.list();\n * await cache.delete(\"phi-3.5-mini-int4\");\n * const usage = await cache.estimateUsage();\n * console.log(`${usage.usage} / ${usage.quota} bytes`);\n * ```\n */\nexport class ModelCache {\n private readonly hasModelHook: ((id: string) => Promise<boolean>) | undefined;\n private readonly deleteModelHook: ((id: string) => Promise<void>) | undefined;\n private readonly estimateHook: () => Promise<CacheUsage>;\n\n constructor(options: ModelCacheOptions = {}) {\n this.hasModelHook = options.hasModel;\n this.deleteModelHook = options.deleteModel;\n this.estimateHook = options.estimate ?? defaultEstimate;\n }\n\n /**\n * Whether the model's weights are present in the browser cache.\n *\n * @param modelId - Friendly id from the registry.\n * @throws UnknownModelError if `modelId` is not in the registry.\n */\n async has(modelId: string): Promise<boolean> {\n const backendId: string = resolveModelPreset(modelId).webllmId;\n const fn = this.hasModelHook ?? (await loadWebLLMCacheHelpers()).hasModelInCache;\n return fn(backendId);\n }\n\n /**\n * Delete a single model's weights from the browser cache. No-op when the\n * model is not cached.\n *\n * @param modelId - Friendly id from the registry.\n * @throws UnknownModelError if `modelId` is not in the registry.\n */\n async delete(modelId: string): Promise<void> {\n const backendId: string = resolveModelPreset(modelId).webllmId;\n const fn = this.deleteModelHook ?? (await loadWebLLMCacheHelpers()).deleteModelInCache;\n await fn(backendId);\n }\n\n /**\n * List the registry models that are currently cached.\n *\n * Iterates `MODEL_PRESETS` and probes each one. Only returns models known\n * to the SDK — models cached by external WebLLM calls outside our registry\n * are not included.\n *\n * @returns Empty list when nothing is cached.\n */\n async list(): Promise<CachedModelEntry[]> {\n const fn = this.hasModelHook ?? (await loadWebLLMCacheHelpers()).hasModelInCache;\n const probes = await Promise.all(\n Object.values(MODEL_PRESETS).map(async (preset) => {\n const cached: boolean = await fn(preset.webllmId);\n if (!cached) return null;\n const entry: CachedModelEntry = {\n id: preset.id,\n backendId: preset.webllmId,\n family: preset.family,\n parameters: preset.parameters,\n };\n return entry;\n })\n );\n return probes.filter((p): p is CachedModelEntry => p !== null);\n }\n\n /**\n * Delete every registry model from the cache. Useful for logout flows or\n * \"reset\" buttons. Models cached outside the registry are not touched.\n */\n async clear(): Promise<void> {\n const fn = this.deleteModelHook ?? (await loadWebLLMCacheHelpers()).deleteModelInCache;\n await Promise.all(Object.values(MODEL_PRESETS).map((p) => fn(p.webllmId)));\n }\n\n /**\n * Aggregate storage stats from the browser. Returned numbers cover the\n * entire origin (Cache API + IndexedDB + Service Workers + OPFS), not\n * just our model cache — use it for \"you have X of Y available\" hints.\n */\n async estimateUsage(): Promise<CacheUsage> {\n return this.estimateHook();\n }\n\n /**\n * Throw a descriptive error if the given id is not in the registry.\n * Exposed for code paths that want to validate before calling other\n * methods (those already throw on their own).\n *\n * @throws UnknownModelError\n */\n static assertKnown(modelId: string): void {\n if (!(modelId in MODEL_PRESETS)) {\n const available = Object.keys(MODEL_PRESETS).join(\", \");\n throw new UnknownModelError(`Unknown model \"${modelId}\". Available models: ${available}.`);\n }\n }\n}\n","import type { TokenChunk } from \"../types\";\n\n/**\n * Drain an async iterable of token chunks into a single string.\n *\n * Useful in tests, for non-streaming consumers, and as a one-line way to\n * reconstruct the final text from a `Chat.stream(...)` call.\n *\n * @param stream - The token-chunk async iterable to consume.\n * @returns The concatenation of every chunk's `text` field.\n */\nexport async function collectStream(stream: AsyncIterable<TokenChunk>): Promise<string> {\n let acc: string = \"\";\n for await (const chunk of stream) {\n acc += chunk.text;\n }\n return acc;\n}\n\n/**\n * Wrap an async iterable so that each `TokenChunk` is also passed to a\n * caller-supplied side-effect callback before being yielded downstream.\n *\n * This is intentionally a passthrough — it does not buffer.\n *\n * @param stream - The upstream token-chunk async iterable.\n * @param onChunk - Side-effect invoked for every chunk.\n * @returns A new async iterable yielding the same chunks.\n */\nexport async function* tap(\n stream: AsyncIterable<TokenChunk>,\n onChunk: (chunk: TokenChunk) => void\n): AsyncIterable<TokenChunk> {\n for await (const chunk of stream) {\n onChunk(chunk);\n yield chunk;\n }\n}\n","/**\n * localm-web — browser-only TypeScript SDK for running LLMs and SLMs locally.\n *\n * Public API surface for v0.1.\n *\n * @packageDocumentation\n */\n\nexport { Chat } from \"./tasks/chat\";\nexport { Completion } from \"./tasks/completion\";\nexport { Embeddings } from \"./tasks/embeddings\";\nexport type { EmbeddingsCreateOptions, EmbedOptions, EmbedPipeline } from \"./tasks/embeddings\";\nexport { Reranker } from \"./tasks/reranker\";\nexport type {\n RerankerCreateOptions,\n RerankOptions,\n RerankPipeline,\n RankedDocument,\n} from \"./tasks/reranker\";\nexport { LMTask } from \"./tasks/lm-task\";\nexport type { LMTaskCreateOptions } from \"./tasks/lm-task\";\n\nexport { ChatReply, CompletionResult } from \"./results\";\n\nexport {\n MODEL_PRESETS,\n resolveModelPreset,\n listSupportedModels,\n EMBEDDING_PRESETS,\n resolveEmbeddingPreset,\n listSupportedEmbeddingModels,\n RERANKER_PRESETS,\n resolveRerankerPreset,\n listSupportedRerankerModels,\n} from \"./presets/models\";\nexport type { EmbeddingPreset, RerankerPreset } from \"./presets/models\";\n\nexport {\n LocalmWebError,\n WebGPUUnavailableError,\n ModelLoadError,\n ModelNotLoadedError,\n UnknownModelError,\n GenerationAbortedError,\n QuotaExceededError,\n BackendNotAvailableError,\n StructuredOutputError,\n} from \"./core/exceptions\";\n\nexport {\n assertJsonSchema,\n serializeJsonSchema,\n parseStructuredOutput,\n} from \"./structured/json-schema\";\n\nexport type { Engine } from \"./core/engine\";\nexport { WorkerEngine } from \"./core/worker-engine\";\nexport { createInferenceWorker } from \"./worker/create-worker\";\nexport type { WorkerLike } from \"./worker/protocol\";\n\nexport { ModelCache } from \"./cache\";\nexport type { CachedModelEntry, CacheUsage, ModelCacheOptions } from \"./cache\";\n\nexport { collectStream, tap } from \"./streaming/token-stream\";\n\nexport type {\n Role,\n FinishReason,\n Message,\n GenerationOptions,\n ModelLoadProgress,\n ModelLoadPhase,\n ProgressCallback,\n TokenChunk,\n ModelPreset,\n} from \"./types\";\n\n/** Current package version. Updated at release time. */\nexport const VERSION: string = \"0.4.0\";\n"],"names":["transformersModulePromise","loadTransformers","buildDefaultPipeline"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localm-web",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Browser-only TypeScript SDK for running LLMs and SLMs locally with WebGPU. Ultralytics-style DX, Vite-first.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"file":"inference.worker-CwvQtobb.js","sources":["../src/core/load-phase.ts","../src/core/exceptions.ts","../src/core/webllm-engine.ts","../src/worker/inference.worker.ts"],"sourcesContent":["import type { ModelLoadPhase } from \"../types\";\n\nconst DOWNLOAD_PATTERN: RegExp = /\\b(fetch|download|loading from cache|cache hit|param)/i;\nconst COMPILE_PATTERN: RegExp = /\\b(compil|shader|kernel|tensor|init|allocat|warm)/i;\n\n/**\n * Classify a runtime status text into a {@link ModelLoadPhase}.\n *\n * Heuristic: match download-related verbs first (network or cache hits are\n * treated as `downloading`), then compile-related verbs. Anything else falls\n * back to the generic `loading` bucket. The `ready` phase is never returned\n * here — callers emit it explicitly when the load resolves.\n *\n * @param text - The raw status string from the runtime.\n * @returns The classified phase.\n */\nexport function classifyLoadPhase(text: string): ModelLoadPhase {\n if (DOWNLOAD_PATTERN.test(text)) return \"downloading\";\n if (COMPILE_PATTERN.test(text)) return \"compiling\";\n return \"loading\";\n}\n","/**\n * Error hierarchy for localm-web.\n *\n * All errors thrown by the SDK extend `LocalmWebError` so consumers can\n * distinguish SDK errors from unrelated runtime errors with a single\n * `instanceof` check.\n */\n\n/** Base class for every error raised by localm-web. */\nexport class LocalmWebError extends Error {\n /**\n * @param message - Human-readable description of the error.\n * @param cause - Underlying error, if any.\n */\n constructor(\n message: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = new.target.name;\n }\n}\n\n/** Thrown when WebGPU is required but not available in the host browser. */\nexport class WebGPUUnavailableError extends LocalmWebError {}\n\n/** Thrown when a model fails to load (network, parsing, runtime init). */\nexport class ModelLoadError extends LocalmWebError {}\n\n/** Thrown when an inference call is made before a model has loaded. */\nexport class ModelNotLoadedError extends LocalmWebError {}\n\n/** Thrown when a model id is not present in the curated registry. */\nexport class UnknownModelError extends LocalmWebError {}\n\n/** Thrown when generation is aborted via an `AbortSignal`. */\nexport class GenerationAbortedError extends LocalmWebError {}\n\n/** Thrown when the browser denies storage quota for the model cache. */\nexport class QuotaExceededError extends LocalmWebError {}\n\n/** Thrown when no usable backend is available on the current platform. */\nexport class BackendNotAvailableError extends LocalmWebError {}\n","import type { Engine } from \"./engine\";\nimport { classifyLoadPhase } from \"./load-phase\";\nimport type { GenerationOptions, Message, ProgressCallback, TokenChunk } from \"../types\";\nimport {\n GenerationAbortedError,\n ModelLoadError,\n ModelNotLoadedError,\n WebGPUUnavailableError,\n} from \"./exceptions\";\n\ntype WebLLMModule = typeof import(\"@mlc-ai/web-llm\");\ntype MLCEngine = import(\"@mlc-ai/web-llm\").MLCEngineInterface;\ntype ChatCompletionMessageParam = import(\"@mlc-ai/web-llm\").ChatCompletionMessageParam;\n\nlet webllmModulePromise: Promise<WebLLMModule> | null = null;\n\nasync function loadWebLLM(): Promise<WebLLMModule> {\n if (!webllmModulePromise) {\n webllmModulePromise = import(\"@mlc-ai/web-llm\");\n }\n return webllmModulePromise;\n}\n\nfunction isWebGPUAvailable(): boolean {\n return typeof navigator !== \"undefined\" && \"gpu\" in navigator;\n}\n\ninterface SamplingParams {\n max_tokens?: number;\n temperature?: number;\n top_p?: number;\n}\n\nfunction buildSamplingParams(options: GenerationOptions): SamplingParams {\n const params: SamplingParams = {};\n if (options.maxTokens !== undefined) params.max_tokens = options.maxTokens;\n if (options.temperature !== undefined) params.temperature = options.temperature;\n if (options.topP !== undefined) params.top_p = options.topP;\n return params;\n}\n\nfunction toChatMessages(messages: Message[]): ChatCompletionMessageParam[] {\n return messages.map((m): ChatCompletionMessageParam => {\n switch (m.role) {\n case \"system\":\n return { role: \"system\", content: m.content };\n case \"user\":\n return { role: \"user\", content: m.content };\n case \"assistant\":\n return { role: \"assistant\", content: m.content };\n case \"tool\":\n return { role: \"tool\", content: m.content, tool_call_id: m.name ?? \"\" };\n }\n });\n}\n\n/**\n * Inference engine backed by [WebLLM (MLC)](https://github.com/mlc-ai/web-llm).\n *\n * Requires WebGPU. The fallback path planned for v0.5 will route to ORT-Web\n * when WebGPU is missing.\n */\nexport class WebLLMEngine implements Engine {\n private engine: MLCEngine | null = null;\n\n isLoaded(): boolean {\n return this.engine !== null;\n }\n\n async load(modelId: string, onProgress?: ProgressCallback): Promise<void> {\n if (!isWebGPUAvailable()) {\n throw new WebGPUUnavailableError(\n \"WebGPU is not available in this browser. The ORT-Web fallback is planned for v0.5.\"\n );\n }\n const webllm = await loadWebLLM();\n try {\n this.engine = await webllm.CreateMLCEngine(modelId, {\n initProgressCallback: (report): void => {\n onProgress?.({\n progress: report.progress,\n text: report.text,\n loaded: 0,\n total: 0,\n phase: classifyLoadPhase(report.text),\n });\n },\n });\n onProgress?.({\n progress: 1,\n text: \"Model ready.\",\n loaded: 0,\n total: 0,\n phase: \"ready\",\n });\n } catch (err) {\n throw new ModelLoadError(`Failed to load model \"${modelId}\".`, err);\n }\n }\n\n async generate(messages: Message[], options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: false,\n });\n return completion.choices[0]?.message?.content ?? \"\";\n }\n\n async *stream(messages: Message[], options: GenerationOptions = {}): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.chat.completions.create({\n ...buildSamplingParams(options),\n messages: toChatMessages(messages),\n stream: true,\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.delta?.content ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming generation failed.\", err);\n }\n }\n\n async complete(prompt: string, options: GenerationOptions = {}): Promise<string> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: false,\n });\n return completion.choices[0]?.text ?? \"\";\n }\n\n async *streamCompletion(\n prompt: string,\n options: GenerationOptions = {}\n ): AsyncIterable<TokenChunk> {\n const engine = this.requireEngine();\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted before start.\");\n }\n const completion = await engine.completions.create({\n ...buildSamplingParams(options),\n prompt,\n stream: true,\n });\n let index: number = 0;\n let finished: boolean = false;\n try {\n for await (const chunk of completion) {\n if (options.signal?.aborted) {\n throw new GenerationAbortedError(\"Generation aborted by signal.\");\n }\n const choice = chunk.choices[0];\n const delta = choice?.text ?? \"\";\n if (delta) {\n yield { text: delta, index, done: false };\n index += 1;\n }\n if (choice?.finish_reason) {\n finished = true;\n yield { text: \"\", index, done: true };\n index += 1;\n }\n }\n if (!finished) {\n yield { text: \"\", index, done: true };\n }\n } catch (err) {\n if (err instanceof GenerationAbortedError) throw err;\n throw new ModelLoadError(\"Streaming completion failed.\", err);\n }\n }\n\n async unload(): Promise<void> {\n if (this.engine) {\n await this.engine.unload();\n this.engine = null;\n }\n }\n\n private requireEngine(): MLCEngine {\n if (!this.engine) {\n throw new ModelNotLoadedError(\"Engine not loaded. Call load() before generation.\");\n }\n return this.engine;\n }\n}\n","/// <reference lib=\"webworker\" />\n\nimport { WebLLMEngine } from \"../core/webllm-engine\";\nimport type { WorkerRequest, WorkerResponse } from \"./protocol\";\n\ndeclare const self: DedicatedWorkerGlobalScope;\n\nconst engine: WebLLMEngine = new WebLLMEngine();\nconst aborts: Map<number, AbortController> = new Map();\n\nfunction reply(message: WorkerResponse): void {\n self.postMessage(message);\n}\n\nfunction fail(id: number, err: unknown): void {\n const error = err instanceof Error ? err : new Error(String(err));\n reply({ op: \"error\", id, name: error.name, message: error.message });\n}\n\nasync function handleLoad(req: Extract<WorkerRequest, { op: \"load\" }>): Promise<void> {\n try {\n await engine.load(req.modelId, (payload) => {\n reply({ op: \"progress\", id: req.id, payload });\n });\n reply({ op: \"loaded\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n }\n}\n\nasync function handleGenerate(req: Extract<WorkerRequest, { op: \"generate\" }>): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n const text: string = await engine.generate(req.messages, {\n ...req.options,\n signal: controller.signal,\n });\n reply({ op: \"generated\", id: req.id, text });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleComplete(req: Extract<WorkerRequest, { op: \"complete\" }>): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n const text: string = await engine.complete(req.prompt, {\n ...req.options,\n signal: controller.signal,\n });\n reply({ op: \"generated\", id: req.id, text });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleStreamCompletion(\n req: Extract<WorkerRequest, { op: \"stream-completion\" }>\n): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n for await (const chunk of engine.streamCompletion(req.prompt, {\n ...req.options,\n signal: controller.signal,\n })) {\n reply({ op: \"token\", id: req.id, chunk });\n }\n reply({ op: \"stream-end\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleStream(req: Extract<WorkerRequest, { op: \"stream\" }>): Promise<void> {\n const controller: AbortController = new AbortController();\n aborts.set(req.id, controller);\n try {\n for await (const chunk of engine.stream(req.messages, {\n ...req.options,\n signal: controller.signal,\n })) {\n reply({ op: \"token\", id: req.id, chunk });\n }\n reply({ op: \"stream-end\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n } finally {\n aborts.delete(req.id);\n }\n}\n\nasync function handleUnload(req: Extract<WorkerRequest, { op: \"unload\" }>): Promise<void> {\n try {\n await engine.unload();\n reply({ op: \"unloaded\", id: req.id });\n } catch (err) {\n fail(req.id, err);\n }\n}\n\nfunction handleIsLoaded(req: Extract<WorkerRequest, { op: \"isLoaded\" }>): void {\n reply({ op: \"is-loaded\", id: req.id, value: engine.isLoaded() });\n}\n\nfunction handleAbort(req: Extract<WorkerRequest, { op: \"abort\" }>): void {\n aborts.get(req.id)?.abort();\n}\n\nself.addEventListener(\"message\", (event: MessageEvent<WorkerRequest>): void => {\n const req = event.data;\n switch (req.op) {\n case \"load\":\n void handleLoad(req);\n return;\n case \"generate\":\n void handleGenerate(req);\n return;\n case \"stream\":\n void handleStream(req);\n return;\n case \"complete\":\n void handleComplete(req);\n return;\n case \"stream-completion\":\n void handleStreamCompletion(req);\n return;\n case \"unload\":\n void handleUnload(req);\n return;\n case \"isLoaded\":\n handleIsLoaded(req);\n return;\n case \"abort\":\n handleAbort(req);\n return;\n }\n});\n"],"names":["engine"],"mappings":"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"}