webml-kit 0.4.0 → 0.4.1

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 (62) hide show
  1. package/package.json +3 -3
  2. package/dist/cache.d.ts +0 -46
  3. package/dist/cache.d.ts.map +0 -1
  4. package/dist/cache.js +0 -161
  5. package/dist/cache.js.map +0 -1
  6. package/dist/decision.d.ts +0 -182
  7. package/dist/decision.d.ts.map +0 -1
  8. package/dist/decision.js +0 -789
  9. package/dist/decision.js.map +0 -1
  10. package/dist/device.d.ts +0 -86
  11. package/dist/device.d.ts.map +0 -1
  12. package/dist/device.js +0 -195
  13. package/dist/device.js.map +0 -1
  14. package/dist/gpu-recovery.d.ts +0 -61
  15. package/dist/gpu-recovery.d.ts.map +0 -1
  16. package/dist/gpu-recovery.js +0 -105
  17. package/dist/gpu-recovery.js.map +0 -1
  18. package/dist/hub.d.ts +0 -97
  19. package/dist/hub.d.ts.map +0 -1
  20. package/dist/hub.js +0 -142
  21. package/dist/hub.js.map +0 -1
  22. package/dist/index.d.ts +0 -63
  23. package/dist/index.d.ts.map +0 -1
  24. package/dist/index.js +0 -65
  25. package/dist/index.js.map +0 -1
  26. package/dist/inputs.d.ts +0 -27
  27. package/dist/inputs.d.ts.map +0 -1
  28. package/dist/inputs.js +0 -155
  29. package/dist/inputs.js.map +0 -1
  30. package/dist/loader.d.ts +0 -100
  31. package/dist/loader.d.ts.map +0 -1
  32. package/dist/loader.js +0 -246
  33. package/dist/loader.js.map +0 -1
  34. package/dist/model-client.d.ts +0 -149
  35. package/dist/model-client.d.ts.map +0 -1
  36. package/dist/model-client.js +0 -318
  37. package/dist/model-client.js.map +0 -1
  38. package/dist/model-worker.d.ts +0 -16
  39. package/dist/model-worker.d.ts.map +0 -1
  40. package/dist/model-worker.js +0 -340
  41. package/dist/model-worker.js.map +0 -1
  42. package/dist/onnx-pipeline.d.ts +0 -20
  43. package/dist/onnx-pipeline.d.ts.map +0 -1
  44. package/dist/onnx-pipeline.js +0 -462
  45. package/dist/onnx-pipeline.js.map +0 -1
  46. package/dist/pipelines/index.d.ts +0 -28
  47. package/dist/pipelines/index.d.ts.map +0 -1
  48. package/dist/pipelines/index.js +0 -140
  49. package/dist/pipelines/index.js.map +0 -1
  50. package/dist/shim-ort.d.ts +0 -6
  51. package/dist/shim-ort.d.ts.map +0 -1
  52. package/dist/shim-ort.js +0 -38
  53. package/dist/shim-ort.js.map +0 -1
  54. package/dist/streaming.d.ts +0 -55
  55. package/dist/streaming.d.ts.map +0 -1
  56. package/dist/streaming.js +0 -142
  57. package/dist/streaming.js.map +0 -1
  58. package/dist/types.d.ts +0 -200
  59. package/dist/types.d.ts.map +0 -1
  60. package/dist/types.js +0 -3
  61. package/dist/types.js.map +0 -1
  62. package/dist/webml-kit.browser.js +0 -2568
