@qvac/sdk 0.11.0 → 0.12.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 (205) hide show
  1. package/NOTICE +1427 -39
  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.js +62 -66
  22. package/dist/client/rpc/ensure-worker-ready.d.ts +9 -0
  23. package/dist/client/rpc/ensure-worker-ready.js +17 -0
  24. package/dist/client/rpc/node-rpc-client.js +4 -1
  25. package/dist/client/rpc/rpc-error.js +14 -1
  26. package/dist/commands/bundle/bare-pack.d.ts +13 -0
  27. package/dist/commands/bundle/bare-pack.js +54 -0
  28. package/dist/commands/bundle/constants.d.ts +7 -0
  29. package/dist/commands/bundle/constants.js +23 -0
  30. package/dist/commands/bundle/entry-gen.d.ts +2 -0
  31. package/dist/commands/bundle/entry-gen.js +48 -0
  32. package/dist/commands/bundle/index.d.ts +20 -0
  33. package/dist/commands/bundle/index.js +141 -0
  34. package/dist/commands/bundle/manifest.d.ts +21 -0
  35. package/dist/commands/bundle/manifest.js +193 -0
  36. package/dist/commands/bundle/plugins.d.ts +9 -0
  37. package/dist/commands/bundle/plugins.js +51 -0
  38. package/dist/commands/index.d.ts +5 -0
  39. package/dist/commands/index.js +2 -0
  40. package/dist/commands/verify/abi.d.ts +52 -0
  41. package/dist/commands/verify/abi.js +125 -0
  42. package/dist/commands/verify/addon-source.d.ts +43 -0
  43. package/dist/commands/verify/addon-source.js +75 -0
  44. package/dist/commands/verify/bundle-source.d.ts +12 -0
  45. package/dist/commands/verify/bundle-source.js +53 -0
  46. package/dist/commands/verify/index.d.ts +59 -0
  47. package/dist/commands/verify/index.js +371 -0
  48. package/dist/commands/verify/node-modules-source.d.ts +11 -0
  49. package/dist/commands/verify/node-modules-source.js +76 -0
  50. package/dist/commands/verify/prebuilds.d.ts +15 -0
  51. package/dist/commands/verify/prebuilds.js +39 -0
  52. package/dist/examples/classification/classify-image.d.ts +2 -0
  53. package/dist/examples/classification/classify-image.js +23 -0
  54. package/dist/examples/diffusion-txt2vid.d.ts +2 -0
  55. package/dist/examples/diffusion-txt2vid.js +81 -0
  56. package/dist/examples/plugins.js +3 -7
  57. package/dist/examples/tools/llamacpp-tools-gemma4.js +2 -5
  58. package/dist/examples/tools/llamacpp-tools-qwen35.js +2 -3
  59. package/dist/examples/transcription/parakeet-ctc-filesystem.js +18 -12
  60. package/dist/examples/transcription/parakeet-microphone-record.js +7 -12
  61. package/dist/examples/transcription/parakeet-microphone-stream.d.ts +2 -0
  62. package/dist/examples/transcription/parakeet-microphone-stream.js +79 -0
  63. package/dist/examples/transcription/parakeet-sortformer-streaming.d.ts +2 -0
  64. package/dist/examples/transcription/parakeet-sortformer-streaming.js +121 -0
  65. package/dist/examples/transcription/parakeet-sortformer.js +58 -55
  66. package/dist/examples/transcription/parakeet-tdt-filesystem.js +18 -15
  67. package/dist/examples/transcription/whispercpp-microphone-conversation.js +6 -1
  68. package/dist/examples/tts/chatterbox.js +7 -16
  69. package/dist/examples/tts/llm-to-tts-streaming.js +3 -10
  70. package/dist/examples/tts/supertonic-multilingual.js +4 -12
  71. package/dist/examples/tts/supertonic.js +5 -12
  72. package/dist/examples/vla-smolvla.d.ts +2 -0
  73. package/dist/examples/vla-smolvla.js +75 -0
  74. package/dist/examples/voice-assistant/voice-assistant.js +3 -10
  75. package/dist/expo/plugins/find-in-ancestor-node-modules.d.ts +14 -0
  76. package/dist/expo/plugins/find-in-ancestor-node-modules.js +24 -0
  77. package/dist/expo/plugins/get-project-root.d.ts +22 -0
  78. package/dist/expo/plugins/get-project-root.js +23 -0
  79. package/dist/expo/plugins/index.d.ts +4 -0
  80. package/dist/expo/plugins/index.js +4 -0
  81. package/dist/expo/plugins/resolve-sdk-package-dir.d.ts +9 -2
  82. package/dist/expo/plugins/resolve-sdk-package-dir.js +38 -11
  83. package/dist/expo/plugins/withDeviceInfo.js +2 -1
  84. package/dist/expo/plugins/withMobileBundle.d.ts +2 -17
  85. package/dist/expo/plugins/withMobileBundle.js +31 -85
  86. package/dist/index.d.ts +4 -3
  87. package/dist/index.js +4 -3
  88. package/dist/models/registry/models.d.ts +3711 -368
  89. package/dist/models/registry/models.js +7066 -3562
  90. package/dist/models/update-models/naming.js +22 -8
  91. package/dist/pear/pre.js +6 -2
  92. package/dist/schemas/cancel.d.ts +5 -0
  93. package/dist/schemas/cancel.js +1 -0
  94. package/dist/schemas/classification.d.ts +53 -0
  95. package/dist/schemas/classification.js +33 -0
  96. package/dist/schemas/common.d.ts +1001 -1739
  97. package/dist/schemas/common.js +6 -1
  98. package/dist/schemas/completion-event.d.ts +3 -0
  99. package/dist/schemas/completion-event.js +1 -0
  100. package/dist/schemas/completion-stream.d.ts +1 -0
  101. package/dist/schemas/download-asset.d.ts +13 -3
  102. package/dist/schemas/engine-addon-map.d.ts +3 -0
  103. package/dist/schemas/engine-addon-map.js +18 -5
  104. package/dist/schemas/get-model-info.d.ts +4 -0
  105. package/dist/schemas/get-model-info.js +2 -0
  106. package/dist/schemas/index.d.ts +3 -0
  107. package/dist/schemas/index.js +3 -0
  108. package/dist/schemas/llamacpp-config.d.ts +12 -2
  109. package/dist/schemas/load-model.d.ts +6152 -10764
  110. package/dist/schemas/load-model.js +75 -7
  111. package/dist/schemas/model-src-utils.d.ts +22 -2
  112. package/dist/schemas/model-types.d.ts +46 -6
  113. package/dist/schemas/model-types.js +28 -5
  114. package/dist/schemas/ocr.d.ts +5 -0
  115. package/dist/schemas/plugin.d.ts +20 -5
  116. package/dist/schemas/plugin.js +21 -4
  117. package/dist/schemas/registry.d.ts +44 -0
  118. package/dist/schemas/registry.js +5 -0
  119. package/dist/schemas/reload-config.d.ts +22 -2
  120. package/dist/schemas/sdcpp-config.d.ts +316 -1
  121. package/dist/schemas/sdcpp-config.js +232 -33
  122. package/dist/schemas/sdk-config.d.ts +654 -1324
  123. package/dist/schemas/sdk-config.js +16 -1
  124. package/dist/schemas/sdk-errors-client.d.ts +5 -0
  125. package/dist/schemas/sdk-errors-client.js +26 -1
  126. package/dist/schemas/sdk-errors-server.d.ts +3 -1
  127. package/dist/schemas/sdk-errors-server.js +20 -5
  128. package/dist/schemas/text-to-speech.d.ts +79 -696
  129. package/dist/schemas/text-to-speech.js +46 -19
  130. package/dist/schemas/transcription-config.d.ts +37 -226
  131. package/dist/schemas/transcription-config.js +76 -15
  132. package/dist/schemas/transcription.d.ts +63 -11
  133. package/dist/schemas/transcription.js +56 -2
  134. package/dist/schemas/translation-config.d.ts +60 -10
  135. package/dist/schemas/vla.d.ts +87 -0
  136. package/dist/schemas/vla.js +108 -0
  137. package/dist/scripts/run-unit-tests.js +8 -2
  138. package/dist/server/bare/ops/transcribe.d.ts +2 -1
  139. package/dist/server/bare/ops/transcribe.js +59 -34
  140. package/dist/server/bare/plugins/ggml-classification/ops/classify.d.ts +6 -0
  141. package/dist/server/bare/plugins/ggml-classification/ops/classify.js +30 -0
  142. package/dist/server/bare/plugins/ggml-classification/plugin.d.ts +32 -0
  143. package/dist/server/bare/plugins/ggml-classification/plugin.js +55 -0
  144. package/dist/server/bare/plugins/ggml-vla/ops/vla-hparams.d.ts +3 -0
  145. package/dist/server/bare/plugins/ggml-vla/ops/vla-hparams.js +12 -0
  146. package/dist/server/bare/plugins/ggml-vla/ops/vla-run.d.ts +3 -0
  147. package/dist/server/bare/plugins/ggml-vla/ops/vla-run.js +75 -0
  148. package/dist/server/bare/plugins/ggml-vla/plugin.d.ts +54 -0
  149. package/dist/server/bare/plugins/ggml-vla/plugin.js +57 -0
  150. package/dist/server/bare/plugins/index.d.ts +3 -1
  151. package/dist/server/bare/plugins/index.js +3 -1
  152. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +3 -0
  153. package/dist/server/bare/plugins/llamacpp-completion/ops/context-overflow.d.ts +29 -0
  154. package/dist/server/bare/plugins/llamacpp-completion/ops/context-overflow.js +65 -0
  155. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +6 -0
  156. package/dist/server/bare/plugins/llamacpp-completion/plugin.js +26 -0
  157. package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +25 -0
  158. package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +5 -0
  159. package/dist/server/bare/plugins/parakeet-transcription/plugin.d.ts +67 -246
  160. package/dist/server/bare/plugins/parakeet-transcription/plugin.js +66 -97
  161. package/dist/server/bare/plugins/sdcpp-generation/ops/video.d.ts +3 -0
  162. package/dist/server/bare/plugins/sdcpp-generation/ops/video.js +199 -0
  163. package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +200 -2
  164. package/dist/server/bare/plugins/sdcpp-generation/plugin.js +61 -8
  165. package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech-stream.js +2 -15
  166. package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech.js +2 -1
  167. package/dist/server/bare/plugins/tts-ggml/plugin.d.ts +75 -0
  168. package/dist/server/bare/plugins/tts-ggml/plugin.js +189 -0
  169. package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +29 -4
  170. package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +12 -1
  171. package/dist/server/bare/registry/model-config-utils.js +7 -0
  172. package/dist/server/bare/runtime/request-context.d.ts +1 -1
  173. package/dist/server/bare/runtime-lifecycle.js +9 -9
  174. package/dist/server/bare/types/addon-responses.d.ts +1 -0
  175. package/dist/server/env.js +4 -1
  176. package/dist/server/plugins/index.d.ts +1 -1
  177. package/dist/server/plugins/index.js +1 -1
  178. package/dist/server/plugins/registry.d.ts +1 -0
  179. package/dist/server/plugins/registry.js +5 -0
  180. package/dist/server/rpc/handler-registry.js +4 -0
  181. package/dist/server/rpc/handler-selection.d.ts +9 -0
  182. package/dist/server/rpc/handler-selection.js +14 -0
  183. package/dist/server/rpc/handler-utils.js +5 -13
  184. package/dist/server/rpc/handlers/classify.d.ts +3 -0
  185. package/dist/server/rpc/handlers/classify.js +4 -0
  186. package/dist/server/rpc/handlers/index.d.ts +2 -0
  187. package/dist/server/rpc/handlers/index.js +2 -0
  188. package/dist/server/rpc/handlers/load-model/handler.js +4 -1
  189. package/dist/server/rpc/handlers/load-model/resolve.js +9 -0
  190. package/dist/server/rpc/handlers/video-stream.d.ts +3 -0
  191. package/dist/server/rpc/handlers/video-stream.js +4 -0
  192. package/dist/server/rpc/profiling/operation-metrics.js +23 -0
  193. package/dist/server/utils/config-transform.js +1 -1
  194. package/dist/server/worker.js +14 -11
  195. package/dist/utils/errors-client.d.ts +15 -0
  196. package/dist/utils/errors-client.js +26 -0
  197. package/dist/utils/errors-server.d.ts +30 -2
  198. package/dist/utils/errors-server.js +51 -3
  199. package/package.json +66 -80
  200. package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +0 -490
  201. package/dist/server/bare/plugins/onnx-tts/plugin.js +0 -272
  202. package/dist/server/bare/plugins/onnx-tts/wav-helper.d.ts +0 -14
  203. package/dist/server/bare/plugins/onnx-tts/wav-helper.js +0 -107
  204. /package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech-stream.d.ts +0 -0
  205. /package/dist/server/bare/plugins/{onnx-tts → tts-ggml}/ops/text-to-speech.d.ts +0 -0
