@qvac/sdk 0.11.0 → 0.12.1

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 (211) hide show
  1. package/NOTICE +1423 -43
  2. package/README.md +2 -0
  3. package/dist/client/api/classify.d.ts +23 -0
  4. package/dist/client/api/classify.js +43 -0
  5. package/dist/client/api/get-model-info.d.ts +1 -1
  6. package/dist/client/api/index.d.ts +4 -0
  7. package/dist/client/api/index.js +4 -0
  8. package/dist/client/api/load-model.js +24 -1
  9. package/dist/client/api/transcribe.d.ts +3 -0
  10. package/dist/client/api/transcribe.js +13 -2
  11. package/dist/client/api/video.d.ts +64 -0
  12. package/dist/client/api/video.js +147 -0
  13. package/dist/client/api/vla-helpers.d.ts +45 -0
  14. package/dist/client/api/vla-helpers.js +130 -0
  15. package/dist/client/api/vla.d.ts +65 -0
  16. package/dist/client/api/vla.js +109 -0
  17. package/dist/client/config-loader/resolve-config.node.d.ts +16 -0
  18. package/dist/client/config-loader/resolve-config.node.js +77 -23
  19. package/dist/client/plugins-factory.d.ts +8 -0
  20. package/dist/client/plugins-factory.js +10 -0
  21. package/dist/client/rpc/bare-client.d.ts +1 -0
  22. package/dist/client/rpc/bare-client.js +66 -66
  23. package/dist/client/rpc/ensure-worker-ready.d.ts +9 -0
  24. package/dist/client/rpc/ensure-worker-ready.js +17 -0
  25. package/dist/client/rpc/expo-rpc-client.d.ts +1 -0
  26. package/dist/client/rpc/expo-rpc-client.js +4 -0
  27. package/dist/client/rpc/node-rpc-client.d.ts +1 -0
  28. package/dist/client/rpc/node-rpc-client.js +97 -5
  29. package/dist/client/rpc/rpc-client.js +115 -8
  30. package/dist/client/rpc/rpc-error.js +14 -1
  31. package/dist/commands/bundle/bare-pack.d.ts +13 -0
  32. package/dist/commands/bundle/bare-pack.js +54 -0
  33. package/dist/commands/bundle/constants.d.ts +7 -0
  34. package/dist/commands/bundle/constants.js +23 -0
  35. package/dist/commands/bundle/entry-gen.d.ts +2 -0
  36. package/dist/commands/bundle/entry-gen.js +48 -0
  37. package/dist/commands/bundle/index.d.ts +20 -0
  38. package/dist/commands/bundle/index.js +141 -0
  39. package/dist/commands/bundle/manifest.d.ts +21 -0
  40. package/dist/commands/bundle/manifest.js +193 -0
  41. package/dist/commands/bundle/plugins.d.ts +9 -0
  42. package/dist/commands/bundle/plugins.js +51 -0
  43. package/dist/commands/index.d.ts +5 -0
  44. package/dist/commands/index.js +2 -0
  45. package/dist/commands/verify/abi.d.ts +52 -0
  46. package/dist/commands/verify/abi.js +125 -0
  47. package/dist/commands/verify/addon-source.d.ts +43 -0
  48. package/dist/commands/verify/addon-source.js +75 -0
  49. package/dist/commands/verify/bundle-source.d.ts +12 -0
  50. package/dist/commands/verify/bundle-source.js +53 -0
  51. package/dist/commands/verify/index.d.ts +59 -0
  52. package/dist/commands/verify/index.js +371 -0
  53. package/dist/commands/verify/node-modules-source.d.ts +11 -0
  54. package/dist/commands/verify/node-modules-source.js +76 -0
  55. package/dist/commands/verify/prebuilds.d.ts +15 -0
  56. package/dist/commands/verify/prebuilds.js +39 -0
  57. package/dist/examples/classification/classify-image.d.ts +2 -0
  58. package/dist/examples/classification/classify-image.js +23 -0
  59. package/dist/examples/diffusion-txt2vid.d.ts +2 -0
  60. package/dist/examples/diffusion-txt2vid.js +81 -0
  61. package/dist/examples/plugins.js +3 -7
  62. package/dist/examples/tools/llamacpp-tools-gemma4.js +2 -5
  63. package/dist/examples/tools/llamacpp-tools-qwen35.js +2 -3
  64. package/dist/examples/transcription/parakeet-ctc-filesystem.js +18 -12
  65. package/dist/examples/transcription/parakeet-microphone-record.js +7 -12
  66. package/dist/examples/transcription/parakeet-microphone-stream.d.ts +2 -0
  67. package/dist/examples/transcription/parakeet-microphone-stream.js +79 -0
  68. package/dist/examples/transcription/parakeet-sortformer-streaming.d.ts +2 -0
  69. package/dist/examples/transcription/parakeet-sortformer-streaming.js +121 -0
  70. package/dist/examples/transcription/parakeet-sortformer.js +58 -55
  71. package/dist/examples/transcription/parakeet-tdt-filesystem.js +18 -15
  72. package/dist/examples/transcription/whispercpp-microphone-conversation.js +6 -1
  73. package/dist/examples/tts/chatterbox.js +7 -16
  74. package/dist/examples/tts/llm-to-tts-streaming.js +3 -10
  75. package/dist/examples/tts/supertonic-multilingual.js +4 -12
  76. package/dist/examples/tts/supertonic.js +5 -12
  77. package/dist/examples/vla-smolvla.d.ts +2 -0
  78. package/dist/examples/vla-smolvla.js +75 -0
  79. package/dist/examples/voice-assistant/voice-assistant.js +3 -10
  80. package/dist/expo/plugins/find-in-ancestor-node-modules.d.ts +14 -0
  81. package/dist/expo/plugins/find-in-ancestor-node-modules.js +24 -0
  82. package/dist/expo/plugins/get-project-root.d.ts +22 -0
  83. package/dist/expo/plugins/get-project-root.js +23 -0
  84. package/dist/expo/plugins/index.d.ts +4 -0
  85. package/dist/expo/plugins/index.js +4 -0
  86. package/dist/expo/plugins/resolve-sdk-package-dir.d.ts +9 -2
  87. package/dist/expo/plugins/resolve-sdk-package-dir.js +38 -11
  88. package/dist/expo/plugins/withDeviceInfo.js +2 -1
  89. package/dist/expo/plugins/withMobileBundle.d.ts +2 -17
  90. package/dist/expo/plugins/withMobileBundle.js +31 -85
  91. package/dist/index.d.ts +5 -3
  92. package/dist/index.js +9 -3
  93. package/dist/models/registry/models.d.ts +3711 -368
  94. package/dist/models/registry/models.js +7066 -3562
  95. package/dist/models/update-models/naming.js +22 -8
  96. package/dist/pear/pre.js +6 -2
  97. package/dist/schemas/cancel.d.ts +5 -0
  98. package/dist/schemas/cancel.js +1 -0
  99. package/dist/schemas/classification.d.ts +53 -0
  100. package/dist/schemas/classification.js +33 -0
  101. package/dist/schemas/common.d.ts +1001 -1739
  102. package/dist/schemas/common.js +6 -1
  103. package/dist/schemas/completion-event.d.ts +3 -0
  104. package/dist/schemas/completion-event.js +1 -0
  105. package/dist/schemas/completion-stream.d.ts +1 -0
  106. package/dist/schemas/download-asset.d.ts +13 -3
  107. package/dist/schemas/engine-addon-map.d.ts +3 -0
  108. package/dist/schemas/engine-addon-map.js +18 -5
  109. package/dist/schemas/get-model-info.d.ts +4 -0
  110. package/dist/schemas/get-model-info.js +2 -0
  111. package/dist/schemas/index.d.ts +3 -0
  112. package/dist/schemas/index.js +3 -0
  113. package/dist/schemas/llamacpp-config.d.ts +12 -2
  114. package/dist/schemas/load-model.d.ts +6152 -10764
  115. package/dist/schemas/load-model.js +75 -7
  116. package/dist/schemas/model-src-utils.d.ts +22 -2
  117. package/dist/schemas/model-types.d.ts +46 -6
  118. package/dist/schemas/model-types.js +28 -5
  119. package/dist/schemas/ocr.d.ts +5 -0
  120. package/dist/schemas/plugin.d.ts +20 -5
  121. package/dist/schemas/plugin.js +21 -4
  122. package/dist/schemas/registry.d.ts +44 -0
  123. package/dist/schemas/registry.js +5 -0
  124. package/dist/schemas/reload-config.d.ts +22 -2
  125. package/dist/schemas/sdcpp-config.d.ts +316 -1
  126. package/dist/schemas/sdcpp-config.js +232 -33
  127. package/dist/schemas/sdk-config.d.ts +654 -1324
  128. package/dist/schemas/sdk-config.js +16 -1
  129. package/dist/schemas/sdk-errors-client.d.ts +7 -0
  130. package/dist/schemas/sdk-errors-client.js +36 -1
  131. package/dist/schemas/sdk-errors-server.d.ts +3 -1
  132. package/dist/schemas/sdk-errors-server.js +20 -5
  133. package/dist/schemas/text-to-speech.d.ts +79 -696
  134. package/dist/schemas/text-to-speech.js +46 -19
  135. package/dist/schemas/transcription-config.d.ts +37 -226
  136. package/dist/schemas/transcription-config.js +76 -15
  137. package/dist/schemas/transcription.d.ts +63 -11
  138. package/dist/schemas/transcription.js +56 -2
  139. package/dist/schemas/translation-config.d.ts +60 -10
  140. package/dist/schemas/vla.d.ts +87 -0
  141. package/dist/schemas/vla.js +108 -0
  142. package/dist/scripts/run-unit-tests.js +8 -2
  143. package/dist/server/bare/ops/transcribe.d.ts +2 -1
  144. package/dist/server/bare/ops/transcribe.js +59 -34
  145. package/dist/server/bare/plugins/ggml-classification/ops/classify.d.ts +6 -0
  146. package/dist/server/bare/plugins/ggml-classification/ops/classify.js +30 -0
  147. package/dist/server/bare/plugins/ggml-classification/plugin.d.ts +32 -0
  148. package/dist/server/bare/plugins/ggml-classification/plugin.js +55 -0
  149. package/dist/server/bare/plugins/ggml-vla/ops/vla-hparams.d.ts +3 -0
  150. package/dist/server/bare/plugins/ggml-vla/ops/vla-hparams.js +12 -0
  151. package/dist/server/bare/plugins/ggml-vla/ops/vla-run.d.ts +3 -0
  152. package/dist/server/bare/plugins/ggml-vla/ops/vla-run.js +75 -0
  153. package/dist/server/bare/plugins/ggml-vla/plugin.d.ts +54 -0
  154. package/dist/server/bare/plugins/ggml-vla/plugin.js +57 -0
  155. package/dist/server/bare/plugins/index.d.ts +3 -1
  156. package/dist/server/bare/plugins/index.js +3 -1
  157. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +3 -0
  158. package/dist/server/bare/plugins/llamacpp-completion/ops/context-overflow.d.ts +29 -0
  159. package/dist/server/bare/plugins/llamacpp-completion/ops/context-overflow.js +65 -0
  160. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +6 -0
  161. package/dist/server/bare/plugins/llamacpp-completion/plugin.js +26 -0
  162. package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +25 -0
  163. package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +5 -0
  164. package/dist/server/bare/plugins/parakeet-transcription/plugin.d.ts +67 -246
  165. package/dist/server/bare/plugins/parakeet-transcription/plugin.js +66 -97
  166. package/dist/server/bare/plugins/sdcpp-generation/ops/video.d.ts +3 -0
  167. package/dist/server/bare/plugins/sdcpp-generation/ops/video.js +199 -0
  168. package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +200 -2
  169. package/dist/server/bare/plugins/sdcpp-generation/plugin.js +61 -8
  170. package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech-stream.js +2 -15
  171. package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech.js +2 -1
  172. package/dist/server/bare/plugins/tts-ggml/plugin.d.ts +75 -0
  173. package/dist/server/bare/plugins/tts-ggml/plugin.js +189 -0
  174. package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +29 -4
  175. package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +12 -1
  176. package/dist/server/bare/registry/model-config-utils.js +7 -0
  177. package/dist/server/bare/runtime/request-context.d.ts +1 -1
  178. package/dist/server/bare/runtime-lifecycle.js +9 -9
  179. package/dist/server/bare/types/addon-responses.d.ts +1 -0
  180. package/dist/server/env.js +4 -1
  181. package/dist/server/plugins/index.d.ts +1 -1
  182. package/dist/server/plugins/index.js +1 -1
  183. package/dist/server/plugins/registry.d.ts +1 -0
  184. package/dist/server/plugins/registry.js +5 -0
  185. package/dist/server/rpc/handler-registry.js +4 -0
  186. package/dist/server/rpc/handler-selection.d.ts +9 -0
  187. package/dist/server/rpc/handler-selection.js +14 -0
  188. package/dist/server/rpc/handler-utils.js +5 -13
  189. package/dist/server/rpc/handlers/classify.d.ts +3 -0
  190. package/dist/server/rpc/handlers/classify.js +4 -0
  191. package/dist/server/rpc/handlers/index.d.ts +2 -0
  192. package/dist/server/rpc/handlers/index.js +2 -0
  193. package/dist/server/rpc/handlers/load-model/handler.js +4 -1
  194. package/dist/server/rpc/handlers/load-model/resolve.js +9 -0
  195. package/dist/server/rpc/handlers/video-stream.d.ts +3 -0
  196. package/dist/server/rpc/handlers/video-stream.js +4 -0
  197. package/dist/server/rpc/profiling/operation-metrics.js +23 -0
  198. package/dist/server/utils/config-transform.js +1 -1
  199. package/dist/server/utils/tools/parsers/qwen35.js +7 -3
  200. package/dist/server/worker.js +14 -11
  201. package/dist/utils/errors-client.d.ts +23 -0
  202. package/dist/utils/errors-client.js +40 -0
  203. package/dist/utils/errors-server.d.ts +30 -2
  204. package/dist/utils/errors-server.js +51 -3
  205. package/package.json +66 -80
  206. package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +0 -490
  207. package/dist/server/bare/plugins/onnx-tts/plugin.js +0 -272
  208. package/dist/server/bare/plugins/onnx-tts/wav-helper.d.ts +0 -14
  209. package/dist/server/bare/plugins/onnx-tts/wav-helper.js +0 -107
  210. /package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech-stream.d.ts +0 -0
  211. /package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech.d.ts +0 -0
