@workglow/cactus 0.3.3 → 0.3.5

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 (38) hide show
  1. package/dist/ai/CactusProvider.browser.d.ts +23 -0
  2. package/dist/ai/CactusProvider.browser.d.ts.map +1 -0
  3. package/dist/ai/CactusQueuedProvider.browser.d.ts +23 -0
  4. package/dist/ai/CactusQueuedProvider.browser.d.ts.map +1 -0
  5. package/dist/ai/common/Cactus_Download.browser.d.ts +9 -0
  6. package/dist/ai/common/Cactus_Download.browser.d.ts.map +1 -0
  7. package/dist/ai/common/Cactus_DownloadRemove.browser.d.ts +9 -0
  8. package/dist/ai/common/Cactus_DownloadRemove.browser.d.ts.map +1 -0
  9. package/dist/ai/common/Cactus_JobRunFns.browser.d.ts +12 -0
  10. package/dist/ai/common/Cactus_JobRunFns.browser.d.ts.map +1 -0
  11. package/dist/ai/common/Cactus_ModelInfo.browser.d.ts +9 -0
  12. package/dist/ai/common/Cactus_ModelInfo.browser.d.ts.map +1 -0
  13. package/dist/ai/common/Cactus_ModelInfo.d.ts.map +1 -1
  14. package/dist/ai/common/Cactus_Runtime.browser.d.ts +6 -0
  15. package/dist/ai/common/Cactus_Runtime.browser.d.ts.map +1 -1
  16. package/dist/ai/common/Cactus_Runtime.d.ts +6 -0
  17. package/dist/ai/common/Cactus_Runtime.d.ts.map +1 -1
  18. package/dist/ai/common/Cactus_ToolCalling.browser.d.ts +9 -0
  19. package/dist/ai/common/Cactus_ToolCalling.browser.d.ts.map +1 -0
  20. package/dist/ai/registerCactus.browser.d.ts +10 -0
  21. package/dist/ai/registerCactus.browser.d.ts.map +1 -0
  22. package/dist/ai/registerCactusInline.browser.d.ts +8 -0
  23. package/dist/ai/registerCactusInline.browser.d.ts.map +1 -0
  24. package/dist/ai/registerCactusWorker.browser.d.ts +7 -0
  25. package/dist/ai/registerCactusWorker.browser.d.ts.map +1 -0
  26. package/dist/ai-runtime.browser.d.ts +2 -2
  27. package/dist/ai-runtime.browser.d.ts.map +1 -1
  28. package/dist/ai-runtime.browser.js +86 -186
  29. package/dist/ai-runtime.browser.js.map +13 -14
  30. package/dist/ai-runtime.js +112 -9
  31. package/dist/ai-runtime.js.map +5 -5
  32. package/dist/ai.browser.d.ts +5 -5
  33. package/dist/ai.browser.d.ts.map +1 -1
  34. package/dist/ai.browser.js +92 -193
  35. package/dist/ai.browser.js.map +13 -14
  36. package/dist/ai.js +111 -9
  37. package/dist/ai.js.map +5 -5
  38. package/package.json +11 -11
@@ -60,6 +60,23 @@ function getCactusSdk() {
60
60
  throw new Error("Cactus SDK not loaded; call loadSdk() first");
61
61
  return _sdk;
62
62
  }