@@ -0,0 +1,109 @@
1
+ import { vlaHparamsResponseSchema, vlaRunResponseSchema, } from "../../schemas/index.js";
2
+ import { decodeBase64, encodeBase64 } from "../../utils/encoding.js";
3
+ import { invokePlugin } from "./invoke-plugin.js";
4
+ const VLA_RUN_HANDLER = "vlaRun";
5
+ const VLA_HPARAMS_HANDLER = "vlaHparams";
6
+ function bytesOf(arr) {
7
+ return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
8
+ }
9
+ function f32FromBase64(b64) {
10
+ const bytes = decodeBase64(b64);
11
+ // base64 round-trip produces a fresh Uint8Array whose buffer starts at 0
12
+ // and isn't shared, so the underlying ArrayBuffer is safe to reinterpret.
13
+ return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
14
+ }
15
+ /**
16
+ * Run SmolVLA inference on a loaded VLA model and return the produced action
17
+ * chunk plus per-stage timings.
18
+ *
19
+ * @param params - Inference inputs.
20
+ * @param params.modelId - Identifier of the loaded VLA model (returned by
21
+ * `loadModel({ modelType: "vla", ... })`).
22
+ * @param params.images - One or more preprocessed camera frames; each is a
23
+ * `Float32Array` of length `3 * imgWidth * imgHeight` in CHW layout, range
24
+ * `[-1, 1]`. Use the addon's `preprocessImage()` (re-exported as
25
+ * `vlaPreprocessImage`) to produce them.
26
+ * @param params.imgWidth - Width of each preprocessed image; must equal
27
+ * `hparams.visionImageSize`.
28
+ * @param params.imgHeight - Height of each preprocessed image; must equal
29
+ * `hparams.visionImageSize`.
30
+ * @param params.state - Robot end-effector / gripper state padded to
31
+ * `hparams.maxStateDim` (use `vlaPadState`).
32
+ * @param params.tokens - Tokenized instruction (`Int32Array` of length
33
+ * `hparams.tokenizerMaxLength`). Tokenize on the consumer side with the
34
+ * SmolVLM2 tokenizer.
35
+ * @param params.mask - Token attention mask (`Uint8Array` matching `tokens`).
36
+ * @param params.noise - Optional seeded noise prior
37
+ * (`Float32Array` of length `hparams.chunkSize * hparams.maxActionDim`).
38
+ * When omitted the addon samples its own prior.
39
+ * @returns A `VlaClientRunResult` with the produced `actions` Float32Array
40
+ * (length `chunkSize * actionDim`), the corresponding `chunkSize` /
41
+ * `actionDim` returned by the addon, and optional per-stage `stats`.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { loadModel, vla, vlaPreprocessImage, vlaPadState, vlaHparams } from "@qvac/sdk";
46
+ *
47
+ * const modelId = await loadModel({ modelSrc: "/path/to/smolvla.gguf", modelType: "vla" });
48
+ * const { hparams } = await vlaHparams({ modelId });
49
+ * const size = hparams.visionImageSize;
50
+ * const front = vlaPreprocessImage(frontPixels, frontW, frontH, { size });
51
+ * const wrist = vlaPreprocessImage(wristPixels, wristW, wristH, { size });
52
+ * const state = vlaPadState(robotState, hparams.maxStateDim);
53
+ * const tokens = new Int32Array(hparams.tokenizerMaxLength);
54
+ * const mask = new Uint8Array(hparams.tokenizerMaxLength);
55
+ * // ...tokenize the instruction into tokens/mask...
56
+ * const { actions } = await vla({
57
+ * modelId, images: [front, wrist], imgWidth: size, imgHeight: size,
58
+ * state, tokens, mask,
59
+ * });
60
+ * ```
61
+ */
62
+ export async function vla(params) {
63
+ const wireRequest = {
64
+ type: "vlaRun",
65
+ modelId: params.modelId,
66
+ images: params.images.map((img) => encodeBase64(bytesOf(img))),
67
+ imgWidth: params.imgWidth,
68
+ imgHeight: params.imgHeight,
69
+ state: encodeBase64(bytesOf(params.state)),
70
+ tokens: encodeBase64(bytesOf(params.tokens)),
71
+ mask: encodeBase64(bytesOf(params.mask)),
72
+ ...(params.noise !== undefined && {
73
+ noise: encodeBase64(bytesOf(params.noise)),
74
+ }),
75
+ };
76
+ const result = await invokePlugin({
77
+ modelId: params.modelId,
78
+ handler: VLA_RUN_HANDLER,
79
+ params: wireRequest,
80
+ });
81
+ const parsed = vlaRunResponseSchema.parse(result);
82
+ return {
83
+ actions: f32FromBase64(parsed.actions),
84
+ actionDim: parsed.actionDim,
85
+ chunkSize: parsed.chunkSize,
86
+ ...(parsed.stats && { stats: parsed.stats }),
87
+ };
88
+ }
89
+ /**
90
+ * Fetch the loaded VLA model's hyperparameters and the active ggml backend
91
+ * name. Useful to size token / state / noise buffers before calling `vla()`.
92
+ *
93
+ * @param params - Identifier of the loaded VLA model.
94
+ * @returns The model's hparams and the human-readable backend name
95
+ * (`"CPU"` / `"Vulkan"` / `"Metal"` / `"OpenCL"` / `null` if the addon
96
+ * has not surfaced one).
97
+ */
98
+ export async function vlaHparams(params) {
99
+ const wireRequest = {
100
+ type: "vlaHparams",
101
+ modelId: params.modelId,
102
+ };
103
+ const result = await invokePlugin({
104
+ modelId: params.modelId,
105
+ handler: VLA_HPARAMS_HANDLER,
106
+ params: wireRequest,
107
+ });
108
+ return vlaHparamsResponseSchema.parse(result);
109
+ }
@@ -1,4 +1,20 @@
1
1
  import { type QvacConfig } from "./config-utils";
