@omote/core 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -3130,6 +3130,12 @@ interface FetchWithCacheOptions {
3130
3130
  validateStale?: boolean;
3131
3131
  /** Progress callback during download */
3132
3132
  onProgress?: (loaded: number, total: number) => void;
3133
+ /** Timeout per fetch attempt in ms. Default: 120_000 (2 min) */
3134
+ timeoutMs?: number;
3135
+ /** Max retry attempts on failure. Default: 2 (3 total attempts) */
3136
+ maxRetries?: number;
3137
+ /** AbortSignal to cancel the entire operation */
3138
+ signal?: AbortSignal;
3133
3139
  }
3134
3140
  /**
3135
3141
  * Fetch a model with caching
package/dist/index.d.ts CHANGED
@@ -3130,6 +3130,12 @@ interface FetchWithCacheOptions {
3130
3130
  validateStale?: boolean;
3131
3131
  /** Progress callback during download */
3132
3132
  onProgress?: (loaded: number, total: number) => void;
3133
+ /** Timeout per fetch attempt in ms. Default: 120_000 (2 min) */
3134
+ timeoutMs?: number;
3135
+ /** Max retry attempts on failure. Default: 2 (3 total attempts) */
3136
+ maxRetries?: number;
3137
+ /** AbortSignal to cancel the entire operation */
3138
+ signal?: AbortSignal;
3133
3139
  }
