web-sdk-pp-detection 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,15 +4,15 @@
4
4
 
5
5
  基于 ONNX Runtime Web 的浏览器端 PP-Detection 目标检测 SDK,支持 PC、移动端与各类 H5 页面。
6
6
 
7
- 当前 SDK 版本为 **0.3.1**,完整变更见[发布说明](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/release-0.3.1.md)。
7
+ 当前 SDK 版本为 **0.3.2**,完整变更见[发布说明](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/release-0.3.2.md)。
8
8
 
9
9
  ## 安装
10
10
 
11
11
  ```bash
12
- pnpm add web-sdk-pp-detection@0.3.1
12
+ pnpm add web-sdk-pp-detection@0.3.2
13
13
  ```
14
14
 
15
- 也可以使用 `npm install web-sdk-pp-detection@0.3.1`。
15
+ 也可以使用 `npm install web-sdk-pp-detection@0.3.2`。
16
16
 
17
17
  ## 快速开始
18
18
 
@@ -37,17 +37,17 @@ await detector.dispose();
37
37
 
38
38
  `classThresholds` 按 manifest 标签名称覆盖目标检测置信度过滤阈值,未配置的类别回退到全局 `threshold`。未知类别名称或超出 `0` 到 `1` 的值会被拒绝。
39
39
 
40
- 工厂必须显式传入 `model` 或 `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.1 FP32 stable manifest,npm 包不包含 ONNX 模型本体。自定义模型也应传入经过验证的 runtime manifest 或清单对象。
40
+ 工厂必须显式传入 `model` 或 `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.2 与 PP-YOLOE 0.1.1 的六个 stable 变体清单,npm 包不内置清单或 ONNX 模型本体。自定义模型也应传入经过验证的 runtime manifest 或清单对象。
41
41
 
42
- 仓库模型 manifest 默认来源为 Hugging Face;在线 Demo 的来源选择默认设为 ModelScope,两者分别属于模型清单和 Demo 配置。
42
+ 两份当前 manifest 和在线 Demo 均默认 ModelScope,并可显式选择 ModelScope 或 Hugging Face。
43
43
 
44
44
  模型初始化耗时可以通过 `detector.loadTimings` 查看。`totalMs` 是初始化总耗时,同时提供 `modelDownloadMs`(网络下载)、`modelCacheReadMs`(缓存读取)、`integrityMs`(SHA-256 完整性校验)和 `sessionMs`(ONNX Runtime Session 创建)。从 0.2.0 起,`loadTimings.modelSource` 区分 `network`、`cache`、`memory`,`runtime.runtimeVersion/environment` 记录实际 ORT 版本和环境快照。初始化总耗时包含 manifest 获取,详细语义见[性能文档](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/performance.md)。
45
45
 
46
46
  ## 运行后端与精度
47
47
 
48
48
  - `backend: "auto"` 优先使用 WebGPU;设置 `allowFallback: true` 后,WebGPU 会话或推理失败才会尝试 WASM(CPU)。也可手动指定 `"webgpu"` 或 `"wasm"`。
49
- - `precision: "auto"` 选择清单中可用的默认稳定精度;当前默认 PicoDet 仅验证 FP32,因此不会臆测切换到 FP16。已验证其他精度时可手动指定 `"fp16"`、`"int8"` 等。
50
- - 默认 PicoDet 1.0.1 FP32 已发布可下载的 stable ONNX 资产,并通过 Linux WASM 与 Windows NVIDIA WebGPU 七张 fixture 验证;FP16、INT8、INT4 FP8 仍需独立证据。
49
+ - `precision: "auto"` 选择清单默认的稳定精度,当前两份清单均为 FP32;`precision: "fp16"` 选择 FP16,`precision: "int8"` 选择 W8A32。
50
+ - 两模型的 FP32、FP16、W8A32 均为 stable。FP16/W8A32 证据仅覆盖 2026-09-12 桌面 WASM/WebGPU 固定 64 图三轮验证;小米 15 实测仅覆盖原 FP32。
51
51
  - 使用默认模型时,显式请求清单中未声明的组合会抛出 `CAPABILITY_UNSUPPORTED`,不会改写无效组合;自定义清单可在单独验证后声明其他组合。上游模型是 float32,不支持 FP64;FP32 约为 FP16 两倍大小并可能更慢、更占显存。
