@qvac/sdk 0.10.2 → 0.11.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.
Files changed (200) hide show
  1. package/NOTICE +92 -54
  2. package/dist/client/api/cancel.d.ts +24 -32
  3. package/dist/client/api/cancel.js +58 -31
  4. package/dist/client/api/client-request-id.d.ts +17 -0
  5. package/dist/client/api/client-request-id.js +27 -0
  6. package/dist/client/api/completion-stream.d.ts +1 -1
  7. package/dist/client/api/completion-stream.js +34 -3
  8. package/dist/client/api/diffusion.d.ts +46 -0
  9. package/dist/client/api/diffusion.js +50 -1
  10. package/dist/client/api/download-asset.d.ts +12 -2
  11. package/dist/client/api/download-asset.js +21 -3
  12. package/dist/client/api/embed.d.ts +8 -4
  13. package/dist/client/api/embed.js +14 -1
  14. package/dist/client/api/get-model-info.d.ts +1 -1
  15. package/dist/client/api/index.d.ts +1 -0
  16. package/dist/client/api/index.js +1 -0
  17. package/dist/client/api/load-model.d.ts +12 -4
  18. package/dist/client/api/load-model.js +25 -1
  19. package/dist/client/api/rag.d.ts +9 -3
  20. package/dist/client/api/rag.js +23 -3
  21. package/dist/client/api/transcribe.d.ts +20 -8
  22. package/dist/client/api/transcribe.js +58 -4
  23. package/dist/client/api/unload-model.d.ts +5 -2
  24. package/dist/client/api/unload-model.js +9 -4
  25. package/dist/client/api/upscale.d.ts +45 -0
  26. package/dist/client/api/upscale.js +99 -0
  27. package/dist/client/rpc/expo-rpc-client.js +2 -1
  28. package/dist/client/rpc/node-rpc-client.js +18 -7
  29. package/dist/client/rpc/rpc-client.d.ts +2 -2
  30. package/dist/client/rpc/rpc-client.js +10 -4
  31. package/dist/client/rpc/rpc-error.d.ts +9 -0
  32. package/dist/client/rpc/rpc-error.js +92 -0
  33. package/dist/examples/cancel-by-request-id.d.ts +38 -0
  34. package/dist/examples/cancel-by-request-id.js +103 -0
  35. package/dist/examples/diffusion-esrgan-upscale.d.ts +2 -0
  36. package/dist/examples/diffusion-esrgan-upscale.js +78 -0
  37. package/dist/examples/download-with-cancel.js +10 -10
  38. package/dist/examples/embed-multi-gpu.d.ts +2 -0
  39. package/dist/examples/embed-multi-gpu.js +46 -0
  40. package/dist/examples/llamacpp-multi-gpu.d.ts +2 -0
  41. package/dist/examples/llamacpp-multi-gpu.js +57 -0
  42. package/dist/examples/rag/rag-hyperdb/cancellation.js +20 -17
  43. package/dist/examples/tools/llamacpp-tools-gemma4.d.ts +2 -0
  44. package/dist/examples/tools/llamacpp-tools-gemma4.js +76 -0
  45. package/dist/examples/tools/llamacpp-tools-qwen35.d.ts +2 -0
  46. package/dist/examples/tools/llamacpp-tools-qwen35.js +70 -0
  47. package/dist/examples/transcription/whispercpp-microphone-conversation.d.ts +2 -0
  48. package/dist/examples/transcription/whispercpp-microphone-conversation.js +102 -0
  49. package/dist/examples/tts/supertonic.js +1 -1
  50. package/dist/expo/plugins/withMobileBundle.d.ts +20 -4
  51. package/dist/expo/plugins/withMobileBundle.js +44 -6
  52. package/dist/index.d.ts +5 -2
  53. package/dist/index.js +21 -1
  54. package/dist/models/registry/models.d.ts +186 -318
  55. package/dist/models/registry/models.js +1522 -1667
  56. package/dist/pear/pre.d.ts +3 -0
  57. package/dist/pear/pre.js +3 -0
  58. package/dist/schemas/cancel.d.ts +167 -28
  59. package/dist/schemas/cancel.js +119 -19
  60. package/dist/schemas/common.d.ts +427 -208
  61. package/dist/schemas/common.js +3 -1
  62. package/dist/schemas/completion-event.d.ts +14 -0
  63. package/dist/schemas/completion-event.js +11 -1
  64. package/dist/schemas/completion-stream.d.ts +15 -1
  65. package/dist/schemas/completion-stream.js +25 -7
  66. package/dist/schemas/download-asset.d.ts +15 -9
  67. package/dist/schemas/download-asset.js +17 -6
  68. package/dist/schemas/embed.d.ts +1 -0
  69. package/dist/schemas/embed.js +5 -0
  70. package/dist/schemas/error.d.ts +31 -0
  71. package/dist/schemas/error.js +29 -1
  72. package/dist/schemas/finetune.d.ts +2 -0
  73. package/dist/schemas/finetune.js +5 -0
  74. package/dist/schemas/get-model-info.d.ts +6 -6
  75. package/dist/schemas/llamacpp-config.d.ts +54 -8
  76. package/dist/schemas/llamacpp-config.js +12 -0
  77. package/dist/schemas/load-model.d.ts +1911 -1260
  78. package/dist/schemas/load-model.js +15 -0
  79. package/dist/schemas/model-src-utils.d.ts +14 -14
  80. package/dist/schemas/model-types.d.ts +10 -10
  81. package/dist/schemas/ocr.d.ts +3 -3
  82. package/dist/schemas/plugin.d.ts +40 -1
  83. package/dist/schemas/plugin.js +10 -1
  84. package/dist/schemas/rag.d.ts +9 -0
  85. package/dist/schemas/rag.js +17 -9
  86. package/dist/schemas/registry.d.ts +30 -30
  87. package/dist/schemas/reload-config.d.ts +14 -14
  88. package/dist/schemas/sdcpp-config.d.ts +131 -17
  89. package/dist/schemas/sdcpp-config.js +163 -1
  90. package/dist/schemas/sdk-config.d.ts +649 -271
  91. package/dist/schemas/sdk-errors-client.d.ts +1 -0
  92. package/dist/schemas/sdk-errors-client.js +5 -0
  93. package/dist/schemas/sdk-errors-server.d.ts +5 -0
  94. package/dist/schemas/sdk-errors-server.js +25 -0
  95. package/dist/schemas/text-to-speech.d.ts +78 -78
  96. package/dist/schemas/transcription-config.d.ts +24 -24
  97. package/dist/schemas/transcription.d.ts +56 -2
  98. package/dist/schemas/transcription.js +27 -0
  99. package/dist/schemas/translate.d.ts +2 -0
  100. package/dist/schemas/translate.js +13 -2
  101. package/dist/schemas/translation-config.d.ts +40 -40
  102. package/dist/schemas/unload-model.d.ts +2 -1
  103. package/dist/schemas/unload-model.js +4 -1
  104. package/dist/scripts/run-unit-tests.js +15 -2
  105. package/dist/server/bare/ops/cancel.d.ts +27 -1
  106. package/dist/server/bare/ops/cancel.js +36 -10
  107. package/dist/server/bare/ops/embed.d.ts +3 -3
  108. package/dist/server/bare/ops/embed.js +144 -28
  109. package/dist/server/bare/ops/kv-cache-utils.d.ts +0 -7
  110. package/dist/server/bare/ops/kv-cache-utils.js +8 -47
  111. package/dist/server/bare/ops/transcribe.d.ts +10 -5
  112. package/dist/server/bare/ops/transcribe.js +196 -41
  113. package/dist/server/bare/ops/translate.d.ts +1 -1
  114. package/dist/server/bare/ops/translate.js +197 -88
  115. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.d.ts +12 -3
  116. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +170 -172
  117. package/dist/server/bare/plugins/llamacpp-completion/ops/finetune.js +125 -21
  118. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.d.ts +154 -0
  119. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.js +448 -0
  120. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.d.ts +21 -73
  121. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.js +18 -101
  122. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +30 -3
  123. package/dist/server/bare/plugins/llamacpp-completion/plugin.js +157 -85
  124. package/dist/server/bare/plugins/llamacpp-completion/transform.d.ts +10 -0
  125. package/dist/server/bare/plugins/llamacpp-completion/transform.js +34 -0
  126. package/dist/server/bare/plugins/llamacpp-embedding/plugin.d.ts +7 -0
  127. package/dist/server/bare/plugins/llamacpp-embedding/plugin.js +10 -1
  128. package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +17 -15
  129. package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +5 -1
  130. package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +3 -3
  131. package/dist/server/bare/plugins/onnx-ocr/plugin.js +3 -0
  132. package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +39 -39
  133. package/dist/server/bare/plugins/onnx-tts/plugin.js +6 -0
  134. package/dist/server/bare/plugins/parakeet-transcription/plugin.d.ts +29 -21
  135. package/dist/server/bare/plugins/parakeet-transcription/plugin.js +4 -1
  136. package/dist/server/bare/plugins/sdcpp-generation/ops/diffusion.js +8 -0
  137. package/dist/server/bare/plugins/sdcpp-generation/ops/upscale.d.ts +3 -0
  138. package/dist/server/bare/plugins/sdcpp-generation/ops/upscale.js +40 -0
  139. package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +91 -16
  140. package/dist/server/bare/plugins/sdcpp-generation/plugin.js +97 -7
  141. package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +22 -3
  142. package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +50 -14
  143. package/dist/server/bare/rag-hyperdb/index.d.ts +1 -1
  144. package/dist/server/bare/rag-hyperdb/index.js +1 -1
  145. package/dist/server/bare/rag-hyperdb/rag-operation-manager.d.ts +26 -4
  146. package/dist/server/bare/rag-hyperdb/rag-operation-manager.js +65 -28
  147. package/dist/server/bare/runtime/disposable-scope.d.ts +36 -0
  148. package/dist/server/bare/runtime/disposable-scope.js +69 -0
  149. package/dist/server/bare/runtime/index.d.ts +8 -0
  150. package/dist/server/bare/runtime/index.js +3 -0
  151. package/dist/server/bare/runtime/request-context.d.ts +49 -0
  152. package/dist/server/bare/runtime/request-context.js +1 -0
  153. package/dist/server/bare/runtime/request-id.d.ts +12 -0
  154. package/dist/server/bare/runtime/request-id.js +14 -0
  155. package/dist/server/bare/runtime/request-registry-singleton.d.ts +4 -0
  156. package/dist/server/bare/runtime/request-registry-singleton.js +27 -0
  157. package/dist/server/bare/runtime/request-registry.d.ts +136 -0
  158. package/dist/server/bare/runtime/request-registry.js +297 -0
  159. package/dist/server/bare/runtime/with-request-context.d.ts +22 -0
  160. package/dist/server/bare/runtime/with-request-context.js +53 -0
  161. package/dist/server/bare/types/addon-responses.d.ts +11 -1
  162. package/dist/server/rpc/handler-registry.js +26 -7
  163. package/dist/server/rpc/handlers/cancel-delegated.js +28 -31
  164. package/dist/server/rpc/handlers/cancelHandler.d.ts +30 -1
  165. package/dist/server/rpc/handlers/cancelHandler.js +54 -22
  166. package/dist/server/rpc/handlers/delete-cache.d.ts +9 -0
  167. package/dist/server/rpc/handlers/delete-cache.js +13 -15
  168. package/dist/server/rpc/handlers/download-asset.js +133 -40
  169. package/dist/server/rpc/handlers/index.d.ts +2 -0
  170. package/dist/server/rpc/handlers/index.js +2 -0
  171. package/dist/server/rpc/handlers/load-model/download-manager.d.ts +60 -2
  172. package/dist/server/rpc/handlers/load-model/download-manager.js +141 -8
  173. package/dist/server/rpc/handlers/load-model/handler.js +213 -98
  174. package/dist/server/rpc/handlers/load-model/http.js +3 -3
  175. package/dist/server/rpc/handlers/load-model/hyperdrive.js +1 -1
  176. package/dist/server/rpc/handlers/load-model/registry.js +1 -1
  177. package/dist/server/rpc/handlers/load-model/resolve-session.d.ts +20 -1
  178. package/dist/server/rpc/handlers/load-model/resolve-session.js +6 -5
  179. package/dist/server/rpc/handlers/load-model/resolve.d.ts +3 -2
  180. package/dist/server/rpc/handlers/load-model/resolve.js +9 -6
  181. package/dist/server/rpc/handlers/load-model/types.d.ts +24 -0
  182. package/dist/server/rpc/handlers/rag.js +133 -15
  183. package/dist/server/rpc/handlers/upscale-stream.d.ts +3 -0
  184. package/dist/server/rpc/handlers/upscale-stream.js +4 -0
  185. package/dist/server/utils/completion-normalizer.js +8 -0
  186. package/dist/server/utils/tools/dialect.js +4 -0
  187. package/dist/server/utils/tools/parser.js +16 -1
  188. package/dist/server/utils/tools/parsers/gemma4native.d.ts +4 -0
  189. package/dist/server/utils/tools/parsers/gemma4native.js +60 -0
  190. package/dist/server/utils/tools/parsers/qwen35.d.ts +4 -0
  191. package/dist/server/utils/tools/parsers/qwen35.js +120 -0
  192. package/dist/utils/aggregate-events.d.ts +9 -0
  193. package/dist/utils/aggregate-events.js +18 -3
  194. package/dist/utils/decorate-promise.d.ts +34 -0
  195. package/dist/utils/decorate-promise.js +35 -0
  196. package/dist/utils/errors-client.d.ts +3 -0
  197. package/dist/utils/errors-client.js +5 -0
  198. package/dist/utils/errors-server.d.ts +85 -0
  199. package/dist/utils/errors-server.js +97 -0
  200. package/package.json +20 -15