3134
3140
  /**
3135
3141
  * Fetch a model with caching
package/dist/index.js CHANGED
@@ -2219,6 +2219,16 @@ function getModelCache() {
2219
2219
  return cacheInstance;
2220
2220
  }
2221
2221
  var MAX_CACHE_SIZE_BYTES = 500 * 1024 * 1024;
2222
+ function fetchWithTimeout(url, timeoutMs, signal) {
2223
+ const controller = new AbortController();
2224
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2225
+ const onCallerAbort = () => controller.abort();
2226
+ signal?.addEventListener("abort", onCallerAbort, { once: true });
2227
+ return fetch(url, { signal: controller.signal }).finally(() => {
2228
+ clearTimeout(timer);
2229
+ signal?.removeEventListener("abort", onCallerAbort);
2230
+ });
2231
+ }
2222
2232
  async function fetchWithCache(url, optionsOrProgress) {
2223
2233
  let options = {};
2224
2234
  if (typeof optionsOrProgress === "function") {
@@ -2272,61 +2282,84 @@ async function fetchWithCache(url, optionsOrProgress) {
2272
2282
  }
2273
2283
  span?.setAttributes({ "fetch.cache_hit": false });
2274
2284
  console.log(`[ModelCache] Cache miss, fetching: ${url}`);
2275
- try {
2276
- const response = await fetch(url);
2277
- if (!response.ok) {
2278
- throw new Error(`Failed to fetch ${url}: ${response.status}`);
2279
- }
2280
- const contentLength = response.headers.get("content-length");
2281
- const total = contentLength ? parseInt(contentLength, 10) : 0;
2282
- const etag = response.headers.get("etag") ?? void 0;
2283
- const tooLargeForCache = total > MAX_CACHE_SIZE_BYTES;
2284
- if (tooLargeForCache) {
2285
- console.log(`[ModelCache] File too large for IndexedDB (${(total / 1024 / 1024).toFixed(0)}MB > 500MB), using HTTP cache only`);
2286
- }
2287
- if (!response.body) {
2288
- const data2 = await response.arrayBuffer();
2285
+ const timeout = options.timeoutMs ?? 12e4;
2286
+ const maxRetries = options.maxRetries ?? 2;
2287
+ let lastError = null;
2288
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2289
+ if (options.signal?.aborted) {
2290
+ throw new Error(`Fetch aborted for ${url}`);
2291
+ }
2292
+ if (attempt > 0) {
2293
+ const backoff = Math.min(2e3 * Math.pow(2, attempt - 1), 16e3);
2294
+ console.log(`[ModelCache] Retry ${attempt}/${maxRetries} after ${backoff}ms: ${url}`);
2295
+ await new Promise((r) => setTimeout(r, backoff));
2296
+ }
2297
+ try {
2298
+ const response = await fetchWithTimeout(url, timeout, options.signal);
2299
+ if (!response.ok) {
2300
+ throw new Error(`Failed to fetch ${url}: ${response.status}`);
2301
+ }
2302
+ const contentLength = response.headers.get("content-length");
2303
+ const total = contentLength ? parseInt(contentLength, 10) : 0;
2304
+ const etag = response.headers.get("etag") ?? void 0;
2305
+ const tooLargeForCache = total > MAX_CACHE_SIZE_BYTES;
2306
+ if (tooLargeForCache) {
2307
+ console.log(`[ModelCache] File too large for IndexedDB (${(total / 1024 / 1024).toFixed(0)}MB > 500MB), using HTTP cache only`);
2308
+ }
2309
+ if (!response.body) {
2310
+ const data2 = await response.arrayBuffer();
2311
+ if (!tooLargeForCache) {
2312
+ await cache.set(cacheKey, data2, etag, version);
2313
+ }
2314
+ span?.setAttributes({
2315
+ "fetch.size_bytes": data2.byteLength,
2316
+ "fetch.cached_to_indexeddb": !tooLargeForCache,
2317
+ ...attempt > 0 && { "fetch.retry_count": attempt }
2318
+ });
2319
+ span?.end();
2320
+ return data2;
2321
+ }
2322
+ const reader = response.body.getReader();
2323
+ const chunks = [];
2324
+ let loaded = 0;
2325
+ while (true) {
2326
+ const { done, value } = await reader.read();
2327
+ if (done) break;
2328
+ chunks.push(value);
2329
+ loaded += value.length;
2330
+ onProgress?.(loaded, total || loaded);
2331
+ }
2332
+ const data = new Uint8Array(loaded);
2333
+ let offset = 0;
2334
+ for (const chunk of chunks) {
2335
+ data.set(chunk, offset);
2336
+ offset += chunk.length;
2337
+ }
2338
+ const buffer = data.buffer;
2289
2339
  if (!tooLargeForCache) {
2290
- await cache.set(cacheKey, data2, etag, version);
2340
+ await cache.set(cacheKey, buffer, etag, version);
2341
+ console.log(`[ModelCache] Cached: ${url} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);
2291
2342
  }
2292
2343
  span?.setAttributes({
2293
- "fetch.size_bytes": data2.byteLength,
2294
- "fetch.cached_to_indexeddb": !tooLargeForCache
2344
+ "fetch.size_bytes": buffer.byteLength,
2345
+ "fetch.cached_to_indexeddb": !tooLargeForCache,
2346
+ ...attempt > 0 && { "fetch.retry_count": attempt }
2295
2347
  });
2296
2348
  span?.end();
2297
- return data2;
2298
- }
2299
- const reader = response.body.getReader();
2300
- const chunks = [];
2301
- let loaded = 0;
2302
- while (true) {
2303
- const { done, value } = await reader.read();
2304
- if (done) break;
2305
- chunks.push(value);
2306
- loaded += value.length;
2307
- onProgress?.(loaded, total || loaded);
2308
- }
2309
- const data = new Uint8Array(loaded);
2310
- let offset = 0;
2311
- for (const chunk of chunks) {
2312
- data.set(chunk, offset);
2313
- offset += chunk.length;
2314
- }
2315
- const buffer = data.buffer;
2316
- if (!tooLargeForCache) {
2317
- await cache.set(cacheKey, buffer, etag, version);
2318
- console.log(`[ModelCache] Cached: ${url} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);
2349
+ return buffer;
2350
+ } catch (error) {
2351
+ lastError = error instanceof Error ? error : new Error(String(error));
2352
+ if (options.signal?.aborted) {
2353
+ span?.endWithError(lastError);
2354
+ throw lastError;
2355
+ }
2356
+ if (attempt < maxRetries) {
2357
+ console.warn(`[ModelCache] Attempt ${attempt + 1} failed for ${url}: ${lastError.message}`);
2358
+ }
2319
2359
  }
2320
- span?.setAttributes({
2321
- "fetch.size_bytes": buffer.byteLength,
2322
- "fetch.cached_to_indexeddb": !tooLargeForCache
2323
- });
2324
- span?.end();
2325
- return buffer;
2326
- } catch (error) {
2327
- span?.endWithError(error instanceof Error ? error : new Error(String(error)));
2328
- throw error;
2329
2360
  }
2361
+ span?.endWithError(lastError);
2362
+ throw lastError;
2330
2363
  }
2331
2364
  async function preloadModels(urls, onProgress) {
2332
2365
  const cache = getModelCache();
@@ -2575,6 +2608,15 @@ function getSessionOptions(backend) {
2575
2608
  graphOptimizationLevel: "all"
2576
2609
  };
2577
2610
  }
2611
+ function withTimeout(promise, ms, label) {
2612
+ return new Promise((resolve, reject) => {
2613
+ const timer = setTimeout(
2614
+ () => reject(new Error(`${label} timed out after ${ms}ms`)),
2615
+ ms
2616
+ );
2617
+ promise.then(resolve, reject).finally(() => clearTimeout(timer));
2618
+ });
2619
+ }
2578
2620
 
2579
2621
  // src/inference/blendshapeUtils.ts
2580
2622
  var LAM_BLENDSHAPES = [
@@ -2847,7 +2889,11 @@ var _Wav2Vec2Inference = class _Wav2Vec2Inference {
2847
2889
  )
2848
2890
  });
2849
2891
  try {
2850
- this.session = await this.ort.InferenceSession.create(modelUrl, sessionOptions);
2892
+ this.session = await withTimeout(
2893
+ this.ort.InferenceSession.create(modelUrl, sessionOptions),
2894
+ 18e4,
2895
+ "Wav2Vec2 InferenceSession.create (iOS URL pass-through)"
2896
+ );
2851
2897
  } catch (sessionErr) {
2852
2898
  logger3.error("iOS: InferenceSession.create() failed", {
2853
2899
  error: sessionErr instanceof Error ? sessionErr.message : String(sessionErr),
@@ -3935,9 +3981,10 @@ var _SenseVoiceInference = class _SenseVoiceInference {
3935
3981
  logger5.info("iOS: passing model URL directly to ORT (low-memory path)", {
3936
3982
  modelUrl: this.config.modelUrl
3937
3983
  });
3938
- this.session = await this.ort.InferenceSession.create(
3939
- this.config.modelUrl,
3940
- sessionOptions
3984
+ this.session = await withTimeout(
3985
+ this.ort.InferenceSession.create(this.config.modelUrl, sessionOptions),
3986
+ 18e4,
3987
+ "SenseVoice InferenceSession.create (iOS URL pass-through)"
3941
3988
  );
3942
3989
  } else {
3943
3990
  const cache = getModelCache();
@@ -4190,6 +4237,12 @@ var WORKER_SCRIPT = `
4190
4237
  var ort = null;
4191
4238
  var session = null;
4192
4239
  var tokenMap = null;
4240
+
4241
+ function fetchWithTimeout(url, timeoutMs) {
4242
+ var controller = new AbortController();
4243
+ var timer = setTimeout(function() { controller.abort(); }, timeoutMs);
4244
+ return fetch(url, { signal: controller.signal }).finally(function() { clearTimeout(timer); });
4245
+ }
4193
4246
  var negMean = null;
4194
4247
  var invStddev = null;
4195
4248
  var languageId = 0;
@@ -4633,7 +4686,7 @@ async function loadOrt(wasmPaths) {
4633
4686
  var ortUrl = wasmPaths + 'ort.wasm.min.js';
4634
4687
 
4635
4688
  // Load the script by fetching and executing it
4636
- var response = await fetch(ortUrl);
4689
+ var response = await fetchWithTimeout(ortUrl, 30000);
4637
4690
  var scriptText = await response.text();
4638
4691
 
4639
4692
  // Create a blob URL for the script
@@ -4659,7 +4712,7 @@ async function loadOrt(wasmPaths) {
4659
4712
  */