52
52
  - `detect` 可接收图片 Blob/File、Canvas/ImageData、`HTMLVideoElement` 或单帧 `VideoFrame`。摄像头和视频播放的权限、帧率控制由宿主页面负责;每次提交一帧后应等待 Promise 完成,并在停止媒体时调用 `dispose()`。
53
53
 
@@ -82,15 +82,15 @@ const detector = await createPPDetection({
82
82
 
83
83
  A browser-first PP-Detection object detection SDK powered by ONNX Runtime Web for desktop, mobile, and H5 pages.
84
84
 
85
- The current SDK version is **0.3.1**. See the [release notes](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/release-0.3.1.md) for the complete changes.
85
+ The current SDK version is **0.3.2**. See the [release notes](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/release-0.3.2.md) for the complete changes.
86
86
 
87
87
  ### Installation
88
88
 
89
89
  ```bash
90
- pnpm add web-sdk-pp-detection@0.3.1
90
+ pnpm add web-sdk-pp-detection@0.3.2
91
91
  ```
92
92
 
93
- `npm install web-sdk-pp-detection@0.3.1` is also supported.
93
+ `npm install web-sdk-pp-detection@0.3.2` is also supported.
94
94
 
95
95
  ### Quick start
96
96
 
@@ -115,17 +115,17 @@ await detector.dispose();
115
115
 
116
116
  `classThresholds` overrides object-detection confidence filtering for matching manifest label names and falls back to the global `threshold` for unspecified classes. Unknown class names and values outside `0` through `1` are rejected.
117
117
 
118
- The factory requires an explicit `model` or `manifest`; omitting both throws `INVALID_MANIFEST`, and `model` takes precedence when both are supplied. The repository provides the PicoDet 1.0.1 FP32 stable manifest; the npm package contains no ONNX model binary. Custom models also require a verified runtime or custom manifest.
118
+ 工厂必须显式传入 `model` `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.2 PP-YOLOE 0.1.1 的六个 stable 变体清单,npm 包不内置清单或 ONNX 模型本体。
119
119
 
120
- The repository model manifest defaults to Hugging Face. The live Demo independently configures its source selector to default to ModelScope.
120
+ 两份当前 manifest 和在线 Demo 均默认 ModelScope,并可显式选择 ModelScope Hugging Face。
121
121
 
122
122
  Detailed initialization timings are available through `detector.loadTimings`. `totalMs` is the full initialization duration. The additive fields `modelDownloadMs`, `modelCacheReadMs`, `integrityMs`, and `sessionMs` separate network download, cache reads, SHA-256 verification, and Session creation. From 0.2.0, `loadTimings.modelSource` distinguishes `network`, `cache`, and `memory`, while `runtime.runtimeVersion/environment` capture the actual ORT version and environment. Initialization totals include manifest retrieval; see [performance](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/performance.md) for the full semantics.
123
123
 
124
124
  ### Backend and precision
125
125
 
126
126
  - `backend: "auto"` prefers WebGPU; with `allowFallback: true`, a failed WebGPU session or inference attempts WASM (CPU). Use `"webgpu"` or `"wasm"` for an explicit choice.
127
- - `precision: "auto"` selects the available default stable precision from the manifest. The default PicoDet 1.0.1 validates FP32 only, so the SDK does not guess an FP16 switch. When another precision has been validated, select it explicitly with `"fp16"`, `"int8"`, and so on.
128
- - The default PicoDet 1.0.1 FP32 variant is a downloadable stable ONNX asset and has passed seven-fixture validation on Linux WASM and Windows NVIDIA WebGPU. FP16, INT8, INT4, and FP8 still require independent release-source and backend evidence.
127
+ - `precision: "auto"` 选择清单默认的稳定精度,当前两份清单均为 FP32;`precision: "fp16"` 选择 FP16,`precision: "int8"` 选择 W8A32。
128
+ - 两模型的 FP32、FP16、W8A32 均为 stable。FP16/W8A32 证据仅覆盖 2026-09-12 桌面 WASM/WebGPU 固定 64 图三轮验证;小米 15 实测仅覆盖原 FP32。
129
129
  - `detect` accepts image Blob/File, Canvas/ImageData, `HTMLVideoElement`, or a single `VideoFrame`. Hosts own camera/video permissions and frame pacing; await each frame Promise and call `dispose()` when media stops.
130
130
  - Explicit pairs absent from the default manifest throw `CAPABILITY_UNSUPPORTED` instead of rewriting an invalid pair. The upstream model is float32, not FP64; FP64 inference is unsupported. FP32 is about twice the size of FP16 and may be slower or use more GPU memory.
131
131
 
@@ -151,7 +151,11 @@ WeChat official-account pages and other H5/WebView integrations are supported. N
151
151
 
152
152
  ### PP-YOLOE 稳定模型(0.3.0 起)
153
153
 
154
- 0.3.0 配套提供 PP-YOLOE+ S 640 FP32 `0.1.0 stable` 清单,模型从 ModelScope Hugging Face 的固定 revision 下载,npm 包不包含权重。见[模型接入](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/v0.3.0/examples/ppyoloe-candidate/README.md)。`allowExperimental` 默认关闭,仅运行 labs 候选时显式开启;blocked 仍被拒绝。Demo 默认 PicoDet,两个模型均默认 ModelScope。
154
+ 当前 PP-YOLOE+ S 640 `0.1.1`,与 PicoDet 1.0.2 一样提供 FP32、FP16、W8A32 stable 变体。见[六变体示例](https://github.com/chenmohan123/web-sdk-PP-Detection/tree/main/examples/model-variants)。`allowExperimental` 默认关闭,仅运行旧 labs 候选时显式开启;blocked 仍被拒绝。Demo 默认 PicoDetModelScope、FP32
155
+
156
+ ### 下载配置(0.3.2 起)
157
+
158
+ 0.3.2 新增 `download.timeoutMs`、`download.idleTimeoutMs` 和 `download.maxRetries`。默认每次请求总时限 180 秒、无新增字节时限 30 秒、最多重试 2 次。仅 ONNX 权重下载使用此策略,清单 JSON 加载沿用既有流程。
155
159
 
156
160
  ### Documentation
157
161
 
@@ -15110,11 +15110,36 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15110
15110
  }
15111
15111
 
15112
15112
  // src/model/download.ts
15113
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
15114
+ var RetryableDownloadError = class extends PPDetectionError {
15115
+ constructor(message, details = {}, cause) {
15116
+ super("MODEL_DOWNLOAD_FAILED", message, details, { cause });
15117
+ }
15118
+ };
15119
+ function resolveDownloadOptions(options = {}) {
15120
+ if (options === null || typeof options !== "object")
15121
+ throw new PPDetectionError("INVALID_INPUT", "\u4E0B\u8F7D\u914D\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
15122
+ const resolved = {
15123
+ timeoutMs: options.timeoutMs === void 0 ? 18e4 : options.timeoutMs,
15124
+ idleTimeoutMs: options.idleTimeoutMs === void 0 ? 3e4 : options.idleTimeoutMs,
15125
+ maxRetries: options.maxRetries === void 0 ? 2 : options.maxRetries
15126
+ };
15127
+ for (const [key, value] of Object.entries(resolved)) {
15128
+ const maximum = key === "maxRetries" ? 5 : 2147483647;
15129
+ if (!Number.isSafeInteger(value) || value < 0 || value > maximum)
15130
+ throw new PPDetectionError("INVALID_INPUT", "\u4E0B\u8F7D\u53C2\u6570\u5FC5\u987B\u4E3A\u5141\u8BB8\u8303\u56F4\u5185\u7684\u5B89\u5168\u6574\u6570", {
15131
+ parameter: key,
15132
+ value,
15133
+ maximum
15134
+ });
15135
+ }
15136
+ return resolved;
15137
+ }
15113
15138
  function now() {
15114
15139
  return globalThis.performance?.now() ?? Date.now();
15115
15140
  }
15116
15141
  function aborted(error, signal) {
15117
- return signal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
15142
+ return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
15118
15143
  }
15119
15144
  function throwIfAborted3(signal) {
15120
15145
  if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88");
@@ -15142,106 +15167,206 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15142
15167
  });
15143
15168
  }
15144
15169
  }
15145
- async function readResponse(response, expectedBytes, signal, onProgress) {
15146
- const totalBytes = contentLength(response, expectedBytes);
15147
- if (!response.body) {
15148
- const bytes2 = await response.arrayBuffer();
15149
- throwIfAborted3(signal);
15150
- onProgress?.({ loadedBytes: bytes2.byteLength, totalBytes });
15151
- return bytes2;
15170
+ function cancelQuietly(cancel) {
15171
+ try {
15172
+ void Promise.resolve(cancel()).catch(() => void 0);
15173
+ } catch {
15152
15174
  }
15153
- const reader = response.body.getReader();
15154
- const chunks = [];
15155
- let loadedBytes = 0;
15175
+ }
15176
+ async function downloadAttempt(asset, fetcher, options, policy, attempt) {
15177
+ const controller = new AbortController();
15178
+ let response;
15179
+ let reader;
15180
+ let active = true;
15181
+ let cancelledBody = false;
15182
+ let stopError;
15183
+ let totalTimer;
15184
+ let idleTimer;
15185
+ let rejectStop;
15186
+ const stopped = new Promise((_resolve, reject) => {
15187
+ rejectStop = reject;
15188
+ });
15189
+ void stopped.catch(() => void 0);
15190
+ const cancelBody = (reason) => {
15191
+ if (cancelledBody || !reader && !response?.body) return;
15192
+ cancelledBody = true;
15193
+ if (reader) cancelQuietly(() => reader.cancel(reason));
15194
+ else cancelQuietly(() => response.body.cancel(reason));
15195
+ };
15196
+ const stop = (error) => {
15197
+ if (!active || stopError) return;
15198
+ stopError = error;
15199
+ rejectStop(error);
15200
+ controller.abort();
15201
+ cancelBody(error);
15202
+ };
15203
+ const abort = () => stop(new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88"));
15204
+ const renewIdle = () => {
15205
+ clearTimeout(idleTimer);
15206
+ if (policy.idleTimeoutMs > 0)
15207
+ idleTimer = setTimeout(
15208
+ () => stop(
15209
+ new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u957F\u65F6\u95F4\u6CA1\u6709\u65B0\u589E\u5B57\u8282", {
15210
+ idleTimeoutMs: policy.idleTimeoutMs
15211
+ })
15212
+ ),
15213
+ policy.idleTimeoutMs
15214
+ );
15215
+ };
15216
+ const checkActive = () => {
15217
+ if (stopError) throw stopError;
15218
+ throwIfAborted3(options.signal);
15219
+ };
15220
+ const network = async (operation) => {
15221
+ try {
15222
+ checkActive();
15223
+ const value = await Promise.race([operation(), stopped]);
15224
+ checkActive();
15225
+ return value;
15226
+ } catch (error) {
15227
+ if (stopError) throw stopError;
15228
+ if (aborted(error, options.signal))
15229
+ throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
15230
+ if (error instanceof PPDetectionError) throw error;
15231
+ throw new RetryableDownloadError(
15232
+ "\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u6216\u54CD\u5E94\u8BFB\u53D6\u5931\u8D25",
15233
+ { sourceKind: asset.source.kind, downloadUrl: asset.source.downloadUrl },
15234
+ error
15235
+ );
15236
+ }
15237
+ };
15238
+ const report = (loadedBytes) => {
15239
+ checkActive();
15240
+ options.onProgress?.({
15241
+ loadedBytes,
15242
+ totalBytes: asset.source.bytes,
15243
+ attempt,
15244
+ maxAttempts: policy.maxRetries + 1
15245
+ });
15246
+ checkActive();
15247
+ };
15248
+ options.signal?.addEventListener("abort", abort, { once: true });
15156
15249
  try {
15250
+ throwIfAborted3(options.signal);
15251
+ if (policy.timeoutMs > 0)
15252
+ totalTimer = setTimeout(
15253
+ () => stop(
15254
+ new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u8D85\u8FC7\u8BF7\u6C42\u603B\u65F6\u9650", { timeoutMs: policy.timeoutMs })
15255
+ ),
15256
+ policy.timeoutMs
15257
+ );
15258
+ renewIdle();
15259
+ report(0);
15260
+ response = await network(
15261
+ () => Promise.resolve(
15262
+ fetcher(asset.source.downloadUrl, {
15263
+ signal: controller.signal,
15264
+ ...attempt > 1 ? { cache: "reload" } : {}
15265
+ })
15266
+ ).then((value) => {
15267
+ response = value;
15268
+ if (!active || stopError) cancelBody(stopError);
15269
+ return value;
15270
+ })
15271
+ );
15272
+ if (!response.ok) {
15273
+ const details = {
15274
+ sourceKind: asset.source.kind,
15275
+ status: response.status,
15276
+ statusText: response.statusText
15277
+ };
15278
+ if (RETRYABLE_STATUSES.has(response.status))
15279
+ throw new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u53EF\u91CD\u8BD5\u72B6\u6001", details);
15280
+ throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", details);
15281
+ }
15282
+ validatePartialResponse(response, asset.source.bytes);
15283
+ contentLength(response, asset.source.bytes);
15284
+ if (!response.body) {
15285
+ const bytes2 = await network(() => response.arrayBuffer());
15286
+ report(bytes2.byteLength);
15287
+ return bytes2;
15288
+ }
15289
+ reader = response.body.getReader();
15290
+ const chunks = [];
15291
+ let loadedBytes = 0;
15157
15292
  while (true) {
15158
- throwIfAborted3(signal);
15159
- const chunk = await reader.read();
15293
+ const chunk = await network(() => reader.read());
15160
15294
  if (chunk.done) break;
15295
+ if (chunk.value.byteLength === 0) continue;
15161
15296
  loadedBytes += chunk.value.byteLength;
15162
- if (loadedBytes > expectedBytes) {
15297
+ if (loadedBytes > asset.source.bytes)
15163
15298
  throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B\u54CD\u5E94\u8D85\u8FC7\u6E05\u5355\u58F0\u660E\u5927\u5C0F", {
15164
- expectedBytes,
15299
+ expectedBytes: asset.source.bytes,
15165
15300
  loadedBytes
15166
15301
  });
15167
- }
15168
15302
  chunks.push(chunk.value);
15169
- onProgress?.({ loadedBytes, totalBytes });
15303
+ renewIdle();
15304
+ report(loadedBytes);
15170
15305
  }
15306
+ const bytes = new Uint8Array(loadedBytes);
15307
+ let offset = 0;
15308
+ for (const chunk of chunks) {
15309
+ bytes.set(chunk, offset);
15310
+ offset += chunk.byteLength;
15311
+ }
15312
+ report(loadedBytes);
15313
+ return bytes.buffer;
15171
15314
  } catch (error) {
15315
+ controller.abort();
15316
+ cancelBody(error);
15317
+ throw error;
15318
+ } finally {
15319
+ active = false;
15320
+ clearTimeout(totalTimer);
15321
+ clearTimeout(idleTimer);
15322
+ options.signal?.removeEventListener("abort", abort);
15172
15323
  try {
15173
- await reader.cancel(error);
15324
+ reader?.releaseLock();
15174
15325
  } catch {
15175
15326
  }
15176
- throw error;
15177
- } finally {
15178
- reader.releaseLock();
15179
- }
15180
- const bytes = new Uint8Array(loadedBytes);
15181
- let offset = 0;
15182
- for (const chunk of chunks) {
15183
- bytes.set(chunk, offset);
15184
- offset += chunk.byteLength;
15185
15327
  }
15186
- onProgress?.({ loadedBytes, totalBytes });
15187
- return bytes.buffer;
15328
+ }
15329
+ function waitForRetry(delayMs, signal) {
15330
+ throwIfAborted3(signal);
15331
+ return new Promise((resolve, reject) => {
15332
+ const abort = () => {
15333
+ clearTimeout(timer);
15334
+ signal?.removeEventListener("abort", abort);
15335
+ reject(new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88"));
15336
+ };
15337
+ const timer = setTimeout(() => {
15338
+ signal?.removeEventListener("abort", abort);
15339
+ resolve();
15340
+ }, delayMs);
15341
+ signal?.addEventListener("abort", abort, { once: true });
15342
+ if (signal?.aborted) abort();
15343
+ });
15188
15344
  }
15189
15345
  async function loadModelAsset(asset, options = {}) {
15190
15346
  throwIfAborted3(options.signal);
15347
+ const policy = resolveDownloadOptions(options.download);
15191
15348
  const fetcher = options.fetcher ?? globalThis.fetch?.bind(globalThis);
15192
15349
  if (!fetcher)
15193
15350
  throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u5F53\u524D\u73AF\u5883\u6CA1\u6709 fetch API", {
15194
15351
  sourceKind: asset.source.kind
15195
15352
  });
15196
15353
  const downloadStarted = now();
15197
- let response;
15198
- try {
15199
- response = await fetcher(asset.source.downloadUrl, { signal: options.signal });
15200
- } catch (error) {
15201
- if (aborted(error, options.signal))
15202
- throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
15203
- throw new PPDetectionError(
15204
- "MODEL_DOWNLOAD_FAILED",
15205
- "\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25",
15206
- {
15207
- sourceKind: asset.source.kind,
15208
- downloadUrl: asset.source.downloadUrl
15209
- },
15210
- { cause: error }
15211
- );
15212
- }
15213
- if (!response.ok) {
15214
- throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", {
15215
- sourceKind: asset.source.kind,
15216
- status: response.status,
15217
- statusText: response.statusText
15218
- });
15219
- }
15220
- validatePartialResponse(response, asset.source.bytes);
15221
15354
  let bytes;
15222
- try {
15223
- bytes = await readResponse(response, asset.source.bytes, options.signal, options.onProgress);
15224
- } catch (error) {
15225
- if (error instanceof PPDetectionError) throw error;
15226
- if (aborted(error, options.signal))
15227
- throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
15228
- throw new PPDetectionError(
15229
- "MODEL_DOWNLOAD_FAILED",
15230
- "\u8BFB\u53D6\u6A21\u578B\u54CD\u5E94\u5931\u8D25",
15231
- { sourceKind: asset.source.kind },
15232
- { cause: error }
15233
- );
15355
+ for (let attempt = 1; ; attempt++) {
15356
+ throwIfAborted3(options.signal);
15357
+ try {
15358
+ bytes = await downloadAttempt(asset, fetcher, options, policy, attempt);
15359
+ break;
15360
+ } catch (error) {
15361
+ throwIfAborted3(options.signal);
15362
+ if (!(error instanceof RetryableDownloadError) || attempt > policy.maxRetries) throw error;
15363
+ await waitForRetry(Math.min(500 * 2 ** (attempt - 1), 4e3), options.signal);
15364
+ }
15234
15365
  }
15235
15366
  const modelDownloadMs = now() - downloadStarted;
15236
15367
  const integrityStarted = now();
15237
15368
  await verifyModelIntegrity(bytes, asset.source, options.signal);
15238
- return {
15239
- bytes,
15240
- timings: {
15241
- modelDownloadMs,
15242
- integrityMs: now() - integrityStarted
15243
- }
15244
- };
15369
+ return { bytes, timings: { modelDownloadMs, integrityMs: now() - integrityStarted } };
15245
15370
  }
15246
15371
 
15247
15372
  // src/model/source-resolver.ts
@@ -15308,6 +15433,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15308
15433
  }
15309
15434
  var ModelManager = class {
15310
15435
  fetcher;
15436
+ download;
15311
15437
  cache;
15312
15438
  coordinator;
15313
15439
  lifecycle = new AbortController();
@@ -15316,6 +15442,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15316
15442
  disposed = false;
15317
15443
  disposePromise;
15318
15444
  constructor(options = {}) {
15445
+ this.download = resolveDownloadOptions(options.download);
15319
15446
  this.fetcher = options.fetcher;
15320
15447
  this.cache = createCache(options.cache);
15321
15448
  this.coordinator = coordinateCache(this.cache);
@@ -15412,6 +15539,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15412
15539
  try {
15413
15540
  loaded = await loadModelAsset(asset, {
15414
15541
  fetcher: this.fetcher,
15542
+ download: this.download,
15415
15543
  signal: options.signal,
15416
15544
  onProgress: options.onProgress
15417
15545
  });
@@ -15896,7 +16024,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
15896
16024
  };
15897
16025
 
15898
16026
  // src/index.ts
15899
- var CURRENT_SDK_VERSION = "0.3.1";
16027
+ var CURRENT_SDK_VERSION = "0.3.2";
15900
16028
  function probePPDetectionCapabilities(options = {}) {
15901
16029
  return probeCapabilities(options);
15902
16030
  }
@@ -16021,6 +16149,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
16021
16149
  simpleManifest(runtimeManifest)
16022
16150
  );
16023
16151
  const modelManager = new ModelManager({
16152
+ download: options.download,
16024
16153
  cache: options.cache === false ? false : options.cache === "memory" ? "memory" : void 0
16025
16154
  });
16026
16155
  let executor;