@@ -1,10 +1,11 @@
1
1
  import { AbortController } from "bare-abort-controller";
2
- import { DownloadCancelledError } from "../../../../utils/errors-server.js";
2
+ import { DownloadCancelledError, InferenceCancelledError, } from "../../../../utils/errors-server.js";
3
3
  import { getServerLogger } from "../../../../logging/index.js";
4
+ import { getRequestRegistry } from "../../../bare/runtime/index.js";
4
5
  const logger = getServerLogger();
5
6
  const activeTransfers = new Map();
6
7
  let nextSubscriberId = 0;
7
- function createSubscriber(onProgress) {
8
+ function createSubscriber(onProgress, requestId) {
8
9
  let resolve;
9
10
  let reject;
10
11
  const promise = new Promise((res, rej) => {
@@ -18,6 +19,7 @@ function createSubscriber(onProgress) {
18
19
  resolve,
19
20
  reject,
20
21
  promise,
22
+ requestId,
21
23
  };
22
24
  }
23
25
  function settleSubscriber(subscriber, result) {
@@ -45,7 +47,7 @@ function deliverProgress(transfer, subscriber, progress) {
45
47
  error,
46
48
  });
47
49
  settleSubscriber(subscriber, error);
48
- transfer.subscribers.delete(subscriber.id);
50
+ removeSubscriber(transfer, subscriber.id);
49
51
  }
50
52
  }
51
53
  function broadcastTransferProgress(transfer, progress) {
@@ -54,12 +56,76 @@ function broadcastTransferProgress(transfer, progress) {
54
56
  deliverProgress(transfer, sub, progress);
55
57
  }
56
58
  }
57
- export function startOrJoinDownload(downloadKey, startDownload, onProgress) {
59
+ /**
60
+ * Idempotent: remove the subscriber from the transfer's roster and tear
61
+ * the transfer down iff no subscribers remain. Wired from two places per
62
+ * subscriber:
63
+ *
64
+ * - the `request.signal.addEventListener("abort", ...)` listener that
65
+ * fires when `registry.cancel({ requestId })` aborts the request's
66
+ * context — this is the "user clicked Stop" cancel path;
67
+ * - `request.scope.defer(...)` which runs at request scope unwind —
68
+ * the safety net catching every other unwind path (handler returns,
69
+ * handler throws for a non-cancel reason, awaited promise settled
70
+ * and the `await using` falls out of scope on the success path).
71
+ *
72
+ * Both ends call this helper so an already-cleaned-up subscriber is a
73
+ * no-op the second time around.
74
+ */
75
+ function removeSubscriber(transfer, subscriberId) {
76
+ if (!transfer.subscribers.has(subscriberId))
77
+ return;
78
+ transfer.subscribers.delete(subscriberId);
79
+ maybeCancelTransfer(transfer);
80
+ }
81
+ /**
82
+ * Last-subscriber rule: when every caller has detached (cancel or
83
+ * progress-callback throw), abort the shared transfer so the underlying
84
+ * HTTP / hyperdrive download tears down. Until then the transfer keeps
85
+ * running for the remaining subscribers — the content-addressed dedup
86
+ * semantics callers rely on.
87
+ */
88
+ function maybeCancelTransfer(transfer) {
89
+ if (transfer.subscribers.size > 0)
90
+ return;
91
+ if (transfer.abortController.signal.aborted)
92
+ return;
93
+ logger.debug(`[download-manager] last subscriber left, aborting transfer ${transfer.downloadKey}`);
94
+ transfer.abortController.abort();
95
+ }
96
+ function attachRequestBinding(transfer, subscriber, request) {
97
+ const onAbort = () => {
98
+ if (!subscriber.settled) {
99
+ settleSubscriber(subscriber, new InferenceCancelledError(request.requestId));
100
+ }
101
+ removeSubscriber(transfer, subscriber.id);
102
+ };
103
+ if (request.signal.aborted) {
104
+ onAbort();
105
+ return;
106
+ }
107
+ request.signal.addEventListener("abort", onAbort, { once: true });
108
+ // Safety net: scope unwind on any handler exit path triggers the same
109
+ // cleanup. If the abort listener already ran it's a no-op. Cleaning
110
+ // up the abort listener here keeps the parent signal from carrying a
111
+ // dangling reference into the next request.
112
+ request.scope.defer(() => {
113
+ request.signal.removeEventListener("abort", onAbort);
114
+ if (!subscriber.settled) {
115
+ settleSubscriber(subscriber, new InferenceCancelledError(request.requestId));
116
+ }
117
+ removeSubscriber(transfer, subscriber.id);
118
+ });
119
+ }
120
+ export function startOrJoinDownload(downloadKey, startDownload, onProgress, request) {
58
121
  const existing = activeTransfers.get(downloadKey);
59
122
  if (existing && !existing.abortController.signal.aborted) {
60
123
  logger.info(`📥 Reusing existing download for: ${downloadKey}`);
61
- const subscriber = createSubscriber(onProgress);
124
+ const subscriber = createSubscriber(onProgress, request?.requestId);
62
125
  existing.subscribers.set(subscriber.id, subscriber);
126
+ if (request) {
127
+ attachRequestBinding(existing, subscriber, request);
128
+ }
63
129
  if (existing.lastProgress) {
64
130
  deliverProgress(existing, subscriber, existing.lastProgress);
65
131
  }
@@ -76,9 +142,12 @@ export function startOrJoinDownload(downloadKey, startDownload, onProgress) {
76
142
  subscribers: new Map(),
77
143
  clearCache: false,
78
144
  };
79
- const initialSubscriber = createSubscriber(onProgress);
145
+ const initialSubscriber = createSubscriber(onProgress, request?.requestId);
80
146
  transfer.subscribers.set(initialSubscriber.id, initialSubscriber);
81
147
  activeTransfers.set(downloadKey, transfer);
148
+ if (request) {
149
+ attachRequestBinding(transfer, initialSubscriber, request);
150
+ }
82
151
  const downloadPromise = startDownload({
83
152
  broadcastProgress: (progress) => {
84
153
  broadcastTransferProgress(transfer, progress);
@@ -110,14 +179,78 @@ export function startOrJoinDownload(downloadKey, startDownload, onProgress) {
110
179
  getCacheHit: () => transfer.cacheHit,
111
180
  };
112
181
  }
182
+ /**
183
+ * Legacy cancel entry point. Callers (`cancelHandler`'s deprecated
184
+ * `case "downloadAsset"` arm, the shutdown sweep, intra-resolve
185
+ * cleanup) call this with a `downloadKey`. The single source of cancel
186
+ * routing is the request registry, so this function resolves each
187
+ * subscriber's request via `registry.cancel({ requestId })`.
188
+ *
189
+ * Subscribers without a `requestId` (legacy callers that didn't pass a
190
+ * registry binding) are settled directly with `DownloadCancelledError`
191
+ * so we don't leak the transfer if a legacy code path holds the only
192
+ * reference.
193
+ */
194
+ /**
195
+ * Set `clearCache=true` on the transfer that owns the subscriber bound
196
+ * to `requestId`, so when the registry's targeted cancel removes that
197
+ * subscriber and we reach last-subscriber teardown, the partial
198
+ * download file is deleted instead of preserved for automatic resume.
199
+ *
200
+ * Lookup is O(transfers * subscribers); transfers are short-lived and
201
+ * subscriber counts per transfer are tiny in practice, so this is
202
+ * fine. Returns `true` if a matching subscriber was found, `false`
203
+ * otherwise — the cancel handler treats both cases identically (the
204
+ * registry cancel still fires) and the return value is informational.
205
+ *
206
+ * Added in 0.11.0 to support `cancel({ requestId, clearCache: true })`
207
+ * for download requests after the wire schema collapse removed the
208
+ * `{ operation: "downloadAsset", downloadKey, clearCache }` arm. The
209
+ * subscriber is the unit of `clearCache` even though the flag lives on
210
+ * the shared transfer: if any subscriber on the transfer asks for
211
+ * clearCache, the partial file is deleted when the last subscriber
212
+ * leaves, matching the pre-collapse behaviour.
213
+ */
214
+ export function markClearCacheForRequest(requestId) {
215
+ for (const transfer of activeTransfers.values()) {
216
+ for (const sub of transfer.subscribers.values()) {
217
+ if (sub.requestId === requestId) {
218
+ transfer.clearCache = true;
219
+ return true;
220
+ }
221
+ }
222
+ }
223
+ return false;
224
+ }
113
225
  export function cancelTransfer(downloadKey, clearCache = false) {
114
226
  const transfer = activeTransfers.get(downloadKey);
115
227
  if (!transfer)
116
228
  return;
117
229
  transfer.clearCache = clearCache;
118
- transfer.abortController.abort();
119
- for (const sub of transfer.subscribers.values()) {
230
+ const registry = getRequestRegistry();
231
+ const orphanSubs = [];
232
+ for (const sub of Array.from(transfer.subscribers.values())) {
233
+ if (sub.requestId !== undefined) {
234
+ registry.cancel({
235
+ requestId: sub.requestId,
236
+ reason: "download-transfer-cancel",
237
+ });
238
+ }
239
+ else {
240
+ orphanSubs.push(sub);
241
+ }
242
+ }
243
+ if (orphanSubs.length === 0) {
244
+ return;
245
+ }
246
+ // Legacy subscribers (no registry binding): settle each with
247
+ // `DownloadCancelledError` and route removal through the
248
+ // `removeSubscriber` helper so the last-subscriber teardown rule is
249
+ // enforced in one place. Registry-bound subscribers are handled by
250
+ // their `attachRequestBinding` listener triggered above.
251
+ for (const sub of orphanSubs) {
120
252
  settleSubscriber(sub, new DownloadCancelledError());
253
+ removeSubscriber(transfer, sub.id);
121
254
  }
122
255
  }
123
256
  export function createHyperdriveDownloadKey(hyperdriveKey, modelFileName) {
@@ -1,3 +1,55 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
1
53
  import { normalizeModelType, PROFILING_KEY, OPERATION_EVENT_KEY, } from "../../../../schemas/index.js";
2
54
  import { loadModel } from "../../../bare/ops/load-model.js";
3
55
  import { createResolveSession } from "./resolve-session.js";
@@ -5,120 +57,183 @@ import { nowMs, generateProfileId } from "../../../../profiling/clock.js";
5
57
  import { getModelEntry, updateModelConfig, } from "../../../bare/registry/model-registry.js";
6
58
  import { generateShortHash, canonicalConfigString, transformConfigForReload, } from "../../../utils/index.js";
7
59
  import { buildDownloadProfilingFields } from "./types.js";
8
- import { ConfigReloadNotSupportedError, ModelTypeMismatchError, ModelIsDelegatedError, ModelNotFoundError, ModelLoadFailedError, PluginLoadConfigValidationFailedError, PluginNotFoundError, } from "../../../../utils/errors-server.js";
60
+ import { ConfigReloadNotSupportedError, InferenceCancelledError, ModelTypeMismatchError, ModelIsDelegatedError, ModelNotFoundError, ModelLoadFailedError, PluginLoadConfigValidationFailedError, PluginNotFoundError, } from "../../../../utils/errors-server.js";
9
61
  import { getServerLogger } from "../../../../logging/index.js";
10
62
  import { getPlugin } from "../../../plugins/index.js";
63
+ import { getRequestRegistry, withRequestContext, } from "../../../bare/runtime/index.js";
64
+ import { generateServerRequestId } from "../../../bare/runtime/request-id.js";
11
65
  const logger = getServerLogger();
12
66
  // ---------------------------------------------------------------------------
13
67
  // Handler
14
68
  // ---------------------------------------------------------------------------
15
69
  export async function handleLoadModel(request, progressCallback) {
16
- if (isReloadConfigRequest(request)) {
17
- return handleConfigReload(request);
18
- }
19
- const { modelSrc, modelName, seed } = request;
20
- const canonicalModelType = normalizeModelType(request.modelType);
21
- const profilingMeta = request[PROFILING_KEY];
22
- const profilingEnabled = profilingMeta?.enabled !== false && !!profilingMeta;
70
+ const env_1 = { stack: [], error: void 0, hasError: false };
23
71
  try {
24
- const plugin = getPlugin(canonicalModelType);
25
- if (!plugin) {
26
- throw new PluginNotFoundError(canonicalModelType);
72
+ if (isReloadConfigRequest(request)) {
73
+ return handleConfigReload(request);
27
74
  }
28
- let resolvedModelConfig = (request.modelConfig ?? {});
29
- const parseResult = plugin.loadConfigSchema.safeParse(resolvedModelConfig);
30
- if (!parseResult.success) {
31
- const details = parseResult.error.issues
32
- .map((i) => `${String(i.path.join("."))}: ${i.message}`)
33
- .join(", ");
34
- throw new PluginLoadConfigValidationFailedError(canonicalModelType, details);
35
- }
36
- resolvedModelConfig = parseResult.data;
37
- const totalLoadStart = profilingEnabled ? nowMs() : 0;
38
- const session = createResolveSession({
39
- progressCallback,
40
- seed,
41
- profilingEnabled,
42
- });
43
- const primaryResolve = session.resolvePrimaryModelPath(modelSrc);
44
- let resolvedModelPath;
45
- let pluginResolveResult;
75
+ const { modelSrc, modelName, seed } = request;
76
+ const canonicalModelType = normalizeModelType(request.modelType);
77
+ const profilingMeta = request[PROFILING_KEY];
78
+ const profilingEnabled = profilingMeta?.enabled !== false && !!profilingMeta;
79
+ const requestId = request.requestId ?? generateServerRequestId();
80
+ // The handler `modelId` is derived from the config hash below — it
81
+ // isn't known until after `resolveConfig` runs. The registry context
82
+ // is opened with `modelId: undefined` so a cancel-by-modelId fired
83
+ // before the load completes is a clean no-op (rather than matching a
84
+ // half-built entry). Cancel-by-`requestId` works from `begin(...)` on.
85
+ const ctx = __addDisposableResource(env_1, getRequestRegistry().begin({
86
+ requestId,
87
+ kind: "loadModel",
88
+ }), true);
89
+ const log = withRequestContext(getServerLogger(), ctx);
90
+ log.debug(`loadModel start modelSrc=${String(modelSrc ?? "")}`);
46
91
  try {
47
- const pluginResolve = plugin.resolveConfig
48
- ? Promise.resolve().then(() => plugin.resolveConfig(resolvedModelConfig, session.createResolveContext(modelSrc, canonicalModelType, modelName)))
49
- : undefined;
50
- [resolvedModelPath, pluginResolveResult] = pluginResolve
51
- ? await Promise.all([primaryResolve, pluginResolve])
52
- : [await primaryResolve, undefined];
53
- }
54
- catch (error) {
55
- session.cancelAll();
56
- throw error;
57
- }
58
- const configStr = canonicalConfigString(request.modelConfig);
59
- const modelHashInput = `${request.modelType}:${modelSrc}:${configStr}`;
60
- const modelId = generateShortHash(modelHashInput);
61
- let pluginArtifacts = {};
62
- if (pluginResolveResult) {
63
- resolvedModelConfig = pluginResolveResult.config;
64
- if (pluginResolveResult.artifacts) {
65
- pluginArtifacts = pluginResolveResult.artifacts;
92
+ const plugin = getPlugin(canonicalModelType);
93
+ if (!plugin) {
94
+ throw new PluginNotFoundError(canonicalModelType);
66
95
  }
67
- }
68
- if ("modelPath" in pluginArtifacts) {
69
- throw new ModelLoadFailedError("Plugin returned reserved key \"modelPath\" in artifacts; primary model resolution is core-owned");
70
- }
71
- if (!resolvedModelPath) {
72
- throw new ModelLoadFailedError("modelPath resolution failed");
73
- }
74
- const loadResult = await loadModel({
75
- modelId,
76
- modelPath: resolvedModelPath,
77
- options: {
78
- ...request,
79
- modelType: canonicalModelType,
80
- modelConfig: resolvedModelConfig,
81
- },
82
- artifacts: Object.keys(pluginArtifacts).length > 0 ? pluginArtifacts : undefined,
83
- modelName,
84
- }, profilingEnabled ? { collectTiming: true } : undefined);
85
- const response = {
86
- type: "loadModel",
87
- success: true,
88
- modelId,
89
- };
90
- if (profilingEnabled) {
91
- const totalLoadTimeMs = nowMs() - totalLoadStart;
92
- const profileId = profilingMeta?.id ?? generateProfileId();
93
- const resolveResult = session.getAggregateResult();
94
- const { gauges, tags } = buildDownloadProfilingFields(resolveResult?.downloadStats, resolveResult?.sourceType);
95
- gauges["totalLoadTime"] = totalLoadTimeMs;
96
- if (loadResult.timing?.modelInitializationTimeMs !== undefined) {
97
- gauges["modelInitializationTime"] =
98
- loadResult.timing.modelInitializationTimeMs;
96
+ let resolvedModelConfig = (request.modelConfig ?? {});
97
+ const parseResult = plugin.loadConfigSchema.safeParse(resolvedModelConfig);
98
+ if (!parseResult.success) {
99
+ const details = parseResult.error.issues
100
+ .map((i) => `${String(i.path.join("."))}: ${i.message}`)
101
+ .join(", ");
102
+ throw new PluginLoadConfigValidationFailedError(canonicalModelType, details);
103
+ }
104
+ resolvedModelConfig = parseResult.data;
105
+ const totalLoadStart = profilingEnabled ? nowMs() : 0;
106
+ const session = createResolveSession({
107
+ progressCallback,
108
+ seed,
109
+ profilingEnabled,
110
+ signal: ctx.signal,
111
+ requestBinding: {
112
+ signal: ctx.signal,
113
+ scope: ctx.scope,
114
+ requestId,
115
+ },
116
+ });
117
+ const primaryResolve = session.resolvePrimaryModelPath(modelSrc);
118
+ let resolvedModelPath;
119
+ let pluginResolveResult;
120
+ try {
121
+ const pluginResolve = plugin.resolveConfig
122
+ ? Promise.resolve().then(() => plugin.resolveConfig(resolvedModelConfig, session.createResolveContext(modelSrc, canonicalModelType, modelName)))
123
+ : undefined;
124
+ [resolvedModelPath, pluginResolveResult] = pluginResolve
125
+ ? await Promise.all([primaryResolve, pluginResolve])
126
+ : [await primaryResolve, undefined];
127
+ }
128
+ catch (error) {
129
+ session.cancelAll();
130
+ throw error;
131
+ }
132
+ // Phase boundary between download and load. The bare load path
133
+ // (`plugin.createModel(...)` / `model.load(false)`) does not accept
134
+ // an abort signal today, so the only honest cancel hook here is the
135
+ // pre-load gate: if cancel arrived during download / resolveConfig,
136
+ // surface `InferenceCancelledError` before sinking time into the
137
+ // addon work.
138
+ if (ctx.signal.aborted) {
139
+ throw new InferenceCancelledError(requestId);
140
+ }
141
+ const configStr = canonicalConfigString(request.modelConfig);
142
+ const modelHashInput = `${request.modelType}:${modelSrc}:${configStr}`;
143
+ const modelId = generateShortHash(modelHashInput);
144
+ let pluginArtifacts = {};
145
+ if (pluginResolveResult) {
146
+ resolvedModelConfig = pluginResolveResult.config;
147
+ if (pluginResolveResult.artifacts) {
148
+ pluginArtifacts = pluginResolveResult.artifacts;
149
+ }
99
150
  }
100
- if (canonicalModelType) {
101
- tags["modelType"] = canonicalModelType;
151
+ if ("modelPath" in pluginArtifacts) {
152
+ throw new ModelLoadFailedError("Plugin returned reserved key \"modelPath\" in artifacts; primary model resolution is core-owned");
102
153
  }
103
- const operationEvent = {
104
- op: "loadModel",
105
- kind: "handler",
106
- ms: totalLoadTimeMs,
107
- profileId,
108
- gauges: Object.keys(gauges).length > 0 ? gauges : undefined,
109
- tags: Object.keys(tags).length > 0 ? tags : undefined,
154
+ if (!resolvedModelPath) {
155
+ throw new ModelLoadFailedError("modelPath resolution failed");
156
+ }
157
+ const loadResult = await loadModel({
158
+ modelId,
159
+ modelPath: resolvedModelPath,
160
+ options: {
161
+ ...request,
162
+ modelType: canonicalModelType,
163
+ modelConfig: resolvedModelConfig,
164
+ },
165
+ artifacts: Object.keys(pluginArtifacts).length > 0 ? pluginArtifacts : undefined,
166
+ modelName,
167
+ }, profilingEnabled ? { collectTiming: true } : undefined);
168
+ // Load phase soft-cancel limitation: `plugin.createModel(...)` and
169
+ // `model.load(false)` ran without a signal. A cancel that arrived
170
+ // during the load phase has just landed in the registry but the
171
+ // model is now loaded and registered. Surface
172
+ // `InferenceCancelledError` so the caller's promise rejects
173
+ // consistently — even though the model state is the "loaded"
174
+ // post-condition. The orphan-model edge case is a known
175
+ // limitation that requires a per-load cancel surface on the addon
176
+ // to fix end-to-end.
177
+ if (ctx.signal.aborted) {
178
+ throw new InferenceCancelledError(requestId);
179
+ }
180
+ const response = {
181
+ type: "loadModel",
182
+ success: true,
183
+ modelId,
184
+ };
185
+ if (profilingEnabled) {
186
+ const totalLoadTimeMs = nowMs() - totalLoadStart;
187
+ const profileId = profilingMeta?.id ?? generateProfileId();
188
+ const resolveResult = session.getAggregateResult();
189
+ const { gauges, tags } = buildDownloadProfilingFields(resolveResult?.downloadStats, resolveResult?.sourceType);
190
+ gauges["totalLoadTime"] = totalLoadTimeMs;
191
+ if (loadResult.timing?.modelInitializationTimeMs !== undefined) {
192
+ gauges["modelInitializationTime"] =
193
+ loadResult.timing.modelInitializationTimeMs;
194
+ }
195
+ if (canonicalModelType) {
196
+ tags["modelType"] = canonicalModelType;
197
+ }
198
+ const operationEvent = {
199
+ op: "loadModel",
200
+ kind: "handler",
201
+ ms: totalLoadTimeMs,
202
+ profileId,
203
+ gauges: Object.keys(gauges).length > 0 ? gauges : undefined,
204
+ tags: Object.keys(tags).length > 0 ? tags : undefined,
205
+ };
206
+ response[OPERATION_EVENT_KEY] = operationEvent;
207
+ }
208
+ return response;
209
+ }
210
+ catch (error) {
211
+ // `InferenceCancelledError` rides the rejection path verbatim: the
212
+ // client side wants a typed cancel error on the promise it `await`s,
213
+ // not a `{ success: false, error }` envelope that surfaces as a
214
+ // generic `ModelLoadFailedError`. Every other error keeps the
215
+ // legacy `success: false` shape for backward compat with consumers
216
+ // that switch on the envelope.
217
+ if (error instanceof InferenceCancelledError) {
218
+ log.info(`loadModel cancelled requestId=${requestId}`);
219
+ throw error;
220
+ }
221
+ logger.error("Error loading model:", error);
222
+ return {
223
+ type: "loadModel",
224
+ success: false,
225
+ error: error instanceof Error ? error.message : "Unknown error",
110
226
  };
111
- response[OPERATION_EVENT_KEY] = operationEvent;
112
227
  }
113
- return response;
114
228
  }
115
- catch (error) {
116
- logger.error("Error loading model:", error);
117
- return {
118
- type: "loadModel",
119
- success: false,
120
- error: error instanceof Error ? error.message : "Unknown error",
121
- };
229
+ catch (e_1) {
230
+ env_1.error = e_1;
231
+ env_1.hasError = true;
232
+ }
233
+ finally {
234
+ const result_1 = __disposeResources(env_1);
235
+ if (result_1)
236
+ await result_1;
122
237
  }
123
238
  }
124
239
  async function handleConfigReload(request) {
@@ -398,7 +398,7 @@ export async function downloadModelFromHttp(url, progressCallback, hooks) {
398
398
  const errorToThrow = error instanceof Error ? error : new Error(String(error));
399
399
  throw errorToThrow;
400
400
  }
401
- }, progressCallback);
401
+ }, progressCallback, hooks?.requestBinding);
402
402
  return applyJoinedDownloadStats(result, hooks);
403
403
  }
404
404
  async function downloadShardedModelFromHttp(shardUrl, progressCallback, hooks) {
@@ -484,7 +484,7 @@ async function downloadShardedModelFromHttp(shardUrl, progressCallback, hooks) {
484
484
  }
485
485
  throw error;
486
486
  }
487
- }, progressCallback);
487
+ }, progressCallback, hooks?.requestBinding);
488
488
  return applyJoinedDownloadStats(result, hooks);
489
489
  }
490
490
  async function downloadShardedModelFromArchive(archiveUrl, progressCallback, hooks) {
@@ -591,7 +591,7 @@ async function downloadShardedModelFromArchive(archiveUrl, progressCallback, hoo
591
591
  }
592
592
  return firstShardPath;
593
593
  }
594
- }, progressCallback);
594
+ }, progressCallback, hooks?.requestBinding);
595
595
  return applyJoinedDownloadStats(result, hooks);
596
596
  }