4660
4713
  async function loadModel(modelUrl, tokensUrl, isIOSDevice, lang, textNorm) {
4661
4714
  // 1. Fetch and parse tokens.txt
4662
- var tokensResponse = await fetch(tokensUrl);
4715
+ var tokensResponse = await fetchWithTimeout(tokensUrl, 30000);
4663
4716
  if (!tokensResponse.ok) {
4664
4717
  throw new Error('Failed to fetch tokens.txt: ' + tokensResponse.status + ' ' + tokensResponse.statusText);
4665
4718
  }
@@ -4682,7 +4735,7 @@ async function loadModel(modelUrl, tokensUrl, isIOSDevice, lang, textNorm) {
4682
4735
  session = await ort.InferenceSession.create(modelUrl, sessionOptions);
4683
4736
  } else {
4684
4737
  // Desktop: fetch ArrayBuffer for potential caching
4685
- var modelResponse = await fetch(modelUrl);
4738
+ var modelResponse = await fetchWithTimeout(modelUrl, 120000);
4686
4739
  if (!modelResponse.ok) {
4687
4740
  throw new Error('Failed to fetch model: ' + modelResponse.status + ' ' + modelResponse.statusText);
4688
4741
  }
@@ -5193,6 +5246,12 @@ var WORKER_SCRIPT2 = `
5193
5246
 
5194
5247
  var ort = null;
5195
5248
 
5249
+ function fetchWithTimeout(url, timeoutMs) {
5250
+ var controller = new AbortController();
5251
+ var timer = setTimeout(function() { controller.abort(); }, timeoutMs);
5252
+ return fetch(url, { signal: controller.signal }).finally(function() { clearTimeout(timer); });
5253
+ }
5254
+
5196
5255
  // SenseVoice state
5197
5256
  var svSession = null;
5198
5257
  var svTokenMap = null;
@@ -5507,7 +5566,7 @@ function symmetrizeBlendshapes(frame) {
5507
5566
  async function loadOrt(wasmPaths, isIOSDevice) {
5508
5567
  if (ort) return;
5509
5568
  var ortUrl = wasmPaths + 'ort.wasm.min.js';
5510
- var response = await fetch(ortUrl);
5569
+ var response = await fetchWithTimeout(ortUrl, 30000);
5511
5570
  var scriptText = await response.text();
5512
5571
  var blob = new Blob([scriptText], { type: 'application/javascript' });
5513
5572
  var blobUrl = URL.createObjectURL(blob);
@@ -5525,7 +5584,7 @@ async function loadOrt(wasmPaths, isIOSDevice) {
5525
5584
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
5526
5585
 
5527
5586
  async function svLoad(msg) {
5528
- var tokensResponse = await fetch(msg.tokensUrl);
5587
+ var tokensResponse = await fetchWithTimeout(msg.tokensUrl, 30000);
5529
5588
  if (!tokensResponse.ok) throw new Error('Failed to fetch tokens.txt: ' + tokensResponse.status);
5530
5589
  var tokensText = await tokensResponse.text();
5531
5590
  svTokenMap = parseTokensFile(tokensText);
@@ -5536,7 +5595,7 @@ async function svLoad(msg) {
5536
5595
  if (msg.isIOS) {
5537
5596
  svSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);
5538
5597
  } else {
5539
- var modelResponse = await fetch(msg.modelUrl);
5598
+ var modelResponse = await fetchWithTimeout(msg.modelUrl, 120000);
5540
5599
  if (!modelResponse.ok) throw new Error('Failed to fetch model: ' + modelResponse.status);
5541
5600
  var modelBuffer = await modelResponse.arrayBuffer();
5542
5601
  svSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), sessionOptions);
@@ -5611,11 +5670,11 @@ async function cpuLoad(msg) {
5611
5670
  }
5612
5671
  cpuSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);
5613
5672
  } else {
5614
- var graphResponse = await fetch(msg.modelUrl);
5673
+ var graphResponse = await fetchWithTimeout(msg.modelUrl, 120000);
5615
5674
  if (!graphResponse.ok) throw new Error('Failed to fetch model graph: ' + graphResponse.status);
5616
5675
  var graphBuffer = await graphResponse.arrayBuffer();
5617
5676
  if (msg.externalDataUrl && dataFilename) {
5618
- var dataResponse = await fetch(msg.externalDataUrl);
5677
+ var dataResponse = await fetchWithTimeout(msg.externalDataUrl, 120000);
5619
5678
  if (!dataResponse.ok) throw new Error('Failed to fetch external data: ' + dataResponse.status);
5620
5679
  var dataBuffer = await dataResponse.arrayBuffer();
5621
5680
  sessionOptions.externalData = [{ path: dataFilename, data: new Uint8Array(dataBuffer) }];
@@ -5667,7 +5726,7 @@ async function vadLoad(msg) {
5667
5726
  vadChunkSize = vadSampleRate === 16000 ? 512 : 256;
5668
5727
  vadContextSize = vadSampleRate === 16000 ? 64 : 32;
5669
5728
 
5670
- var response = await fetch(msg.modelUrl);
5729
+ var response = await fetchWithTimeout(msg.modelUrl, 60000);
5671
5730
  if (!response.ok) throw new Error('Failed to fetch VAD model: ' + response.status);
5672
5731
  var modelBuffer = await response.arrayBuffer();
5673
5732
  vadSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), {
@@ -6515,7 +6574,11 @@ var _Wav2ArkitCpuInference = class _Wav2ArkitCpuInference {
6515
6574
  // URL string — ORT fetches directly into WASM
6516
6575
  }];
6517
6576
  }
6518
- this.session = await this.ort.InferenceSession.create(modelUrl, sessionOptions);
6577
+ this.session = await withTimeout(
6578
+ this.ort.InferenceSession.create(modelUrl, sessionOptions),
6579
+ 18e4,
6580
+ "Wav2ArkitCpu InferenceSession.create (iOS URL pass-through)"
6581
+ );
6519
6582
  } else {
6520
6583
  const cache = getModelCache();
6521
6584
  const isCached = await cache.has(modelUrl);
@@ -6783,6 +6846,12 @@ var WORKER_SCRIPT3 = `
6783
6846
  var ort = null;
6784
6847
  var session = null;
6785
6848
 
6849
+ function fetchWithTimeout(url, timeoutMs) {
6850
+ var controller = new AbortController();
6851
+ var timer = setTimeout(function() { controller.abort(); }, timeoutMs);
6852
+ return fetch(url, { signal: controller.signal }).finally(function() { clearTimeout(timer); });
6853
+ }
6854
+
6786
6855
  // Precomputed symmetric index pairs from LAM_BLENDSHAPES alphabetical ordering
6787
6856
  // Used to average left/right blendshape pairs for symmetrized output
6788
6857
  const SYMMETRIC_INDEX_PAIRS = [
@@ -6832,7 +6901,7 @@ async function loadOrt(wasmPaths) {
6832
6901
  const ortUrl = wasmPaths + 'ort.wasm.min.js';
6833
6902
 
6834
6903
  // Load the script by fetching and executing it
6835
- const response = await fetch(ortUrl);
6904
+ const response = await fetchWithTimeout(ortUrl, 30000);
6836
6905
  const scriptText = await response.text();
6837
6906
 
6838
6907
  // Create a blob URL for the script
@@ -6874,7 +6943,7 @@ async function loadModel(modelUrl, externalDataUrl, isIOS) {
6874
6943
  session = await ort.InferenceSession.create(modelUrl, sessionOptions);
6875
6944
  } else {
6876
6945
  // Desktop: fetch model graph as ArrayBuffer
6877
- const graphResponse = await fetch(modelUrl);
6946
+ const graphResponse = await fetchWithTimeout(modelUrl, 120000);
6878
6947
  if (!graphResponse.ok) {
6879
6948
  throw new Error('Failed to fetch model graph: ' + graphResponse.status + ' ' + graphResponse.statusText);
6880
6949
  }
@@ -6882,7 +6951,7 @@ async function loadModel(modelUrl, externalDataUrl, isIOS) {
6882
6951
 
6883
6952
  // Fetch external data file if present
6884
6953
  if (externalDataUrl && dataFilename) {
6885
- const dataResponse = await fetch(externalDataUrl);
6954
+ const dataResponse = await fetchWithTimeout(externalDataUrl, 120000);
6886
6955
  if (!dataResponse.ok) {
6887
6956
  throw new Error('Failed to fetch external data: ' + dataResponse.status + ' ' + dataResponse.statusText);
6888
6957
  }
@@ -8017,6 +8086,13 @@ var WORKER_SCRIPT4 = `
8017
8086
 
8018
8087
  var ort = null;
8019
8088
  var session = null;
8089
+
8090
+ function fetchWithTimeout(url, timeoutMs) {
8091
+ var controller = new AbortController();
8092
+ var timer = setTimeout(function() { controller.abort(); }, timeoutMs);
8093
+ return fetch(url, { signal: controller.signal }).finally(function() { clearTimeout(timer); });
8094
+ }
8095
+
8020
8096
  var sampleRate = 16000;
8021
8097
  var chunkSize = 512;
8022
8098
  var contextSize = 64;
@@ -8032,7 +8108,7 @@ async function loadOrt(wasmPaths) {
8032
8108
  const ortUrl = wasmPaths + 'ort.wasm.min.js';
8033
8109
 
8034
8110
  // Load the script by fetching and executing it
8035
- const response = await fetch(ortUrl);
8111
+ const response = await fetchWithTimeout(ortUrl, 30000);
8036
8112
  const scriptText = await response.text();
8037
8113
 
8038
8114
  // Create a blob URL for the script
@@ -8062,7 +8138,7 @@ async function loadModel(modelUrl, sr) {
8062
8138
  contextSize = sr === 16000 ? 64 : 32;
8063
8139
 
8064
8140
  // Fetch model data
8065
- const response = await fetch(modelUrl);
8141
+ const response = await fetchWithTimeout(modelUrl, 60000);
8066
8142
  if (!response.ok) {
8067
8143
  throw new Error('Failed to fetch model: ' + response.status + ' ' + response.statusText);
8068
8144
  }