@@ -1,2568 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
6
-
7
- // src/streaming.ts
8
- var TokenStream = class {
9
- queue = [];
10
- resolve = null;
11
- done = false;
12
- error = null;
13
- abortHandler = null;
14
- constructor(signal) {
15
- if (signal) {
16
- this.abortHandler = () => {
17
- this.abort(new Error("Stream aborted"));
18
- };
19
- signal.addEventListener("abort", this.abortHandler, { once: true });
20
- }
21
- }
22
- /** Push a new token event into the stream. */
23
- push(event) {
24
- if (this.done) return;
25
- if (this.resolve) {
26
- const r = this.resolve;
27
- this.resolve = null;
28
- r({ value: event, done: false });
29
- } else {
30
- this.queue.push(event);
31
- }
32
- }
33
- /** Signal that generation is complete. */
34
- end() {
35
- this.done = true;
36
- this.cleanup();
37
- if (this.resolve) {
38
- const r = this.resolve;
39
- this.resolve = null;
40
- r({ value: void 0, done: true });
41
- }
42
- }
43
- /** Signal an error. */
44
- abort(error) {
45
- this.error = error;
46
- this.done = true;
47
- this.cleanup();
48
- if (this.resolve) {
49
- const r = this.resolve;
50
- this.resolve = null;
51
- r({ value: void 0, done: true });
52
- }
53
- }
54
- /** Get the accumulated error, if any. */
55
- getError() {
56
- return this.error;
57
- }
58
- cleanup() {
59
- if (this.abortHandler) {
60
- this.abortHandler = null;
61
- }
62
- }
63
- // ─── AsyncIterable implementation ───
64
- [Symbol.asyncIterator]() {
65
- return {
66
- next: () => {
67
- if (this.queue.length > 0) {
68
- return Promise.resolve({
69
- value: this.queue.shift(),
70
- done: false
71
- });
72
- }
73
- if (this.done) {
74
- return Promise.resolve({
75
- value: void 0,
76
- done: true
77
- });
78
- }
79
- return new Promise((resolve) => {
80
- this.resolve = resolve;
81
- });
82
- },
83
- return: () => {
84
- this.done = true;
85
- this.cleanup();
86
- return Promise.resolve({
87
- value: void 0,
88
- done: true
89
- });
90
- }
91
- };
92
- }
93
- };
94
- async function collectStream(stream) {
95
- let text = "";
96
- let tps = 0;
97
- let numTokens = 0;
98
- let timeToFirstToken = 0;
99
- for await (const event of stream) {
100
- text += event.token;
101
- tps = event.tps;
102
- numTokens = event.numTokens;
103
- if (timeToFirstToken === 0) {
104
- timeToFirstToken = event.timeToFirstToken;
105
- }
106
- }
107
- const error = stream.getError();
108
- if (error) throw error;
109
- return { text, tps, numTokens, timeToFirstToken };
110
- }
111
-
112
- // src/gpu-recovery.ts
113
- var GPURecovery = class {
114
- state = "idle";
115
- listeners = /* @__PURE__ */ new Map();
116
- maxRetries;
117
- baseDelay;
118
- constructor(options) {
119
- this.maxRetries = options?.maxRetries ?? 3;
120
- this.baseDelay = options?.baseDelayMs ?? 1e3;
121
- }
122
- /** Current recovery state. */
123
- getState() {
124
- return this.state;
125
- }
126
- /** Register a listener for recovery events. */
127
- on(event, listener) {
128
- if (!this.listeners.has(event)) {
129
- this.listeners.set(event, /* @__PURE__ */ new Set());
130
- }
131
- this.listeners.get(event).add(listener);
132
- }
133
- /** Remove a listener. */
134
- off(event, listener) {
135
- this.listeners.get(event)?.delete(listener);
136
- }
137
- emit(event, data) {
138
- this.listeners.get(event)?.forEach((fn) => fn(data));
139
- }
140
- setState(state) {
141
- this.state = state;
142
- this.emit("state-change", state);
143
- }
144
- /**
145
- * Watch a GPU device for loss. When the device is lost, automatically
146
- * attempt to re-acquire an adapter.
147
- *
148
- * @returns The same device (for chaining)
149
- */
150
- watchDevice(device) {
151
- device.lost.then((info) => {
152
- const reason = info.message || "unknown";
153
- if (info.reason === "destroyed") {
154
- this.setState("idle");
155
- return;
156
- }
157
- this.setState("lost");
158
- this.emit("lost", { reason });
159
- this.attemptRecovery(0);
160
- });
161
- this.setState("idle");
162
- return device;
163
- }
164
- async attemptRecovery(attempt) {
165
- if (attempt >= this.maxRetries) {
166
- this.setState("failed");
167
- this.emit("failed", {
168
- attempts: attempt,
169
- lastError: "Max retries exceeded"
170
- });
171
- return;
172
- }
173
- this.setState("recovering");
174
- const delay = this.baseDelay * 2 ** attempt;
175
- await new Promise((r) => setTimeout(r, delay));
176
- try {
177
- if (typeof navigator === "undefined" || !("gpu" in navigator)) {
178
- throw new Error("WebGPU not available");
179
- }
180
- const adapter = await navigator.gpu.requestAdapter({
181
- powerPreference: "high-performance"
182
- });
183
- if (!adapter) {
184
- throw new Error("No GPU adapter available");
185
- }
186
- this.setState("recovered");
187
- this.emit("recovered", { adapter });
188
- } catch (e) {
189
- const msg = e instanceof Error ? e.message : String(e);
190
- this.attemptRecovery(attempt + 1);
191
- }
192
- }
193
- };
194
-
195
- // src/model-client.ts
196
- var ModelClient = class {
197
- worker = null;
198
- workerUrl;
199
- listeners = /* @__PURE__ */ new Map();
200
- pendingRequests = /* @__PURE__ */ new Map();
201
- requestCounter = 0;
202
- deviceInfo = null;
203
- loadedModels = /* @__PURE__ */ new Set();
204
- progressCallback = null;
205
- gpuRecovery;
206
- /**
207
- * Create a new ModelClient.
208
- *
209
- * @param workerUrl - URL to the model-worker.js file. If omitted,
210
- * creates a Blob URL from the bundled worker (requires bundler support).
211
- */
212
- constructor(workerUrl) {
213
- this.workerUrl = workerUrl ?? null;
214
- this.gpuRecovery = new GPURecovery();
215
- this.gpuRecovery.on("lost", ({ reason }) => {
216
- this.emit("device-lost", { reason });
217
- });
218
- this.gpuRecovery.on("recovered", () => {
219
- this.emit("device-recovered", {});
220
- });
221
- }
222
- // ─── Worker Lifecycle ───
223
- getWorker() {
224
- if (!this.worker) {
225
- const url = this.workerUrl ?? new URL("./model-worker.js", import.meta.url);
226
- this.worker = new Worker(url, { type: "module" });
227
- this.worker.addEventListener("message", this.handleMessage.bind(this));
228
- this.worker.addEventListener("error", (e) => {
229
- this.emit("error", { message: e.message });
230
- });
231
- }
232
- return this.worker;
233
- }
234
- send(cmd) {
235
- this.getWorker().postMessage(cmd);
236
- }
237
- nextId() {
238
- return `req_${++this.requestCounter}_${Date.now()}`;
239
- }
240
- // ─── Message Handler ───
241
- handleMessage(e) {
242
- const msg = e.data;
243
- switch (msg.type) {
244
- case "device-info": {
245
- this.deviceInfo = msg.data;
246
- const pending = this.pendingRequests.get("detect");
247
- if (pending) {
248
- pending.resolve(msg.data);
249
- this.pendingRequests.delete("detect");
250
- }
251
- break;
252
- }
253
- case "progress": {
254
- this.progressCallback?.(msg.data);
255
- this.emit("progress", msg.data);
256
- break;
257
- }
258
- case "ready": {
259
- this.loadedModels.add(msg.modelKey);
260
- this.emit("ready", { modelKey: msg.modelKey });
261
- const pending = this.pendingRequests.get("load");
262
- if (pending) {
263
- pending.resolve(void 0);
264
- this.pendingRequests.delete("load");
265
- }
266
- break;
267
- }
268
- case "token": {
269
- const pending = this.pendingRequests.get(msg.id);
270
- if (pending?.stream) {
271
- pending.stream.push(msg.data);
272
- }
273
- break;
274
- }
275
- case "result": {
276
- const pending = this.pendingRequests.get(msg.id);
277
- if (pending) {
278
- if (pending.stream) {
279
- pending.stream.end();
280
- }
281
- pending.resolve(msg.data);
282
- this.pendingRequests.delete(msg.id);
283
- }
284
- break;
285
- }
286
- case "error": {
287
- const pending = this.pendingRequests.get(msg.id);
288
- if (pending) {
289
- if (pending.stream) {
290
- pending.stream.abort(new Error(msg.data));
291
- }
292
- pending.reject(new Error(msg.data));
293
- this.pendingRequests.delete(msg.id);
294
- }
295
- this.emit("error", { message: msg.data, id: msg.id });
296
- break;
297
- }
298
- case "device-lost": {
299
- this.emit("device-lost", { reason: msg.reason });
300
- break;
301
- }
302
- case "device-recovered": {
303
- this.emit("device-recovered", {});
304
- break;
305
- }
306
- }
307
- }
308
- // ─── Events ───
309
- /** Register an event listener. */
310
- on(event, listener) {
311
- if (!this.listeners.has(event)) {
312
- this.listeners.set(event, /* @__PURE__ */ new Set());
313
- }
314
- this.listeners.get(event).add(listener);
315
- return this;
316
- }
317
- /** Remove an event listener. */
318
- off(event, listener) {
319
- this.listeners.get(event)?.delete(listener);
320
- return this;
321
- }
322
- emit(event, data) {
323
- this.listeners.get(event)?.forEach((fn) => fn(data));
324
- }
325
- // ─── Public API ───
326
- /**
327
- * Detect the best available compute backend.
328
- *
329
- * ```ts
330
- * const info = await client.detect();
331
- * console.log(info.backend); // 'webgpu'
332
- * console.log(info.gpu?.vendor); // 'apple'
333
- * console.log(info.recommendedDtype); // 'q4'
334
- * ```
335
- */
336
- async detect() {
337
- if (this.deviceInfo) return this.deviceInfo;
338
- return new Promise((resolve, reject) => {
339
- this.pendingRequests.set("detect", { resolve, reject });
340
- this.send({ type: "check" });
341
- });
342
- }
343
- /**
344
- * Load a model pipeline.
345
- *
346
- * ```ts
347
- * await client.load({
348
- * task: 'text-generation',
349
- * modelId: 'onnx-community/Bonsai-1.7B-ONNX',
350
- * dtype: 'q4',
351
- * onProgress: ({ percent }) => updateUI(percent),
352
- * });
353
- * ```
354
- */
355
- async load(options) {
356
- this.progressCallback = options.onProgress ?? null;
357
- const config = {
358
- task: options.task,
359
- modelId: options.modelId,
360
- dtype: options.dtype,
361
- device: options.device,
362
- revision: options.revision
363
- };
364
- return new Promise((resolve, reject) => {
365
- this.pendingRequests.set("load", { resolve, reject });
366
- this.send({ type: "load", config });
367
- });
368
- }
369
- /**
370
- * Run one-shot inference for any pipeline task.
371
- *
372
- * ```ts
373
- * // Image classification
374
- * const labels = await client.run('image-classification', imageUrl);
375
- *
376
- * // Speech recognition
377
- * const { text } = await client.run('automatic-speech-recognition', audioBlob);
378
- *
379
- * // Embeddings
380
- * const vectors = await client.run('feature-extraction', 'Hello world');
381
- * ```
382
- */
383
- async run(task, input, options) {
384
- const id = this.nextId();
385
- return new Promise((resolve, reject) => {
386
- this.pendingRequests.set(id, { resolve, reject });
387
- this.send({ type: "run", id, task, input, options });
388
- });
389
- }
390
- /**
391
- * Generate text with streaming tokens.
392
- *
393
- * Returns an `AsyncIterable<TokenEvent>` that yields tokens as they're generated.
394
- *
395
- * ```ts
396
- * for await (const { token, tps } of client.stream('Tell me a joke')) {
397
- * process.stdout.write(token);
398
- * }
399
- * ```
400
- */
401
- stream(input, options) {
402
- const id = this.nextId();
403
- const stream = new TokenStream(options?.signal);
404
- this.pendingRequests.set(id, {
405
- resolve: () => {
406
- },
407
- // Result comes through the stream
408
- reject: (err) => stream.abort(err),
409
- stream
410
- });
411
- this.send({
412
- type: "run",
413
- id,
414
- task: "text-generation",
415
- input,
416
- options
417
- });
418
- return stream;
419
- }
420
- /**
421
- * Generate text and wait for the complete result.
422
- *
423
- * ```ts
424
- * const { text, tps, numTokens } = await client.generate('Hello!');
425
- * ```
426
- */
427
- async generate(input, options) {
428
- const tokenStream = this.stream(input, options);
429
- const collected = await collectStream(tokenStream);
430
- return {
431
- ...collected,
432
- totalTime: 0
433
- // Will be filled from worker result
434
- };
435
- }
436
- /**
437
- * Interrupt an ongoing text generation.
438
- */
439
- interrupt() {
440
- this.send({ type: "interrupt" });
441
- }
442
- /**
443
- * Reset the KV cache (start a new conversation).
444
- */
445
- reset() {
446
- this.send({ type: "reset" });
447
- }
448
- /**
449
- * Dispose a loaded model and free memory.
450
- *
451
- * @param modelKey - Specific model key (task::modelId), or omit to dispose all.
452
- */
453
- dispose(modelKey) {
454
- this.send({ type: "dispose", modelKey });
455
- if (modelKey) {
456
- this.loadedModels.delete(modelKey);
457
- } else {
458
- this.loadedModels.clear();
459
- }
460
- }
461
- /**
462
- * Check if a model is currently loaded.
463
- */
464
- isLoaded(task, modelId) {
465
- return this.loadedModels.has(`${task}::${modelId}`);
466
- }
467
- /**
468
- * Terminate the worker completely.
469
- */
470
- terminate() {
471
- this.worker?.terminate();
472
- this.worker = null;
473
- this.loadedModels.clear();
474
- this.pendingRequests.clear();
475
- this.deviceInfo = null;
476
- }
477
- };
478
-
479
- // src/hub.ts
480
- var HF_API = "https://huggingface.co/api";
481
- var WEBGPU_ORGS = [
482
- "onnx-community",
483
- "Xenova",
484
- "webml-community"
485
- ];
486
- async function searchModels(options = {}) {
487
- const params = new URLSearchParams();
488
- params.set("library", "transformers.js");
489
- if (options.task) params.set("pipeline_tag", options.task);
490
- if (options.query) params.set("search", options.query);
491
- if (options.author) params.set("author", options.author);
492
- if (options.sort === "trending") {
493
- params.set("sort", "trending");
494
- } else if (options.sort) {
495
- params.set("sort", options.sort === "modified" ? "lastModified" : options.sort);
496
- params.set("direction", options.direction === "asc" ? "1" : "-1");
497
- } else {
498
- params.set("sort", "downloads");
499
- params.set("direction", "-1");
500
- }
501
- params.set("limit", String(Math.min(options.limit ?? 20, 100)));
502
- const url = `${HF_API}/models?${params.toString()}`;
503
- const response = await fetch(url);
504
- if (!response.ok) {
505
- throw new Error(`HF Hub API error: ${response.status} ${response.statusText}`);
506
- }
507
- const data = await response.json();
508
- return data.map(normalizeModel);
509
- }
510
- async function listModelsForTask(task, limit = 10) {
511
- return searchModels({ task, sort: "downloads", limit });
512
- }
513
- async function trendingModels(limit = 10) {
514
- return searchModels({ sort: "trending", limit });
515
- }
516
- async function listWebGPUModels(options = {}) {
517
- const results = [];
518
- for (const org of WEBGPU_ORGS) {
519
- const models = await searchModels({ ...options, author: org });
520
- results.push(...models);
521
- }
522
- const seen = /* @__PURE__ */ new Set();
523
- return results.filter((m) => {
524
- if (seen.has(m.modelId)) return false;
525
- seen.add(m.modelId);
526
- return true;
527
- }).sort((a, b) => b.downloads - a.downloads);
528
- }
529
- async function getModelInfo(modelId) {
530
- const response = await fetch(`${HF_API}/models/${modelId}`);
531
- if (!response.ok) {
532
- throw new Error(`Model not found: ${modelId} (${response.status})`);
533
- }
534
- const data = await response.json();
535
- return normalizeModel(data);
536
- }
537
- function normalizeModel(raw) {
538
- const id = raw.modelId ?? raw.id ?? "unknown";
539
- const tags = raw.tags ?? [];
540
- const author = raw.author ?? id.split("/")[0] ?? "unknown";
541
- return {
542
- modelId: id,
543
- task: raw.pipeline_tag ?? "unknown",
544
- author,
545
- downloads: raw.downloads ?? 0,
546
- likes: raw.likes ?? 0,
547
- lastModified: raw.lastModified ?? "",
548
- tags,
549
- webgpuCompatible: tags.includes("onnx") || WEBGPU_ORGS.some((org) => id.startsWith(org))
550
- };
551
- }
552
-
553
- // src/shim-ort.ts
554
- var shim_ort_exports = {};
555
- __export(shim_ort_exports, {
556
- InferenceSession: () => InferenceSession,
557
- Tensor: () => Tensor,
558
- default: () => shim_ort_default,
559
- env: () => env
560
- });
561
- var getOrt = () => {
562
- if (typeof globalThis !== "undefined" && globalThis.ort) {
563
- return globalThis.ort;
564
- }
565
- return {};
566
- };
567
- var ortProxy = new Proxy(
568
- {},
569
- {
570
- get(_target, prop) {
571
- const ort = getOrt();
572
- return ort[prop];
573
- }
574
- }
575
- );
576
- var shim_ort_default = ortProxy;
577
- var InferenceSession = new Proxy(
578
- {},
579
- {
580
- get(_target, prop) {
581
- return getOrt().InferenceSession?.[prop];
582
- }
583
- }
584
- );
585
- var Tensor = function(...args) {
586
- const OrtTensor = getOrt().Tensor;
587
- return new OrtTensor(...args);
588
- };
589
- var env = new Proxy(
590
- {},
591
- {
592
- get(_target, prop) {
593
- return getOrt().env?.[prop];
594
- },
595
- set(_target, prop, value) {
596
- const ort = getOrt();
597
- if (ort.env) {
598
- ort.env[prop] = value;
599
- }
600
- return true;
601
- }
602
- }
603
- );
604
-
605
- // src/onnx-pipeline.ts
606
- var runtimeOrt = typeof globalThis !== "undefined" && globalThis.ort ? globalThis.ort : shim_ort_exports;
607
- if (runtimeOrt?.env?.wasm) {
608
- runtimeOrt.env.wasm.simd = true;
609
- if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) {
610
- runtimeOrt.env.wasm.numThreads = Math.min(navigator.hardwareConcurrency, 4);
611
- }
612
- }
613
- var HF_BASE = "https://huggingface.co";
614
- async function fetchRepoFiles(modelId) {
615
- try {
616
- const res = await fetch(`https://huggingface.co/api/models/${modelId}`);
617
- if (!res.ok) return [];
618
- const data = await res.json();
619
- return (data.siblings ?? []).map((s) => s.rfilename);
620
- } catch {
621
- return [];
622
- }
623
- }
624
- function resolveAssetUrl(modelId, filename, revision = "main") {
625
- if (filename.startsWith("http://") || filename.startsWith("https://") || filename.startsWith("/") || filename.startsWith("./")) {
626
- return filename;
627
- }
628
- return `${HF_BASE}/${modelId}/resolve/${revision}/${filename}`;
629
- }
630
- var DEFAULT_ONNX_CACHE_NAME = "webml-kit-onnx-cache";
631
- async function downloadAsset(url, fileName, onProgress, useCache = true, cacheName = DEFAULT_ONNX_CACHE_NAME) {
632
- if (typeof process !== "undefined" && process.versions?.node && !url.startsWith("http://") && !url.startsWith("https://")) {
633
- const fs = await import("node:fs/promises");
634
- const path = url.startsWith("file://") ? new URL(url) : url;
635
- const buf = await fs.readFile(path);
636
- onProgress?.({
637
- status: "downloading",
638
- file: fileName,
639
- loaded: buf.byteLength,
640
- total: buf.byteLength,
641
- percent: 100
642
- });
643
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
644
- }
645
- let cache = null;
646
- if (useCache && typeof caches !== "undefined") {
647
- try {
648
- cache = await caches.open(cacheName);
649
- const cached = await cache.match(url);
650
- if (cached) {
651
- const buf = await cached.arrayBuffer();
652
- onProgress?.({
653
- status: "ready",
654
- file: fileName,
655
- loaded: buf.byteLength,
656
- total: buf.byteLength,
657
- percent: 100
658
- });
659
- return buf;
660
- }
661
- } catch (err) {
662
- console.warn("Cache API lookup failed, fetching over network:", err);
663
- }
664
- }
665
- const response = await fetch(url);
666
- if (!response.ok) {
667
- throw new Error(`Failed to fetch ${fileName} from ${url}: ${response.status} ${response.statusText}`);
668
- }
669
- const contentLength = response.headers.get("content-length");
670
- const total = contentLength ? parseInt(contentLength, 10) : 0;
671
- if (!response.body || total === 0) {
672
- const buffer = await response.arrayBuffer();
673
- if (cache) {
674
- try {
675
- await cache.put(url, new Response(buffer.slice(0), {
676
- headers: {
677
- "Content-Type": "application/octet-stream",
678
- "Content-Length": String(buffer.byteLength)
679
- }
680
- }));
681
- } catch (err) {
682
- console.warn("Failed to cache asset in Cache API:", err);
683
- }
684
- }
685
- onProgress?.({
686
- status: "downloading",
687
- file: fileName,
688
- loaded: buffer.byteLength,
689
- total: buffer.byteLength,
690
- percent: 100
691
- });
692
- return buffer;
693
- }
694
- const reader = response.body.getReader();
695
- const chunks = [];
696
- let loaded = 0;
697
- while (true) {
698
- const { done, value } = await reader.read();
699
- if (done) break;
700
- if (value) {
701
- chunks.push(value);
702
- loaded += value.length;
703
- const percent = total > 0 ? Math.round(loaded / total * 100) : 0;
704
- onProgress?.({
705
- status: "downloading",
706
- file: fileName,
707
- loaded,
708
- total,
709
- percent
710
- });
711
- }
712
- }
713
- const merged = new Uint8Array(loaded);
714
- let offset = 0;
715
- for (const chunk of chunks) {
716
- merged.set(chunk, offset);
717
- offset += chunk.length;
718
- }
719
- if (cache) {
720
- try {
721
- await cache.put(url, new Response(merged.buffer.slice(0), {
722
- headers: {
723
- "Content-Type": "application/octet-stream",
724
- "Content-Length": String(loaded)
725
- }
726
- }));
727
- } catch (err) {
728
- console.warn("Failed to cache asset in Cache API:", err);
729
- }
730
- }
731
- return merged.buffer;
732
- }
733
- async function createSession(modelBufferOrPath, preferredBackend = "webgpu") {
734
- const model = modelBufferOrPath instanceof ArrayBuffer ? new Uint8Array(modelBufferOrPath) : modelBufferOrPath;
735
- if (preferredBackend === "webgpu") {
736
- try {
737
- const session2 = await runtimeOrt.InferenceSession.create(model, {
738
- executionProviders: ["webgpu"]
739
- });
740
- return { session: session2, backend: "webgpu" };
741
- } catch (err) {
742
- console.warn("WebGPU session creation failed, falling back to wasm:", err);
743
- }
744
- }
745
- const session = await runtimeOrt.InferenceSession.create(model, {
746
- executionProviders: ["wasm"]
747
- });
748
- return { session, backend: "wasm" };
749
- }
750
- function decodeWavToFloat32(buffer) {
751
- const view = new DataView(buffer);
752
- const riff = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
753
- const wave = String.fromCharCode(view.getUint8(8), view.getUint8(9), view.getUint8(10), view.getUint8(11));
754
- if (riff !== "RIFF" || wave !== "WAVE") {
755
- return new Float32Array(buffer);
756
- }
757
- let offset = 12;
758
- let audioFormat = 1;
759
- let numChannels = 1;
760
- let bitsPerSample = 16;
761
- let dataOffset = 0;
762
- let dataLength = 0;
763
- while (offset < view.byteLength - 8) {
764
- const chunkId = String.fromCharCode(
765
- view.getUint8(offset),
766
- view.getUint8(offset + 1),
767
- view.getUint8(offset + 2),
768
- view.getUint8(offset + 3)
769
- );
770
- const chunkSize = view.getUint32(offset + 4, true);
771
- if (chunkId === "fmt ") {
772
- audioFormat = view.getUint16(offset + 8, true);
773
- numChannels = view.getUint16(offset + 10, true);
774
- bitsPerSample = view.getUint16(offset + 22, true);
775
- } else if (chunkId === "data") {
776
- dataOffset = offset + 8;
777
- dataLength = chunkSize;
778
- break;
779
- }
780
- offset += 8 + chunkSize;
781
- }
782
- if (dataOffset === 0) {
783
- return new Float32Array(buffer);
784
- }
785
- if (audioFormat === 1 && bitsPerSample === 16) {
786
- const numSamples = Math.floor(dataLength / (2 * numChannels));
787
- const pcm = new Float32Array(numSamples);
788
- for (let i = 0; i < numSamples; i++) {
789
- let sum = 0;
790
- for (let c = 0; c < numChannels; c++) {
791
- const idx = dataOffset + (i * numChannels + c) * 2;
792
- if (idx + 1 < view.byteLength) {
793
- sum += view.getInt16(idx, true) / 32768;
794
- }
795
- }
796
- pcm[i] = sum / numChannels;
797
- }
798
- return pcm;
799
- }
800
- if (audioFormat === 3 && bitsPerSample === 32) {
801
- const numSamples = Math.floor(dataLength / (4 * numChannels));
802
- const pcm = new Float32Array(numSamples);
803
- for (let i = 0; i < numSamples; i++) {
804
- let sum = 0;
805
- for (let c = 0; c < numChannels; c++) {
806
- const idx = dataOffset + (i * numChannels + c) * 4;
807
- if (idx + 3 < view.byteLength) {
808
- sum += view.getFloat32(idx, true);
809
- }
810
- }
811
- pcm[i] = sum / numChannels;
812
- }
813
- return pcm;
814
- }
815
- return new Float32Array(buffer.slice(dataOffset, dataOffset + dataLength));
816
- }
817
- function toFloat32Array(input) {
818
- if (input instanceof Float32Array) return input;
819
- if (Array.isArray(input)) return new Float32Array(input);
820
- if (input instanceof ArrayBuffer) return decodeWavToFloat32(input);
821
- if (ArrayBuffer.isView(input)) return new Float32Array(input.buffer, input.byteOffset, input.byteLength / 4);
822
- if (typeof input === "object" && input !== null && "audio" in input) {
823
- return toFloat32Array(input.audio);
824
- }
825
- throw new Error(`Unsupported audio input format: ${typeof input}`);
826
- }
827
- function decodeCTC(logprobs, timeSteps, numClasses, vocab) {
828
- let prevClass = 0;
829
- let text = "";
830
- for (let t = 0; t < timeSteps; t++) {
831
- const offset = t * numClasses;
832
- let maxVal = -Infinity;
833
- let argmax = 0;
834
- for (let c = 0; c < numClasses; c++) {
835
- const val = logprobs[offset + c];
836
- if (val > maxVal) {
837
- maxVal = val;
838
- argmax = c;
839
- }
840
- }
841
- if (argmax === 0) {
842
- prevClass = 0;
843
- continue;
844
- }
845
- if (argmax !== prevClass) {
846
- const token = vocab[argmax] ?? "";
847
- text += token;
848
- prevClass = argmax;
849
- }
850
- }
851
- return text.replace(/\u2581/g, " ").replace(/\s+/g, " ").trim();
852
- }
853
- async function createOnnxPipeline(config, onProgress) {
854
- const preferredDevice = config.device ?? "webgpu";
855
- const revision = config.revision ?? "main";
856
- const useCache = config.cache !== false;
857
- const cacheName = config.cacheName || DEFAULT_ONNX_CACHE_NAME;
858
- let modelFile = config.modelFile;
859
- let preprocessorFile = config.preprocessorFile;
860
- let vocabFile = config.vocabFile;
861
- if (!modelFile && !config.modelId.endsWith(".onnx")) {
862
- const repoFiles = await fetchRepoFiles(config.modelId);
863
- if (repoFiles.length > 0) {
864
- if (!modelFile) {
865
- modelFile = repoFiles.find((f) => f.endsWith(".onnx") && !f.includes("preprocess")) ?? repoFiles.find((f) => f.endsWith(".onnx"));
866
- }
867
- if (!preprocessorFile) {
868
- preprocessorFile = repoFiles.find((f) => f.endsWith(".onnx") && f.includes("preprocess"));
869
- }
870
- if (!vocabFile) {
871
- vocabFile = repoFiles.find((f) => f.endsWith(".json") && (f.includes("vocab") || f.includes("tokens")));
872
- }
873
- }
874
- }
875
- if (!modelFile) {
876
- modelFile = config.modelId.endsWith(".onnx") ? config.modelId : "model.onnx";
877
- }
878
- const modelUrl = resolveAssetUrl(config.modelId, modelFile, revision);
879
- const modelBuffer = await downloadAsset(modelUrl, modelFile, onProgress, useCache, cacheName);
880
- let prepSession = null;
881
- if (preprocessorFile) {
882
- const prepUrl = resolveAssetUrl(config.modelId, preprocessorFile, revision);
883
- const prepBuffer = await downloadAsset(prepUrl, preprocessorFile, onProgress, useCache, cacheName);
884
- const { session } = await createSession(prepBuffer, preferredDevice);
885
- prepSession = session;
886
- }
887
- let vocab = {};
888
- if (vocabFile) {
889
- const vocabUrl = resolveAssetUrl(config.modelId, vocabFile, revision);
890
- try {
891
- if (typeof process !== "undefined" && process.versions?.node && !vocabUrl.startsWith("http://") && !vocabUrl.startsWith("https://")) {
892
- const fs = await import("node:fs/promises");
893
- const path = vocabUrl.startsWith("file://") ? new URL(vocabUrl) : vocabUrl;
894
- const text = await fs.readFile(path, "utf-8");
895
- vocab = JSON.parse(text);
896
- } else {
897
- let cachedRes = null;
898
- if (useCache && typeof caches !== "undefined") {
899
- try {
900
- const cache = await caches.open(cacheName);
901
- cachedRes = await cache.match(vocabUrl) ?? null;
902
- } catch {
903
- }
904
- }
905
- if (cachedRes) {
906
- vocab = await cachedRes.json();
907
- } else {
908
- const res = await fetch(vocabUrl);
909
- if (res.ok) {
910
- if (useCache && typeof caches !== "undefined") {
911
- try {
912
- const cache = await caches.open(cacheName);
913
- await cache.put(vocabUrl, res.clone());
914
- } catch {
915
- }
916
- }
917
- vocab = await res.json();
918
- }
919
- }
920
- }
921
- } catch {
922
- }
923
- }
924
- let { session: modelSession, backend } = await createSession(modelBuffer, preferredDevice);
925
- if (config.task === "automatic-speech-recognition") {
926
- const runner = async (input) => {
927
- const pcm = toFloat32Array(input);
928
- const executeInference = async (sess) => {
929
- let acousticSignal;
930
- let acousticLength;
931
- if (prepSession) {
932
- const audioTensor = new runtimeOrt.Tensor("float32", pcm, [1, pcm.length]);
933
- const lengthTensor = new runtimeOrt.Tensor("int64", BigInt64Array.from([BigInt(pcm.length)]), [1]);
934
- const prepOutputs = await prepSession.run({
935
- audio_signal: audioTensor,
936
- length: lengthTensor
937
- });
938
- acousticSignal = prepOutputs.processed_signal ?? Object.values(prepOutputs)[0];
939
- acousticLength = prepOutputs.processed_length ?? Object.values(prepOutputs)[1];
940
- } else {
941
- acousticSignal = new runtimeOrt.Tensor("float32", pcm, [1, pcm.length]);
942
- acousticLength = new runtimeOrt.Tensor("int64", BigInt64Array.from([BigInt(pcm.length)]), [1]);
943
- }
944
- const feeds = {};
945
- const inputNames = sess.inputNames;
946
- if (inputNames.length >= 2) {
947
- feeds[inputNames[0]] = acousticSignal;
948
- feeds[inputNames[1]] = acousticLength;
949
- } else if (inputNames.length === 1) {
950
- feeds[inputNames[0]] = acousticSignal;
951
- }
952
- const outputs = await sess.run(feeds);
953
- const logprobsTensor = outputs.logprobs ?? Object.values(outputs)[0];
954
- const dims = logprobsTensor.dims;
955
- const timeSteps = dims.length === 3 ? dims[1] : dims.length === 2 ? dims[0] : 1;
956
- const numClasses = dims[dims.length - 1];
957
- const data = logprobsTensor.data;
958
- const text = decodeCTC(data, timeSteps, numClasses, vocab);
959
- return { text };
960
- };
961
- try {
962
- return await executeInference(modelSession);
963
- } catch (err) {
964
- if (backend === "webgpu") {
965
- console.warn("WebGPU inference failed, retrying on WASM provider:", err);
966
- const wasmResult = await createSession(modelBuffer, "wasm");
967
- modelSession = wasmResult.session;
968
- backend = "wasm";
969
- return await executeInference(modelSession);
970
- }
971
- throw err;
972
- }
973
- };
974
- const instance2 = runner;
975
- instance2.sessions = prepSession ? [prepSession, modelSession] : [modelSession];
976
- instance2.backend = backend;
977
- instance2.dispose = () => {
978
- prepSession?.release();
979
- modelSession.release();
980
- };
981
- return instance2;
982
- }
983
- const genericRunner = async (input) => {
984
- const feeds = {};
985
- if (typeof input === "object" && input !== null) {
986
- for (const [k, v] of Object.entries(input)) {
987
- if (v instanceof (runtimeOrt.Tensor ?? Tensor)) {
988
- feeds[k] = v;
989
- } else if (v instanceof Float32Array) {
990
- feeds[k] = new runtimeOrt.Tensor("float32", v, [1, v.length]);
991
- }
992
- }
993
- }
994
- const outputs = await modelSession.run(feeds);
995
- const result = {};
996
- for (const [k, v] of Object.entries(outputs)) {
997
- result[k] = v.data;
998
- }
999
- return result;
1000
- };
1001
- const instance = genericRunner;
1002
- instance.sessions = [modelSession];
1003
- instance.backend = backend;
1004
- instance.dispose = () => {
1005
- modelSession.release();
1006
- };
1007
- return instance;
1008
- }
1009
-
1010
- // src/inputs.ts
1011
- async function coerceAudio(input) {
1012
- if (input instanceof Float32Array) {
1013
- return input;
1014
- }
1015
- if (Array.isArray(input)) {
1016
- return new Float32Array(input);
1017
- }
1018
- if (input instanceof ArrayBuffer) {
1019
- return decodeWavToFloat32(input);
1020
- }
1021
- if (ArrayBuffer.isView(input)) {
1022
- if (input instanceof Uint8Array) {
1023
- const copy = new Uint8Array(input.byteLength);
1024
- copy.set(new Uint8Array(input.buffer, input.byteOffset, input.byteLength));
1025
- return decodeWavToFloat32(copy.buffer);
1026
- }
1027
- return new Float32Array(input.buffer, input.byteOffset, Math.floor(input.byteLength / 4));
1028
- }
1029
- if (typeof Blob !== "undefined" && input instanceof Blob) {
1030
- const buffer = await input.arrayBuffer();
1031
- if (typeof AudioContext !== "undefined" || typeof globalThis.webkitAudioContext !== "undefined") {
1032
- try {
1033
- const AudioCtx = globalThis.AudioContext || globalThis.webkitAudioContext;
1034
- const ctx = new AudioCtx({ sampleRate: 16e3 });
1035
- const audioBuf = await ctx.decodeAudioData(buffer.slice(0));
1036
- ctx.close();
1037
- return audioBuf.getChannelData(0);
1038
- } catch {
1039
- return decodeWavToFloat32(buffer);
1040
- }
1041
- }
1042
- return decodeWavToFloat32(buffer);
1043
- }
1044
- if (typeof input === "string") {
1045
- const response = await fetch(input);
1046
- if (!response.ok) {
1047
- throw new Error(`Failed to fetch audio from ${input}: ${response.status} ${response.statusText}`);
1048
- }
1049
- const buffer = await response.arrayBuffer();
1050
- return decodeWavToFloat32(buffer);
1051
- }
1052
- if (typeof input === "object" && input !== null) {
1053
- if ("audio" in input) {
1054
- return coerceAudio(input.audio);
1055
- }
1056
- if ("pcm" in input) {
1057
- return coerceAudio(input.pcm);
1058
- }
1059
- if ("getChannelData" in input && typeof input.getChannelData === "function") {
1060
- return input.getChannelData(0);
1061
- }
1062
- }
1063
- throw new Error(`Unsupported audio input type: ${typeof input}`);
1064
- }
1065
- async function listenMic(onChunk, options = {}) {
1066
- if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
1067
- throw new Error("Microphone access is only available in browser environments with getUserMedia.");
1068
- }
1069
- const sampleRate = options.sampleRate ?? 16e3;
1070
- const intervalSeconds = options.intervalSeconds ?? 3;
1071
- const stream = await navigator.mediaDevices.getUserMedia({
1072
- audio: {
1073
- sampleRate,
1074
- channelCount: 1,
1075
- echoCancellation: true,
1076
- noiseSuppression: true
1077
- }
1078
- });
1079
- const AudioCtx = globalThis.AudioContext || globalThis.webkitAudioContext;
1080
- const audioCtx = new AudioCtx({ sampleRate });
1081
- const source = audioCtx.createMediaStreamSource(stream);
1082
- let pcmBuffer = [];
1083
- const maxSamples = Math.floor(sampleRate * intervalSeconds);
1084
- let workletNode = null;
1085
- let scriptProcessor = null;
1086
- const handleData = (inputData) => {
1087
- for (let i = 0; i < inputData.length; i++) {
1088
- pcmBuffer.push(inputData[i]);
1089
- }
1090
- if (pcmBuffer.length >= maxSamples) {
1091
- const chunk = new Float32Array(pcmBuffer);
1092
- pcmBuffer = [];
1093
- onChunk(chunk);
1094
- }
1095
- };
1096
- if (audioCtx.audioWorklet && typeof AudioWorkletNode !== "undefined") {
1097
- const workletCode = `
1098
- class RecorderProcessor extends AudioWorkletProcessor {
1099
- process(inputs) {
1100
- const input = inputs[0];
1101
- if (input && input[0]) {
1102
- this.port.postMessage(input[0]);
1103
- }
1104
- return true;
1105
- }
1106
- }
1107
- registerProcessor('recorder-processor', RecorderProcessor);
1108
- `;
1109
- const blob = new Blob([workletCode], { type: "application/javascript" });
1110
- const workletUrl = URL.createObjectURL(blob);
1111
- await audioCtx.audioWorklet.addModule(workletUrl);
1112
- URL.revokeObjectURL(workletUrl);
1113
- workletNode = new AudioWorkletNode(audioCtx, "recorder-processor");
1114
- workletNode.port.onmessage = (e) => {
1115
- handleData(e.data);
1116
- };
1117
- source.connect(workletNode);
1118
- } else {
1119
- const bufferSize = 4096;
1120
- scriptProcessor = audioCtx.createScriptProcessor(bufferSize, 1, 1);
1121
- scriptProcessor.onaudioprocess = (e) => {
1122
- handleData(e.inputBuffer.getChannelData(0));
1123
- };
1124
- source.connect(scriptProcessor);
1125
- scriptProcessor.connect(audioCtx.destination);
1126
- }
1127
- const stop = () => {
1128
- if (pcmBuffer.length > 0) {
1129
- onChunk(new Float32Array(pcmBuffer));
1130
- pcmBuffer = [];
1131
- }
1132
- if (workletNode) workletNode.disconnect();
1133
- if (scriptProcessor) scriptProcessor.disconnect();
1134
- source.disconnect();
1135
- stream.getTracks().forEach((track) => track.stop());
1136
- audioCtx.close();
1137
- };
1138
- return { stop };
1139
- }
1140
-
1141
- // src/decision.ts
1142
- var OPENJEV_MODELS = {
1143
- "minicpm5-2b": {
1144
- id: "minicpm5-2b",
1145
- name: "MiniCPM 5 2B (OpenJev)",
1146
- url: "https://huggingface.co/openjev/MiniCPM-2B-GGUF/resolve/main/minicpm-2b-q4_k_m.gguf",
1147
- family: "minicpm",
1148
- sizeMB: 1250
1149
- },
1150
- "qwen3-0.6b": {
1151
- id: "qwen3-0.6b",
1152
- name: "Qwen 3 0.6B (OpenJev)",
1153
- url: "https://huggingface.co/openjev/Qwen3-0.6B-GGUF/resolve/main/qwen3-0.6b-q4_k_m.gguf",
1154
- family: "qwen",
1155
- sizeMB: 480
1156
- },
1157
- "qwen3.5-4b": {
1158
- id: "qwen3.5-4b",
1159
- name: "Qwen 3.5 4B (OpenJev)",
1160
- url: "https://huggingface.co/openjev/Qwen3.5-4B-GGUF/resolve/main/qwen3.5-4b-q4_k_m.gguf",
1161
- family: "qwen",
1162
- sizeMB: 2450
1163
- }
1164
- };
1165
- var CONCEPT_KEYWORDS = {
1166
- positive: [
1167
- "good",
1168
- "great",
1169
- "excellent",
1170
- "amazing",
1171
- "love",
1172
- "positive",
1173
- "satisfied",
1174
- "helpful",
1175
- "fast",
1176
- "smooth",
1177
- "awesome",
1178
- "best",
1179
- "wonderful",
1180
- "safe",
1181
- "compliant",
1182
- "approve",
1183
- "approved",
1184
- "pass",
1185
- "passed",
1186
- "valid",
1187
- "success"
1188
- ],
1189
- negative: [
1190
- "bad",
1191
- "terrible",
1192
- "awful",
1193
- "horrible",
1194
- "hate",
1195
- "negative",
1196
- "poor",
1197
- "worst",
1198
- "broken",
1199
- "disappointing",
1200
- "failed",
1201
- "fail",
1202
- "reject",
1203
- "rejected",
1204
- "deny",
1205
- "denied",
1206
- "invalid",
1207
- "violate",
1208
- "violation",
1209
- "disallow",
1210
- "fraud"
1211
- ],
1212
- urgent: [
1213
- "urgent",
1214
- "critical",
1215
- "emergency",
1216
- "asap",
1217
- "immediately",
1218
- "blocking",
1219
- "p0",
1220
- "p1",
1221
- "severe",
1222
- "fatal",
1223
- "escalate",
1224
- "outage",
1225
- "unacceptable",
1226
- "furious",
1227
- "enraged",
1228
- "losing"
1229
- ],
1230
- moderate: [
1231
- "moderate",
1232
- "medium",
1233
- "normal",
1234
- "standard",
1235
- "p2",
1236
- "routine",
1237
- "average",
1238
- "intermediate",
1239
- "fair",
1240
- "regular"
1241
- ],
1242
- low: [
1243
- "low",
1244
- "minor",
1245
- "trivial",
1246
- "easy",
1247
- "p3",
1248
- "p4",
1249
- "info",
1250
- "minimal",
1251
- "casual",
1252
- "beginner",
1253
- "none",
1254
- "negligible"
1255
- ],
1256
- bug: [
1257
- "bug",
1258
- "error",
1259
- "500",
1260
- "crash",
1261
- "fail",
1262
- "failing",
1263
- "exception",
1264
- "broken",
1265
- "traceback",
1266
- "glitch",
1267
- "unexpected",
1268
- "hang",
1269
- "freeze",
1270
- "defect"
1271
- ],
1272
- security: [
1273
- "security",
1274
- "unauthorized",
1275
- "breach",
1276
- "vulnerability",
1277
- "hack",
1278
- "exploit",
1279
- "leak",
1280
- "compromise",
1281
- "threat",
1282
- "suspicious",
1283
- "malicious",
1284
- "token",
1285
- "auth"
1286
- ],
1287
- billing: [
1288
- "billing",
1289
- "bill",
1290
- "charge",
1291
- "invoice",
1292
- "payment",
1293
- "payout",
1294
- "refund",
1295
- "subscription",
1296
- "fee",
1297
- "price",
1298
- "cost",
1299
- "credit",
1300
- "tax",
1301
- "receipt"
1302
- ],
1303
- technical: [
1304
- "code",
1305
- "ai",
1306
- "software",
1307
- "data",
1308
- "model",
1309
- "api",
1310
- "server",
1311
- "database",
1312
- "endpoint",
1313
- "infra",
1314
- "function",
1315
- "developer",
1316
- "algorithm"
1317
- ],
1318
- toxicity: [
1319
- "toxic",
1320
- "idiot",
1321
- "stupid",
1322
- "hate",
1323
- "scam",
1324
- "threat",
1325
- "abuse",
1326
- "harass",
1327
- "offensive",
1328
- "vulgar",
1329
- "kill",
1330
- "attack"
1331
- ]
1332
- };
1333
- function stringifyState(state) {
1334
- if (state === null || state === void 0) return "";
1335
- if (typeof state === "string") return state;
1336
- try {
1337
- return JSON.stringify(state, null, 2);
1338
- } catch {
1339
- return String(state);
1340
- }
1341
- }
1342
- function wordMatch(text, word) {
1343
- if (!word || !text) return false;
1344
- if (word.includes(" ")) return text.includes(word);
1345
- return new RegExp("(^|[^a-z0-9])" + word + "([^a-z0-9]|$)", "i").test(text);
1346
- }
1347
- function extractKeywords(text) {
1348
- const stopWords = /* @__PURE__ */ new Set([
1349
- "a",
1350
- "an",
1351
- "and",
1352
- "are",
1353
- "as",
1354
- "at",
1355
- "be",
1356
- "by",
1357
- "for",
1358
- "from",
1359
- "has",
1360
- "he",
1361
- "in",
1362
- "is",
1363
- "it",
1364
- "its",
1365
- "of",
1366
- "on",
1367
- "that",
1368
- "the",
1369
- "to",
1370
- "was",
1371
- "were",
1372
- "will",
1373
- "with",
1374
- "this",
1375
- "your",
1376
- "what",
1377
- "which"
1378
- ]);
1379
- return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
1380
- }
1381
- function normalizeOptions(rawOptions) {
1382
- if (!rawOptions || typeof rawOptions !== "object") {
1383
- throw new Error("options must provide at least 2 distinct choices (either array or key-description record)");
1384
- }
1385
- let entries = [];
1386
- if (Array.isArray(rawOptions)) {
1387
- entries = rawOptions.map((opt) => String(opt).trim()).filter((opt) => opt.length > 0).map((opt) => ({ key: opt, description: opt }));
1388
- } else {
1389
- entries = Object.entries(rawOptions).map(([key, desc]) => ({
1390
- key: String(key).trim(),
1391
- description: String(desc).trim() || String(key).trim()
1392
- }));
1393
- }
1394
- if (entries.length < 2) {
1395
- throw new Error("options must provide at least 2 distinct choices (either array or key-description record)");
1396
- }
1397
- return entries;
1398
- }
1399
- function normalizeCriteria(rawCriteria) {
1400
- if (!rawCriteria || typeof rawCriteria !== "object") {
1401
- throw new Error("criteria must contain at least 2 levels (array or criteria record)");
1402
- }
1403
- let levels = [];
1404
- if (Array.isArray(rawCriteria)) {
1405
- levels = rawCriteria.map((item, idx) => ({
1406
- index: idx,
1407
- key: String(item).trim(),
1408
- description: String(item).trim()
1409
- })).filter((lvl) => lvl.key.length > 0);
1410
- } else {
1411
- const keys = Object.keys(rawCriteria);
1412
- const areNumeric = keys.every((k) => !isNaN(Number(k)));
1413
- if (areNumeric) {
1414
- keys.sort((a, b) => Number(a) - Number(b));
1415
- }
1416
- levels = keys.map((key, idx) => ({
1417
- index: idx,
1418
- key,
1419
- description: String(rawCriteria[key]).trim() || key
1420
- }));
1421
- }
1422
- if (levels.length < 2) {
1423
- throw new Error("criteria must contain at least 2 levels (array or criteria record)");
1424
- }
1425
- return levels;
1426
- }
1427
- function validateModelOptions(options) {
1428
- const modelPreset = options.model ?? "qwen3-0.6b";
1429
- const mode = options.mode ?? "auto";
1430
- if (!["auto", "wllama", "heuristic"].includes(mode)) {
1431
- throw new Error(`Invalid mode "${mode}". Supported modes: auto, wllama, heuristic`);
1432
- }
1433
- if (options.noulThreshold !== void 0) {
1434
- if (typeof options.noulThreshold !== "number" || options.noulThreshold < 0 || options.noulThreshold > 1) {
1435
- throw new Error("threshold must be between 0 and 1");
1436
- }
1437
- }
1438
- if (modelPreset in OPENJEV_MODELS) {
1439
- return {
1440
- modelId: modelPreset,
1441
- modelUrl: options.modelUrl || OPENJEV_MODELS[modelPreset].url,
1442
- mode
1443
- };
1444
- }
1445
- if (options.modelUrl || modelPreset.includes("/") || modelPreset.startsWith("http")) {
1446
- return {
1447
- modelId: modelPreset,
1448
- modelUrl: options.modelUrl || modelPreset,
1449
- mode
1450
- };
1451
- }
1452
- throw new Error(
1453
- `Unknown model preset "${modelPreset}". Supported presets: ${Object.keys(OPENJEV_MODELS).join(", ")} (or provide a custom modelUrl)`
1454
- );
1455
- }
1456
- var HeuristicDecisionEngine = class {
1457
- model;
1458
- isHeuristic = true;
1459
- _isLoaded = false;
1460
- options;
1461
- constructor(options = {}) {
1462
- const validated = validateModelOptions(options);
1463
- this.model = validated.modelId;
1464
- this.options = options;
1465
- }
1466
- get isLoaded() {
1467
- return this._isLoaded;
1468
- }
1469
- async init() {
1470
- if (this._isLoaded) return;
1471
- const onProgress = this.options.onProgress;
1472
- if (onProgress) {
1473
- onProgress({
1474
- status: "loading",
1475
- loaded: 50,
1476
- total: 100,
1477
- percent: 50,
1478
- detail: "Initializing heuristic decision heuristics"
1479
- });
1480
- onProgress({
1481
- status: "ready",
1482
- loaded: 100,
1483
- total: 100,
1484
- percent: 100,
1485
- detail: "Heuristic decision engine ready"
1486
- });
1487
- }
1488
- this._isLoaded = true;
1489
- }
1490
- async choice(input) {
1491
- const startTime = performance.now();
1492
- await this.init();
1493
- if (input.state === void 0 || input.state === null) {
1494
- throw new Error("state is required for choice evaluation");
1495
- }
1496
- const normOptions = normalizeOptions(input.options);
1497
- const stateStr = stringifyState(input.state).toLowerCase();
1498
- const questionStr = String(input.question || input.instructions || "").toLowerCase();
1499
- const questionWords = extractKeywords(questionStr);
1500
- const rawScores = {};
1501
- for (const opt of normOptions) {
1502
- const optKeyLower = opt.key.toLowerCase();
1503
- const optDescLower = opt.description.toLowerCase();
1504
- let score = 0.2;
1505
- if (wordMatch(stateStr, optKeyLower)) score += 3.5;
1506
- if (optDescLower !== optKeyLower && wordMatch(stateStr, optDescLower)) score += 2.5;
1507
- const descKeywords = extractKeywords(optDescLower + " " + optKeyLower);
1508
- for (const kw of descKeywords) {
1509
- if (wordMatch(stateStr, kw)) score += 1.2;
1510
- }
1511
- for (const qw of questionWords) {
1512
- if (optDescLower.includes(qw)) score += 0.5;
1513
- }
1514
- for (const [concept, words] of Object.entries(CONCEPT_KEYWORDS)) {
1515
- const matchesOption = words.some((w) => optKeyLower.includes(w) || optDescLower.includes(w));
1516
- if (matchesOption) {
1517
- const stateMatches = words.filter((w) => wordMatch(stateStr, w));
1518
- if (stateMatches.length > 0) {
1519
- score += 1.8 + Math.min(stateMatches.length * 0.4, 2);
1520
- }
1521
- }
1522
- }
1523
- rawScores[opt.key] = Math.max(0.01, score);
1524
- }
1525
- const expScores = normOptions.map((opt) => Math.exp(rawScores[opt.key]));
1526
- const expSum = expScores.reduce((a, b) => a + b, 0) || 1;
1527
- const probabilities = {};
1528
- let winningChoice = normOptions[0].key;
1529
- let maxProb = -1;
1530
- for (let i = 0; i < normOptions.length; i++) {
1531
- const optKey = normOptions[i].key;
1532
- const prob = Number((expScores[i] / expSum).toFixed(2));
1533
- probabilities[optKey] = prob;
1534
- if (prob > maxProb) {
1535
- maxProb = prob;
1536
- winningChoice = optKey;
1537
- }
1538
- }
1539
- const sum = Object.values(probabilities).reduce((a, b) => a + b, 0);
1540
- if (sum > 0 && sum !== 1) {
1541
- const diff = Number((1 - sum).toFixed(2));
1542
- probabilities[winningChoice] = Math.max(0, Number((probabilities[winningChoice] + diff).toFixed(2)));
1543
- maxProb = probabilities[winningChoice];
1544
- }
1545
- const confidence = Number(Math.min(0.99, Math.max(0.51, maxProb * 1.02)).toFixed(2));
1546
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
1547
- return {
1548
- choice: winningChoice,
1549
- confidence,
1550
- probabilities,
1551
- latencyMs
1552
- };
1553
- }
1554
- async directChoice(input) {
1555
- return this.choice(input);
1556
- }
1557
- async noul(input) {
1558
- const startTime = performance.now();
1559
- await this.init();
1560
- if (input.state === void 0 || input.state === null) {
1561
- throw new Error("state is required for noul evaluation");
1562
- }
1563
- const statement = String(input.statement || input.instructions || "").trim();
1564
- if (!statement) {
1565
- throw new Error("statement is required for noul evaluation");
1566
- }
1567
- const threshold = input.threshold ?? this.options.noulThreshold ?? 0.5;
1568
- if (typeof threshold !== "number" || threshold < 0 || threshold > 1) {
1569
- throw new Error("threshold must be between 0 and 1");
1570
- }
1571
- const stateStr = stringifyState(input.state).toLowerCase();
1572
- const statementLower = statement.toLowerCase();
1573
- const statementWords = extractKeywords(statementLower);
1574
- let affirmativeScore = 0;
1575
- let negativeScore = 0;
1576
- for (const [concept, words] of Object.entries(CONCEPT_KEYWORDS)) {
1577
- const statementHasConcept = words.some((w) => statementLower.includes(w));
1578
- if (statementHasConcept) {
1579
- const stateMatches = words.filter((w) => wordMatch(stateStr, w));
1580
- if (stateMatches.length > 0) {
1581
- affirmativeScore += 2 + Math.min(stateMatches.length * 0.6, 3);
1582
- } else {
1583
- negativeScore += 0.8;
1584
- }
1585
- }
1586
- }
1587
- let matchCount = 0;
1588
- for (const kw of statementWords) {
1589
- if (wordMatch(stateStr, kw)) matchCount++;
1590
- }
1591
- if (statementWords.length > 0) {
1592
- affirmativeScore += matchCount / statementWords.length * 3;
1593
- }
1594
- const isNegated = statementLower.includes("not ") || statementLower.includes("never ") || statementLower.includes("no ") || statementLower.includes("non-");
1595
- const netSignal = affirmativeScore - negativeScore;
1596
- let prob = 1 / (1 + Math.exp(-1.2 * (netSignal - 0.5)));
1597
- if (isNegated) {
1598
- prob = 1 - prob;
1599
- }
1600
- const noulVal = Number(Math.min(0.99, Math.max(0.01, prob)).toFixed(2));
1601
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
1602
- return {
1603
- noul: noulVal,
1604
- passed: noulVal >= threshold,
1605
- latencyMs
1606
- };
1607
- }
1608
- async score(input) {
1609
- const startTime = performance.now();
1610
- await this.init();
1611
- if (input.state === void 0 || input.state === null) {
1612
- throw new Error("state is required for score evaluation");
1613
- }
1614
- const levels = normalizeCriteria(input.criteria);
1615
- const numLevels = levels.length;
1616
- const stateStr = stringifyState(input.state).toLowerCase();
1617
- const instructionsStr = String(input.instructions || input.question || "").toLowerCase();
1618
- let targetCenter = (numLevels - 1) * 0.5;
1619
- const isHigh = CONCEPT_KEYWORDS.urgent.some((w) => wordMatch(stateStr, w)) || CONCEPT_KEYWORDS.toxicity.some((w) => wordMatch(stateStr, w)) || stateStr.includes("500") || stateStr.includes("fatal") || stateStr.includes("unacceptable") || stateStr.includes("severe");
1620
- const isLow = CONCEPT_KEYWORDS.low.some((w) => wordMatch(stateStr, w)) || stateStr.includes("trivial") || stateStr.includes("minor") || stateStr.includes("routine");
1621
- const isMedium = CONCEPT_KEYWORDS.moderate.some((w) => wordMatch(stateStr, w)) || CONCEPT_KEYWORDS.bug.some((w) => wordMatch(stateStr, w));
1622
- if (isHigh) {
1623
- targetCenter = numLevels - 1;
1624
- } else if (isLow) {
1625
- targetCenter = 0;
1626
- } else if (isMedium) {
1627
- targetCenter = (numLevels - 1) * 0.5;
1628
- } else {
1629
- let bestMatchIdx = -1;
1630
- let bestMatchScore = 0;
1631
- for (const lvl of levels) {
1632
- const descWords = extractKeywords(lvl.description + " " + lvl.key);
1633
- let s = 0;
1634
- for (const w of descWords) {
1635
- if (wordMatch(stateStr, w)) s++;
1636
- }
1637
- if (s > bestMatchScore) {
1638
- bestMatchScore = s;
1639
- bestMatchIdx = lvl.index;
1640
- }
1641
- }
1642
- if (bestMatchIdx >= 0) {
1643
- targetCenter = bestMatchIdx;
1644
- }
1645
- }
1646
- if (instructionsStr.includes("safe") || instructionsStr.includes("quality") || instructionsStr.includes("satisfaction")) {
1647
- const isPositive = CONCEPT_KEYWORDS.positive.some((w) => wordMatch(stateStr, w));
1648
- const isNegative = CONCEPT_KEYWORDS.negative.some((w) => wordMatch(stateStr, w));
1649
- if (isPositive) targetCenter = numLevels - 1;
1650
- else if (isNegative) targetCenter = 0;
1651
- }
1652
- const rawWeights = [];
1653
- let totalWeight = 0;
1654
- for (let i = 0; i < numLevels; i++) {
1655
- const dist = Math.abs(i - targetCenter);
1656
- const weight = Math.exp(-1.5 * dist * dist) + 0.05;
1657
- rawWeights.push(weight);
1658
- totalWeight += weight;
1659
- }
1660
- const probabilities = {};
1661
- let weightedScore = 0;
1662
- let maxProb = 0;
1663
- for (let i = 0; i < numLevels; i++) {
1664
- const lvl = levels[i];
1665
- const normProb = Number((rawWeights[i] / totalWeight).toFixed(2));
1666
- probabilities[lvl.key] = normProb;
1667
- if (lvl.key !== String(lvl.index)) {
1668
- probabilities[String(lvl.index)] = normProb;
1669
- }
1670
- weightedScore += lvl.index * normProb;
1671
- if (normProb > maxProb) maxProb = normProb;
1672
- }
1673
- const confidence = Number(Math.min(0.98, Math.max(0.55, maxProb * 1.05)).toFixed(2));
1674
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
1675
- return {
1676
- score: Number(weightedScore.toFixed(2)),
1677
- confidence,
1678
- probabilities,
1679
- latencyMs
1680
- };
1681
- }
1682
- dispose() {
1683
- this._isLoaded = false;
1684
- }
1685
- };
1686
- var OpenJevWllamaEngine = class {
1687
- model;
1688
- isHeuristic = false;
1689
- _isLoaded = false;
1690
- options;
1691
- modelUrl;
1692
- wllamaInstance = null;
1693
- constructor(options = {}) {
1694
- const validated = validateModelOptions(options);
1695
- this.model = validated.modelId;
1696
- this.modelUrl = validated.modelUrl;
1697
- this.options = options;
1698
- }
1699
- get isLoaded() {
1700
- return this._isLoaded;
1701
- }
1702
- async init() {
1703
- if (this._isLoaded && this.wllamaInstance) return;
1704
- const onProgress = this.options.onProgress;
1705
- onProgress?.({
1706
- status: "downloading",
1707
- loaded: 0,
1708
- total: 100,
1709
- percent: 0,
1710
- detail: `Downloading OpenJev model ${this.model}`
1711
- });
1712
- let WllamaClass = null;
1713
- try {
1714
- const mod = await import("@wllama/wllama/esm/index.js");
1715
- WllamaClass = mod.Wllama || mod.default?.Wllama || mod.default;
1716
- } catch {
1717
- const mod = await import("@wllama/wllama");
1718
- WllamaClass = mod.Wllama || mod.default?.Wllama || mod.default;
1719
- }
1720
- if (!WllamaClass) {
1721
- throw new Error("@wllama/wllama could not be loaded in this environment");
1722
- }
1723
- const pathConfig = this.options.wasmPaths || {
1724
- default: "https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/wllama.wasm",
1725
- "single-thread/wllama.wasm": "https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/single-thread/wllama.wasm",
1726
- "multi-thread/wllama.wasm": "https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/multi-thread/wllama.wasm"
1727
- };
1728
- this.wllamaInstance = new WllamaClass(pathConfig, {
1729
- suppressNativeLog: true
1730
- });
1731
- onProgress?.({
1732
- status: "loading",
1733
- loaded: 40,
1734
- total: 100,
1735
- percent: 40,
1736
- detail: "Initializing runtime context"
1737
- });
1738
- await this.wllamaInstance.loadModelFromUrl(this.modelUrl, {
1739
- useCache: true,
1740
- onProgress: (p) => {
1741
- const percent = p.total > 0 ? Math.round(p.loaded / p.total * 100) : 50;
1742
- onProgress?.({
1743
- status: "downloading",
1744
- loaded: p.loaded,
1745
- total: p.total,
1746
- percent,
1747
- detail: `Loading model shards (${percent}%)`
1748
- });
1749
- }
1750
- });
1751
- onProgress?.({
1752
- status: "ready",
1753
- loaded: 100,
1754
- total: 100,
1755
- percent: 100,
1756
- detail: `OpenJev model ${this.model} initialized`
1757
- });
1758
- this._isLoaded = true;
1759
- }
1760
- async choice(input) {
1761
- const startTime = performance.now();
1762
- await this.init();
1763
- const normOptions = normalizeOptions(input.options);
1764
- const stateStr = stringifyState(input.state);
1765
- const question = input.question || input.instructions || "Select the most appropriate option";
1766
- const prompt = `Context:
1767
- ${stateStr}
1768
-
1769
- Question: ${question}
1770
- Choices:
1771
- ${normOptions.map((opt, idx) => `(${idx + 1}) ${opt.key}: ${opt.description}`).join("\n")}
1772
-
1773
- Answer:`;
1774
- const res = await this.wllamaInstance.createCompletion({
1775
- prompt,
1776
- max_tokens: 1,
1777
- temperature: 0,
1778
- logprobs: true,
1779
- top_logprobs: Math.max(10, normOptions.length * 2)
1780
- });
1781
- const probabilities = {};
1782
- const textOut = (res?.text || "").trim().toLowerCase();
1783
- let winningChoice = normOptions[0].key;
1784
- let maxScore = -Infinity;
1785
- const topLogprobs = res?.choices?.[0]?.logprobs?.content?.[0]?.top_logprobs || [];
1786
- if (topLogprobs.length > 0) {
1787
- let sumExp = 0;
1788
- const rawExp = {};
1789
- for (const opt of normOptions) {
1790
- const keyLower = opt.key.toLowerCase();
1791
- let bestLp = -20;
1792
- for (const lp of topLogprobs) {
1793
- const t = (lp.token || "").trim().toLowerCase();
1794
- if (t.includes(keyLower) || keyLower.includes(t)) {
1795
- if (lp.logprob > bestLp) bestLp = lp.logprob;
1796
- }
1797
- }
1798
- const expVal = Math.exp(bestLp);
1799
- rawExp[opt.key] = expVal;
1800
- sumExp += expVal;
1801
- }
1802
- for (const opt of normOptions) {
1803
- const prob = sumExp > 0 ? Number((rawExp[opt.key] / sumExp).toFixed(2)) : Number((1 / normOptions.length).toFixed(2));
1804
- probabilities[opt.key] = prob;
1805
- if (prob > maxScore) {
1806
- maxScore = prob;
1807
- winningChoice = opt.key;
1808
- }
1809
- }
1810
- } else {
1811
- for (let i = 0; i < normOptions.length; i++) {
1812
- const opt = normOptions[i];
1813
- const isMatch = textOut.includes(opt.key.toLowerCase()) || textOut.includes(String(i + 1));
1814
- const prob = isMatch ? 0.85 : Number((0.15 / Math.max(normOptions.length - 1, 1)).toFixed(2));
1815
- probabilities[opt.key] = prob;
1816
- if (prob > maxScore) {
1817
- maxScore = prob;
1818
- winningChoice = opt.key;
1819
- }
1820
- }
1821
- }
1822
- const confidence = Number(Math.max(0.51, Math.min(0.99, maxScore)).toFixed(2));
1823
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
1824
- return {
1825
- choice: winningChoice,
1826
- confidence,
1827
- probabilities,
1828
- latencyMs
1829
- };
1830
- }
1831
- async directChoice(input) {
1832
- return this.choice(input);
1833
- }
1834
- async noul(input) {
1835
- const startTime = performance.now();
1836
- await this.init();
1837
- const statement = String(input.statement || input.instructions || "").trim();
1838
- if (!statement) {
1839
- throw new Error("statement is required for noul evaluation");
1840
- }
1841
- const threshold = input.threshold ?? this.options.noulThreshold ?? 0.5;
1842
- const stateStr = stringifyState(input.state);
1843
- const prompt = `Context:
1844
- ${stateStr}
1845
-
1846
- Statement: ${statement}
1847
- Does the statement hold true? Answer Yes or No:
1848
- Answer:`;
1849
- const res = await this.wllamaInstance.createCompletion({
1850
- prompt,
1851
- max_tokens: 1,
1852
- temperature: 0,
1853
- logprobs: true,
1854
- top_logprobs: 10
1855
- });
1856
- const topLogprobs = res?.choices?.[0]?.logprobs?.content?.[0]?.top_logprobs || [];
1857
- let pYes = 0.5;
1858
- if (topLogprobs.length > 0) {
1859
- let lpYes = -20;
1860
- let lpNo = -20;
1861
- for (const lp of topLogprobs) {
1862
- const t = (lp.token || "").trim().toLowerCase();
1863
- if (t === "yes" || t === "true") lpYes = Math.max(lpYes, lp.logprob);
1864
- if (t === "no" || t === "false") lpNo = Math.max(lpNo, lp.logprob);
1865
- }
1866
- const expYes = Math.exp(lpYes);
1867
- const expNo = Math.exp(lpNo);
1868
- pYes = expYes / (expYes + expNo || 1);
1869
- } else {
1870
- const text = (res?.text || "").trim().toLowerCase();
1871
- pYes = text.startsWith("y") || text.startsWith("t") ? 0.9 : 0.1;
1872
- }
1873
- const noulVal = Number(Math.min(0.99, Math.max(0.01, pYes)).toFixed(2));
1874
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
1875
- return {
1876
- noul: noulVal,
1877
- passed: noulVal >= threshold,
1878
- latencyMs
1879
- };
1880
- }
1881
- async score(input) {
1882
- const startTime = performance.now();
1883
- await this.init();
1884
- const levels = normalizeCriteria(input.criteria);
1885
- const numLevels = levels.length;
1886
- const stateStr = stringifyState(input.state);
1887
- const instructions = input.instructions || input.question || "Score the situation along ordered levels";
1888
- const prompt = `Context:
1889
- ${stateStr}
1890
-
1891
- Instructions: ${instructions}
1892
- Levels:
1893
- ${levels.map((l) => `(${l.index}) ${l.key}: ${l.description}`).join("\n")}
1894
-
1895
- Best matching level number (0-${numLevels - 1}):`;
1896
- const res = await this.wllamaInstance.createCompletion({
1897
- prompt,
1898
- max_tokens: 1,
1899
- temperature: 0,
1900
- logprobs: true,
1901
- top_logprobs: Math.max(10, numLevels * 2)
1902
- });
1903
- const topLogprobs = res?.choices?.[0]?.logprobs?.content?.[0]?.top_logprobs || [];
1904
- const probabilities = {};
1905
- let weightedScore = 0;
1906
- let maxProb = 0;
1907
- if (topLogprobs.length > 0) {
1908
- const rawExp = [];
1909
- let totalExp = 0;
1910
- for (let i = 0; i < numLevels; i++) {
1911
- const lvl = levels[i];
1912
- let bestLp = -20;
1913
- for (const lp of topLogprobs) {
1914
- const t = (lp.token || "").trim().toLowerCase();
1915
- if (t === String(i) || t === lvl.key.toLowerCase()) {
1916
- bestLp = Math.max(bestLp, lp.logprob);
1917
- }
1918
- }
1919
- const expVal = Math.exp(bestLp);
1920
- rawExp.push(expVal);
1921
- totalExp += expVal;
1922
- }
1923
- for (let i = 0; i < numLevels; i++) {
1924
- const lvl = levels[i];
1925
- const prob = totalExp > 0 ? Number((rawExp[i] / totalExp).toFixed(2)) : Number((1 / numLevels).toFixed(2));
1926
- probabilities[lvl.key] = prob;
1927
- if (lvl.key !== String(lvl.index)) {
1928
- probabilities[String(lvl.index)] = prob;
1929
- }
1930
- weightedScore += lvl.index * prob;
1931
- if (prob > maxProb) maxProb = prob;
1932
- }
1933
- } else {
1934
- const text = (res?.text || "").trim();
1935
- const matchedIdx = parseInt(text, 10);
1936
- const center = !isNaN(matchedIdx) && matchedIdx >= 0 && matchedIdx < numLevels ? matchedIdx : Math.floor(numLevels / 2);
1937
- for (let i = 0; i < numLevels; i++) {
1938
- const lvl = levels[i];
1939
- const prob = i === center ? 0.85 : Number((0.15 / Math.max(numLevels - 1, 1)).toFixed(2));
1940
- probabilities[lvl.key] = prob;
1941
- if (lvl.key !== String(lvl.index)) {
1942
- probabilities[String(lvl.index)] = prob;
1943
- }
1944
- weightedScore += lvl.index * prob;
1945
- if (prob > maxProb) maxProb = prob;
1946
- }
1947
- }
1948
- const confidence = Number(Math.max(0.55, Math.min(0.98, maxProb * 1.05)).toFixed(2));
1949
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
1950
- return {
1951
- score: Number(weightedScore.toFixed(2)),
1952
- confidence,
1953
- probabilities,
1954
- latencyMs
1955
- };
1956
- }
1957
- async dispose() {
1958
- if (this.wllamaInstance) {
1959
- try {
1960
- await this.wllamaInstance.exit();
1961
- } catch {
1962
- }
1963
- this.wllamaInstance = null;
1964
- }
1965
- this._isLoaded = false;
1966
- }
1967
- };
1968
- function isWllamaSupported() {
1969
- const isBrowser = typeof window !== "undefined";
1970
- const isWorker = typeof WorkerGlobalScope !== "undefined" || typeof self !== "undefined" && typeof self.postMessage === "function";
1971
- return (isBrowser || isWorker) && typeof WebAssembly !== "undefined";
1972
- }
1973
- function createDecisionEngine(options = {}) {
1974
- const mode = options.mode ?? "auto";
1975
- if (mode === "heuristic") {
1976
- return new HeuristicDecisionEngine(options);
1977
- }
1978
- if (mode === "wllama") {
1979
- return new OpenJevWllamaEngine(options);
1980
- }
1981
- if (isWllamaSupported()) {
1982
- return new OpenJevWllamaEngine(options);
1983
- }
1984
- return new HeuristicDecisionEngine(options);
1985
- }
1986
- async function directChoice(input, options) {
1987
- const engine = createDecisionEngine(options);
1988
- await engine.init();
1989
- return engine.choice(input);
1990
- }
1991
-
1992
- // src/loader.ts
1993
- async function inferTask(modelId) {
1994
- const lower = modelId.toLowerCase();
1995
- if (lower.includes("openjev") || lower.includes("jev") || lower.includes("decision") || lower === "minicpm5-2b" || lower === "qwen3-0.6b" || lower === "qwen3.5-4b") {
1996
- return "decision";
1997
- }
1998
- if (lower.includes("kokoro") || lower.includes("speecht5") || lower.includes("mms-tts") || lower.includes("tts")) {
1999
- return "text-to-speech";
2000
- }
2001
- if (lower.includes("whisper") || lower.includes("asr") || lower.includes("speech") || lower.includes("sushrota") || lower.includes("parakeet")) {
2002
- return "automatic-speech-recognition";
2003
- }
2004
- if (lower.includes("llama") || lower.includes("qwen") || lower.includes("gpt") || lower.includes("mistral") || lower.includes("phi") || lower.includes("gemma") || lower.includes("bonsai")) {
2005
- return "text-generation";
2006
- }
2007
- if (lower.includes("detr") || lower.includes("yolo")) {
2008
- return "object-detection";
2009
- }
2010
- if (lower.includes("vit") || lower.includes("resnet") || lower.includes("mobilenet")) {
2011
- return "image-classification";
2012
- }
2013
- if (lower.includes("minilm") || lower.includes("bge") || lower.includes("embed")) {
2014
- return "feature-extraction";
2015
- }
2016
- if (lower.endsWith(".onnx")) {
2017
- return "raw-onnx";
2018
- }
2019
- try {
2020
- const info = await getModelInfo(modelId);
2021
- if (info.task && info.task !== "unknown") {
2022
- return info.task;
2023
- }
2024
- const tags = info.tags || [];
2025
- if (tags.includes("automatic-speech-recognition") || tags.includes("audio")) {
2026
- return "automatic-speech-recognition";
2027
- }
2028
- if (tags.includes("text-generation")) {
2029
- return "text-generation";
2030
- }
2031
- if (tags.includes("image-classification")) {
2032
- return "image-classification";
2033
- }
2034
- } catch {
2035
- }
2036
- return "text-generation";
2037
- }
2038
- async function webmlImpl(modelId, options = {}) {
2039
- const task = options.task ?? await inferTask(modelId);
2040
- if (task === "decision") {
2041
- const engine = createDecisionEngine({
2042
- model: modelId,
2043
- onProgress: options.onProgress ? (e) => options.onProgress?.({
2044
- status: e.status,
2045
- loaded: e.loaded,
2046
- total: e.total,
2047
- percent: e.percent,
2048
- file: e.detail
2049
- }) : void 0
2050
- });
2051
- await engine.init();
2052
- const runner2 = async (input, runOptions) => {
2053
- if (typeof input === "object" && input !== null && "options" in input) {
2054
- return engine.choice(input);
2055
- }
2056
- if (typeof input === "object" && input !== null && "statement" in input) {
2057
- return engine.noul(input);
2058
- }
2059
- if (typeof input === "object" && input !== null && "criteria" in input) {
2060
- return engine.score(input);
2061
- }
2062
- return engine.choice({
2063
- state: input,
2064
- options: runOptions?.options || ["yes", "no"]
2065
- });
2066
- };
2067
- const model2 = Object.assign(runner2, {
2068
- task,
2069
- modelId,
2070
- client: null,
2071
- run: async (input, runOptions) => {
2072
- return runner2(input, runOptions);
2073
- },
2074
- dispose: () => {
2075
- engine.dispose();
2076
- },
2077
- choice: (choiceInput) => engine.choice(choiceInput),
2078
- directChoice: (choiceInput) => engine.directChoice(choiceInput),
2079
- noul: (noulInput) => engine.noul(noulInput),
2080
- score: (scoreInput) => engine.score(scoreInput),
2081
- stream: () => {
2082
- throw new Error("Streaming is not supported for decision models");
2083
- },
2084
- generate: async () => {
2085
- throw new Error("generate is not supported for decision models; use choice() or score()");
2086
- },
2087
- transcribe: async () => {
2088
- throw new Error("transcribe is not supported for decision models");
2089
- },
2090
- classify: async () => {
2091
- throw new Error("Use choice() or score() for decision models");
2092
- },
2093
- embed: async () => {
2094
- throw new Error("embed is not supported for decision models");
2095
- },
2096
- detect: async () => {
2097
- throw new Error("detect is not supported for decision models");
2098
- },
2099
- listen: async () => {
2100
- throw new Error("listen is not supported for decision models");
2101
- }
2102
- });
2103
- return model2;
2104
- }
2105
- const client = new ModelClient(options.workerUrl);
2106
- await client.load({
2107
- task,
2108
- modelId,
2109
- dtype: options.dtype,
2110
- device: options.device,
2111
- revision: options.revision,
2112
- onProgress: options.onProgress
2113
- });
2114
- const runner = async (input, runOptions) => {
2115
- if (task === "automatic-speech-recognition") {
2116
- const pcm = await coerceAudio(input);
2117
- return client.run("automatic-speech-recognition", pcm, runOptions);
2118
- }
2119
- return client.run(task, input, runOptions);
2120
- };
2121
- const model = Object.assign(runner, {
2122
- task,
2123
- modelId,
2124
- client,
2125
- run: (input, runOptions) => {
2126
- return runner(input, runOptions);
2127
- },
2128
- dispose: () => {
2129
- client.dispose();
2130
- client.terminate();
2131
- },
2132
- stream: (input, genOptions) => {
2133
- return client.stream(input, genOptions);
2134
- },
2135
- generate: (input, genOptions) => {
2136
- return client.generate(input, genOptions);
2137
- },
2138
- transcribe: async (input, runOptions) => {
2139
- const pcm = await coerceAudio(input);
2140
- return client.run("automatic-speech-recognition", pcm, runOptions);
2141
- },
2142
- classify: (input, runOptions) => {
2143
- return client.run("image-classification", input, runOptions);
2144
- },
2145
- embed: (input, runOptions) => {
2146
- return client.run("feature-extraction", input, runOptions);
2147
- },
2148
- detect: (input, runOptions) => {
2149
- return client.run("object-detection", input, runOptions);
2150
- },
2151
- listen: async (listenOptions) => {
2152
- return listenMic(
2153
- async (pcm) => {
2154
- try {
2155
- const res = await client.run("automatic-speech-recognition", pcm);
2156
- if (res?.text?.trim()) {
2157
- listenOptions.onTranscript(res.text.trim());
2158
- }
2159
- } catch (err) {
2160
- console.warn("Transcription error during mic listen:", err);
2161
- }
2162
- },
2163
- { intervalSeconds: listenOptions.intervalSeconds }
2164
- );
2165
- }
2166
- });
2167
- return model;
2168
- }
2169
- var webml = Object.assign(webmlImpl, {
2170
- decision: (options) => {
2171
- return createDecisionEngine(options);
2172
- },
2173
- directChoice: (input, options) => {
2174
- return directChoice(input, options);
2175
- }
2176
- });
2177
- var loader_default = webml;
2178
-
2179
- // src/device.ts
2180
- var VRAM_THRESHOLDS = [
2181
- { min: 8 * 1024 ** 3, dtype: "fp16" },
2182
- // 8 GB+ → fp16
2183
- { min: 4 * 1024 ** 3, dtype: "q8" },
2184
- // 4 GB+ → q8
2185
- { min: 2 * 1024 ** 3, dtype: "q4" },
2186
- // 2 GB+ → q4
2187
- { min: 0, dtype: "q4" }
2188
- // < 2 GB → q4 (safest)
2189
- ];
2190
- async function getGPUAdapter() {
2191
- if (typeof navigator === "undefined") return null;
2192
- if (!("gpu" in navigator)) return null;
2193
- try {
2194
- const adapter = await navigator.gpu.requestAdapter({
2195
- powerPreference: "high-performance"
2196
- });
2197
- return adapter;
2198
- } catch {
2199
- return null;
2200
- }
2201
- }
2202
- async function getGPUInfo(adapter) {
2203
- const info = adapter.info ?? adapter.requestAdapterInfo?.();
2204
- const resolved = info instanceof Promise ? await info : info;
2205
- const vram = Number(adapter.limits?.maxBufferSize ?? 0);
2206
- return {
2207
- vendor: resolved?.vendor ?? "unknown",
2208
- architecture: resolved?.architecture ?? "unknown",
2209
- description: resolved?.description ?? "unknown",
2210
- vram,
2211
- vramFormatted: formatSize(vram)
2212
- };
2213
- }
2214
- function recommendDtype(vram) {
2215
- for (const { min, dtype } of VRAM_THRESHOLDS) {
2216
- if (vram >= min) return dtype;
2217
- }
2218
- return "q4";
2219
- }
2220
- async function checkWebGPU() {
2221
- const adapter = await getGPUAdapter();
2222
- return adapter !== null;
2223
- }
2224
- function checkWASM() {
2225
- return typeof WebAssembly !== "undefined";
2226
- }
2227
- async function detectDevice() {
2228
- const adapter = await getGPUAdapter();
2229
- if (adapter) {
2230
- const gpu = await getGPUInfo(adapter);
2231
- return {
2232
- backend: "webgpu",
2233
- gpu,
2234
- recommendedDtype: recommendDtype(gpu.vram)
2235
- };
2236
- }
2237
- if (checkWASM()) {
2238
- return {
2239
- backend: "wasm",
2240
- gpu: null,
2241
- recommendedDtype: "q4"
2242
- };
2243
- }
2244
- return {
2245
- backend: "cpu",
2246
- gpu: null,
2247
- recommendedDtype: "q4"
2248
- };
2249
- }
2250
- var SIZE_UNITS = {
2251
- b: 1,
2252
- kb: 1024,
2253
- mb: 1024 ** 2,
2254
- gb: 1024 ** 3,
2255
- tb: 1024 ** 4
2256
- };
2257
- function parseSize(input) {
2258
- if (typeof input === "number") return input;
2259
- const match = input.trim().match(/^([\d.]+)\s*(b|kb|mb|gb|tb)$/i);
2260
- if (!match) {
2261
- throw new Error(
2262
- `Invalid size format: "${input}". Use something like "4GB", "512MB", or a number in bytes.`
2263
- );
2264
- }
2265
- const value = parseFloat(match[1]);
2266
- const unit = match[2].toLowerCase();
2267
- return Math.round(value * SIZE_UNITS[unit]);
2268
- }
2269
- function formatSize(bytes) {
2270
- if (bytes >= 1024 ** 4) return `${(bytes / 1024 ** 4).toFixed(1)} TB`;
2271
- if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
2272
- if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
2273
- if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2274
- return `${bytes} B`;
2275
- }
2276
- async function canRun(estimatedSize) {
2277
- const bytes = parseSize(estimatedSize);
2278
- const device = await detectDevice();
2279
- if (device.backend === "webgpu" && device.gpu) {
2280
- const available = device.gpu.vram - 512 * 1024 ** 2;
2281
- if (bytes > available) {
2282
- return {
2283
- ok: false,
2284
- backend: device.backend,
2285
- reason: `Model (~${formatSize(bytes)}) exceeds available VRAM (~${formatSize(available)})`
2286
- };
2287
- }
2288
- }
2289
- return { ok: true, backend: device.backend };
2290
- }
2291
-
2292
- // src/cache.ts
2293
- var CACHE_PREFIXES = ["transformers-cache", "webml-kit"];
2294
- function isModelCache(name) {
2295
- return CACHE_PREFIXES.some((prefix) => name.startsWith(prefix));
2296
- }
2297
- async function getCacheBackend() {
2298
- if (typeof caches !== "undefined") {
2299
- return "cache-api";
2300
- }
2301
- if (typeof navigator !== "undefined" && "storage" in navigator) {
2302
- try {
2303
- const root = await navigator.storage.getDirectory();
2304
- if (root) return "opfs";
2305
- } catch {
2306
- }
2307
- }
2308
- if (typeof indexedDB !== "undefined") {
2309
- return "indexeddb";
2310
- }
2311
- return "cache-api";
2312
- }
2313
- async function isCached(modelId) {
2314
- if (typeof caches === "undefined") return false;
2315
- try {
2316
- const keys = await caches.keys();
2317
- const matchedCaches = keys.filter((k) => isModelCache(k));
2318
- for (const cacheName of matchedCaches) {
2319
- const cache = await caches.open(cacheName);
2320
- const cacheKeys = await cache.keys();
2321
- const hasModel = cacheKeys.some(
2322
- (req) => req.url.includes(encodeURIComponent(modelId)) || req.url.includes(modelId)
2323
- );
2324
- if (hasModel) return true;
2325
- }
2326
- } catch {
2327
- }
2328
- return false;
2329
- }
2330
- async function getCacheSize() {
2331
- if (typeof navigator === "undefined" || !("storage" in navigator)) return 0;
2332
- try {
2333
- const estimate = await navigator.storage.estimate();
2334
- return estimate.usage ?? 0;
2335
- } catch {
2336
- return 0;
2337
- }
2338
- }
2339
- async function listCachedModels() {
2340
- if (typeof caches === "undefined") return [];
2341
- const models = [];
2342
- try {
2343
- const keys = await caches.keys();
2344
- const matchedCaches = keys.filter((k) => isModelCache(k));
2345
- for (const cacheName of matchedCaches) {
2346
- const cache = await caches.open(cacheName);
2347
- const cacheKeys = await cache.keys();
2348
- const modelUrls = /* @__PURE__ */ new Map();
2349
- for (const request of cacheKeys) {
2350
- const url = request.url;
2351
- const match = url.match(/huggingface\.co\/([^/]+\/[^/]+)\//);
2352
- const id = match ? match[1] : url.split("/").filter(Boolean).slice(-2).join("/") || url;
2353
- const response = await cache.match(request);
2354
- const size = response ? Number(response.headers.get("content-length") ?? 0) : 0;
2355
- modelUrls.set(id, (modelUrls.get(id) ?? 0) + size);
2356
- }
2357
- for (const [modelId, sizeBytes] of modelUrls) {
2358
- models.push({
2359
- modelId,
2360
- sizeBytes,
2361
- size: formatSize(sizeBytes),
2362
- lastAccessed: /* @__PURE__ */ new Date()
2363
- // Cache API doesn't track this
2364
- });
2365
- }
2366
- }
2367
- } catch {
2368
- }
2369
- return models;
2370
- }
2371
- async function clearCache(modelId) {
2372
- if (typeof caches === "undefined") return;
2373
- try {
2374
- const keys = await caches.keys();
2375
- const matchedCaches = keys.filter((k) => isModelCache(k));
2376
- for (const cacheName of matchedCaches) {
2377
- if (!modelId) {
2378
- await caches.delete(cacheName);
2379
- } else {
2380
- const cache = await caches.open(cacheName);
2381
- const cacheKeys = await cache.keys();
2382
- for (const request of cacheKeys) {
2383
- if (request.url.includes(encodeURIComponent(modelId)) || request.url.includes(modelId)) {
2384
- await cache.delete(request);
2385
- }
2386
- }
2387
- }
2388
- }
2389
- } catch {
2390
- }
2391
- }
2392
-
2393
- // src/pipelines/index.ts
2394
- var PIPELINE_REGISTRY = {
2395
- "text-generation": {
2396
- defaultModel: "onnx-community/Llama-3.2-1B-Instruct-ONNX",
2397
- defaultDtype: "q4",
2398
- supportsStreaming: true,
2399
- usesKVCache: true
2400
- },
2401
- "text-classification": {
2402
- defaultModel: "Xenova/distilbert-base-uncased-finetuned-sst-2-english",
2403
- defaultDtype: "q8",
2404
- supportsStreaming: false,
2405
- usesKVCache: false
2406
- },
2407
- "image-classification": {
2408
- defaultModel: "Xenova/vit-base-patch16-224",
2409
- defaultDtype: "fp32",
2410
- supportsStreaming: false,
2411
- usesKVCache: false
2412
- },
2413
- "object-detection": {
2414
- defaultModel: "Xenova/detr-resnet-50",
2415
- defaultDtype: "fp32",
2416
- supportsStreaming: false,
2417
- usesKVCache: false
2418
- },
2419
- "automatic-speech-recognition": {
2420
- defaultModel: "onnx-community/whisper-tiny.en",
2421
- defaultDtype: "q8",
2422
- supportsStreaming: false,
2423
- usesKVCache: false
2424
- },
2425
- "text-to-speech": {
2426
- defaultModel: "onnx-community/Kokoro-82M-v1.0-ONNX",
2427
- defaultDtype: "q8",
2428
- supportsStreaming: false,
2429
- usesKVCache: false
2430
- },
2431
- "translation": {
2432
- defaultModel: "Xenova/nllb-200-distilled-600M",
2433
- defaultDtype: "q8",
2434
- supportsStreaming: false,
2435
- usesKVCache: false
2436
- },
2437
- "summarization": {
2438
- defaultModel: "Xenova/distilbart-cnn-6-6",
2439
- defaultDtype: "q8",
2440
- supportsStreaming: false,
2441
- usesKVCache: false
2442
- },
2443
- "feature-extraction": {
2444
- defaultModel: "Xenova/all-MiniLM-L6-v2",
2445
- defaultDtype: "fp32",
2446
- supportsStreaming: false,
2447
- usesKVCache: false
2448
- },
2449
- "image-to-text": {
2450
- defaultModel: "Xenova/vit-gpt2-image-captioning",
2451
- defaultDtype: "q8",
2452
- supportsStreaming: false,
2453
- usesKVCache: false
2454
- },
2455
- "zero-shot-classification": {
2456
- defaultModel: "Xenova/mobilebert-uncased-mnli",
2457
- defaultDtype: "q8",
2458
- supportsStreaming: false,
2459
- usesKVCache: false
2460
- },
2461
- "fill-mask": {
2462
- defaultModel: "Xenova/bert-base-uncased",
2463
- defaultDtype: "q8",
2464
- supportsStreaming: false,
2465
- usesKVCache: false
2466
- },
2467
- "question-answering": {
2468
- defaultModel: "Xenova/distilbert-base-uncased-distilled-squad",
2469
- defaultDtype: "q8",
2470
- supportsStreaming: false,
2471
- usesKVCache: false
2472
- },
2473
- "token-classification": {
2474
- defaultModel: "Xenova/bert-base-NER",
2475
- defaultDtype: "q8",
2476
- supportsStreaming: false,
2477
- usesKVCache: false
2478
- },
2479
- "depth-estimation": {
2480
- defaultModel: "Xenova/depth-anything-small-hf",
2481
- defaultDtype: "fp32",
2482
- supportsStreaming: false,
2483
- usesKVCache: false
2484
- },
2485
- "image-segmentation": {
2486
- defaultModel: "Xenova/detr-resnet-50-panoptic",
2487
- defaultDtype: "fp32",
2488
- supportsStreaming: false,
2489
- usesKVCache: false
2490
- },
2491
- "raw-onnx": {
2492
- defaultModel: "",
2493
- defaultDtype: "fp32",
2494
- supportsStreaming: false,
2495
- usesKVCache: false
2496
- },
2497
- "decision": {
2498
- defaultModel: "qwen3-0.6b",
2499
- defaultDtype: "q4",
2500
- supportsStreaming: false,
2501
- usesKVCache: false
2502
- },
2503
- "custom": {
2504
- defaultModel: "",
2505
- defaultDtype: "fp32",
2506
- supportsStreaming: false,
2507
- usesKVCache: false
2508
- }
2509
- };
2510
- function getPipelineDefaults(task) {
2511
- const defaults = PIPELINE_REGISTRY[task];
2512
- if (!defaults) {
2513
- throw new Error(`Unknown pipeline task: ${task}`);
2514
- }
2515
- return defaults;
2516
- }
2517
- function supportsStreaming(task) {
2518
- return PIPELINE_REGISTRY[task]?.supportsStreaming ?? false;
2519
- }
2520
-
2521
- // src/index.ts
2522
- var index_default = loader_default;
2523
- export {
2524
- GPURecovery,
2525
- HeuristicDecisionEngine,
2526
- ModelClient,
2527
- OPENJEV_MODELS,
2528
- OpenJevWllamaEngine,
2529
- PIPELINE_REGISTRY,
2530
- TokenStream,
2531
- WEBGPU_ORGS,
2532
- canRun,
2533
- checkWASM,
2534
- checkWebGPU,
2535
- clearCache,
2536
- coerceAudio,
2537
- collectStream,
2538
- createDecisionEngine,
2539
- createOnnxPipeline,
2540
- decodeCTC,
2541
- decodeWavToFloat32,
2542
- index_default as default,
2543
- detectDevice,
2544
- directChoice,
2545
- formatSize,
2546
- getCacheBackend,
2547
- getCacheSize,
2548
- getGPUAdapter,
2549
- getGPUInfo,
2550
- getModelInfo,
2551
- getPipelineDefaults,
2552
- inferTask,
2553
- isCached,
2554
- listCachedModels,
2555
- listModelsForTask,
2556
- listWebGPUModels,
2557
- listenMic,
2558
- normalizeCriteria,
2559
- normalizeOptions,
2560
- parseSize,
2561
- recommendDtype,
2562
- searchModels,
2563
- supportsStreaming,
2564
- toFloat32Array,
2565
- trendingModels,
2566
- validateModelOptions,
2567
- webml
2568
- };