2
+ /** Config filenames searched under a project root (bundle/verify/commands). */
3
+ export declare const CONFIG_CANDIDATES: readonly ["qvac.config.ts", "qvac.config.mjs", "qvac.config.js", "qvac.config.json"];
4
+ /** Load and validate a config file at an explicit path (used by RPC init and commands). */
5
+ export declare function loadConfigFromPath(configPath: string): Promise<QvacConfig>;
6
+ /**
7
+ * Resolve a config file path under `projectRoot` (or an explicit relative/absolute path).
8
+ * Used by `bundleSdk` / `verifyBundle` when the caller supplies `projectRoot`.
9
+ */
10
+ export declare function resolveConfigFileInProject(projectRoot: string, explicitPath?: string): Promise<string | null>;
11
+ /**
12
+ * Load `qvac.config.*` for a known project directory (commands tooling API).
13
+ */
14
+ export declare function resolveConfigForProject(projectRoot: string, explicitPath?: string): Promise<{
15
+ configPath: string | null;
16
+ config: QvacConfig;
17
+ }>;
2
18
  /**
3
19
  * Resolution order for Node.js:
4
20
  * 1. QVAC_CONFIG_PATH environment variable
@@ -6,9 +6,16 @@ import { promises as fs } from "fs";
6
6
  import path from "path";
7
7
  import { pathToFileURL } from "url";
8
8
  import { validateConfig, parseJsonConfig, } from "./config-utils.js";
9
- import { ConfigFileParseFailedError } from "../../utils/errors-client.js";
9
+ import { ConfigFileInvalidError, ConfigFileParseFailedError, } from "../../utils/errors-client.js";
10
10
  import { getClientLogger } from "../../logging/index.js";
11
11
  const logger = getClientLogger();
12
+ /** Config filenames searched under a project root (bundle/verify/commands). */
13
+ export const CONFIG_CANDIDATES = [
14
+ "qvac.config.ts",
15
+ "qvac.config.mjs",
16
+ "qvac.config.js",
17
+ "qvac.config.json",
18
+ ];
12
19
  async function findProjectRoot() {
13
20
  try {
14
21
  let currentDir = path.resolve(process.cwd());
@@ -65,20 +72,76 @@ async function loadJsConfig(filePath) {
65
72
  throw new ConfigFileParseFailedError(filePath, error instanceof Error ? error.message : String(error), error);
66
73
  }
67
74
  }
68
- async function findConfigFile(searchDir) {
69
- const configFiles = [
70
- { name: "qvac.config.ts", type: "ts" },
71
- { name: "qvac.config.js", type: "js" },
72
- { name: "qvac.config.json", type: "json" },
73
- ];
74
- for (const { name, type } of configFiles) {
75
+ async function loadTsConfig(filePath) {
76
+ let tsxApiPath;
77
+ try {
78
+ const { createRequire } = await import("node:module");
79
+ const require = createRequire(import.meta.url);
80
+ tsxApiPath = require.resolve("tsx/esm/api");
81
+ }
82
+ catch (error) {
83
+ throw new ConfigFileInvalidError(filePath, "Loading a TypeScript qvac.config.ts requires the optional peer dependency `tsx`. Install it as a devDependency, or use qvac.config.mjs / qvac.config.js / qvac.config.json instead.", error);
84
+ }
85
+ try {
86
+ const tsxModule = (await import(tsxApiPath));
87
+ const mod = await tsxModule.tsImport(filePath, import.meta.url);
88
+ return validateConfig(mod.default ?? mod);
89
+ }
90
+ catch (error) {
91
+ throw new ConfigFileParseFailedError(filePath, error instanceof Error ? error.message : String(error), error);
92
+ }
93
+ }
94
+ /** Load and validate a config file at an explicit path (used by RPC init and commands). */
95
+ export async function loadConfigFromPath(configPath) {
96
+ const ext = path.extname(configPath).toLowerCase();
97
+ if (ext === ".json") {
98
+ return loadJsonConfig(configPath);
99
+ }
100
+ if (ext === ".js" || ext === ".mjs") {
101
+ return loadJsConfig(configPath);
102
+ }
103
+ if (ext === ".ts") {
104
+ return loadTsConfig(configPath);
105
+ }
106
+ throw new ConfigFileInvalidError(configPath, `Unsupported config format: ${ext}. Use .json, .js, .mjs, or .ts`);
107
+ }
108
+ async function findConfigFileInDir(searchDir) {
109
+ for (const name of CONFIG_CANDIDATES) {
75
110
  const filePath = path.resolve(searchDir, name);
76
111
  if (await fileExists(filePath)) {
77
- return { path: filePath, type };
112
+ return filePath;
78
113
  }
79
114
  }
80
115
  return undefined;
81
116
  }
117
+ /**
118
+ * Resolve a config file path under `projectRoot` (or an explicit relative/absolute path).
119
+ * Used by `bundleSdk` / `verifyBundle` when the caller supplies `projectRoot`.
120
+ */
121
+ export async function resolveConfigFileInProject(projectRoot, explicitPath) {
122
+ if (explicitPath) {
123
+ const absPath = path.isAbsolute(explicitPath)
124
+ ? explicitPath
125
+ : path.join(projectRoot, explicitPath);
126
+ if (await fileExists(absPath)) {
127
+ return absPath;
128
+ }
129
+ throw new ConfigFileInvalidError(absPath, "Config file not found at explicit path");
130
+ }
131
+ const found = await findConfigFileInDir(projectRoot);
132
+ return found ?? null;
133
+ }
134
+ /**
135
+ * Load `qvac.config.*` for a known project directory (commands tooling API).
136
+ */
137
+ export async function resolveConfigForProject(projectRoot, explicitPath) {
138
+ const configPath = await resolveConfigFileInProject(projectRoot, explicitPath);
139
+ if (!configPath) {
140
+ return { configPath: null, config: {} };
141
+ }
142
+ const config = await loadConfigFromPath(configPath);
143
+ return { configPath, config };
144
+ }
82
145
  function getResourcesPath() {
83
146
  const { resourcesPath } = process;
84
147
  return typeof resourcesPath === "string" ? resourcesPath : undefined;
@@ -111,14 +174,7 @@ export async function resolveConfig() {
111
174
  if (configPath) {
112
175
  const normalizedPath = path.resolve(configPath);
113
176
  if (await fileExists(normalizedPath)) {
114
- const ext = normalizedPath.endsWith(".json")
115
- ? "json"
116
- : normalizedPath.endsWith(".ts")
117
- ? "ts"
118
- : "js";
119
- const config = ext === "json"
120
- ? await loadJsonConfig(normalizedPath)
121
- : await loadJsConfig(normalizedPath);
177
+ const config = await loadConfigFromPath(normalizedPath);
122
178
  logger.info(`āœ… Loaded config from: ${normalizedPath}`);
123
179
  return config;
124
180
  }
@@ -131,12 +187,10 @@ export async function resolveConfig() {
131
187
  }
132
188
  const projectRoot = await findProjectRoot();
133
189
  if (projectRoot) {
134
- const configFile = await findConfigFile(projectRoot);
135
- if (configFile) {
136
- const config = configFile.type === "json"
137
- ? await loadJsonConfig(configFile.path)
138
- : await loadJsConfig(configFile.path);
139
- logger.info(`āœ… Loaded config from: ${configFile.path}`);
190
+ const configFilePath = await findConfigFileInDir(projectRoot);
191
+ if (configFilePath) {
192
+ const config = await loadConfigFromPath(configFilePath);
193
+ logger.info(`āœ… Loaded config from: ${configFilePath}`);
140
194
  return config;
141
195
  }
142
196
  }
@@ -0,0 +1,8 @@
1
+ import * as hostApi from "./api/index";
2
+ import type { QvacPlugin } from "../schemas/plugin";
3
+ /**
4
+ * Register plugins and return the client host API. Designed for
5
+ * @qvac/bare-sdk consumers assembling explicit plugin subsets.
6
+ */
7
+ export declare function plugins(pluginList: readonly QvacPlugin[]): typeof hostApi;
8
+ //# sourceMappingURL=plugins-factory.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { registerPlugins } from "../server/plugins/index.js";
2
+ import * as hostApi from "./api/index.js";
3
+ /**
4
+ * Register plugins and return the client host API. Designed for
5
+ * @qvac/bare-sdk consumers assembling explicit plugin subsets.
6
+ */
7
+ export function plugins(pluginList) {
8
+ registerPlugins(pluginList);
9
+ return hostApi;
10
+ }
@@ -1,59 +1,27 @@
1
- import { normalizeModelType } from "../../schemas/index.js";
1
+ import { normalizeModelType, PROFILING_KEY } from "../../schemas/index.js";
2
2
  import os from "bare-os";
3
- import { handlers } from "../../server/rpc/handlers/index.js";
4
3
  import { registry } from "../../server/rpc/handler-registry.js";
4
+ import { handlerSupportsProgress, selectHandler, } from "../../server/rpc/handler-selection.js";
5
5
  import { createErrorResponse } from "../../schemas/index.js";
6
- import { PearWorkerEntryRequiredError, RPCNoHandlerError, RPCRequestNotSentError, } from "../../utils/errors-client.js";
6
+ import { RPCNoHandlerError, RPCRequestNotSentError, } from "../../utils/errors-client.js";
7
7
  import { initializeConfig } from "../init-hooks.js";
8
8
  import { setSDKConfig } from "../../server/bare/registry/config-registry.js";
9
9
  import { setRuntimeContext } from "../../server/bare/registry/runtime-context-registry.js";
10
10
  import { resolveModelConfig } from "../../server/bare/registry/model-config-registry.js";
11
11
  import { resolveConfig } from "../config-loader/resolve-config.bare.js";
12
- import { getClientLogger } from "../../logging/index.js";
13
- import { getAllPlugins } from "../../server/plugins/index.js";
14
12
  import { initializeWorkerCore, shutdownBareDirectWorker, cleanupForTerminate, } from "../../server/worker-core.js";
15
13
  import { assertLifecycleAllowed } from "../../server/bare/runtime-lifecycle.js";
16
- const logger = getClientLogger();
17
- /**
18
- * Load worker entry to register plugins before any SDK calls.
19
- *
20
- * If plugins are already registered (e.g., by a Pear worker bootstrap),
21
- * skip loading the default worker but still initialize worker core.
22
- *
23
- * NOTE: For Pear apps, the fallback (loading default worker) cannot work.
24
- *
25
- * The default worker is intentionally excluded from `pear stage --compact`
26
- * to enable tree-shaking of unused built-in plugins. When a Pear app tries
27
- * to dynamically import it at runtime, the path resolves to a pear:// URL.
28
- * The default worker imports built-in plugins, which load native addons
29
- * (.bare files). Native addons cannot be loaded from pear:// URLs, causing
30
- * UNSUPPORTED_PROTOCOL errors.
31
- *
32
- * Instead, we detect Pear runtime and throw a clear error directing users
33
- * to use the generated worker entry (qvac/worker.pear.entry.mjs).
34
- *
35
- * The fallback only works in non-Pear environments.
36
- */
37
- async function loadWorkerEntry() {
14
+ import { ensurePluginsRegistered } from "./ensure-worker-ready.js";
15
+ async function ensureWorkerReady() {
38
16
  initializeWorkerCore();
39
- if (getAllPlugins().length > 0) {
40
- logger.info("šŸ“¦ Plugins already registered, worker core initialized");
41
- return;
42
- }
43
- const { isPear } = await import("which-runtime");
44
- if (isPear) {
45
- throw new PearWorkerEntryRequiredError("qvac/worker.pear.entry.mjs");
46
- }
47
- // Fallback: load default worker (all built-in plugins)
48
- logger.info("šŸ“¦ Loading default worker (all built-in plugins)");
49
- const workerPath = "../../server/" + "worker.js";
50
- await import(workerPath);
17
+ await ensurePluginsRegistered();
51
18
  }
52
- let workerEntryLoaded = false;
53
- // Get the handler for a request type
54
- function getHandler(type) {
55
- const handler = handlers[type];
56
- return typeof handler === "function" ? handler : undefined;
19
+ function getHandlerEntry(type) {
20
+ const entry = registry[type];
21
+ if (!entry) {
22
+ throw new RPCNoHandlerError(type);
23
+ }
24
+ return entry;
57
25
  }
58
26
  function applyDeviceDefaultsToLoadModel(request) {
59
27
  if (request.type !== "loadModel" || !("modelSrc" in request)) {
@@ -70,16 +38,49 @@ function applyDeviceDefaultsToLoadModel(request) {
70
38
  const configWithDefaults = resolveModelConfig(canonicalType, rawConfig);
71
39
  return { ...request, modelConfig: configWithDefaults };
72
40
  }
73
- function supportsProgressStreaming(request) {
74
- return ("withProgress" in request &&
75
- request.withProgress &&
76
- ["loadModel", "downloadAsset", "rag", "finetune"].includes(request.type));
41
+ function getProfilingMetaFromRequest(request) {
42
+ if (PROFILING_KEY in request) {
43
+ return request[PROFILING_KEY];
44
+ }
45
+ return undefined;
46
+ }
47
+ function createDelegatedOptions(request, progressCallback) {
48
+ const profilingMeta = getProfilingMetaFromRequest(request);
49
+ if (!profilingMeta && !progressCallback) {
50
+ return undefined;
51
+ }
52
+ const options = {};
53
+ if (progressCallback) {
54
+ options.progressCallback = progressCallback;
55
+ }
56
+ if (profilingMeta) {
57
+ options.profilingMeta = profilingMeta;
58
+ }
59
+ return options;
60
+ }
61
+ function executeDirectHandler(request, handler, isDelegated) {
62
+ const directHandler = handler;
63
+ if (isDelegated) {
64
+ return directHandler(request, createDelegatedOptions(request));
65
+ }
66
+ return directHandler(request);
67
+ }
68
+ function isAsyncGenerator(result) {
69
+ return (typeof result === "object" &&
70
+ result !== null &&
71
+ Symbol.asyncIterator in result);
77
72
  }
78
- async function* streamWithProgress(request, handler) {
73
+ async function* streamWithProgress(request, handler, isDelegated) {
79
74
  const queue = [];
80
75
  const errors = [];
81
76
  let done = false;
82
- handler(request, (update) => queue.push(update))
77
+ function progressCallback(update) {
78
+ queue.push(update);
79
+ }
80
+ const directHandler = handler;
81
+ Promise.resolve(directHandler(request, isDelegated
82
+ ? createDelegatedOptions(request, progressCallback)
83
+ : progressCallback))
83
84
  .then((final) => {
84
85
  queue.push(final);
85
86
  done = true;
@@ -103,25 +104,23 @@ async function* streamWithProgress(request, handler) {
103
104
  }
104
105
  export async function send(request) {
105
106
  assertLifecycleAllowed(request);
106
- const handler = getHandler(request.type);
107
- if (!handler)
108
- throw new RPCNoHandlerError(request.type);
109
107
  const processedRequest = applyDeviceDefaultsToLoadModel(request);
110
- return (await handler(processedRequest));
108
+ const entry = getHandlerEntry(processedRequest.type);
109
+ const { handler, isDelegated } = selectHandler(entry, processedRequest);
110
+ return (await executeDirectHandler(processedRequest, handler, isDelegated));
111
111
  }
112
112
  async function* stream(request) {
113
113
  assertLifecycleAllowed(request);
114
- const handler = getHandler(request.type);
115
- if (!handler)
116
- throw new RPCNoHandlerError(request.type);
117
- if (supportsProgressStreaming(request)) {
118
- const processedRequest = applyDeviceDefaultsToLoadModel(request);
119
- yield* streamWithProgress(processedRequest, handler);
114
+ const processedRequest = applyDeviceDefaultsToLoadModel(request);
115
+ const entry = getHandlerEntry(processedRequest.type);
116
+ const { handler, isDelegated } = selectHandler(entry, processedRequest);
117
+ if (handlerSupportsProgress(entry, processedRequest)) {
118
+ yield* streamWithProgress(processedRequest, handler, isDelegated);
120
119
  }
121
120
  else {
122
- const result = handler(request);
121
+ const result = executeDirectHandler(processedRequest, handler, isDelegated);
123
122
  // Check if the handler returns a Promise or AsyncGenerator
124
- if (Symbol.asyncIterator in result) {
123
+ if (isAsyncGenerator(result)) {
125
124
  // It's an AsyncGenerator
126
125
  yield* result;
127
126
  }
@@ -195,10 +194,7 @@ function createMockRPCRequest() {
195
194
  }
196
195
  let configInitialized = false;
197
196
  export async function getRPC() {
198
- if (!workerEntryLoaded) {
199
- await loadWorkerEntry();
200
- workerEntryLoaded = true;
201
- }
197
+ await ensureWorkerReady();
202
198
  const mockRPC = {
203
199
  request() {
204
200
  return createMockRPCRequest();
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Throw if no plugins are registered before an SDK call. On Pear,
3
+ * point at the generated worker entry; otherwise raise a generic
4
+ * registration error. `isPear` is overridable for tests.
5
+ */
6
+ export declare function ensurePluginsRegistered(opts?: {
7
+ isPear?: boolean;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=ensure-worker-ready.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { getAllPlugins } from "../../server/plugins/index.js";
2
+ import { PearWorkerEntryRequiredError, WorkerPluginsNotRegisteredError, } from "../../utils/errors-client.js";
3
+ const PEAR_WORKER_ENTRY_PATH = "qvac/worker.pear.entry.mjs";
4
+ /**
5
+ * Throw if no plugins are registered before an SDK call. On Pear,
6
+ * point at the generated worker entry; otherwise raise a generic
7
+ * registration error. `isPear` is overridable for tests.
8
+ */
9
+ export async function ensurePluginsRegistered(opts) {
10
+ if (getAllPlugins().length > 0)
11
+ return;
12
+ const isPear = opts?.isPear ?? (await import("which-runtime")).isPear;
13
+ if (isPear) {
14
+ throw new PearWorkerEntryRequiredError(PEAR_WORKER_ENTRY_PATH);
15
+ }
16
+ throw new WorkerPluginsNotRegisteredError();
17
+ }
@@ -219,7 +219,10 @@ async function ensureRPC() {
219
219
  WORKER_PATH,
220
220
  JSON.stringify({
221
221
  QVAC_IPC_SOCKET_PATH: socketPath,
222
- HOME_DIR: os.homedir(),
222
+ // Snap's HOME can be revision-scoped; SNAP_USER_COMMON is stable.
223
+ HOME_DIR: process.env["SNAP_USER_COMMON"]
224
+ ? String(process.env["SNAP_USER_COMMON"])
225
+ : os.homedir(),
223
226
  }),
224
227
  ],
225
228
  stdio: ["inherit", "inherit", "inherit"],
@@ -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