@happyvertical/ai 0.102.1 → 0.102.2

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.
Files changed (64) hide show
  1. package/AGENT.md +1 -1
  2. package/README.md +123 -2
  3. package/dist/chunks/{anthropic-6ZDF32jo.js → anthropic-CxOrttgj.js} +3 -4
  4. package/dist/chunks/{anthropic-6ZDF32jo.js.map → anthropic-CxOrttgj.js.map} +1 -1
  5. package/dist/chunks/{bedrock-BQi3Z9jR.js → bedrock-B0cNEXFa.js} +3 -4
  6. package/dist/chunks/{bedrock-BQi3Z9jR.js.map → bedrock-B0cNEXFa.js.map} +1 -1
  7. package/dist/chunks/{bifrost-DsI2I4ka.js → bifrost-CyO3-hBv.js} +4 -4
  8. package/dist/chunks/{bifrost-DsI2I4ka.js.map → bifrost-CyO3-hBv.js.map} +1 -1
  9. package/dist/chunks/{byteplus-modelark-Dq8AgCQD.js → byteplus-modelark-DZ0YL2vl.js} +3 -4
  10. package/dist/chunks/{byteplus-modelark-Dq8AgCQD.js.map → byteplus-modelark-DZ0YL2vl.js.map} +1 -1
  11. package/dist/chunks/{claude-cli-DIFxvG3c.js → claude-cli-CtBp-Uk-.js} +3 -4
  12. package/dist/chunks/{claude-cli-DIFxvG3c.js.map → claude-cli-CtBp-Uk-.js.map} +1 -1
  13. package/dist/chunks/{continuation-D0NisZA1.js → continuation-7o7400bp.js} +3 -3
  14. package/dist/chunks/continuation-7o7400bp.js.map +1 -0
  15. package/dist/chunks/{gateway-audio-models-Dn7Wrc6P.js → gateway-audio-models-BSZGgjd8.js} +2 -2
  16. package/dist/chunks/{gateway-audio-models-Dn7Wrc6P.js.map → gateway-audio-models-BSZGgjd8.js.map} +1 -1
  17. package/dist/chunks/{gemini-BLFwWN90.js → gemini-DUk4ssth.js} +3 -4
  18. package/dist/chunks/{gemini-BLFwWN90.js.map → gemini-DUk4ssth.js.map} +1 -1
  19. package/dist/chunks/{huggingface-BuRwlABZ.js → huggingface-C41BZOoI.js} +3 -4
  20. package/dist/chunks/{huggingface-BuRwlABZ.js.map → huggingface-C41BZOoI.js.map} +1 -1
  21. package/dist/chunks/{litellm-CpfCT6jN.js → litellm-BLR5cs8B.js} +4 -4
  22. package/dist/chunks/{litellm-CpfCT6jN.js.map → litellm-BLR5cs8B.js.map} +1 -1
  23. package/dist/chunks/{ollama-CnrskTUg.js → ollama--y88p8Bz.js} +4 -5
  24. package/dist/chunks/{ollama-CnrskTUg.js.map → ollama--y88p8Bz.js.map} +1 -1
  25. package/dist/chunks/{openai-compat-video-CJIDdkUO.js → openai-compat-video-erarPEz-.js} +3 -4
  26. package/dist/chunks/{openai-compat-video-CJIDdkUO.js.map → openai-compat-video-erarPEz-.js.map} +1 -1
  27. package/dist/chunks/{qwen-tts-PzEkWcEb.js → qwen-tts-C9w7teY6.js} +2 -2
  28. package/dist/chunks/{qwen-tts-PzEkWcEb.js.map → qwen-tts-C9w7teY6.js.map} +1 -1
  29. package/dist/chunks/{rate-limit-BzWfHA0e.js → rate-limit-Bln_w0rH.js} +3 -280
  30. package/dist/chunks/rate-limit-Bln_w0rH.js.map +1 -0
  31. package/dist/chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  32. package/dist/chunks/{seevio-D2y5Io7C.js → seevio-CosohVa0.js} +3 -4
  33. package/dist/chunks/{seevio-D2y5Io7C.js.map → seevio-CosohVa0.js.map} +1 -1
  34. package/dist/chunks/{src-CLT6r1W0.js → src-DDEz_uOe.js} +25 -30
  35. package/dist/chunks/{src-CLT6r1W0.js.map → src-DDEz_uOe.js.map} +1 -1
  36. package/dist/chunks/{typesafe-CVUqQ8N1.js → typesafe-BNM-QAJ2.js} +3 -4
  37. package/dist/chunks/{typesafe-CVUqQ8N1.js.map → typesafe-BNM-QAJ2.js.map} +1 -1
  38. package/dist/chunks/usage-BaQKo8bc.js +484 -0
  39. package/dist/chunks/usage-BaQKo8bc.js.map +1 -0
  40. package/dist/chunks/webllm-CWgY5nnP.js +584 -0
  41. package/dist/chunks/webllm-CWgY5nnP.js.map +1 -0
  42. package/dist/index.js +3 -4
  43. package/dist/local.d.ts +33 -0
  44. package/dist/local.d.ts.map +1 -0
  45. package/dist/local.js +3 -0
  46. package/dist/node.js +4 -5
  47. package/dist/node.js.map +1 -1
  48. package/dist/shared/continuation.d.ts +1 -1
  49. package/dist/shared/continuation.d.ts.map +1 -1
  50. package/dist/shared/factory.d.ts.map +1 -1
  51. package/dist/shared/providers/webllm-shim.d.ts +9 -0
  52. package/dist/shared/providers/webllm-shim.d.ts.map +1 -0
  53. package/dist/shared/providers/webllm.d.ts +115 -0
  54. package/dist/shared/providers/webllm.d.ts.map +1 -0
  55. package/dist/shared/types.d.ts +73 -2
  56. package/dist/shared/types.d.ts.map +1 -1
  57. package/metadata.json +1 -0
  58. package/package.json +15 -2
  59. package/dist/chunks/continuation-D0NisZA1.js.map +0 -1
  60. package/dist/chunks/rate-limit-BzWfHA0e.js.map +0 -1
  61. package/dist/chunks/types-DB5W6_F-.js +0 -153
  62. package/dist/chunks/types-DB5W6_F-.js.map +0 -1
  63. package/dist/chunks/usage-C1Y1Nlg4.js +0 -35
  64. package/dist/chunks/usage-C1Y1Nlg4.js.map +0 -1