597
597
  async function downloadShardsWithConcurrency(shardsToDownload, allShards, concurrency, signal, downloadKey, overallTotal, progressCallback) {
@@ -557,6 +557,6 @@ export async function downloadModelFromHyperdrive(hyperdriveKey, modelFileName,
557
557
  const checksumToValidate = expectedChecksum || model?.sha256Checksum || "";
558
558
  await downloadSingleFileToFilesystem(modelPath, modelFileName, hyperdriveKey, corestoreDir, downloadKey, checksumToValidate, ctx.broadcastProgress, seed, ctx.signal, hooks, ctx.shouldClearCache);
559
559
  return modelPath;
560
- }, progressCallback);
560
+ }, progressCallback, hooks?.requestBinding);
561
561
  return applyJoinedDownloadStats(result, hooks);
562
562
  }
@@ -298,6 +298,6 @@ export async function downloadModelFromRegistry(registryPath, registrySource, pr
298
298
  throw error;
299
299
  }
300
300
  return modelPath;
301
- }, progressCallback);
301
+ }, progressCallback, hooks?.requestBinding);
302
302
  return applyJoinedDownloadStats(result, hooks);
303
303
  }
@@ -1,9 +1,28 @@
1
+ import type { AbortSignal } from "bare-abort-controller";
1
2
  import type { ModelProgressUpdate, ResolveContext } from "../../../../schemas/index";