63
+ function assetFilenames(entry) {
64
+ return [entry.assets.weights, entry.assets.vocab, entry.assets.config];
65
+ }
66
+ async function getRemoteAssetSize(url, signal) {
67
+ try {
68
+ const response = await fetch(url, { method: "HEAD", signal });
69
+ if (!response.ok)
70
+ return;
71
+ const contentLength = response.headers.get("content-length");
72
+ if (!contentLength)
73
+ return;
74
+ const size = Number(contentLength);
75
+ return Number.isFinite(size) ? size : undefined;
76
+ } catch {
77
+ return;
78
+ }
79
+ }
63
80
  async function fetchAssetBytesBrowser(url) {
64
81
  const cachesApi = globalThis.caches;
65
82
  const cache = await cachesApi.open(CACTUS_CACHE_NAME);
@@ -131,6 +148,47 @@ function markModelCached(model_id) {
131
148
  function isModelCached(model_id) {
132
149
  return cactusEngines.has(model_id) || cactusCachedModelIds.has(model_id);
133
150
  }
151
+ async function getCactusModelCacheInfo(_model, entry, detail, signal) {
152
+ const cachesApi = globalThis.caches;
153
+ const cache = await cachesApi.open(CACTUS_CACHE_NAME);
154
+ const filenames = assetFilenames(entry);
155
+ const cacheHits = await Promise.all(filenames.map(async (filename) => {
156
+ const url = cactusAssetUrl(entry, filename);
157
+ const hit = await cache.match(url);
158
+ return { filename, url, hit };
159
+ }));
160
+ const allCached = cacheHits.every(({ hit }) => Boolean(hit));
161
+ if (detail === "files") {
162
+ return {
163
+ allCached,
164
+ file_sizes: Object.fromEntries(filenames.map((filename) => [filename, 0]))
165
+ };
166
+ }
167
+ if (detail !== "files_with_metadata") {
168
+ return { allCached, file_sizes: null };
169
+ }
170
+ const file_sizes = {};
171
+ await Promise.all(cacheHits.map(async ({ filename, url, hit }) => {
172
+ if (hit) {
173
+ const contentLength = hit.headers.get("content-length");
174
+ const contentLengthSize = contentLength ? Number(contentLength) : undefined;
175
+ if (contentLengthSize !== undefined && Number.isFinite(contentLengthSize)) {
176
+ file_sizes[filename] = contentLengthSize;
177
+ } else {
178
+ file_sizes[filename] = (await hit.clone().arrayBuffer()).byteLength;
179
+ }
180
+ return;
181
+ }
182
+ const remoteSize = await getRemoteAssetSize(url, signal);
183
+ if (remoteSize !== undefined) {
184
+ file_sizes[filename] = remoteSize;
185
+ }
186
+ }));
187
+ return {
188
+ allCached,
189
+ file_sizes: Object.keys(file_sizes).length > 0 ? file_sizes : null
190
+ };
191
+ }
134
192
  var cactusSessions = new Map;
135
193
  async function deleteCactusSession(id) {
136
194
  return cactusSessions.delete(id);
@@ -173,10 +231,10 @@ async function disposeCactusResources() {
173
231
  cactusCachedModelIds.clear();
174
232
  cactusSessions.clear();
175
233
  }
176
- // src/ai/registerCactusInline.ts
234
+ // src/ai/registerCactusInline.browser.ts
177
235
  import { registerProviderInline } from "@workglow/ai/provider-utils";
178
236
 
179
- // src/ai/CactusQueuedProvider.ts
237
+ // src/ai/CactusQueuedProvider.browser.ts
180
238
  import { QueuedAiProvider } from "@workglow/ai";
181
239
 
182
240
  // src/ai/common/Cactus_CapabilitySets.ts
@@ -204,167 +262,7 @@ function inferCactusCapabilities(_model) {
204
262
  return ["tool-use", "model.download", "model.download-remove", "model.search", "model.info"];
205
263
  }
206
264
 
207
- // src/ai/common/Cactus_Runtime.ts
208
- import fs from "node:fs/promises";
209
- import path from "node:path";
210
- var _sdk2;
211
- var _sdkInitPromise2;
212
- async function loadSdk2() {
213
- _sdkInitPromise2 ??= import("needle-rs").then(async (mod) => {
214
- const init = mod.default;
215
- if (typeof init === "function") {
216
- await init();
217
- }
218
- _sdk2 = mod;
219
- return mod;
220
- }).catch((err) => {
221
- _sdkInitPromise2 = undefined;
222
- _sdk2 = undefined;
223
- throw new Error(`needle-rs is required for LOCAL_CACTUS tasks. Install it with: bun add needle-rs (cause: ${String(err)})`);
224
- });
225
- return _sdkInitPromise2;
226
- }
227
- function hasBrowserCacheStorage() {
228
- return typeof globalThis !== "undefined" && "caches" in globalThis && typeof globalThis.caches?.open === "function";
229
- }
230
- function modelsDirOf(model) {
231
- return model.provider_config.models_dir ?? CACTUS_DEFAULT_MODELS_DIR;
232
- }
233
- async function fetchAssetBytesBrowser2(url) {
234
- const cachesApi = globalThis.caches;
235
- const cache = await cachesApi.open(CACTUS_CACHE_NAME);
236
- const hit = await cache.match(url);
237
- if (hit) {
238
- return new Uint8Array(await hit.arrayBuffer());
239
- }
240
- const resp = await fetch(url);
241
- if (!resp.ok)
242
- throw new Error(`Cactus asset fetch failed (${resp.status}) for ${url}`);
243
- await cache.put(url, resp.clone());
244
- return new Uint8Array(await resp.arrayBuffer());
245
- }
246
- async function fetchAssetBytesNode(url, models_dir, model_id, filename) {
247
- const resolvedDir = models_dir.startsWith("~/") ? path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", models_dir.slice(2), model_id) : path.resolve(models_dir, model_id);
248
- const filePath = path.join(resolvedDir, filename);
249
- try {
250
- const buf = await fs.readFile(filePath);
251
- return new Uint8Array(buf);
252
- } catch {}
253
- const resp = await fetch(url);
254
- if (!resp.ok)
255
- throw new Error(`Cactus asset fetch failed (${resp.status}) for ${url}`);
256
- const bytes = new Uint8Array(await resp.arrayBuffer());
257
- await fs.mkdir(resolvedDir, { recursive: true });
258
- const tmpPath = `${filePath}.tmp`;
259
- await fs.writeFile(tmpPath, bytes);
260
- await fs.rename(tmpPath, filePath);
261
- return bytes;
262
- }
263
- async function fetchAssetBytes2(model, filename) {
264
- const model_id = model.provider_config.model_id;
265
- const entry = getCactusCatalogEntry(model_id);
266
- if (!entry)
267
- throw new Error(`Unknown Cactus model_id: ${model_id}`);
268
- const url = cactusAssetUrl(entry, filename);
269
- if (hasBrowserCacheStorage()) {
270
- return fetchAssetBytesBrowser2(url);
271
- }
272
- return fetchAssetBytesNode(url, modelsDirOf(model), model_id, filename);
273
- }
274
- var cactusEngines2 = new Map;
275
- var cactusConfigJson2 = new Map;
276
- var cactusCachedModelIds2 = new Set;
277
- var cactusEngineLoadsInFlight2 = new Map;
278
- async function getOrLoadEngine2(model) {
279
- const model_id = model.provider_config.model_id;
280
- const cached = cactusEngines2.get(model_id);
281
- if (cached)
282
- return cached;
283
- const inFlight = cactusEngineLoadsInFlight2.get(model_id);
284
- if (inFlight)
285
- return inFlight;
286
- const loadPromise = (async () => {
287
- const sdk = await loadSdk2();
288
- const entry = getCactusCatalogEntry(model_id);
289
- if (!entry)
290
- throw new Error(`Unknown Cactus model_id: ${model_id}`);
291
- const [weightsBytes, vocabBytes, configBytes] = await Promise.all([
292
- fetchAssetBytes2(model, entry.assets.weights),
293
- fetchAssetBytes2(model, entry.assets.vocab),
294
- fetchAssetBytes2(model, entry.assets.config)
295
- ]);
296
- try {
297
- const text = new TextDecoder().decode(configBytes);
298
- cactusConfigJson2.set(model_id, JSON.parse(text));
299
- } catch {
300
- cactusConfigJson2.set(model_id, null);
301
- }
302
- const vocabText = new TextDecoder().decode(vocabBytes);
303
- const engine = sdk.NeedleWasm.load(weightsBytes, vocabText);
304
- if (!engine) {
305
- throw new Error(`needle-rs NeedleWasm.load returned undefined for model ${model_id}`);
306
- }
307
- cactusEngines2.set(model_id, engine);
308
- return engine;
309
- })().finally(() => {
310
- cactusEngineLoadsInFlight2.delete(model_id);
311
- });
312
- cactusEngineLoadsInFlight2.set(model_id, loadPromise);
313
- return loadPromise;
314
- }
315
- function isModelLoaded2(model_id) {
316
- return cactusEngines2.has(model_id);
317
- }
318
- function markModelCached2(model_id) {
319
- cactusCachedModelIds2.add(model_id);
320
- }
321
- function isModelCached2(model_id) {
322
- return cactusEngines2.has(model_id) || cactusCachedModelIds2.has(model_id);
323
- }
324
- var cactusSessions2 = new Map;
325
- async function deleteCactusSession2(id) {
326
- return cactusSessions2.delete(id);
327
- }
328
- async function removeBrowserCacheEntries2(entry) {
329
- if (!hasBrowserCacheStorage())
330
- return;
331
- const cachesApi = globalThis.caches;
332
- const cache = await cachesApi.open(CACTUS_CACHE_NAME);
333
- for (const filename of [entry.assets.weights, entry.assets.vocab, entry.assets.config]) {
334
- const url = cactusAssetUrl(entry, filename);
335
- try {
336
- await cache.delete(url);
337
- } catch {}
338
- }
339
- }
340
- async function removeNodeCacheDir(model, model_id) {
341
- if (hasBrowserCacheStorage())
342
- return;
343
- const models_dir = modelsDirOf(model);
344
- const resolvedDir = models_dir.startsWith("~/") ? path.join(process.env.HOME ?? process.env.USERPROFILE ?? ".", models_dir.slice(2), model_id) : path.resolve(models_dir, model_id);
345
- await fs.rm(resolvedDir, { recursive: true, force: true });
346
- }
347
- function disposeCactusEngine2(model_id) {
348
- const engine = cactusEngines2.get(model_id);
349
- if (engine) {
350
- try {
351
- engine.free?.();
352
- } catch {}
353
- }
354
- cactusEngines2.delete(model_id);
355
- cactusConfigJson2.delete(model_id);
356
- cactusCachedModelIds2.delete(model_id);
357
- }
358
- async function removeCachedAssets2(model) {
359
- const model_id = model.provider_config.model_id;
360
- const entry = getCactusCatalogEntry(model_id);
361
- if (!entry)
362
- return;
363
- await Promise.all([removeBrowserCacheEntries2(entry), removeNodeCacheDir(model, model_id)]);
364
- disposeCactusEngine2(model_id);
365
- }
366
-
367
- // src/ai/CactusQueuedProvider.ts
265
+ // src/ai/CactusQueuedProvider.browser.ts
368
266
  class CactusQueuedProvider extends QueuedAiProvider {
369
267
  name = LOCAL_CACTUS;
370
268
  displayName = "Cactus (Needle)";
@@ -383,11 +281,11 @@ class CactusQueuedProvider extends QueuedAiProvider {
383
281
  return crypto.randomUUID();
384
282
  }
385
283
  async disposeSession(sessionId) {
386
- await deleteCactusSession2(sessionId);
284
+ await deleteCactusSession(sessionId);
387
285
  }
388
286
  }
389
287
 
390
- // src/ai/common/Cactus_Download.ts
288
+ // src/ai/common/Cactus_Download.browser.ts
391
289
  var Cactus_Download = async (input, model, _signal, emit) => {
392
290
  if (!model)
393
291
  throw new Error("Model config is required for ModelDownloadTask.");
@@ -402,30 +300,31 @@ var Cactus_Download = async (input, model, _signal, emit) => {
402
300
  message: `Downloading ${assets[i]}`,
403
301
  progress: Math.round((i + 0.5) / assets.length * 99)
404
302
  });
405
- await fetchAssetBytes2(model, assets[i]);
303
+ await fetchAssetBytes(model, assets[i]);
406
304
  }
407
- markModelCached2(model_id);
305
+ markModelCached(model_id);
408
306
  emit({ type: "finish", data: { model: input.model } });
409
307
  };
410
308
 
411
- // src/ai/common/Cactus_DownloadRemove.ts
309
+ // src/ai/common/Cactus_DownloadRemove.browser.ts
412
310
  var Cactus_DownloadRemove = async (input, model, _signal, emit) => {
413
311
  if (!model)
414
312
  throw new Error("Model config is required for ModelDownloadRemoveTask.");
415
- await removeCachedAssets2(model);
313
+ await removeCachedAssets(model);
416
314
  emit({ type: "finish", data: { model: input.model } });
417
315
  };
418
316
 
419
- // src/ai/common/Cactus_ModelInfo.ts
420
- var Cactus_ModelInfo = async (input, model, _signal, emit) => {
317
+ // src/ai/common/Cactus_ModelInfo.browser.ts
318
+ var Cactus_ModelInfo = async (input, model, signal, emit) => {
421
319
  if (!model)
422
320
  throw new Error("Model config is required for ModelInfoTask.");
423
321
  const model_id = model.provider_config.model_id;
424
322
  const entry = getCactusCatalogEntry(model_id);
425
323
  if (!entry)
426
324
  throw new Error(`Unknown Cactus model_id: ${model_id}`);
427
- const is_loaded = isModelLoaded2(model_id);
428
- const is_cached = isModelCached2(model_id);
325
+ const is_loaded = isModelLoaded(model_id);
326
+ const cacheInfo = await getCactusModelCacheInfo(model, entry, input.detail, signal);
327
+ const is_cached = is_loaded || isModelCached(model_id) || cacheInfo.allCached;
429
328
  emit({
430
329
  type: "finish",
431
330
  data: {
@@ -436,7 +335,7 @@ var Cactus_ModelInfo = async (input, model, _signal, emit) => {
436
335
  supports_node: true,
437
336
  is_cached,
438
337
  is_loaded,
439
- file_sizes: null
338
+ file_sizes: cacheInfo.file_sizes
440
339
  }
441
340
  });
442
341
  };
@@ -462,9 +361,9 @@ var Cactus_ModelSearch = async (input, _model, _signal, emit) => {
462
361
  emit({ type: "finish", data: { results } });
463
362
  };
464
363
 
465
- // src/ai/common/Cactus_ToolCalling.ts
364
+ // src/ai/common/Cactus_ToolCalling.browser.ts
466
365
  import { extractMessageText } from "@workglow/ai/provider-utils";
467
- import { filterValidToolCalls } from "@workglow/ai/worker";
366
+ import { filterValidToolCalls, sanitizeToolArgs } from "@workglow/ai/worker";
468
367
  function buildToolsJson(tools) {
469
368
  return JSON.stringify(tools.map((t) => ({
470
369
  name: t.name,
@@ -492,7 +391,7 @@ function parseToolCalls(raw) {
492
391
  return obj.map((o, i) => ({
493
392
  id: `call_${i}`,
494
393
  name: String(o.name ?? ""),
495
- input: o.arguments ?? o.params ?? {}
394
+ input: sanitizeToolArgs(o.arguments ?? o.params ?? {})
496
395
  }));
497
396
  }
498
397
  if (obj && typeof obj === "object" && typeof obj.name === "string") {
@@ -500,7 +399,7 @@ function parseToolCalls(raw) {
500
399
  {
501
400
  id: "call_0",
502
401
  name: obj.name,
503
- input: obj.arguments ?? obj.params ?? {}
402
+ input: sanitizeToolArgs(obj.arguments ?? obj.params ?? {})
504
403
  }
505
404
  ];
506
405
  }
@@ -512,7 +411,7 @@ var Cactus_ToolCalling = async (input, model, signal, emit) => {
512
411
  throw new Error("Model config is required for ToolCallingTask.");
513
412
  if (signal.aborted)
514
413
  throw signal.reason ?? new Error("The operation was aborted");
515
- const engine = await getOrLoadEngine2(model);
414
+ const engine = await getOrLoadEngine(model);
516
415
  const query = promptText(input);
517
416
  const toolsJson = buildToolsJson(input.tools);
518
417
  let raw = "";
@@ -533,7 +432,7 @@ var Cactus_ToolCalling = async (input, model, signal, emit) => {
533
432
  emit({ type: "finish", data: { text: raw, toolCalls: validToolCalls } });
534
433
  };
535
434
 
536
- // src/ai/common/Cactus_JobRunFns.ts
435
+ // src/ai/common/Cactus_JobRunFns.browser.ts
537
436
  var CACTUS_RUN_FNS = [
538
437
  { serves: CACTUS_TOOL_USE, runFn: Cactus_ToolCalling },
539
438
  { serves: CACTUS_MODEL_DOWNLOAD, runFn: Cactus_Download },
@@ -543,14 +442,14 @@ var CACTUS_RUN_FNS = [
543
442
  ];
544
443
  var CACTUS_PREVIEW_TASKS = {};
545
444
 
546
- // src/ai/registerCactusInline.ts
445
+ // src/ai/registerCactusInline.browser.ts
547
446
  async function registerCactusInline(options) {
548
447
  await registerProviderInline(new CactusQueuedProvider(CACTUS_RUN_FNS, CACTUS_PREVIEW_TASKS), "Cactus", options);
549
448
  }
550
- // src/ai/registerCactusWorker.ts
449
+ // src/ai/registerCactusWorker.browser.ts
551
450
  import { registerProviderWorker } from "@workglow/ai/provider-utils";
552
451
 
553
- // src/ai/CactusProvider.ts
452
+ // src/ai/CactusProvider.browser.ts
554
453
  import { AiProvider } from "@workglow/ai/worker";
555
454
  class CactusProvider extends AiProvider {
556
455
  name = LOCAL_CACTUS;
@@ -570,11 +469,11 @@ class CactusProvider extends AiProvider {
570
469
  return crypto.randomUUID();
571
470
  }
572
471
  async disposeSession(sessionId) {
573
- await deleteCactusSession2(sessionId);
472
+ await deleteCactusSession(sessionId);
574
473
  }
575
474
  }
576
475
 
577
- // src/ai/registerCactusWorker.ts
476
+ // src/ai/registerCactusWorker.browser.ts
578
477
  async function registerCactusWorker() {
579
478
  await registerProviderWorker((ws) => new CactusProvider(CACTUS_RUN_FNS, CACTUS_PREVIEW_TASKS).registerOnWorkerServer(ws), "Cactus");
580
479
  }
@@ -588,6 +487,7 @@ export {
588
487
  isModelCached,
589
488
  getOrLoadEngine,
590
489
  getCactusSdk,
490
+ getCactusModelCacheInfo,
591
491
  fetchAssetBytes,
592
492
  disposeCactusResources,
593
493
  deleteCactusSession,
@@ -596,4 +496,4 @@ export {
596
496
  cactusConfigJson
597
497
  };
598
498
 
599
- //# debugId=E5F05A02A40778DA64756E2164756E21
499
+ //# debugId=50314F7F9017421A64756E2164756E21