@@ -10,7 +10,7 @@ import { fileURLToPath } from "node:url";
10
10
  import { initializeConfig } from "../init-hooks.js";
11
11
  import { resolveConfig } from "../config-loader/resolve-config.node.js";
12
12
  import { getClientLogger } from "../../logging/index.js";
13
- import { RPCInitTimeoutError } from "../../utils/errors-client.js";
13
+ import { RPCInitTimeoutError, WorkerCrashedError, WorkerShutdownError, } from "../../utils/errors-client.js";
14
14
  const RPC_INIT_TIMEOUT_MS = 30_000;
15
15
  const logger = getClientLogger();
16
16
  let rpcInstance = null;
@@ -19,6 +19,11 @@ let bareWorkerProc = null;
19
19
  let ipcServer = null;
20
20
  let currentSocketPath = null;
21
21
  let closePromise = null;
22
+ // Aborted when the worker dies (crash) or close() runs (planned). Used
23
+ // to unblock in-flight `req.reply()` callers — bare-rpc's `_onerror`
24
+ // does not iterate `_outgoingRequests`, so without this signal they
25
+ // would hang on the dead socket.
26
+ let workerLifeController = null;
22
27
  /**
23
28
  * Find project root by looking for package.json (sync version)
24
29
  * Safe to call at module load time
@@ -148,9 +153,42 @@ function snapshotAndResetState() {
148
153
  bareWorkerProc = null;
149
154
  ipcServer = null;
150
155
  currentSocketPath = null;
156
+ workerLifeController = null;
151
157
  return { workerToClose, serverToClose, socketPathToClose };
152
158
  }
159
+ export function getWorkerLifeSignal() {
160
+ return workerLifeController?.signal ?? null;
161
+ }
162
+ // `spawn` carries this listener's own captured resources. Module-level
163
+ // state may belong to a newer ensureRPC() by the time we fire.
164
+ function handlePostHandshakeExit(code, exitSignal, spawn) {
165
+ if (spawn.controller.signal.aborted) {
166
+ // close() already handled teardown.
167
+ logger.debug(`Bare worker exited after planned shutdown (code=${code}, signal=${exitSignal})`);
168
+ return;
169
+ }
170
+ logger.info(`🪦 Bare worker exited post-handshake (code=${code}, signal=${exitSignal})`);
171
+ // Only clear module state if we're still the active spawn.
172
+ if (workerLifeController === spawn.controller) {
173
+ rpcInstance = null;
174
+ rpcPromise = null;
175
+ bareWorkerProc = null;
176
+ ipcServer = null;
177
+ currentSocketPath = null;
178
+ workerLifeController = null;
179
+ }
180
+ try {
181
+ spawn.server.close();
182
+ }
183
+ catch (err) {
184
+ logger.debug("Failed to close IPC server after worker crash", { err });
185
+ }
186
+ spawn.controller.abort(new WorkerCrashedError(code, exitSignal));
187
+ bestEffortUnlinkSocket(spawn.socketPath);
188
+ }
153
189
  function closeSyncForExit() {
190
+ // Abort before kill so the exit handler sees planned intent.
191
+ workerLifeController?.abort(new WorkerShutdownError());
154
192
  const { workerToClose, serverToClose, socketPathToClose } = snapshotAndResetState();
155
193
  if (workerToClose) {
156
194
  try {
@@ -180,6 +218,9 @@ async function ensureRPC() {
180
218
  }
181
219
  const socketPath = createSocketPath();
182
220
  currentSocketPath = socketPath;
221
+ // Allocated here so the init race and exit handler share one controller.
222
+ const spawnController = new AbortController();
223
+ workerLifeController = spawnController;
183
224
  rpcPromise = new Promise((resolve, reject) => {
184
225
  let settled = false;
185
226
  const timer = setTimeout(() => {
@@ -191,6 +232,7 @@ async function ensureRPC() {
191
232
  bareWorkerProc = null;
192
233
  ipcServer = null;
193
234
  currentSocketPath = null;
235
+ workerLifeController = null;
194
236
  reject(new RPCInitTimeoutError(RPC_INIT_TIMEOUT_MS));
195
237
  }, RPC_INIT_TIMEOUT_MS);
196
238
  ipcServer = createServer((socket) => {
@@ -211,23 +253,35 @@ async function ensureRPC() {
211
253
  bareWorkerProc = null;
212
254
  ipcServer = null;
213
255
  currentSocketPath = null;
256
+ workerLifeController = null;
214
257
  reject(error);
215
258
  });
216
259
  ipcServer.listen(socketPath, () => {
260
+ const spawnResources = {
261
+ controller: spawnController,
262
+ server: ipcServer,
263
+ socketPath,
264
+ };
217
265
  bareWorkerProc = spawn("bare", {
218
266
  args: [
219
267
  WORKER_PATH,
220
268
  JSON.stringify({
221
269
  QVAC_IPC_SOCKET_PATH: socketPath,
222
- HOME_DIR: os.homedir(),
270
+ // Snap's HOME can be revision-scoped; SNAP_USER_COMMON is stable.
271
+ HOME_DIR: process.env["SNAP_USER_COMMON"]
272
+ ? String(process.env["SNAP_USER_COMMON"])
273
+ : os.homedir(),
223
274
  }),
224
275
  ],
225
276
  stdio: ["inherit", "inherit", "inherit"],
226
277
  });
227
278
  if (bareWorkerProc) {
228
- bareWorkerProc.on("exit", (code) => {
229
- if (settled)
279
+ bareWorkerProc.on("exit", (code, exitSignal) => {
280
+ if (settled) {
281
+ handlePostHandshakeExit(code, exitSignal, spawnResources);
230
282
  return;
283
+ }
284
+ // Worker died before handshake — reject the init promise.
231
285
  settled = true;
232
286
  clearTimeout(timer);
233
287
  rpcPromise = null;
@@ -235,6 +289,7 @@ async function ensureRPC() {
235
289
  bareWorkerProc = null;
236
290
  ipcServer = null;
237
291
  currentSocketPath = null;
292
+ workerLifeController = null;
238
293
  reject(new RPCInitTimeoutError(RPC_INIT_TIMEOUT_MS, new Error(`Worker process exited with code ${code} before IPC connection was established`)));
239
294
  });
240
295
  }
@@ -245,9 +300,25 @@ async function ensureRPC() {
245
300
  runtime: "node",
246
301
  platform: process.platform,
247
302
  };
248
- await initializeConfig(rpc, resolveConfig, runtimeContext);
303
+ // init-hooks calls bare-rpc directly, bypassing rpc-client's race.
304
+ await Promise.race([
305
+ initializeConfig(rpc, resolveConfig, runtimeContext),
306
+ rejectOnAbort(spawnController.signal),
307
+ ]);
249
308
  return rpc;
250
309
  }
310
+ function rejectOnAbort(signal) {
311
+ return new Promise((_, reject) => {
312
+ const fail = () => reject(signal.reason instanceof Error
313
+ ? signal.reason
314
+ : new WorkerCrashedError(null, null));
315
+ if (signal.aborted) {
316
+ fail();
317
+ return;
318
+ }
319
+ signal.addEventListener("abort", fail, { once: true });
320
+ });
321
+ }
251
322
  export async function getRPC() {
252
323
  return ensureRPC();
253
324
  }
@@ -257,6 +328,24 @@ export async function createDuplexSession(payload, commandId) {
257
328
  const requestStream = req.createRequestStream();
258
329
  const responseStream = req.createResponseStream({ encoding: "utf-8" });
259
330
  requestStream.write(payload, "utf-8");
331
+ // Destroy on worker death so consumers' for-await throws instead of hangs.
332
+ const lifeSignal = workerLifeController?.signal;
333
+ if (lifeSignal && !lifeSignal.aborted) {
334
+ const onAbort = () => {
335
+ const err = lifeSignal.reason instanceof Error
336
+ ? lifeSignal.reason
337
+ : new WorkerCrashedError(null, null);
338
+ try {
339
+ requestStream.destroy?.(err);
340
+ }
341
+ catch { }
342
+ try {
343
+ responseStream.destroy?.(err);
344
+ }
345
+ catch { }
346
+ };
347
+ lifeSignal.addEventListener("abort", onAbort, { once: true });
348
+ }
260
349
  return { requestStream, responseStream };
261
350
  }
262
351
  export async function close() {
@@ -267,6 +356,9 @@ export async function close() {
267
356
  if (!rpcInstance && !rpcPromise && !bareWorkerProc && !ipcServer)
268
357
  return;
269
358
  logger.info("🧹 Closing RPC client");
359
+ // Abort before kill: exit handler sees planned intent; any in-flight
360
+ // caller (contract violators) rejects with WorkerShutdownError.
361
+ workerLifeController?.abort(new WorkerShutdownError());
270
362
  const { workerToClose, serverToClose, socketPathToClose } = snapshotAndResetState();
271
363
  closePromise = (async () => {
272
364
  if (workerToClose) {
@@ -2,7 +2,8 @@ import { requestSchema, responseSchema, PROFILING_TRAILER_KEY, } from "../../sch
2
2
  import { reconstructError } from "./rpc-error.js";
3
3
  import { withTimeout, withTimeoutStream } from "../../utils/withTimeout.js";
4
4
  import { getClientLogger, summarizeRequest } from "../../logging/index.js";
5
- import { getRPC, close as closeRPC, createDuplexSession } from "#rpc";
5
+ import { getRPC, close as closeRPC, createDuplexSession, getWorkerLifeSignal, } from "#rpc";
6
+ import { WorkerCrashedError } from "../../utils/errors-client.js";
6
7
  import { nowMs, shouldProfile, shouldIncludeServerBreakdown, generateId as createProfileId, createProfilingMeta, createProfilingDisabledMeta, injectProfilingMetaIntoObject, extractProfilingMeta, stripProfilingMeta, recordFailure, } from "../../profiling/index.js";
7
8
  import { createClientTimings, createClientStreamTimings, recordClientEvents, recordClientStreamEvents, cacheConnectionTime, flushConnectionTime, resetConnectionTracking, } from "./profiling/index.js";
8
9
  const logger = getClientLogger();
@@ -13,6 +14,98 @@ function getNextCommandId() {
13
14
  commandCounter = (commandCounter + 1) % Number.MAX_SAFE_INTEGER;
14
15
  return commandCounter;
15
16
  }
17
+ // Race in-flight reply/stream pulls against the worker-life signal —
18
+ // bare-rpc's `_onerror` does not iterate `_outgoingRequests`, so without
19
+ // this they hang on a dead socket.
20
+ function lifeSignalAbortError(signal) {
21
+ if (signal.reason instanceof Error)
22
+ return signal.reason;
23
+ return new WorkerCrashedError(null, null);
24
+ }
25
+ async function awaitWithLifeSignal(p) {
26
+ // Null on Bare-direct / Expo and pre-spawn.
27
+ const lifeSignal = getWorkerLifeSignal();
28
+ if (!lifeSignal)
29
+ return p;
30
+ if (lifeSignal.aborted)
31
+ throw lifeSignalAbortError(lifeSignal);
32
+ return await new Promise((resolve, reject) => {
33
+ let settled = false;
34
+ const onAbort = () => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ lifeSignal.removeEventListener("abort", onAbort);
39
+ reject(lifeSignalAbortError(lifeSignal));
40
+ };
41
+ lifeSignal.addEventListener("abort", onAbort, { once: true });
42
+ p.then((value) => {
43
+ if (settled)
44
+ return;
45
+ settled = true;
46
+ lifeSignal.removeEventListener("abort", onAbort);
47
+ resolve(value);
48
+ }, (err) => {
49
+ if (settled)
50
+ return;
51
+ settled = true;
52
+ lifeSignal.removeEventListener("abort", onAbort);
53
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- forwarding upstream rejection as-is
54
+ reject(err);
55
+ });
56
+ });
57
+ }
58
+ async function* iterateWithLifeSignal(source) {
59
+ // Null on Bare-direct / Expo and pre-spawn.
60
+ const lifeSignal = getWorkerLifeSignal();
61
+ if (!lifeSignal) {
62
+ yield* source;
63
+ return;
64
+ }
65
+ if (lifeSignal.aborted)
66
+ throw lifeSignalAbortError(lifeSignal);
67
+ try {
68
+ while (true) {
69
+ const nextP = source.next();
70
+ const result = await new Promise((resolve, reject) => {
71
+ let settled = false;
72
+ const onAbort = () => {
73
+ if (settled)
74
+ return;
75
+ settled = true;
76
+ lifeSignal.removeEventListener("abort", onAbort);
77
+ reject(lifeSignalAbortError(lifeSignal));
78
+ };
79
+ lifeSignal.addEventListener("abort", onAbort, { once: true });
80
+ nextP.then((value) => {
81
+ if (settled)
82
+ return;
83
+ settled = true;
84
+ lifeSignal.removeEventListener("abort", onAbort);
85
+ resolve(value);
86
+ }, (err) => {
87
+ if (settled)
88
+ return;
89
+ settled = true;
90
+ lifeSignal.removeEventListener("abort", onAbort);
91
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- forwarding upstream rejection as-is
92
+ reject(err);
93
+ });
94
+ });
95
+ if (result.done)
96
+ return;
97
+ yield result.value;
98
+ }
99
+ }
100
+ finally {
101
+ // Fire-and-forget cleanup. If the source is suspended on a never-
102
+ // settling read (worker dead), `.return()` propagates an abrupt
103
+ // completion through the for-await chain so inner streams get
104
+ // destroyed. The `.catch` swallows the case where the underlying
105
+ // stream cannot honour `.return()` cleanly.
106
+ void source.return?.(undefined).catch(() => { });
107
+ }
108
+ }
16
109
  function checkAndThrowError(response) {
17
110
  if (response.type === "error") {
18
111
  // Use the typed-error reconstructor map in `rpc-error.ts` so the
@@ -23,12 +116,28 @@ function checkAndThrowError(response) {
23
116
  throw reconstructError(response);
24
117
  }
25
118
  }
119
+ function invalidateRPCInstance() {
120
+ rpcInstance = null;
121
+ firstConnectionPending = true;
122
+ resetConnectionTracking();
123
+ }
26
124
  async function getRPCInstance() {
27
125
  if (rpcInstance)
28
126
  return { rpc: await rpcInstance };
29
127
  const connectionStart = firstConnectionPending ? nowMs() : null;
30
128
  rpcInstance = getRPC();
31
129
  const rpc = await rpcInstance;
130
+ // Drop the cache on worker death so the next call respawns and
131
+ // records connectionMs again as a "first" connection.
132
+ const lifeSignal = getWorkerLifeSignal();
133
+ if (lifeSignal?.aborted) {
134
+ invalidateRPCInstance();
135
+ }
136
+ else if (lifeSignal) {
137
+ lifeSignal.addEventListener("abort", invalidateRPCInstance, {
138
+ once: true,
139
+ });
140
+ }
32
141
  if (connectionStart !== null && firstConnectionPending) {
33
142
  firstConnectionPending = false;
34
143
  return { rpc, connectionMs: nowMs() - connectionStart };
@@ -63,7 +172,7 @@ async function sendBase(request, rpc, options, signalDisable = false) {
63
172
  : parsedRequest;
64
173
  const payload = JSON.stringify(payloadObj);
65
174
  req.send(payload, "utf-8");
66
- const response = await withTimeout(req.reply("utf-8"), options?.timeout);
175
+ const response = await awaitWithLifeSignal(withTimeout(req.reply("utf-8"), options?.timeout));
67
176
  const resPayload = responseSchema.parse(JSON.parse(response?.toString() || "{}"));
68
177
  logger.debug("ResPayload", { type: resPayload.type });
69
178
  checkAndThrowError(resPayload);
@@ -87,7 +196,7 @@ async function sendProfiled(request, rpc, options) {
87
196
  timings.requestStringifyMs = nowMs() - stringifyStart;
88
197
  timings.sendStart = nowMs();
89
198
  req.send(payload, "utf-8");
90
- const response = await withTimeout(req.reply("utf-8"), options?.timeout);
199
+ const response = await awaitWithLifeSignal(withTimeout(req.reply("utf-8"), options?.timeout));
91
200
  timings.firstResponseAt = nowMs();
92
201
  const parseStart = nowMs();
93
202
  const rawParsed = JSON.parse(response?.toString() || "{}");
@@ -140,7 +249,7 @@ async function* streamBase(request, rpc, options = {}, signalDisable = false) {
140
249
  }
141
250
  }
142
251
  const streamWithTimeout = withTimeoutStream(processStream(), options?.timeout);
143
- for await (const chunk of streamWithTimeout) {
252
+ for await (const chunk of iterateWithLifeSignal(streamWithTimeout)) {
144
253
  buffer += chunk.toString();
145
254
  // Process complete lines (newline-delimited JSON)
146
255
  const lines = buffer.split("\n");
@@ -181,7 +290,7 @@ async function* streamProfiled(request, rpc, options = {}) {
181
290
  }
182
291
  }
183
292
  const streamWithTimeout = withTimeoutStream(processStream(), options?.timeout);
184
- for await (const chunk of streamWithTimeout) {
293
+ for await (const chunk of iterateWithLifeSignal(streamWithTimeout)) {
185
294
  const chunkTime = nowMs();
186
295
  if (timings.firstChunkAt === undefined) {
187
296
  timings.firstChunkAt = chunkTime;
@@ -361,8 +470,6 @@ async function duplexProfiled(request, options = {}) {
361
470
  export async function close() {
362
471
  if (!rpcInstance)
363
472
  return;
364
- rpcInstance = null;
365
- firstConnectionPending = true;
366
- resetConnectionTracking();
473
+ invalidateRPCInstance();
367
474
  await closeRPC();
368
475
  }
@@ -1,4 +1,4 @@
1
- import { RequestIdConflictError, RequestNotFoundError, RequestRejectedByPolicyError, } from "../../utils/errors-server.js";
1
+ import { ContextOverflowError, RequestIdConflictError, RequestNotFoundError, RequestRejectedByPolicyError, } from "../../utils/errors-server.js";
2
2
  export class RPCError extends Error {
3
3
  timestamp;
4
4
  remoteStack;
@@ -59,6 +59,16 @@ function readStringField(fields, key, fallback) {
59
59
  const value = fields?.[key];
60
60
  return typeof value === "string" ? value : fallback;
61
61
  }
62
+ /** Read a number field from the `typedFields` envelope, returning `undefined` if missing or non-number. */
63
+ function readOptionalNumberField(fields, key) {
64
+ const value = fields?.[key];
65
+ return typeof value === "number" ? value : undefined;
66
+ }
67
+ /** Read a string field, returning `undefined` (not a fallback string) if missing. */
68
+ function readOptionalStringField(fields, key) {
69
+ const value = fields?.[key];
70
+ return typeof value === "string" && value.length > 0 ? value : undefined;
71
+ }
62
72
  /**
63
73
  * Map of server-thrown `QvacErrorBase` subclasses that need to survive
64
74
  * the RPC boundary as their original class — `err instanceof
@@ -98,6 +108,9 @@ const RECONSTRUCTORS = {
98
108
  REQUEST_REJECTED_BY_POLICY: (response) => {
99
109
  return new RequestRejectedByPolicyError(readStringField(response.typedFields, "requestId", ""), readStringField(response.typedFields, "kind", ""), readStringField(response.typedFields, "modelId", ""), readStringField(response.typedFields, "reason", response.message), response.cause);
100
110
  },
111
+ CONTEXT_OVERFLOW: (response) => {
112
+ return new ContextOverflowError(readOptionalNumberField(response.typedFields, "promptTokens"), readOptionalNumberField(response.typedFields, "ctxSize"), readOptionalStringField(response.typedFields, "modelId"), response.cause);
113
+ },
101
114
  };
102
115
  /**
103
116
  * Rebuild the original server-thrown typed error from its serialised
@@ -0,0 +1,13 @@
1
+ import type { Logger } from "../../logging/types";
2
+ interface RunBarePackOptions {
3
+ entryPath: string;
4
+ outputPath: string;
5
+ hosts: string[];
6
+ importsMapPath: string;
7
+ deferModules: string[];
8
+ quiet: boolean;
9
+ logger: Logger;
10
+ }
11
+ export declare function runBarePack(options: RunBarePackOptions): Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=bare-pack.d.ts.map
@@ -0,0 +1,54 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { spawn } from "node:child_process";
5
+ import { platform, execPath } from "node:process";
6
+ import { BarePackNotInstalledError, BarePackError, } from "../../utils/errors-client.js";
7
+ const require = createRequire(import.meta.url);
8
+ function resolveBarePackBin() {
9
+ try {
10
+ const barePackPkgPath = require.resolve("bare-pack/package");
11
+ const barePackDir = path.dirname(barePackPkgPath);
12
+ return path.join(barePackDir, "bin.js");
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export async function runBarePack(options) {
19
+ const { entryPath, outputPath, hosts, importsMapPath, deferModules, quiet, logger, } = options;
20
+ const barePackBin = resolveBarePackBin();
21
+ if (!barePackBin || !fs.existsSync(barePackBin)) {
22
+ throw new BarePackNotInstalledError();
23
+ }
24
+ return new Promise((resolve, reject) => {
25
+ const hostArgs = hosts.flatMap((h) => ["--host", h]);
26
+ const deferArgs = deferModules.flatMap((m) => ["--defer", m]);
27
+ const args = [
28
+ ...hostArgs,
29
+ "--linked",
30
+ "--imports",
31
+ importsMapPath,
32
+ ...deferArgs,
33
+ "--out",
34
+ outputPath,
35
+ entryPath,
36
+ ];
37
+ const isWindows = platform === "win32";
38
+ const command = isWindows ? execPath : barePackBin;
39
+ const spawnArgs = isWindows ? [barePackBin, ...args] : args;
40
+ logger.debug(`\nšŸ“¦ Running: ${command} ${spawnArgs.join(" ")}`);
41
+ const proc = spawn(command, spawnArgs, {
42
+ stdio: quiet ? "ignore" : "inherit",
43
+ });
44
+ proc.on("close", (code) => {
45
+ if (code === 0) {
46
+ resolve();
47
+ }
48
+ else {
49
+ reject(new BarePackError(code ?? 1, entryPath, outputPath));
50
+ }
51
+ });
52
+ proc.on("error", reject);
53
+ });
54
+ }
@@ -0,0 +1,7 @@
1
+ export declare const BUILTIN_PLUGINS: Record<string, {
2
+ exportName: string;
3
+ }>;
4
+ export declare const BUILTIN_SUFFIXES: string[];
5
+ export declare const DEFAULT_HOSTS: string[];
6
+ export declare const DEFAULT_SDK_NAME = "@qvac/sdk";
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,23 @@
1
+ export const BUILTIN_PLUGINS = {
2
+ "llamacpp-completion": { exportName: "llmPlugin" },
3
+ "llamacpp-embedding": { exportName: "embeddingsPlugin" },
4
+ "whispercpp-transcription": { exportName: "whisperPlugin" },
5
+ "parakeet-transcription": { exportName: "parakeetPlugin" },
6
+ "nmtcpp-translation": { exportName: "nmtPlugin" },
7
+ "tts-ggml": { exportName: "ttsPlugin" },
8
+ "onnx-ocr": { exportName: "ocrPlugin" },
9
+ "sdcpp-generation": { exportName: "diffusionPlugin" },
10
+ };
11
+ export const BUILTIN_SUFFIXES = Object.keys(BUILTIN_PLUGINS);
12
+ export const DEFAULT_HOSTS = [
13
+ "darwin-arm64",
14
+ "darwin-x64",
15
+ "linux-arm64",
16
+ "linux-x64",
17
+ "win32-x64",
18
+ "android-arm64",
19
+ "ios-arm64",
20
+ "ios-arm64-simulator",
21
+ "ios-x64-simulator",
22
+ ];
23
+ export const DEFAULT_SDK_NAME = "@qvac/sdk";
@@ -0,0 +1,2 @@
1
+ export declare function generateWorkerEntry(pluginSpecifiers: string[], sdkName: string): string;
2
+ //# sourceMappingURL=entry-gen.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { parseBuiltinSpecifier } from "./plugins.js";
2
+ export function generateWorkerEntry(pluginSpecifiers, sdkName) {
3
+ const imports = [];
4
+ const registrations = [];
5
+ let varIndex = 0;
6
+ for (const specifier of pluginSpecifiers) {
7
+ const builtin = parseBuiltinSpecifier(specifier, sdkName);
8
+ if (builtin) {
9
+ imports.push(`import { ${builtin.exportName} } from "${sdkName}/${builtin.suffix}/plugin";`);
10
+ registrations.push(`registerPlugin(${builtin.exportName});`);
11
+ }
12
+ else {
13
+ const varName = `customPlugin${varIndex++}`;
14
+ imports.push(`import ${varName} from "${specifier}";`);
15
+ registrations.push(`registerPlugin(${varName});`);
16
+ }
17
+ }
18
+ const importsStr = imports.join("\n");
19
+ const registrationsStr = registrations.join("\n");
20
+ const pluginsList = pluginSpecifiers.map((p) => `* - ${p}`).join("\n");
21
+ return `/**
22
+ * QVAC SDK Worker Entry (auto-generated)
23
+ * Generated by: @qvac/sdk/commands bundleSdk
24
+ * Plugins: ${pluginSpecifiers.length}
25
+ *
26
+ ${pluginsList}
27
+ */
28
+
29
+ import { initializeWorkerCore, ensureRPCSetup } from "${sdkName}/worker-core";
30
+ import { registerPlugin } from "${sdkName}/plugins";
31
+ import { getServerLogger } from "${sdkName}/logging";
32
+
33
+ ${importsStr}
34
+
35
+ const { hasRPCConfig } = initializeWorkerCore();
36
+
37
+ const logger = getServerLogger();
38
+ logger.info("🐻 QVAC Worker (custom bundle)");
39
+ logger.info("šŸ“¦ Plugins: ${pluginSpecifiers.length}");
40
+
41
+ ${registrationsStr}
42
+
43
+ // Auto-setup RPC if config present
44
+ if (hasRPCConfig) {
45
+ ensureRPCSetup();
46
+ }
47
+ `;
48
+ }
@@ -0,0 +1,20 @@
1
+ export interface BundleSdkOptions {
2
+ projectRoot?: string | undefined;
3
+ configPath?: string | undefined;
4
+ sdkPath?: string | undefined;
5
+ hosts?: string[] | undefined;
6
+ defer?: string[] | undefined;
7
+ quiet?: boolean | undefined;
8
+ verbose?: boolean | undefined;
9
+ }
10
+ export interface BundleSdkResult {
11
+ bundlePath: string;
12
+ plugins: string[];
13
+ addons: string[];
14
+ entryPaths: {
15
+ worker: string;
16
+ };
17
+ manifestPath: string;
18
+ }
19
+ export declare function bundleSdk(options?: BundleSdkOptions): Promise<BundleSdkResult>;
20
+ //# sourceMappingURL=index.d.ts.map