2
- import type { ResolveResult } from "./types";
3
+ import type { DownloadRequestBinding, ResolveResult } from "./types";
3
4
  export interface ResolveSessionOptions {
4
5
  progressCallback?: ((update: ModelProgressUpdate) => void) | undefined;
5
6
  seed?: boolean | undefined;
6
7
  profilingEnabled: boolean;
8
+ /**
9
+ * Optional cancel signal — typically `ctx.signal` from the surrounding
10
+ * `await using ctx = registry.begin(...)` block. When provided,
11
+ * `resolveModelPath` short-circuits with `InferenceCancelledError` if the
12
+ * signal is already aborted on entry; the same signal also propagates
13
+ * to in-progress transfers via the request binding below so cancel
14
+ * tears them down end-to-end.
15
+ */
16
+ signal?: AbortSignal | undefined;
17
+ /**
18
+ * Optional per-request binding threaded into every `startOrJoinDownload`
19
+ * call. The download manager wires a per-subscriber abort listener
20
+ * against `binding.signal`, registers a scope-defer cleanup, and stamps
21
+ * the subscriber with `binding.requestId` so the legacy
22
+ * `cancelTransfer(downloadKey)` path can route through
23
+ * `registry.cancel({ requestId })`.
24
+ */
25
+ requestBinding?: DownloadRequestBinding | undefined;
7
26
  }