@@ -0,0 +1,584 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { b as extractTextContent, g as ModelNotFoundError, l as prepareRequestControls, m as ContextLengthError, o as normalizeBaseAIOptions, s as normalizeChatOptions, t as emitUsage, u as AIError, v as WebGPUUnavailableError, y as WebLLMPeerMissingError } from "./usage-BaQKo8bc.js";
3
+ //#region src/shared/providers/webllm.ts
4
+ /**
5
+ * WebLLM provider: runs a chat model entirely in the browser on WebGPU.
6
+ *
7
+ * Backed by `@mlc-ai/web-llm`, an optional peer dependency that is imported
8
+ * lazily on first use, so neither the root entry nor `getAI()` for other
9
+ * provider types ever loads it. Reachable through `getAI({ type: 'webllm' })`
10
+ * and the `@happyvertical/ai/local` subpath.
11
+ *
12
+ * WebLLM's engine API is OpenAI-shaped, so requests and responses map almost
13
+ * one to one. Structured output (`responseSchema`) becomes WebLLM's
14
+ * grammar-constrained `response_format`, which keeps a small model from
15
+ * emitting a value outside the schema.
16
+ */
17
+ var webllm_exports = /* @__PURE__ */ __exportAll({
18
+ DEFAULT_WEBLLM_MODEL: () => DEFAULT_WEBLLM_MODEL,
19
+ WebLLMProvider: () => WebLLMProvider,
20
+ disposeWebLLMEngines: () => disposeWebLLMEngines,
21
+ importWebLLM: () => importWebLLM,
22
+ isWebGPUAvailable: () => isWebGPUAvailable
23
+ });
24
+ var PROVIDER = "webllm";
25
+ /** Small enough for most GPUs (about 0.9 GB of VRAM); override with `defaultModel`. */
26
+ var DEFAULT_WEBLLM_MODEL = "Llama-3.2-1B-Instruct-q4f16_1-MLC";
27
+ /** Context window assumed when a model record does not declare one. */
28
+ var DEFAULT_CONTEXT_LENGTH = 4096;
29
+ var WEBLLM_CAPABILITIES = {
30
+ chat: true,
31
+ completion: true,
32
+ embeddings: false,
33
+ streaming: true,
34
+ functions: true,
35
+ vision: false,
36
+ fineTuning: false,
37
+ imageEmbeddings: false,
38
+ imageGeneration: false,
39
+ videoGeneration: false,
40
+ tts: false,
41
+ voiceCloning: false,
42
+ voiceDesign: false,
43
+ maxContextLength: DEFAULT_CONTEXT_LENGTH,
44
+ supportedOperations: [
45
+ "chat",
46
+ "completion",
47
+ "streaming",
48
+ "functions"
49
+ ]
50
+ };
51
+ /**
52
+ * Per-engine request queues. WebLLM runs one generation at a time per engine
53
+ * and `interruptGenerate()` stops whichever one is running, so requests are
54
+ * serialized here and only the running request may interrupt.
55
+ */
56
+ var engineQueues = /* @__PURE__ */ new WeakMap();
57
+ /** Engines shared by every provider instance that uses the prebuilt model list. */
58
+ var sharedEngines = /* @__PURE__ */ new Map();
59
+ /**
60
+ * Imports the optional peer. Isolated so a missing install becomes a typed
61
+ * {@link WebLLMPeerMissingError} instead of a bare module-resolution failure.
62
+ */
63
+ async function importWebLLM() {
64
+ try {
65
+ const loaded = await import("@mlc-ai/web-llm");
66
+ const module = typeof loaded?.CreateMLCEngine === "function" ? loaded : loaded?.default;
67
+ if (typeof module?.CreateMLCEngine !== "function") throw new Error("@mlc-ai/web-llm did not export CreateMLCEngine");
68
+ return module;
69
+ } catch (error) {
70
+ throw new WebLLMPeerMissingError(error);
71
+ }
72
+ }
73
+ /** True when this runtime exposes WebGPU (`navigator.gpu`). */
74
+ function isWebGPUAvailable() {
75
+ const nav = globalThis.navigator;
76
+ return Boolean(nav?.gpu);
77
+ }
78
+ /**
79
+ * Unloads every engine this module created and forgets them. Caller-supplied
80
+ * engines are never touched.
81
+ */
82
+ async function disposeWebLLMEngines() {
83
+ const entries = [...sharedEngines.values()];
84
+ sharedEngines.clear();
85
+ await Promise.allSettled(entries.map(async (entry) => (await entry.promise).unload?.()));
86
+ }
87
+ function unsupported(feature) {
88
+ throw new AIError(`${feature} is not supported by the webllm provider.`, "NOT_SUPPORTED", PROVIDER);
89
+ }
90
+ function stripUndefined(value) {
91
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
92
+ }
93
+ /**
94
+ * In-browser chat provider on WebLLM (WebGPU). See the module header.
95
+ */
96
+ var WebLLMProvider = class {
97
+ options;
98
+ /** Model the caller named, if any; a supplied engine is loaded with it already. */
99
+ explicitModel;
100
+ factoryEngine;
101
+ localEngines = /* @__PURE__ */ new Map();
102
+ constructor(options) {
103
+ this.explicitModel = options.defaultModel ?? options.model;
104
+ this.options = normalizeBaseAIOptions({
105
+ ...options,
106
+ defaultModel: this.explicitModel ?? "Llama-3.2-1B-Instruct-q4f16_1-MLC"
107
+ });
108
+ }
109
+ async acquireEngine(model, signal) {
110
+ const supplied = this.options.engine;
111
+ if (supplied) {
112
+ if (typeof supplied !== "function") return supplied;
113
+ this.factoryEngine ??= supplied().catch((error) => {
114
+ this.factoryEngine = void 0;
115
+ throw error;
116
+ });
117
+ return abortable(this.factoryEngine, signal);
118
+ }
119
+ if (!isWebGPUAvailable()) throw new WebGPUUnavailableError(PROVIDER);
120
+ const cache = this.options.appConfig ? this.localEngines : sharedEngines;
121
+ let entry = cache.get(model);
122
+ if (!entry) {
123
+ const listeners = /* @__PURE__ */ new Set();
124
+ const created = {
125
+ listeners,
126
+ promise: this.createEngine(model, listeners)
127
+ };
128
+ created.promise.catch(() => {
129
+ if (cache.get(model) === created) cache.delete(model);
130
+ });
131
+ cache.set(model, created);
132
+ entry = created;
133
+ }
134
+ const onProgress = this.options.onLoadProgress;
135
+ if (onProgress) entry.listeners.add(onProgress);
136
+ try {
137
+ return await abortable(entry.promise, signal);
138
+ } finally {
139
+ if (onProgress) entry.listeners.delete(onProgress);
140
+ }
141
+ }
142
+ async createEngine(model, listeners) {
143
+ return (await importWebLLM()).CreateMLCEngine(model, {
144
+ ...this.options.appConfig ? { appConfig: this.options.appConfig } : {},
145
+ initProgressCallback: (report) => {
146
+ for (const listener of listeners) try {
147
+ listener(report);
148
+ } catch {}
149
+ }
150
+ });
151
+ }
152
+ mapMessages(messages) {
153
+ return messages.map((message) => {
154
+ if (message.role === "user" && Array.isArray(message.content)) return {
155
+ role: "user",
156
+ content: message.content.map((part) => part.type === "text" ? {
157
+ type: "text",
158
+ text: part.text
159
+ } : {
160
+ type: "image_url",
161
+ image_url: { url: part.image_url.url }
162
+ }),
163
+ ...message.name ? { name: message.name } : {}
164
+ };
165
+ const content = extractTextContent(message.content);
166
+ if (message.role === "tool" || message.role === "function") return {
167
+ role: "tool",
168
+ content,
169
+ tool_call_id: message.tool_call_id ?? message.name ?? ""
170
+ };
171
+ if (message.role === "assistant") return stripUndefined({
172
+ role: "assistant",
173
+ content: content || (message.tool_calls?.length ? null : ""),
174
+ tool_calls: message.tool_calls?.map((call) => ({
175
+ id: call.id,
176
+ type: call.type,
177
+ function: {
178
+ name: call.function.name,
179
+ arguments: call.function.arguments
180
+ }
181
+ }))
182
+ });
183
+ return {
184
+ role: message.role,
185
+ content,
186
+ ...message.name ? { name: message.name } : {}
187
+ };
188
+ });
189
+ }
190
+ mapResponseFormat(options) {
191
+ if (options.responseSchema !== void 0) return {
192
+ type: "json_object",
193
+ schema: typeof options.responseSchema === "string" ? options.responseSchema : JSON.stringify(options.responseSchema)
194
+ };
195
+ return options.responseFormat?.type === "json_object" ? { type: "json_object" } : void 0;
196
+ }
197
+ buildRequest(messages, options, model, stream, includeTools) {
198
+ const tools = includeTools && options.tools?.length ? options.tools.map((tool) => ({
199
+ type: "function",
200
+ function: {
201
+ name: tool.function.name,
202
+ description: tool.function.description,
203
+ parameters: tool.function.parameters
204
+ }
205
+ })) : void 0;
206
+ return stripUndefined({
207
+ model,
208
+ messages: this.mapMessages(messages),
209
+ max_tokens: options.maxTokens,
210
+ temperature: options.temperature,
211
+ top_p: options.topP,
212
+ n: options.n,
213
+ stop: options.stop,
214
+ frequency_penalty: options.frequencyPenalty,
215
+ presence_penalty: options.presencePenalty,
216
+ seed: options.seed,
217
+ tools,
218
+ tool_choice: tools ? options.toolChoice : void 0,
219
+ response_format: this.mapResponseFormat(options),
220
+ stream,
221
+ stream_options: stream ? { include_usage: true } : void 0
222
+ });
223
+ }
224
+ /** Model id to send. A caller-supplied engine already has its model loaded. */
225
+ requestModel(model, options) {
226
+ if (!this.options.engine) return model;
227
+ return options.model ?? this.explicitModel;
228
+ }
229
+ mapUsage(usage) {
230
+ const raw = usage;
231
+ if (!raw) return void 0;
232
+ return {
233
+ promptTokens: raw.prompt_tokens ?? 0,
234
+ completionTokens: raw.completion_tokens ?? 0,
235
+ totalTokens: raw.total_tokens ?? 0
236
+ };
237
+ }
238
+ mapFinishReason(reason) {
239
+ switch (reason) {
240
+ case "length": return "length";
241
+ case "tool_calls": return "tool_calls";
242
+ default: return "stop";
243
+ }
244
+ }
245
+ /**
246
+ * WebLLM reports `abort` when generation was interrupted. The provider's own
247
+ * aborts are raised before this point, so reaching it means someone else
248
+ * interrupted the engine (for example a caller sharing a supplied engine);
249
+ * a truncated reply must not look like a normal stop.
250
+ */
251
+ assertNotInterrupted(reason, model) {
252
+ if (reason === "abort") throw new AIError("Generation was interrupted before it finished", "AI_INTERRUPTED", PROVIDER, model, true);
253
+ }
254
+ mapError(error, model) {
255
+ if (error instanceof AIError) return error;
256
+ const name = error?.name ?? "";
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ if (name === "WebGPUNotAvailableError" || name === "WebGPUNotFoundError") return new WebGPUUnavailableError(PROVIDER, message);
259
+ if (name === "ContextWindowSizeExceededError" || name === "PrefillChunkSizeSmallerThanImageError") return new ContextLengthError(PROVIDER, model);
260
+ if (name === "ModelNotFoundError" || name === "SpecifiedModelNotFoundError" || name === "UnsupportedModelIdError" || name === "ModelNotLoadedError") return new ModelNotFoundError(model ?? message, PROVIDER);
261
+ const mapped = new AIError(message, name === "DeviceLostError" ? "WEBGPU_DEVICE_LOST" : "API_ERROR", PROVIDER, model);
262
+ mapped.cause = error;
263
+ return mapped;
264
+ }
265
+ /** Throws the right AI error when the request ended by timeout or caller abort. */
266
+ abortError(controls, options, model) {
267
+ if (controls?.didTimeout()) return new AIError(`AI request timed out after ${controls.timeout}ms`, "AI_TIMEOUT", PROVIDER, model);
268
+ if (options.signal?.aborted) return new AIError("AI request aborted by caller", "AI_ABORTED", PROVIDER, model);
269
+ }
270
+ /**
271
+ * Waits for this request's turn on the engine; resolves to a release fn. A
272
+ * queued request that is aborted or times out leaves the queue at once and
273
+ * never interrupts the generation ahead of it.
274
+ */
275
+ async lockEngine(engine, signal) {
276
+ const previous = engineQueues.get(engine) ?? Promise.resolve();
277
+ let release = () => {};
278
+ const mine = new Promise((resolve) => {
279
+ release = resolve;
280
+ });
281
+ engineQueues.set(engine, previous.then(() => mine));
282
+ try {
283
+ await abortable(previous, signal);
284
+ } catch (error) {
285
+ release();
286
+ throw error;
287
+ }
288
+ return release;
289
+ }
290
+ /** Interrupts generation when the composed signal fires; returns a detach fn. */
291
+ bindInterrupt(engine, signal) {
292
+ const interrupt = () => {
293
+ try {
294
+ Promise.resolve(engine.interruptGenerate()).catch(() => {});
295
+ } catch {}
296
+ };
297
+ if (signal.aborted) interrupt();
298
+ signal.addEventListener("abort", interrupt, { once: true });
299
+ return () => signal.removeEventListener("abort", interrupt);
300
+ }
301
+ async chat(messages, options = {}) {
302
+ const startTime = Date.now();
303
+ let controls;
304
+ let detach;
305
+ let release;
306
+ let pending;
307
+ let holdRelease;
308
+ const model = options.model || this.options.defaultModel || "Llama-3.2-1B-Instruct-q4f16_1-MLC";
309
+ try {
310
+ options = normalizeChatOptions(this.options, options, PROVIDER, model);
311
+ const engine = await this.acquireEngine(model, options.signal);
312
+ controls = prepareRequestControls(this.options, options);
313
+ release = await this.lockEngine(engine, controls.signal);
314
+ detach = this.bindInterrupt(engine, controls.signal);
315
+ pending = engine.chat.completions.create(this.buildRequest(messages, options, this.requestModel(model, options), false, true));
316
+ const started = pending;
317
+ let settled = false;
318
+ const markSettled = () => {
319
+ settled = true;
320
+ };
321
+ started.then(markSettled, markSettled);
322
+ holdRelease = () => settled ? void 0 : started;
323
+ const response = await abortable(pending, controls.signal);
324
+ if (controls.signal.aborted) throw this.abortError(controls, options, model) ?? controls.signal.reason;
325
+ const choice = response?.choices?.[0];
326
+ this.assertNotInterrupted(choice?.finish_reason, model);
327
+ if (!choice) throw new AIError("No choices returned from webllm", "NO_CHOICES", PROVIDER);
328
+ const usage = this.mapUsage(response.usage);
329
+ emitUsage(this.options, PROVIDER, "chat", response.model || model, usage, startTime, options.usageTags);
330
+ const toolCalls = choice.message?.tool_calls?.map((call) => ({
331
+ id: call.id,
332
+ type: "function",
333
+ function: {
334
+ name: call.function.name,
335
+ arguments: call.function.arguments
336
+ }
337
+ }));
338
+ return {
339
+ content: choice.message?.content || "",
340
+ usage,
341
+ model: response.model || model,
342
+ finishReason: this.mapFinishReason(choice.finish_reason),
343
+ ...toolCalls?.length ? { toolCalls } : {}
344
+ };
345
+ } catch (error) {
346
+ throw this.abortError(controls, options, model) ?? this.mapError(error, model);
347
+ } finally {
348
+ detach?.();
349
+ releaseWhenIdle(release, holdRelease?.());
350
+ controls?.cleanup();
351
+ }
352
+ }
353
+ async complete(prompt, options = {}) {
354
+ return this.chat([{
355
+ role: "user",
356
+ content: prompt
357
+ }], {
358
+ model: options.model,
359
+ maxTokens: options.maxTokens,
360
+ temperature: options.temperature,
361
+ topP: options.topP,
362
+ n: options.n,
363
+ stop: options.stop,
364
+ stream: options.stream,
365
+ onProgress: options.onProgress,
366
+ signal: options.signal,
367
+ timeout: options.timeout,
368
+ reasoning: options.reasoning,
369
+ usageTags: options.usageTags,
370
+ continueOnLength: options.continueOnLength
371
+ });
372
+ }
373
+ async message(text, options = {}) {
374
+ const messages = [...options.history || [], {
375
+ role: options.role || "user",
376
+ content: text
377
+ }];
378
+ return (await this.chat(messages, {
379
+ model: options.model,
380
+ maxTokens: options.maxTokens,
381
+ temperature: options.temperature,
382
+ topP: options.topP,
383
+ stop: options.stop,
384
+ stream: options.stream,
385
+ frequencyPenalty: options.frequencyPenalty,
386
+ presencePenalty: options.presencePenalty,
387
+ responseFormat: options.responseFormat,
388
+ responseSchema: options.responseSchema,
389
+ seed: options.seed,
390
+ tools: options.tools,
391
+ toolChoice: options.toolChoice,
392
+ onProgress: options.onProgress,
393
+ signal: options.signal,
394
+ timeout: options.timeout,
395
+ reasoning: options.reasoning,
396
+ usageTags: options.usageTags,
397
+ continueOnLength: options.continueOnLength
398
+ })).content;
399
+ }
400
+ /**
401
+ * Streams a chat completion as text chunks. Like the other providers, a
402
+ * stream yields text only; use `chat()` for rounds that may call tools.
403
+ * Breaking out of the loop interrupts generation on the engine.
404
+ */
405
+ async *stream(messages, options = {}) {
406
+ const startTime = Date.now();
407
+ let controls;
408
+ let detach;
409
+ let engine;
410
+ let release;
411
+ let iterator;
412
+ let iteratorDone = false;
413
+ let holdRelease;
414
+ const model = options.model || this.options.defaultModel || "Llama-3.2-1B-Instruct-q4f16_1-MLC";
415
+ try {
416
+ options = normalizeChatOptions(this.options, options, PROVIDER, model);
417
+ engine = await this.acquireEngine(model, options.signal);
418
+ controls = prepareRequestControls(this.options, options);
419
+ release = await this.lockEngine(engine, controls.signal);
420
+ detach = this.bindInterrupt(engine, controls.signal);
421
+ const pending = engine.chat.completions.create(this.buildRequest(messages, options, this.requestModel(model, options), true, false));
422
+ let chunks;
423
+ try {
424
+ chunks = await abortable(pending, controls.signal);
425
+ } catch (error) {
426
+ holdRelease = pending.then((late) => abandonStream(late[Symbol.asyncIterator](), engine), () => {});
427
+ throw error;
428
+ }
429
+ iterator = chunks[Symbol.asyncIterator]();
430
+ let finishReason;
431
+ let usage;
432
+ for (;;) {
433
+ const step = await iterator.next();
434
+ if (step.done) {
435
+ iteratorDone = true;
436
+ break;
437
+ }
438
+ const chunk = step.value;
439
+ const choice = chunk.choices?.[0];
440
+ finishReason = choice?.finish_reason ?? finishReason;
441
+ if (chunk.usage) usage = this.mapUsage(chunk.usage);
442
+ const content = choice?.delta?.content;
443
+ if (content) {
444
+ options.onProgress?.(content);
445
+ yield content;
446
+ }
447
+ }
448
+ if (controls.signal.aborted) throw this.abortError(controls, options, model) ?? controls.signal.reason;
449
+ this.assertNotInterrupted(finishReason, model);
450
+ options.onFinishReason?.(this.mapFinishReason(finishReason));
451
+ emitUsage(this.options, PROVIDER, "stream", model, usage, startTime, options.usageTags);
452
+ } catch (error) {
453
+ throw this.abortError(controls, options, model) ?? this.mapError(error, model);
454
+ } finally {
455
+ if (iterator && !iteratorDone && engine) {
456
+ try {
457
+ Promise.resolve(engine.interruptGenerate()).catch(() => {});
458
+ } catch {}
459
+ await drainIterator(iterator);
460
+ }
461
+ detach?.();
462
+ releaseWhenIdle(release, holdRelease);
463
+ controls?.cleanup();
464
+ }
465
+ }
466
+ async countTokens(text) {
467
+ return Math.ceil(text.length / 4);
468
+ }
469
+ async getModels() {
470
+ const webllm = await importWebLLM();
471
+ const list = this.options.appConfig?.model_list ?? webllm.prebuiltAppConfig.model_list;
472
+ const functionCalling = new Set(webllm.functionCallingModelIds ?? []);
473
+ return list.filter((record) => record.model_type !== 1).map((record) => {
474
+ const vision = record.model_type === 2;
475
+ const functions = functionCalling.has(record.model_id);
476
+ return {
477
+ id: record.model_id,
478
+ name: record.model_id,
479
+ description: record.vram_required_MB ? `Runs on-device via WebGPU (~${Math.round(record.vram_required_MB)} MB VRAM)` : "Runs on-device via WebGPU",
480
+ contextLength: record.overrides?.context_window_size ?? DEFAULT_CONTEXT_LENGTH,
481
+ capabilities: [
482
+ "text",
483
+ "chat",
484
+ ...functions ? ["functions"] : [],
485
+ ...vision ? ["vision"] : []
486
+ ],
487
+ supportsFunctions: functions,
488
+ supportsVision: vision
489
+ };
490
+ });
491
+ }
492
+ async getCapabilities() {
493
+ return { ...WEBLLM_CAPABILITIES };
494
+ }
495
+ async embed(_text, _options = {}) {
496
+ return unsupported("Embeddings");
497
+ }
498
+ async embedImage(_image, _options = {}) {
499
+ return unsupported("Image embeddings");
500
+ }
501
+ async describeImage(_image, _prompt, _options = {}) {
502
+ return unsupported("Image description");
503
+ }
504
+ async generateImage(_prompt, _options = {}) {
505
+ return unsupported("Image generation");
506
+ }
507
+ async submitVideoGenerationJob(_options) {
508
+ return unsupported("Video generation");
509
+ }
510
+ async getVideoGenerationJob(_handle) {
511
+ return unsupported("Video generation");
512
+ }
513
+ async fetchVideoGenerationResult(_handle) {
514
+ return unsupported("Video generation");
515
+ }
516
+ async cancelVideoGenerationJob(_handle) {
517
+ return unsupported("Video generation");
518
+ }
519
+ async validateVideoGenerationAccess() {
520
+ return unsupported("Video generation");
521
+ }
522
+ async synthesizeSpeech(_text, _options) {
523
+ return unsupported("Text-to-speech");
524
+ }
525
+ streamSpeech(_text, _options) {
526
+ const error = new AIError("Text-to-speech streaming is not supported by the webllm provider.", "NOT_SUPPORTED", PROVIDER);
527
+ return { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
528
+ }
529
+ async cloneVoice(_options) {
530
+ return unsupported("Voice cloning");
531
+ }
532
+ async designVoice(_options) {
533
+ return unsupported("Voice design");
534
+ }
535
+ async getVoices(_options) {
536
+ return unsupported("Voice listing");
537
+ }
538
+ };
539
+ /** Runs a WebLLM stream to its end, ignoring errors; used after interrupting it. */
540
+ async function drainIterator(iterator) {
541
+ try {
542
+ while (!(await iterator.next()).done);
543
+ } catch {}
544
+ }
545
+ /** Releases an engine queue slot now, or once `idle` has settled. */
546
+ function releaseWhenIdle(release, idle) {
547
+ if (!release) return;
548
+ if (idle) idle.then(release, release);
549
+ else release();
550
+ }
551
+ /**
552
+ * Finishes a stream nobody is reading. WebLLM resets its interrupt flag on the
553
+ * first `next()`, so interrupt only after pulling one chunk, then drain it so
554
+ * the engine's lock is released.
555
+ */
556
+ async function abandonStream(iterator, engine) {
557
+ try {
558
+ if ((await iterator.next()).done) return;
559
+ await Promise.resolve(engine?.interruptGenerate());
560
+ } catch {
561
+ return;
562
+ }
563
+ await drainIterator(iterator);
564
+ }
565
+ /** Rejects with the signal's reason if it fires first; the underlying work continues. */
566
+ function abortable(promise, signal) {
567
+ if (!signal) return promise;
568
+ if (signal.aborted) return Promise.reject(new AIError("AI request aborted by caller", "AI_ABORTED", PROVIDER));
569
+ return new Promise((resolve, reject) => {
570
+ const onAbort = () => reject(new AIError("AI request aborted by caller", "AI_ABORTED", PROVIDER));
571
+ signal.addEventListener("abort", onAbort, { once: true });
572
+ promise.then((value) => {
573
+ signal.removeEventListener("abort", onAbort);
574
+ resolve(value);
575
+ }, (error) => {
576
+ signal.removeEventListener("abort", onAbort);
577
+ reject(error);
578
+ });
579
+ });
580
+ }
581
+ //#endregion
582
+ export { isWebGPUAvailable as a, importWebLLM as i, WebLLMProvider as n, webllm_exports as o, disposeWebLLMEngines as r, DEFAULT_WEBLLM_MODEL as t };
583
+
584
+ //# sourceMappingURL=webllm-CWgY5nnP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webllm-CWgY5nnP.js","names":[],"sources":["../../src/shared/providers/webllm.ts"],"sourcesContent":["// biome-ignore-all lint/style/useNamingConvention: WebLLM's OpenAI-shaped wire format and model records are snake_case.\n/**\n * WebLLM provider: runs a chat model entirely in the browser on WebGPU.\n *\n * Backed by `@mlc-ai/web-llm`, an optional peer dependency that is imported\n * lazily on first use, so neither the root entry nor `getAI()` for other\n * provider types ever loads it. Reachable through `getAI({ type: 'webllm' })`\n * and the `@happyvertical/ai/local` subpath.\n *\n * WebLLM's engine API is OpenAI-shaped, so requests and responses map almost\n * one to one. Structured output (`responseSchema`) becomes WebLLM's\n * grammar-constrained `response_format`, which keeps a small model from\n * emitting a value outside the schema.\n */\n\nimport {\n normalizeBaseAIOptions,\n normalizeChatOptions,\n type PreparedRequestControls,\n prepareRequestControls,\n} from '../safety';\nimport type {\n AICapabilities,\n AIInterface,\n AIMessage,\n AIModel,\n AIResponse,\n AIToolCall,\n ChatOptions,\n CompletionOptions,\n EmbeddingOptions,\n EmbeddingResponse,\n ImageDescriptionOptions,\n ImageEmbeddingOptions,\n ImageGenerationOptions,\n ImageGenerationResponse,\n MessageOptions,\n TokenUsage,\n TTSOptions,\n TTSResponse,\n VideoGenerationJob,\n VideoGenerationOptions,\n VideoGenerationResult,\n VideoGenerationStatusResult,\n Voice,\n VoiceCloneOptions,\n VoiceDesignOptions,\n VoiceListOptions,\n WebLLMEngineLike,\n WebLLMLoadProgress,\n WebLLMOptions,\n} from '../types';\nimport {\n AIError,\n ContextLengthError,\n extractTextContent,\n ModelNotFoundError,\n WebGPUUnavailableError,\n WebLLMPeerMissingError,\n} from '../types';\nimport { emitUsage } from './usage';\n\nconst PROVIDER = 'webllm';\n\n/** Small enough for most GPUs (about 0.9 GB of VRAM); override with `defaultModel`. */\nexport const DEFAULT_WEBLLM_MODEL = 'Llama-3.2-1B-Instruct-q4f16_1-MLC';\n\n/** Context window assumed when a model record does not declare one. */\nconst DEFAULT_CONTEXT_LENGTH = 4096;\n\nconst WEBLLM_CAPABILITIES: AICapabilities = {\n chat: true,\n completion: true,\n embeddings: false,\n streaming: true,\n functions: true,\n vision: false,\n fineTuning: false,\n imageEmbeddings: false,\n imageGeneration: false,\n videoGeneration: false,\n tts: false,\n voiceCloning: false,\n voiceDesign: false,\n maxContextLength: DEFAULT_CONTEXT_LENGTH,\n supportedOperations: ['chat', 'completion', 'streaming', 'functions'],\n};\n\n/** Structural subset of the `@mlc-ai/web-llm` module this provider uses. */\ninterface WebLLMModule {\n CreateMLCEngine(\n model: string,\n config?: {\n appConfig?: unknown;\n initProgressCallback?: (report: WebLLMLoadProgress) => void;\n },\n ): Promise<WebLLMEngineLike>;\n prebuiltAppConfig: { model_list: WebLLMModelRecord[] };\n functionCallingModelIds?: string[];\n}\n\ninterface WebLLMModelRecord {\n model_id: string;\n vram_required_MB?: number;\n low_resource_required?: boolean;\n /** WebLLM `ModelType`: 0 or absent = LLM, 1 = embedding, 2 = VLM. */\n model_type?: number;\n overrides?: { context_window_size?: number };\n}\n\ninterface EngineEntry {\n promise: Promise<WebLLMEngineLike>;\n listeners: Set<(report: WebLLMLoadProgress) => void>;\n}\n\n/**\n * Per-engine request queues. WebLLM runs one generation at a time per engine\n * and `interruptGenerate()` stops whichever one is running, so requests are\n * serialized here and only the running request may interrupt.\n */\nconst engineQueues = new WeakMap<object, Promise<void>>();\n\n/** Engines shared by every provider instance that uses the prebuilt model list. */\nconst sharedEngines = new Map<string, EngineEntry>();\n\n/**\n * Imports the optional peer. Isolated so a missing install becomes a typed\n * {@link WebLLMPeerMissingError} instead of a bare module-resolution failure.\n */\nexport async function importWebLLM(): Promise<WebLLMModule> {\n try {\n const loaded = (await import('@mlc-ai/web-llm')) as unknown as\n | (WebLLMModule & { default?: WebLLMModule })\n | undefined;\n const module =\n typeof loaded?.CreateMLCEngine === 'function' ? loaded : loaded?.default;\n if (typeof module?.CreateMLCEngine !== 'function') {\n throw new Error('@mlc-ai/web-llm did not export CreateMLCEngine');\n }\n return module;\n } catch (error) {\n throw new WebLLMPeerMissingError(error);\n }\n}\n\n/** True when this runtime exposes WebGPU (`navigator.gpu`). */\nexport function isWebGPUAvailable(): boolean {\n const nav = (globalThis as { navigator?: { gpu?: unknown } }).navigator;\n return Boolean(nav?.gpu);\n}\n\n/**\n * Unloads every engine this module created and forgets them. Caller-supplied\n * engines are never touched.\n */\nexport async function disposeWebLLMEngines(): Promise<void> {\n const entries = [...sharedEngines.values()];\n sharedEngines.clear();\n await Promise.allSettled(\n entries.map(async (entry) => (await entry.promise).unload?.()),\n );\n}\n\nfunction unsupported(feature: string): never {\n throw new AIError(\n `${feature} is not supported by the webllm provider.`,\n 'NOT_SUPPORTED',\n PROVIDER,\n );\n}\n\nfunction stripUndefined<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined),\n ) as T;\n}\n\n/**\n * In-browser chat provider on WebLLM (WebGPU). See the module header.\n */\nexport class WebLLMProvider implements AIInterface {\n private options: WebLLMOptions;\n /** Model the caller named, if any; a supplied engine is loaded with it already. */\n private readonly explicitModel?: string;\n private factoryEngine?: Promise<WebLLMEngineLike>;\n private readonly localEngines = new Map<string, EngineEntry>();\n\n constructor(options: WebLLMOptions) {\n this.explicitModel = options.defaultModel ?? options.model;\n this.options = normalizeBaseAIOptions({\n ...options,\n defaultModel: this.explicitModel ?? DEFAULT_WEBLLM_MODEL,\n });\n }\n\n // --------------------------------------------------------------------------\n // Engine lifecycle\n // --------------------------------------------------------------------------\n\n private async acquireEngine(\n model: string,\n signal?: AbortSignal,\n ): Promise<WebLLMEngineLike> {\n const supplied = this.options.engine;\n if (supplied) {\n if (typeof supplied !== 'function') return supplied;\n this.factoryEngine ??= supplied().catch((error) => {\n this.factoryEngine = undefined;\n throw error;\n });\n return abortable(this.factoryEngine, signal);\n }\n\n if (!isWebGPUAvailable()) {\n throw new WebGPUUnavailableError(PROVIDER);\n }\n\n // A custom appConfig changes what a model id means, so it must not share\n // an engine with other callers.\n const cache = this.options.appConfig ? this.localEngines : sharedEngines;\n let entry = cache.get(model);\n if (!entry) {\n const listeners = new Set<(report: WebLLMLoadProgress) => void>();\n const created: EngineEntry = {\n listeners,\n promise: this.createEngine(model, listeners),\n };\n created.promise.catch(() => {\n if (cache.get(model) === created) cache.delete(model);\n });\n cache.set(model, created);\n entry = created;\n }\n\n const onProgress = this.options.onLoadProgress;\n if (onProgress) entry.listeners.add(onProgress);\n try {\n return await abortable(entry.promise, signal);\n } finally {\n if (onProgress) entry.listeners.delete(onProgress);\n }\n }\n\n private async createEngine(\n model: string,\n listeners: Set<(report: WebLLMLoadProgress) => void>,\n ): Promise<WebLLMEngineLike> {\n const webllm = await importWebLLM();\n return webllm.CreateMLCEngine(model, {\n ...(this.options.appConfig ? { appConfig: this.options.appConfig } : {}),\n initProgressCallback: (report) => {\n for (const listener of listeners) {\n try {\n listener(report);\n } catch {\n // A progress listener must never break model loading.\n }\n }\n },\n });\n }\n\n // --------------------------------------------------------------------------\n // Request / response mapping\n // --------------------------------------------------------------------------\n\n private mapMessages(messages: AIMessage[]): Record<string, unknown>[] {\n return messages.map((message) => {\n if (message.role === 'user' && Array.isArray(message.content)) {\n return {\n role: 'user',\n content: message.content.map((part) =>\n part.type === 'text'\n ? { type: 'text', text: part.text }\n : { type: 'image_url', image_url: { url: part.image_url.url } },\n ),\n ...(message.name ? { name: message.name } : {}),\n };\n }\n const content = extractTextContent(message.content);\n if (message.role === 'tool' || message.role === 'function') {\n return {\n role: 'tool',\n content,\n tool_call_id: message.tool_call_id ?? message.name ?? '',\n };\n }\n if (message.role === 'assistant') {\n return stripUndefined({\n role: 'assistant',\n content: content || (message.tool_calls?.length ? null : ''),\n tool_calls: message.tool_calls?.map((call) => ({\n id: call.id,\n type: call.type,\n function: {\n name: call.function.name,\n arguments: call.function.arguments,\n },\n })),\n });\n }\n return {\n role: message.role,\n content,\n ...(message.name ? { name: message.name } : {}),\n };\n });\n }\n\n private mapResponseFormat(\n options: Pick<ChatOptions, 'responseFormat' | 'responseSchema'>,\n ): Record<string, unknown> | undefined {\n if (options.responseSchema !== undefined) {\n const schema =\n typeof options.responseSchema === 'string'\n ? options.responseSchema\n : JSON.stringify(options.responseSchema);\n // WebLLM compiles the schema to a grammar and masks logits, so the\n // output cannot leave it.\n return { type: 'json_object', schema };\n }\n return options.responseFormat?.type === 'json_object'\n ? { type: 'json_object' }\n : undefined;\n }\n\n private buildRequest(\n messages: AIMessage[],\n options: ChatOptions & { maxTokens: number },\n model: string | undefined,\n stream: boolean,\n includeTools: boolean,\n ): Record<string, unknown> {\n const tools =\n includeTools && options.tools?.length\n ? options.tools.map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.function.name,\n description: tool.function.description,\n parameters: tool.function.parameters,\n },\n }))\n : undefined;\n return stripUndefined({\n model,\n messages: this.mapMessages(messages),\n max_tokens: options.maxTokens,\n temperature: options.temperature,\n top_p: options.topP,\n n: options.n,\n stop: options.stop,\n frequency_penalty: options.frequencyPenalty,\n presence_penalty: options.presencePenalty,\n seed: options.seed,\n tools,\n tool_choice: tools ? options.toolChoice : undefined,\n response_format: this.mapResponseFormat(options),\n stream,\n stream_options: stream ? { include_usage: true } : undefined,\n });\n }\n\n /** Model id to send. A caller-supplied engine already has its model loaded. */\n private requestModel(\n model: string,\n options: ChatOptions,\n ): string | undefined {\n if (!this.options.engine) return model;\n return options.model ?? this.explicitModel;\n }\n\n private mapUsage(usage: unknown): TokenUsage | undefined {\n const raw = usage as\n | {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n }\n | undefined\n | null;\n if (!raw) return undefined;\n return {\n promptTokens: raw.prompt_tokens ?? 0,\n completionTokens: raw.completion_tokens ?? 0,\n totalTokens: raw.total_tokens ?? 0,\n };\n }\n\n private mapFinishReason(reason: string | null | undefined) {\n switch (reason) {\n case 'length':\n return 'length' as const;\n case 'tool_calls':\n return 'tool_calls' as const;\n default:\n return 'stop' as const;\n }\n }\n\n /**\n * WebLLM reports `abort` when generation was interrupted. The provider's own\n * aborts are raised before this point, so reaching it means someone else\n * interrupted the engine (for example a caller sharing a supplied engine);\n * a truncated reply must not look like a normal stop.\n */\n private assertNotInterrupted(\n reason: string | null | undefined,\n model: string,\n ): void {\n if (reason === 'abort') {\n throw new AIError(\n 'Generation was interrupted before it finished',\n 'AI_INTERRUPTED',\n PROVIDER,\n model,\n true,\n );\n }\n }\n\n private mapError(error: unknown, model?: string): AIError {\n if (error instanceof AIError) return error;\n const name = (error as { name?: string } | undefined)?.name ?? '';\n const message = error instanceof Error ? error.message : String(error);\n if (name === 'WebGPUNotAvailableError' || name === 'WebGPUNotFoundError') {\n return new WebGPUUnavailableError(PROVIDER, message);\n }\n if (\n name === 'ContextWindowSizeExceededError' ||\n name === 'PrefillChunkSizeSmallerThanImageError'\n ) {\n return new ContextLengthError(PROVIDER, model);\n }\n if (\n name === 'ModelNotFoundError' ||\n name === 'SpecifiedModelNotFoundError' ||\n name === 'UnsupportedModelIdError' ||\n name === 'ModelNotLoadedError'\n ) {\n return new ModelNotFoundError(model ?? message, PROVIDER);\n }\n const mapped = new AIError(\n message,\n name === 'DeviceLostError' ? 'WEBGPU_DEVICE_LOST' : 'API_ERROR',\n PROVIDER,\n model,\n );\n mapped.cause = error;\n return mapped;\n }\n\n /** Throws the right AI error when the request ended by timeout or caller abort. */\n private abortError(\n controls: PreparedRequestControls | undefined,\n options: ChatOptions,\n model?: string,\n ): AIError | undefined {\n if (controls?.didTimeout()) {\n return new AIError(\n `AI request timed out after ${controls.timeout}ms`,\n 'AI_TIMEOUT',\n PROVIDER,\n model,\n );\n }\n if (options.signal?.aborted) {\n return new AIError(\n 'AI request aborted by caller',\n 'AI_ABORTED',\n PROVIDER,\n model,\n );\n }\n return undefined;\n }\n\n /**\n * Waits for this request's turn on the engine; resolves to a release fn. A\n * queued request that is aborted or times out leaves the queue at once and\n * never interrupts the generation ahead of it.\n */\n private async lockEngine(\n engine: WebLLMEngineLike,\n signal: AbortSignal,\n ): Promise<() => void> {\n const previous = engineQueues.get(engine) ?? Promise.resolve();\n let release: () => void = () => {};\n const mine = new Promise<void>((resolve) => {\n release = resolve;\n });\n engineQueues.set(\n engine,\n previous.then(() => mine),\n );\n try {\n await abortable(previous, signal);\n } catch (error) {\n release();\n throw error;\n }\n return release;\n }\n\n /** Interrupts generation when the composed signal fires; returns a detach fn. */\n private bindInterrupt(\n engine: WebLLMEngineLike,\n signal: AbortSignal,\n ): () => void {\n const interrupt = () => {\n try {\n void Promise.resolve(engine.interruptGenerate()).catch(() => {});\n } catch {\n // Best effort: the request is already being abandoned.\n }\n };\n if (signal.aborted) interrupt();\n signal.addEventListener('abort', interrupt, { once: true });\n return () => signal.removeEventListener('abort', interrupt);\n }\n\n // --------------------------------------------------------------------------\n // Chat\n // --------------------------------------------------------------------------\n\n async chat(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): Promise<AIResponse> {\n const startTime = Date.now();\n let controls: PreparedRequestControls | undefined;\n let detach: (() => void) | undefined;\n let release: (() => void) | undefined;\n let pending: Promise<any> | undefined;\n let holdRelease: (() => Promise<unknown> | undefined) | undefined;\n const model =\n options.model || this.options.defaultModel || DEFAULT_WEBLLM_MODEL;\n try {\n options = normalizeChatOptions(this.options, options, PROVIDER, model);\n // Load before arming the request timeout: a multi-GB download must not\n // consume the generation budget.\n const engine = await this.acquireEngine(model, options.signal);\n controls = prepareRequestControls(this.options, options);\n release = await this.lockEngine(engine, controls.signal);\n detach = this.bindInterrupt(engine, controls.signal);\n\n pending = engine.chat.completions.create(\n this.buildRequest(\n messages,\n options as ChatOptions & { maxTokens: number },\n this.requestModel(model, options),\n false,\n true,\n ),\n );\n const started: Promise<unknown> = pending;\n let settled = false;\n const markSettled = () => {\n settled = true;\n };\n started.then(markSettled, markSettled);\n holdRelease = () => (settled ? undefined : started);\n const response = await abortable(pending, controls.signal);\n if (controls.signal.aborted) {\n throw (\n this.abortError(controls, options, model) ?? controls.signal.reason\n );\n }\n\n const choice = response?.choices?.[0];\n this.assertNotInterrupted(choice?.finish_reason, model);\n if (!choice) {\n throw new AIError(\n 'No choices returned from webllm',\n 'NO_CHOICES',\n PROVIDER,\n );\n }\n const usage = this.mapUsage(response.usage);\n emitUsage(\n this.options,\n PROVIDER,\n 'chat',\n response.model || model,\n usage,\n startTime,\n options.usageTags,\n );\n const toolCalls: AIToolCall[] | undefined =\n choice.message?.tool_calls?.map(\n (call: {\n id: string;\n function: { name: string; arguments: string };\n }) => ({\n id: call.id,\n type: 'function' as const,\n function: {\n name: call.function.name,\n arguments: call.function.arguments,\n },\n }),\n );\n return {\n content: choice.message?.content || '',\n usage,\n model: response.model || model,\n finishReason: this.mapFinishReason(choice.finish_reason),\n ...(toolCalls?.length ? { toolCalls } : {}),\n };\n } catch (error) {\n throw (\n this.abortError(controls, options, model) ?? this.mapError(error, model)\n );\n } finally {\n detach?.();\n releaseWhenIdle(release, holdRelease?.());\n controls?.cleanup();\n }\n }\n\n async complete(\n prompt: string,\n options: CompletionOptions = {},\n ): Promise<AIResponse> {\n return this.chat([{ role: 'user', content: prompt }], {\n model: options.model,\n maxTokens: options.maxTokens,\n temperature: options.temperature,\n topP: options.topP,\n n: options.n,\n stop: options.stop,\n stream: options.stream,\n onProgress: options.onProgress,\n signal: options.signal,\n timeout: options.timeout,\n reasoning: options.reasoning,\n usageTags: options.usageTags,\n continueOnLength: options.continueOnLength,\n });\n }\n\n async message(text: string, options: MessageOptions = {}): Promise<string> {\n const messages: AIMessage[] = [\n ...(options.history || []),\n { role: options.role || 'user', content: text },\n ];\n const response = await this.chat(messages, {\n model: options.model,\n maxTokens: options.maxTokens,\n temperature: options.temperature,\n topP: options.topP,\n stop: options.stop,\n stream: options.stream,\n frequencyPenalty: options.frequencyPenalty,\n presencePenalty: options.presencePenalty,\n responseFormat: options.responseFormat,\n responseSchema: options.responseSchema,\n seed: options.seed,\n tools: options.tools,\n toolChoice: options.toolChoice,\n onProgress: options.onProgress,\n signal: options.signal,\n timeout: options.timeout,\n reasoning: options.reasoning,\n usageTags: options.usageTags,\n continueOnLength: options.continueOnLength,\n });\n return response.content;\n }\n\n /**\n * Streams a chat completion as text chunks. Like the other providers, a\n * stream yields text only; use `chat()` for rounds that may call tools.\n * Breaking out of the loop interrupts generation on the engine.\n */\n async *stream(\n messages: AIMessage[],\n options: ChatOptions = {},\n ): AsyncIterable<string> {\n const startTime = Date.now();\n let controls: PreparedRequestControls | undefined;\n let detach: (() => void) | undefined;\n let engine: WebLLMEngineLike | undefined;\n let release: (() => void) | undefined;\n let iterator: AsyncIterator<WebLLMChunk> | undefined;\n let iteratorDone = false;\n let holdRelease: Promise<unknown> | undefined;\n const model =\n options.model || this.options.defaultModel || DEFAULT_WEBLLM_MODEL;\n try {\n options = normalizeChatOptions(this.options, options, PROVIDER, model);\n engine = await this.acquireEngine(model, options.signal);\n controls = prepareRequestControls(this.options, options);\n release = await this.lockEngine(engine, controls.signal);\n detach = this.bindInterrupt(engine, controls.signal);\n\n const pending: Promise<AsyncIterable<WebLLMChunk>> =\n engine.chat.completions.create(\n this.buildRequest(\n messages,\n options as ChatOptions & { maxTokens: number },\n this.requestModel(model, options),\n true,\n false,\n ),\n );\n let chunks: AsyncIterable<WebLLMChunk>;\n try {\n chunks = await abortable(pending, controls.signal);\n } catch (error) {\n // The caller is gone but the engine may still start this request.\n // Keep our queue closed until it has been interrupted and drained, or\n // the next request would wait behind it and could interrupt it.\n holdRelease = pending.then(\n (late) => abandonStream(late[Symbol.asyncIterator](), engine),\n () => {},\n );\n throw error;\n }\n // Iterated by hand: WebLLM releases its per-model lock only when its\n // generator runs to completion, never on an early `return()`.\n iterator = chunks[Symbol.asyncIterator]();\n\n let finishReason: string | null | undefined;\n let usage: TokenUsage | undefined;\n for (;;) {\n const step = await iterator.next();\n if (step.done) {\n iteratorDone = true;\n break;\n }\n const chunk = step.value;\n const choice = chunk.choices?.[0];\n finishReason = choice?.finish_reason ?? finishReason;\n if (chunk.usage) usage = this.mapUsage(chunk.usage);\n const content = choice?.delta?.content;\n if (content) {\n options.onProgress?.(content);\n yield content;\n }\n }\n if (controls.signal.aborted) {\n throw (\n this.abortError(controls, options, model) ?? controls.signal.reason\n );\n }\n this.assertNotInterrupted(finishReason, model);\n options.onFinishReason?.(this.mapFinishReason(finishReason));\n emitUsage(\n this.options,\n PROVIDER,\n 'stream',\n model,\n usage,\n startTime,\n options.usageTags,\n );\n } catch (error) {\n throw (\n this.abortError(controls, options, model) ?? this.mapError(error, model)\n );\n } finally {\n if (iterator && !iteratorDone && engine) {\n // Consumer stopped early (or the loop threw): stop the GPU, then let\n // the engine's generator finish so its lock is released before the\n // next request is admitted.\n try {\n void Promise.resolve(engine.interruptGenerate()).catch(() => {});\n } catch {\n // Best effort.\n }\n await drainIterator(iterator);\n }\n detach?.();\n releaseWhenIdle(release, holdRelease);\n controls?.cleanup();\n }\n }\n\n async countTokens(text: string): Promise<number> {\n // WebLLM exposes no standalone tokenizer; ~4 characters per token.\n return Math.ceil(text.length / 4);\n }\n\n // --------------------------------------------------------------------------\n // Models and capabilities\n // --------------------------------------------------------------------------\n\n async getModels(): Promise<AIModel[]> {\n const webllm = await importWebLLM();\n const list =\n (\n this.options.appConfig as\n | { model_list?: WebLLMModelRecord[] }\n | undefined\n )?.model_list ?? webllm.prebuiltAppConfig.model_list;\n const functionCalling = new Set(webllm.functionCallingModelIds ?? []);\n return list\n .filter((record) => record.model_type !== 1) // embedding models\n .map((record) => {\n const vision = record.model_type === 2;\n const functions = functionCalling.has(record.model_id);\n return {\n id: record.model_id,\n name: record.model_id,\n description: record.vram_required_MB\n ? `Runs on-device via WebGPU (~${Math.round(record.vram_required_MB)} MB VRAM)`\n : 'Runs on-device via WebGPU',\n contextLength:\n record.overrides?.context_window_size ?? DEFAULT_CONTEXT_LENGTH,\n capabilities: [\n 'text',\n 'chat',\n ...(functions ? ['functions'] : []),\n ...(vision ? ['vision'] : []),\n ],\n supportsFunctions: functions,\n supportsVision: vision,\n };\n });\n }\n\n async getCapabilities(): Promise<AICapabilities> {\n return { ...WEBLLM_CAPABILITIES };\n }\n\n // --------------------------------------------------------------------------\n // No in-browser equivalent\n // --------------------------------------------------------------------------\n\n async embed(\n _text: string | string[],\n _options: EmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return unsupported('Embeddings');\n }\n\n async embedImage(\n _image: string | Buffer,\n _options: ImageEmbeddingOptions = {},\n ): Promise<EmbeddingResponse> {\n return unsupported('Image embeddings');\n }\n\n async describeImage(\n _image: string | Buffer,\n _prompt?: string,\n _options: ImageDescriptionOptions = {},\n ): Promise<string> {\n return unsupported('Image description');\n }\n\n async generateImage(\n _prompt: string,\n _options: ImageGenerationOptions = {},\n ): Promise<ImageGenerationResponse> {\n return unsupported('Image generation');\n }\n\n async submitVideoGenerationJob(\n _options: VideoGenerationOptions,\n ): Promise<VideoGenerationJob> {\n return unsupported('Video generation');\n }\n\n async getVideoGenerationJob(\n _handle: VideoGenerationJob,\n ): Promise<VideoGenerationStatusResult> {\n return unsupported('Video generation');\n }\n\n async fetchVideoGenerationResult(\n _handle: VideoGenerationJob,\n ): Promise<VideoGenerationResult> {\n return unsupported('Video generation');\n }\n\n async cancelVideoGenerationJob(_handle: VideoGenerationJob): Promise<void> {\n return unsupported('Video generation');\n }\n\n async validateVideoGenerationAccess(): Promise<boolean> {\n return unsupported('Video generation');\n }\n\n async synthesizeSpeech(\n _text: string,\n _options?: TTSOptions,\n ): Promise<TTSResponse> {\n return unsupported('Text-to-speech');\n }\n\n streamSpeech(_text: string, _options?: TTSOptions): AsyncIterable<Buffer> {\n const error = new AIError(\n 'Text-to-speech streaming is not supported by the webllm provider.',\n 'NOT_SUPPORTED',\n PROVIDER,\n );\n return {\n [Symbol.asyncIterator]: () => ({\n next: () => Promise.reject(error),\n }),\n };\n }\n\n async cloneVoice(_options: VoiceCloneOptions): Promise<Voice> {\n return unsupported('Voice cloning');\n }\n\n async designVoice(_options: VoiceDesignOptions): Promise<Voice> {\n return unsupported('Voice design');\n }\n\n async getVoices(_options?: VoiceListOptions): Promise<Voice[]> {\n return unsupported('Voice listing');\n }\n}\n\n/** One streamed WebLLM chunk (structural subset). */\ninterface WebLLMChunk {\n choices?: Array<{\n delta?: { content?: string | null };\n finish_reason?: string | null;\n }>;\n usage?: unknown;\n}\n\n/** Runs a WebLLM stream to its end, ignoring errors; used after interrupting it. */\nasync function drainIterator(\n iterator: AsyncIterator<WebLLMChunk>,\n): Promise<void> {\n try {\n while (!(await iterator.next()).done) {\n // discard\n }\n } catch {\n // The engine already stopped; nothing more to release.\n }\n}\n\n/** Releases an engine queue slot now, or once `idle` has settled. */\nfunction releaseWhenIdle(\n release: (() => void) | undefined,\n idle?: Promise<unknown>,\n): void {\n if (!release) return;\n if (idle) void idle.then(release, release);\n else release();\n}\n\n/**\n * Finishes a stream nobody is reading. WebLLM resets its interrupt flag on the\n * first `next()`, so interrupt only after pulling one chunk, then drain it so\n * the engine's lock is released.\n */\nasync function abandonStream(\n iterator: AsyncIterator<WebLLMChunk>,\n engine: WebLLMEngineLike | undefined,\n): Promise<void> {\n try {\n if ((await iterator.next()).done) return;\n await Promise.resolve(engine?.interruptGenerate());\n } catch {\n return;\n }\n await drainIterator(iterator);\n}\n\n/** Rejects with the signal's reason if it fires first; the underlying work continues. */\nfunction abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) {\n return Promise.reject(\n new AIError('AI request aborted by caller', 'AI_ABORTED', PROVIDER),\n );\n }\n return new Promise<T>((resolve, reject) => {\n const onAbort = () =>\n reject(\n new AIError('AI request aborted by caller', 'AI_ABORTED', PROVIDER),\n );\n signal.addEventListener('abort', onAbort, { once: true });\n promise.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n"],"mappings":"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"}