web-sdk-pp-detection 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -228,6 +228,12 @@ interface PPDetectionFallback {
228
228
  readonly variantId: string;
229
229
  }
230
230
  interface PPDetectionRuntimeInfo {
231
+ readonly runtimeVersion?: string | null;
232
+ readonly environment?: Readonly<{
233
+ userAgent: string | null;
234
+ platform: string | null;
235
+ capturedAt: string;
236
+ }>;
231
237
  readonly requestedBackend: BackendPreference;
232
238
  readonly backend: Backend;
233
239
  readonly precision: Precision;
@@ -252,6 +258,7 @@ interface PPDetectionModelSourceInfo {
252
258
  readonly sha256: string;
253
259
  }
254
260
  interface PPDetectionLoadTimings {
261
+ readonly modelSource?: "network" | "cache" | "memory";
255
262
  readonly modelDownloadMs?: number;
256
263
  readonly modelCacheReadMs?: number;
257
264
  readonly integrityMs?: number;
@@ -418,6 +425,9 @@ interface OrtInferenceSession {
418
425
  interface OrtModule {
419
426
  readonly env: {
420
427
  readonly wasm?: Record<string, unknown>;
428
+ readonly versions?: {
429
+ readonly web?: string;
430
+ };
421
431
  };
422
432
  readonly InferenceSession: {
423
433
  create(model: ArrayBuffer, options: Record<string, unknown>): Promise<OrtInferenceSession>;
@@ -425,6 +435,7 @@ interface OrtModule {
425
435
  readonly Tensor?: new (type: string, data: unknown, dims: readonly number[]) => unknown;
426
436
  }
427
437
  interface OrtSessionHandle {
438
+ readonly runtimeVersion?: string | null;
428
439
  readonly plan: ExecutionPlan;
429
440
  readonly sessionMs: number;
430
441
  run(feeds: Record<string, unknown>, options?: {
@@ -522,11 +533,16 @@ interface CacheEstimate {
522
533
  readonly entries: number;
523
534
  }
524
535
  interface ModelCache {
536
+ readonly scope?: object;
525
537
  get(key: string): Promise<ArrayBuffer | undefined>;
526
538
  put(key: string, bytes: ArrayBuffer): Promise<void>;
527
539
  clearCurrent(key: string): Promise<void>;
528
540
  clearAll(): Promise<void>;
529
541
  estimate(): Promise<CacheEstimate>;
542
+ list?(): Promise<readonly {
543
+ key: string;
544
+ bytes: number;
545
+ }[]>;
530
546
  close?(): Promise<void> | void;
531
547
  }
532
548
 
@@ -559,6 +575,7 @@ interface LoadedManagedModel {
559
575
  declare class ModelManager {
560
576
  private readonly fetcher?;
561
577
  private readonly cache;
578
+ private readonly coordinator;
562
579
  private readonly lifecycle;
563
580
  private readonly activeLoads;
564
581
  private currentKey?;
@@ -569,8 +586,8 @@ declare class ModelManager {
569
586
  load(options: LoadManagedModelOptions): Promise<LoadedManagedModel>;
570
587
  private loadActive;
571
588
  estimate(): Promise<CacheEstimate>;
572
- getCacheEstimate(): Promise<CacheEstimate>;
573
- clearCurrentModelCache(): Promise<void>;
589
+ getCacheEstimate(model?: ModelIdentity): Promise<CacheEstimate>;
590
+ clearCurrentModelCache(model?: ModelIdentity): Promise<void>;
574
591
  clearAllCache(): Promise<void>;
575
592
  dispose(): Promise<void>;
576
593
  }
@@ -583,6 +600,10 @@ declare class MemoryModelCache implements ModelCache {
583
600
  clearAll(): Promise<void>;
584
601
  estimate(): Promise<CacheEstimate>;
585
602
  close(): Promise<void>;
603
+ list(): Promise<readonly {
604
+ key: string;
605
+ bytes: number;
606
+ }[]>;
586
607
  }
587
608
 
588
609
  interface IndexedDBModelCacheOptions {
@@ -590,6 +611,7 @@ interface IndexedDBModelCacheOptions {
590
611
  readonly databaseName?: string;
591
612
  }
592
613
  declare class IndexedDBModelCache implements ModelCache {
614
+ readonly scope: object;
593
615
  private readonly factory;
594
616
  private readonly databaseName;
595
617
  private database?;
@@ -600,6 +622,10 @@ declare class IndexedDBModelCache implements ModelCache {
600
622
  clearAll(): Promise<void>;
601
623
  estimate(): Promise<CacheEstimate>;
602
624
  close(): Promise<void>;
625
+ list(): Promise<readonly {
626
+ key: string;
627
+ bytes: number;
628
+ }[]>;
603
629
  private open;
604
630
  private request;
605
631
  }
@@ -607,7 +633,7 @@ declare class IndexedDBModelCache implements ModelCache {
607
633
  declare global {
608
634
  var __PPDETECTION_SCRIPT_URL__: string | undefined;
609
635
  }
610
- declare const CURRENT_SDK_VERSION = "0.1.1";
636
+ declare const CURRENT_SDK_VERSION = "0.2.0";
611
637
 
612
638
  declare function probePPDetectionCapabilities(options?: CapabilityProbeOptions): DetectionCapabilities;
613
639
 
package/dist/index.js CHANGED
@@ -622,7 +622,11 @@ var PPDetectionDetectorImplementation = class {
622
622
  original: { width: decoded.width, height: decoded.height }
623
623
  },
624
624
  model: this.model,
625
- runtime: this.runtime,
625
+ runtime: {
626
+ ...this.runtime,
627
+ fallbacks: this.runtime.fallbacks.map((fallback) => ({ ...fallback })),
628
+ ...this.runtime.environment ? { environment: { ...this.runtime.environment } } : {}
629
+ },
626
630
  timings: {
627
631
  decodeMs: decoded.decodeMs,
628
632
  preprocessMs,
@@ -1115,8 +1119,84 @@ function adaptModelManifest(manifest) {
1115
1119
  });
1116
1120
  }
1117
1121
 
1122
+ // src/cache/coordination.ts
1123
+ var CacheCoordinator = class {
1124
+ caches = /* @__PURE__ */ new Map();
1125
+ generation = 0;
1126
+ keys = /* @__PURE__ */ new Map();
1127
+ pending = Promise.resolve();
1128
+ guard(key) {
1129
+ const generation = this.generation;
1130
+ const keyGeneration = this.keys.get(key) ?? 0;
1131
+ this.keys.set(key, keyGeneration);
1132
+ return () => generation === this.generation && keyGeneration === (this.keys.get(key) ?? 0);
1133
+ }
1134
+ run(operation) {
1135
+ const pending = this.pending.then(operation);
1136
+ this.pending = pending.catch(() => void 0);
1137
+ return pending;
1138
+ }
1139
+ clear(key, matches) {
1140
+ if (matches) {
1141
+ for (const [candidate, generation] of this.keys) {
1142
+ if (matches(candidate)) this.keys.set(candidate, generation + 1);
1143
+ }
1144
+ } else if (key === void 0) this.generation += 1;
1145
+ else this.keys.set(key, (this.keys.get(key) ?? 0) + 1);
1146
+ return this.run(async () => {
1147
+ const outcomes = await Promise.allSettled(
1148
+ [...this.caches.keys()].map(async (cache) => {
1149
+ if (matches && cache.list) {
1150
+ for (const entry of await cache.list()) {
1151
+ if (matches(entry.key)) await cache.clearCurrent(entry.key);
1152
+ }
1153
+ } else if (key === void 0) await cache.clearAll();
1154
+ else await cache.clearCurrent(key);
1155
+ })
1156
+ );
1157
+ const failure = outcomes.find((outcome) => outcome.status === "rejected");
1158
+ if (failure?.status === "rejected") throw failure.reason;
1159
+ });
1160
+ }
1161
+ unregister(cache) {
1162
+ const count = this.caches.get(cache) ?? 0;
1163
+ if (count > 1) {
1164
+ this.caches.set(cache, count - 1);
1165
+ return false;
1166
+ }
1167
+ this.caches.delete(cache);
1168
+ return true;
1169
+ }
1170
+ };
1171
+ var coordinators = /* @__PURE__ */ new WeakMap();
1172
+ function coordinateCache(cache) {
1173
+ const scope = cache.scope ?? cache;
1174
+ let coordinator = coordinators.get(scope);
1175
+ if (!coordinator) {
1176
+ coordinator = new CacheCoordinator();
1177
+ coordinators.set(scope, coordinator);
1178
+ }
1179
+ coordinator.caches.set(cache, (coordinator.caches.get(cache) ?? 0) + 1);
1180
+ return coordinator;
1181
+ }
1182
+ var databaseScopes = /* @__PURE__ */ new WeakMap();
1183
+ function databaseScope(factory, name) {
1184
+ let scopes = databaseScopes.get(factory);
1185
+ if (!scopes) {
1186
+ scopes = /* @__PURE__ */ new Map();
1187
+ databaseScopes.set(factory, scopes);
1188
+ }
1189
+ let scope = scopes.get(name);
1190
+ if (!scope) {
1191
+ scope = {};
1192
+ scopes.set(name, scope);
1193
+ }
1194
+ return scope;
1195
+ }
1196
+
1118
1197
  // src/cache/indexeddb-cache.ts
1119
1198
  var IndexedDBModelCache = class {
1199
+ scope;
1120
1200
  factory;
1121
1201
  databaseName;
1122
1202
  database;
@@ -1125,6 +1205,7 @@ var IndexedDBModelCache = class {
1125
1205
  if (!factory) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 IndexedDB");
1126
1206
  this.factory = factory;
1127
1207
  this.databaseName = options.databaseName ?? "web-sdk-pp-detection-models-v1";
1208
+ this.scope = databaseScope(factory, this.databaseName);
1128
1209
  }
1129
1210
  async get(key) {
1130
1211
  const record3 = await this.request(
@@ -1178,6 +1259,24 @@ var IndexedDBModelCache = class {
1178
1259
  (await this.database).close();
1179
1260
  this.database = void 0;
1180
1261
  }
1262
+ async list() {
1263
+ const database = await this.open();
1264
+ return await new Promise((resolve, reject) => {
1265
+ const transaction = database.transaction("models", "readonly");
1266
+ const request = transaction.objectStore("models").openCursor();
1267
+ const entries = [];
1268
+ request.onerror = () => reject(request.error ?? new Error("\u8BFB\u53D6\u7F13\u5B58\u5BB9\u91CF\u5931\u8D25"));
1269
+ request.onsuccess = () => {
1270
+ const cursor = request.result;
1271
+ if (!cursor) return;
1272
+ const record3 = cursor.value;
1273
+ entries.push({ key: record3.key, bytes: record3.size });
1274
+ cursor.continue();
1275
+ };
1276
+ transaction.oncomplete = () => resolve(entries);
1277
+ transaction.onabort = () => reject(transaction.error ?? new Error("\u7F13\u5B58\u5BB9\u91CF\u4E8B\u52A1\u4E2D\u6B62"));
1278
+ });
1279
+ }
1181
1280
  open() {
1182
1281
  if (this.database) return this.database;
1183
1282
  this.database = new Promise((resolve, reject) => {
@@ -1247,6 +1346,11 @@ var MemoryModelCache = class {
1247
1346
  this.entries.clear();
1248
1347
  return Promise.resolve();
1249
1348
  }
1349
+ list() {
1350
+ return Promise.resolve(
1351
+ [...this.entries].map(([key, bytes]) => ({ key, bytes: bytes.byteLength }))
1352
+ );
1353
+ }
1250
1354
  };
1251
1355
 
1252
1356
  // src/cache/model-cache.ts
@@ -1254,9 +1358,11 @@ var TieredModelCache = class {
1254
1358
  constructor(memory, persistent) {
1255
1359
  this.memory = memory;
1256
1360
  this.persistent = persistent;
1361
+ this.scope = persistent?.scope ?? persistent ?? memory;
1257
1362
  }
1258
1363
  memory;
1259
1364
  persistent;
1365
+ scope;
1260
1366
  async get(key) {
1261
1367
  const memoryValue = await this.memory.get(key);
1262
1368
  if (memoryValue) return memoryValue;
@@ -1279,6 +1385,13 @@ var TieredModelCache = class {
1279
1385
  estimate() {
1280
1386
  return this.persistent?.estimate() ?? this.memory.estimate();
1281
1387
  }
1388
+ async list() {
1389
+ const entries = /* @__PURE__ */ new Map();
1390
+ for (const cache of [this.memory, this.persistent]) {
1391
+ for (const entry of await cache?.list?.() ?? []) entries.set(entry.key, entry);
1392
+ }
1393
+ return [...entries.values()];
1394
+ }
1282
1395
  async close() {
1283
1396
  await this.memory.close?.();
1284
1397
  await this.persistent?.close?.();
@@ -1488,6 +1601,14 @@ function resolveModelAsset(selection, manifest) {
1488
1601
 
1489
1602
  // src/model/model-manager.ts
1490
1603
  var SDK_CACHE_NAMESPACE = "web-sdk-pp-detection:cache-v1";
1604
+ function matchesModel(key, model) {
1605
+ try {
1606
+ const parts = JSON.parse(key);
1607
+ return Array.isArray(parts) && parts[0] === SDK_CACHE_NAMESPACE && (model === void 0 || parts[1] === model.id && parts[2] === model.version);
1608
+ } catch {
1609
+ return false;
1610
+ }
1611
+ }
1491
1612
  function now2() {
1492
1613
  return globalThis.performance?.now() ?? Date.now();
1493
1614
  }
@@ -1511,6 +1632,7 @@ function throwIfAborted4(signal) {
1511
1632
  var ModelManager = class {
1512
1633
  fetcher;
1513
1634
  cache;
1635
+ coordinator;
1514
1636
  lifecycle = new AbortController();
1515
1637
  activeLoads = /* @__PURE__ */ new Set();
1516
1638
  currentKey;
@@ -1519,6 +1641,7 @@ var ModelManager = class {
1519
1641
  constructor(options = {}) {
1520
1642
  this.fetcher = options.fetcher;
1521
1643
  this.cache = createCache(options.cache);
1644
+ this.coordinator = coordinateCache(this.cache);
1522
1645
  }
1523
1646
  cacheKey(variant3, source2, model = { id: "unknown", version: "unknown" }) {
1524
1647
  return JSON.stringify([
@@ -1557,16 +1680,24 @@ var ModelManager = class {
1557
1680
  const variant3 = resolveModelVariant(manifest, options.variantId);
1558
1681
  const sourceKind2 = options.sourceKind ?? "auto";
1559
1682
  const sources = resolveModelSources(variant3, sourceKind2);
1683
+ const guards = new Map(
1684
+ sources.map((source2) => {
1685
+ const key = this.cacheKey(variant3, source2, manifest.model);
1686
+ return [key, this.coordinator.guard(key)];
1687
+ })
1688
+ );
1560
1689
  const failures = [];
1561
1690
  let lastError;
1562
1691
  for (const source2 of sources) {
1563
1692
  throwIfAborted4(options.signal);
1564
1693
  const asset = { model: manifest.model, variant: variant3, source: source2 };
1565
1694
  const cacheKey = this.cacheKey(variant3, source2, manifest.model);
1695
+ this.currentKey = cacheKey;
1696
+ const canMutate = guards.get(cacheKey);
1566
1697
  const cacheStarted = now2();
1567
1698
  let cached;
1568
1699
  try {
1569
- cached = await this.cache.get(cacheKey);
1700
+ cached = await this.coordinator.run(() => this.cache.get(cacheKey));
1570
1701
  } catch (error) {
1571
1702
  if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1572
1703
  throwIfAborted4(options.signal);
@@ -1577,7 +1708,6 @@ var ModelManager = class {
1577
1708
  const integrityStarted = now2();
1578
1709
  try {
1579
1710
  await verifyModelIntegrity(cached, source2, options.signal);
1580
- this.currentKey = cacheKey;
1581
1711
  return {
1582
1712
  bytes: cached,
1583
1713
  manifest,
@@ -1591,7 +1721,9 @@ var ModelManager = class {
1591
1721
  } catch (error) {
1592
1722
  if (error instanceof PPDetectionError && error.code === "ABORTED") throw error;
1593
1723
  try {
1594
- await this.cache.clearCurrent(cacheKey);
1724
+ await this.coordinator.run(async () => {
1725
+ if (canMutate()) await this.cache.clearCurrent(cacheKey);
1726
+ });
1595
1727
  } catch (clearError) {
1596
1728
  if (clearError instanceof PPDetectionError && clearError.code === "ABORTED")
1597
1729
  throw clearError;
@@ -1627,11 +1759,12 @@ var ModelManager = class {
1627
1759
  }
1628
1760
  throwIfAborted4(options.signal);
1629
1761
  try {
1630
- await this.cache.put(cacheKey, loaded.bytes);
1762
+ await this.coordinator.run(async () => {
1763
+ if (canMutate()) await this.cache.put(cacheKey, loaded.bytes);
1764
+ });
1631
1765
  } catch {
1632
1766
  }
1633
1767
  throwIfAborted4(options.signal);
1634
- this.currentKey = cacheKey;
1635
1768
  return {
1636
1769
  bytes: loaded.bytes,
1637
1770
  manifest,
@@ -1656,16 +1789,44 @@ var ModelManager = class {
1656
1789
  estimate() {
1657
1790
  return this.getCacheEstimate();
1658
1791
  }
1659
- getCacheEstimate() {
1660
- return this.cache.estimate();
1792
+ getCacheEstimate(model) {
1793
+ return this.coordinator.run(async () => {
1794
+ if (!this.cache.list) {
1795
+ if (model)
1796
+ throw new PPDetectionError(
1797
+ "CAPABILITY_UNSUPPORTED",
1798
+ "\u81EA\u5B9A\u4E49\u7F13\u5B58\u9700\u5B9E\u73B0 list \u624D\u80FD\u6309\u6A21\u578B\u4F30\u7B97"
1799
+ );
1800
+ return this.cache.estimate();
1801
+ }
1802
+ const unique = /* @__PURE__ */ new Map();
1803
+ for (const cache of this.coordinator.caches.keys()) {
1804
+ for (const entry of await cache.list?.() ?? []) unique.set(entry.key, entry);
1805
+ }
1806
+ const entries = [...unique.values()].filter((entry) => matchesModel(entry.key, model));
1807
+ return {
1808
+ bytes: entries.reduce((sum, entry) => sum + entry.bytes, 0),
1809
+ entries: entries.length
1810
+ };
1811
+ });
1661
1812
  }
1662
- async clearCurrentModelCache() {
1813
+ async clearCurrentModelCache(model) {
1814
+ if (model) {
1815
+ if (!model.id || !model.version)
1816
+ throw new PPDetectionError("INVALID_MANIFEST", "\u7F13\u5B58\u6A21\u578B\u8EAB\u4EFD\u5FC5\u987B\u5305\u542B id \u548C version");
1817
+ if (!this.cache.list)
1818
+ throw new PPDetectionError(
1819
+ "CAPABILITY_UNSUPPORTED",
1820
+ "\u81EA\u5B9A\u4E49\u7F13\u5B58\u9700\u5B9E\u73B0 list \u624D\u80FD\u6309\u6A21\u578B\u6E05\u7406"
1821
+ );
1822
+ await this.coordinator.clear(void 0, (key) => matchesModel(key, model));
1823
+ return;
1824
+ }
1663
1825
  if (!this.currentKey) return;
1664
- await this.cache.clearCurrent(this.currentKey);
1826
+ await this.coordinator.clear(this.currentKey);
1665
1827
  }
1666
1828
  async clearAllCache() {
1667
- await this.cache.clearAll();
1668
- this.currentKey = void 0;
1829
+ await this.coordinator.clear(void 0, (key) => matchesModel(key));
1669
1830
  }
1670
1831
  async dispose() {
1671
1832
  if (this.disposePromise) return await this.disposePromise;
@@ -1674,7 +1835,7 @@ var ModelManager = class {
1674
1835
  this.disposePromise = (async () => {
1675
1836
  await Promise.all([...this.activeLoads]);
1676
1837
  this.currentKey = void 0;
1677
- await this.cache.close?.();
1838
+ if (this.coordinator.unregister(this.cache)) await this.cache.close?.();
1678
1839
  })();
1679
1840
  await this.disposePromise;
1680
1841
  }
@@ -1688,7 +1849,7 @@ function mapError(error, phase) {
1688
1849
  if (error instanceof PPDetectionError) return error;
1689
1850
  const message = error instanceof Error ? error.message : String(error);
1690
1851
  const details = { phase, causeMessage: message };
1691
- if (/abort|cancel/i.test(message))
1852
+ if (error instanceof Error && error.name === "AbortError")
1692
1853
  return new PPDetectionError("ABORTED", "\u63A8\u7406\u5DF2\u53D6\u6D88", details, { cause: error });
1693
1854
  if (/memory|out.of.memory|allocation/i.test(message))
1694
1855
  return new PPDetectionError("OUT_OF_MEMORY", "\u8FD0\u884C\u65F6\u5185\u5B58\u4E0D\u8DB3", details, { cause: error });
@@ -1733,6 +1894,7 @@ async function createOrtSession(modelBytes, plan, options = {}) {
1733
1894
  return {
1734
1895
  plan,
1735
1896
  sessionMs,
1897
+ runtimeVersion: ort.env.versions?.web ?? null,
1736
1898
  async run(feeds, runOptions = {}) {
1737
1899
  if (disposed) throw new PPDetectionError("DISPOSED", "\u4F1A\u8BDD\u5DF2\u91CA\u653E", { phase: "run" });
1738
1900
  if (runOptions.signal?.aborted)
@@ -2051,7 +2213,7 @@ var WorkerBridge = class {
2051
2213
  };
2052
2214
 
2053
2215
  // src/index.ts
2054
- var CURRENT_SDK_VERSION = "0.1.1";
2216
+ var CURRENT_SDK_VERSION = "0.2.0";
2055
2217
  function probePPDetectionCapabilities(options = {}) {
2056
2218
  return probeCapabilities(options);
2057
2219
  }
@@ -2146,6 +2308,7 @@ function workerUrl() {
2146
2308
  return new URL("./inference.worker.js", import.meta.url);
2147
2309
  }
2148
2310
  async function createPPDetection(options = {}) {
2311
+ const loadStartedAt = now4();
2149
2312
  if (options.model === void 0 && options.manifest === void 0)
2150
2313
  throw new PPDetectionError("INVALID_MANIFEST", "\u521B\u5EFA PPDetection \u5B9E\u4F8B\u9700\u8981 manifest \u6216 model");
2151
2314
  const capabilities = probeCapabilities();
@@ -2178,7 +2341,6 @@ async function createPPDetection(options = {}) {
2178
2341
  });
2179
2342
  let executor;
2180
2343
  try {
2181
- const loadStartedAt = now4();
2182
2344
  options.onProgress?.({ phase: "model", status: "start" });
2183
2345
  let modelBytes;
2184
2346
  let actualSource;
@@ -2190,10 +2352,17 @@ async function createPPDetection(options = {}) {
2190
2352
  throw new PPDetectionError("MODEL_SOURCE_UNAVAILABLE", "\u6A21\u578B\u53D8\u4F53\u6CA1\u6709\u53EF\u7528\u6765\u6E90", {
2191
2353
  variantId: variant3.id
2192
2354
  });
2355
+ const integrityStartedAt = now4();
2193
2356
  await verifyModelIntegrity(memoryData, source2, options.signal);
2357
+ const integrityMs = now4() - integrityStartedAt;
2194
2358
  modelBytes = memoryData;
2195
2359
  actualSource = source2;
2196
- loadTimings = { sessionMs: 0, totalMs: now4() - loadStartedAt, integrityMs: 0 };
2360
+ loadTimings = {
2361
+ sessionMs: 0,
2362
+ totalMs: now4() - loadStartedAt,
2363
+ integrityMs,
2364
+ modelSource: "memory"
2365
+ };
2197
2366
  } else {
2198
2367
  const loaded = await modelManager.load({
2199
2368
  manifest: runtimeManifest,
@@ -2205,20 +2374,26 @@ async function createPPDetection(options = {}) {
2205
2374
  modelBytes = loaded.bytes;
2206
2375
  variant3 = loaded.variant;
2207
2376
  actualSource = loaded.source;
2208
- loadTimings = { ...loaded.timings, sessionMs: 0, totalMs: now4() - loadStartedAt };
2377
+ loadTimings = {
2378
+ ...loaded.timings,
2379
+ sessionMs: 0,
2380
+ totalMs: now4() - loadStartedAt,
2381
+ modelSource: loaded.fromCache ? "cache" : "network"
2382
+ };
2209
2383
  }
2210
2384
  options.onProgress?.({ phase: "model", status: "complete" });
2211
2385
  const fallbacks = [];
2212
2386
  const createExecutorForPlan = async (candidatePlan) => {
2213
2387
  options.onProgress?.({ phase: "session", status: "start" });
2214
2388
  let bridge;
2389
+ const sessionStartedAt = now4();
2215
2390
  try {
2216
2391
  if (candidatePlan.executionMode === "worker") {
2217
2392
  if (typeof Worker !== "function")
2218
2393
  throw new PPDetectionError("CAPABILITY_UNSUPPORTED", "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Worker");
2219
2394
  const worker = new Worker(workerUrl(), { type: "module" });
2220
2395
  bridge = new WorkerBridge(worker);
2221
- await bridge.load(modelBytes.slice(0), candidatePlan, {
2396
+ const metadata = await bridge.load(modelBytes.slice(0), candidatePlan, {
2222
2397
  onProgress: (event) => options.onProgress?.({
2223
2398
  phase: "session",
2224
2399
  status: event.status
@@ -2228,6 +2403,7 @@ async function createPPDetection(options = {}) {
2228
2403
  numThreads: options.ort?.wasm?.numThreads
2229
2404
  }
2230
2405
  });
2406
+ runtimeVersion = typeof metadata === "object" && metadata !== null && "runtimeVersion" in metadata && typeof metadata.runtimeVersion === "string" ? metadata.runtimeVersion : null;
2231
2407
  const activeBridge = bridge;
2232
2408
  options.onProgress?.({ phase: "session", status: "complete" });
2233
2409
  return {
@@ -2245,7 +2421,7 @@ async function createPPDetection(options = {}) {
2245
2421
  wasmPaths: options.ort?.wasm?.paths,
2246
2422
  numThreads: options.ort?.wasm?.numThreads
2247
2423
  });
2248
- sessionMs = session.sessionMs;
2424
+ runtimeVersion = session.runtimeVersion ?? null;
2249
2425
  options.onProgress?.({ phase: "session", status: "complete" });
2250
2426
  return {
2251
2427
  run(input, signal) {
@@ -2269,10 +2445,13 @@ async function createPPDetection(options = {}) {
2269
2445
  { phase: "create", causeMessage: message },
2270
2446
  { cause: error }
2271
2447
  );
2448
+ } finally {
2449
+ sessionMs += now4() - sessionStartedAt;
2272
2450
  }
2273
2451
  };
2274
2452
  let selectedPlan = plan;
2275
2453
  let sessionMs = 0;
2454
+ let runtimeVersion = null;
2276
2455
  let selectedCandidateIndex = -1;
2277
2456
  for (const [candidateIndex, candidate] of plan.candidates.entries()) {
2278
2457
  const candidatePlan = {
@@ -2314,6 +2493,12 @@ async function createPPDetection(options = {}) {
2314
2493
  }
2315
2494
  if (!executor) throw new PPDetectionError("SESSION_CREATE_FAILED", "\u65E0\u6CD5\u521B\u5EFA\u68C0\u6D4B Session");
2316
2495
  const runtime = {
2496
+ runtimeVersion,
2497
+ environment: {
2498
+ userAgent: globalThis.navigator?.userAgent ?? null,
2499
+ platform: globalThis.navigator?.platform ?? null,
2500
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
2501
+ },
2317
2502
  requestedBackend: options.backend ?? "auto",
2318
2503
  backend: selectedPlan.actualBackend,
2319
2504
  precision: selectedPlan.actualPrecision,
@@ -2324,8 +2509,9 @@ async function createPPDetection(options = {}) {
2324
2509
  let activeExecutor = executor;
2325
2510
  const fallbackExecutor = {
2326
2511
  async run(input, signal) {
2512
+ const attemptInput = runtime.mode === "worker" && options.allowFallback === true && selectedCandidateIndex < plan.candidates.length - 1 ? { ...input, data: input.data.slice() } : input;
2327
2513
  try {
2328
- return await activeExecutor.run(input, signal);
2514
+ return await activeExecutor.run(attemptInput, signal);
2329
2515
  } catch (error) {
2330
2516
  if (options.allowFallback !== true || selectedCandidateIndex < 0 || selectedCandidateIndex >= plan.candidates.length - 1) {
2331
2517
  throw error;
@@ -2350,6 +2536,7 @@ async function createPPDetection(options = {}) {
2350
2536
  runtime.backend = nextPlan.actualBackend;
2351
2537
  runtime.precision = nextPlan.actualPrecision;
2352
2538
  runtime.mode = nextPlan.executionMode;
2539
+ runtime.runtimeVersion = runtimeVersion;
2353
2540
  const fallback = {
2354
2541
  cause: mapped.cause ?? mapped,
2355
2542
  code: mapped.code,
@@ -2381,6 +2568,7 @@ async function createPPDetection(options = {}) {
2381
2568
  disposeResources: () => modelManager.dispose()
2382
2569
  });
2383
2570
  await detector.load({ signal: options.signal });
2571
+ loadTimings.totalMs = now4() - loadStartedAt;
2384
2572
  options.onProgress?.({ phase: "ready", status: "complete" });
2385
2573
  return detector;
2386
2574
  } catch (error) {
@@ -2397,8 +2585,11 @@ async function createPPDetection(options = {}) {
2397
2585
  }
2398
2586
  async function clearModelCache() {
2399
2587
  const manager = new ModelManager();
2400
- await manager.clearAllCache();
2401
- await manager.dispose();
2588
+ try {
2589
+ await manager.clearAllCache();
2590
+ } finally {
2591
+ await manager.dispose();
2592
+ }
2402
2593
  }
2403
2594
 
2404
2595
  export { CURRENT_SDK_VERSION, IndexedDBModelCache, MemoryModelCache, ModelManager, PPDetectionError, adaptModelManifest, clearModelCache, createOrtSession, createPPDetection, loadModelAsset, parseDetectionManifest, parseModelManifest, probeCapabilities, probePPDetectionCapabilities, resolveModelAsset, selectExecutionPlan };