8
27
  export interface ResolveSession {
9
28
  resolvePrimaryModelPath(modelSrc: unknown): Promise<string>;
@@ -2,7 +2,7 @@ import { resolveModelPath, resolveModelPathWithStats, } from "./resolve.js";
2
2
  import { cancelTransfer } from "./download-manager.js";
3
3
  import { mergeDownloadStats } from "./download-stats.js";
4
4
  export function createResolveSession(options) {
5
- const { progressCallback, seed, profilingEnabled } = options;
5
+ const { progressCallback, seed, profilingEnabled, signal, requestBinding } = options;
6
6
  let primaryResult;
7
7
  const resolveResults = [];
8
8
  const activeDownloadKeys = new Set();
@@ -10,23 +10,24 @@ export function createResolveSession(options) {
10
10
  onDownloadKey(key) {
11
11
  activeDownloadKeys.add(key);
12
12
  },
13
+ ...(requestBinding !== undefined && { requestBinding }),
13
14
  };
14
15
  async function resolvePrimaryModelPath(modelSrc) {
15
16
  if (profilingEnabled) {
16
- const result = await resolveModelPathWithStats(modelSrc, progressCallback, seed, downloadHooks);
17
+ const result = await resolveModelPathWithStats(modelSrc, progressCallback, seed, signal, downloadHooks);
17
18
  primaryResult = result;
18
19
  resolveResults.push(result);
19
20
  return result.path;
20
21
  }
21
- return resolveModelPath(modelSrc, progressCallback, seed, downloadHooks);
22
+ return resolveModelPath(modelSrc, progressCallback, seed, signal, downloadHooks);
22
23
  }
23
24
  async function resolveForPlugin(src) {
24
25
  if (profilingEnabled) {
25
- const result = await resolveModelPathWithStats(src, progressCallback, seed, downloadHooks);
26
+ const result = await resolveModelPathWithStats(src, progressCallback, seed, signal, downloadHooks);
26
27
  resolveResults.push(result);
27
28
  return result.path;
28
29
  }
29
- return resolveModelPath(src, progressCallback, seed, downloadHooks);
30
+ return resolveModelPath(src, progressCallback, seed, signal, downloadHooks);
30
31
  }
31
32
  function createResolveContext(modelSrc, modelType, modelName) {
32
33
  return {