@qvac/sdk 0.20.0 → 0.21.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 (104) hide show
  1. package/NOTICE +134 -66
  2. package/README.md +39 -3
  3. package/dist/examples/advisory-model-fit.d.ts +17 -16
  4. package/dist/examples/advisory-model-fit.js +18 -18
  5. package/dist/examples/asr/parakeet-indic-conformer-filesystem.js +3 -3
  6. package/dist/examples/asr/parakeet-unified-coreml.d.ts +2 -0
  7. package/dist/examples/asr/parakeet-unified-coreml.js +113 -0
  8. package/dist/examples/assess-model-fit.js +9 -4
  9. package/dist/examples/model-fit-ui/server.js +5 -2
  10. package/dist/examples/rpc-inference.d.ts +2 -0
  11. package/dist/examples/rpc-inference.js +51 -0
  12. package/dist/examples/rpc-server.d.ts +2 -0
  13. package/dist/examples/rpc-server.js +32 -0
  14. package/dist/examples/tools/llamacpp-deferred-tools.d.ts +2 -0
  15. package/dist/examples/tools/llamacpp-deferred-tools.js +77 -0
  16. package/dist/examples/tts/audio8.js +4 -0
  17. package/dist/examples/tts/moss-dialogue.d.ts +2 -0
  18. package/dist/examples/tts/moss-dialogue.js +63 -0
  19. package/dist/examples/tts/moss.d.ts +2 -0
  20. package/dist/examples/tts/moss.js +71 -0
  21. package/dist/scripts/contract/build-contract.d.ts +2 -1
  22. package/dist/scripts/contract/build-contract.js +4 -1
  23. package/dist/scripts/contract/build-numeric-constants.d.ts +15 -0
  24. package/dist/scripts/contract/build-numeric-constants.js +13 -0
  25. package/dist/scripts/contract/method-shapes.d.ts +3 -0
  26. package/dist/scripts/contract/method-shapes.js +3 -0
  27. package/dist/scripts/enforce-inference-versions.d.ts +18 -0
  28. package/dist/scripts/enforce-inference-versions.js +158 -0
  29. package/dist/scripts/generate-bare-kit-runtimes.d.ts +2 -0
  30. package/dist/scripts/generate-bare-kit-runtimes.js +106 -0
  31. package/dist/src/client/api/assess-model-fit.d.ts +11 -5
  32. package/dist/src/client/api/assess-model-fit.js +30 -8
  33. package/dist/src/client/api/bci-transcribe.d.ts +4 -1
  34. package/dist/src/client/api/get-system-resources.d.ts +8 -0
  35. package/dist/src/client/api/get-system-resources.js +8 -0
  36. package/dist/src/client/api/index.d.ts +1 -0
  37. package/dist/src/client/api/index.js +1 -0
  38. package/dist/src/client/api/rpc-server.d.ts +20 -0
  39. package/dist/src/client/api/rpc-server.js +45 -0
  40. package/dist/src/client/api/text-to-speech.d.ts +9 -0
  41. package/dist/src/client/api/text-to-speech.js +9 -0
  42. package/dist/src/client/api/transcribe.d.ts +21 -15
  43. package/dist/src/client/api/transcribe.js +65 -20
  44. package/dist/src/client/rpc/rpc-error.js +11 -7
  45. package/dist/src/commands/bundle/entry-gen.d.ts +2 -2
  46. package/dist/src/commands/bundle/entry-gen.js +8 -4
  47. package/dist/src/commands/bundle/index.d.ts +20 -0
  48. package/dist/src/commands/bundle/index.js +88 -12
  49. package/dist/src/commands/bundle/plugins.js +1 -1
  50. package/dist/src/commands/command-logger.d.ts +6 -0
  51. package/dist/src/commands/command-logger.js +12 -0
  52. package/dist/src/commands/host-prebuilds/index.d.ts +71 -0
  53. package/dist/src/commands/host-prebuilds/index.js +227 -0
  54. package/dist/src/commands/host-prebuilds/package-manager.d.ts +54 -0
  55. package/dist/src/commands/host-prebuilds/package-manager.js +278 -0
  56. package/dist/src/commands/index.d.ts +10 -1
  57. package/dist/src/commands/index.js +6 -0
  58. package/dist/src/commands/verify/abi.d.ts +22 -2
  59. package/dist/src/commands/verify/abi.js +49 -27
  60. package/dist/src/commands/verify/addon-source.d.ts +22 -0
  61. package/dist/src/commands/verify/addon-source.js +69 -3
  62. package/dist/src/commands/verify/bare-kit-runtime.d.ts +38 -0
  63. package/dist/src/commands/verify/bare-kit-runtime.js +201 -0
  64. package/dist/src/commands/verify/bare-kit-runtimes.d.ts +12 -0
  65. package/dist/src/commands/verify/bare-kit-runtimes.js +108 -0
  66. package/dist/src/commands/verify/bundle-source.js +16 -15
  67. package/dist/src/commands/verify/engines-advice.d.ts +73 -0
  68. package/dist/src/commands/verify/engines-advice.js +271 -0
  69. package/dist/src/commands/verify/index.d.ts +22 -0
  70. package/dist/src/commands/verify/index.js +151 -13
  71. package/dist/src/commands/verify/node-modules-source.js +43 -39
  72. package/dist/src/commands/verify/prebuilds.d.ts +19 -0
  73. package/dist/src/commands/verify/prebuilds.js +52 -13
  74. package/dist/src/expo/plugins/index.d.ts +2 -0
  75. package/dist/src/expo/plugins/withMobileBundle.d.ts +15 -5
  76. package/dist/src/expo/plugins/withMobileBundle.js +68 -42
  77. package/dist/src/expo/plugins/withQvacSDK.d.ts +7 -1
  78. package/dist/src/expo/plugins/withQvacSDK.js +6 -3
  79. package/dist/src/index.d.ts +5 -2
  80. package/dist/src/index.js +4 -2
  81. package/dist/src/schemas/sdk-errors-client.d.ts +2 -0
  82. package/dist/src/schemas/sdk-errors-client.js +16 -0
  83. package/dist/src/schemas/sdk-errors-server.d.ts +1 -0
  84. package/dist/src/schemas/sdk-errors-server.js +5 -0
  85. package/dist/src/utils/errors-client.d.ts +15 -0
  86. package/dist/src/utils/errors-client.js +26 -0
  87. package/dist/src/utils/errors-server.d.ts +1 -0
  88. package/dist/src/utils/errors-server.js +1 -0
  89. package/dist/src/utils/mcp-adapter.js +7 -1
  90. package/dist/src/utils/tool-helpers.d.ts +4 -0
  91. package/dist/src/utils/tool-helpers.js +2 -0
  92. package/dist/src/worker/ggml-rpc-server-provider.d.ts +2 -0
  93. package/dist/src/worker/ggml-rpc-server-provider.js +1 -0
  94. package/dist/src/worker/rpc-server-provider.d.ts +3 -0
  95. package/dist/src/worker/rpc-server-provider.js +1 -0
  96. package/package.json +31 -27
  97. package/dist/scripts/enforce-inference-peer-dependencies.d.ts +0 -2
  98. package/dist/scripts/enforce-inference-peer-dependencies.js +0 -39
  99. package/dist/src/deprecate.d.ts +0 -2
  100. package/dist/src/deprecate.js +0 -9
  101. package/dist/src/worker/legacy-worker-entry.d.ts +0 -2
  102. package/dist/src/worker/legacy-worker-entry.js +0 -3
  103. package/dist/src/worker/worker-core.d.ts +0 -7
  104. package/dist/src/worker/worker-core.js +0 -8
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Advisory llama.cpp fit check (QVAC-22629).
2
+ * Advisory model fit check.
3
3
  *
4
- * Before a completion or embedding load, the SDK runs `@qvac/model-fit` in one
5
- * disposable Bare child and projects whether the exact configuration it is
6
- * about to load will fit in device memory.
4
+ * Before a load, the SDK runs the fitter belonging to the engine that would
5
+ * run it and projects whether the exact configuration it is about to load will
6
+ * fit in device memory. Desktop runs it in a disposable Bare child; mobile,
7
+ * which cannot spawn one, runs it on a worker thread.
7
8
  *
8
9
  * The result is ADVISORY. It never blocks a load. `does-not-fit` is logged and
9
10
  * the ordinary load path runs unchanged. Crashes, timeouts, malformed
10
11
  * responses, unsupported configurations, and internal errors all resolve to
11
- * "no evidence" and are equally non-blocking. Nothing consumes the verdict
12
- * yet — this PR only produces it.
12
+ * "no evidence" and are equally non-blocking. No verdict changes the load;
13
+ * `getLoadedModelInfo` returns it as `fitProbe`.
13
14
  *
14
15
  * The verdict is emitted on the SDK server log stream, not to stdout, so this
15
16
  * example subscribes to `loggingStream({ id: SDK_LOG_ID })` and reprints the
@@ -19,9 +20,8 @@
19
20
  * What fits on this machine (Apple M4 Pro, 24 GiB unified memory)
20
21
  * ---------------------------------------------------------------------------
21
22
  *
22
- * Measured with `@qvac/model-fit@0.8.0` (the first release carrying the
23
- * qvac-fabric#214 memory-reporting fix) at the default 1024 MiB margin. The
24
- * fitter budgets against what the machine can actually keep resident
23
+ * Measured at the default 1024 MiB margin. The fitter budgets against what the
24
+ * machine can actually keep resident
25
25
  * (total − wired − compressor: 17.4 GiB on this machine at idle), not the raw
26
26
  * RAM figure.
27
27
  *
@@ -30,10 +30,12 @@
30
30
  * gpt-oss-20B Q4_K_M 10.8 GiB @ 32k ctx
31
31
  * gte-large fp16 0.6 GiB (embedding, context pinned to 512)
32
32
  *
33
- * PROJECTED NOT TO FIT — try any of these to see a `does-not-fit` verdict
33
+ * PROJECTED NOT TO FIT — with `gpu_layers: 99` pinned
34
34
  * gpt-oss-20B Q4_K_M 10.8 GiB @ 128k ctx with an f32 KV cache ← below
35
- * Gemma 4 31B Q4_K_M 18.3 GiB @ 1k ctx ← the weights alone do not
36
- * fit with gpu_layers: 99
35
+ *
36
+ * These rows were measured with `gpu_layers` pinned at 99. Loads no longer pin
37
+ * the layer count, and the `does-not-fit` row has not been re-measured
38
+ * unpinned.
37
39
  *
38
40
  * gpt-oss-20B at 128k context FITS with the default KV cache and DOES NOT FIT
39
41
  * once `cache-type-k`/`cache-type-v` are set to `f32`. Same model, same
@@ -42,10 +44,9 @@
42
44
  *
43
45
  * Two boundaries worth understanding when reading verdicts:
44
46
  *
45
- * 1. The verdict answers for a PLACEMENT, not a model. The SDK's schema
46
- * default is `gpu_layers: 99` ("everything on the GPU"), so `does-not-fit`
47
- * means "not at this placement". Omit `gpu_layers` and the fitter is free
48
- * to move layers to the CPU side instead.
47
+ * 1. The verdict answers for a PLACEMENT, not a model. With `gpu_layers`
48
+ * unset, the fitter may move layers to the CPU side; setting it pins the
49
+ * layer count, and `does-not-fit` then means "not at this placement".
49
50
  * 2. A `does-not-fit` configuration can still RUN on macOS when the OS
50
51
  * compresses and pages hard enough — and a `fits` configuration right at
51
52
  * the boundary can still fail at first decode under memory pressure.
@@ -98,8 +99,7 @@ try {
98
99
  // Unloaded before the next phase, so the second verdict is measured on an
99
100
  // idle machine and stays comparable to the fixture tables above. Leaving it
100
101
  // loaded would shift the verdict: the check reserves resident weight bytes
101
- // through the fit margin, and since model-fit 0.8.0 the fit child also sees
102
- // system-wide wired memory.
102
+ // through the fit margin, and the fitter also sees system-wide wired memory.
103
103
  await unloadModel({ modelId: smallModelId, clearStorage: false });
104
104
  watchVerdicts();
105
105
  // 2. A load the fitter projects NOT to fit. The point of this example is that
@@ -4,14 +4,14 @@
4
4
  * Usage:
5
5
  * bun run examples/asr/parakeet-indic-conformer-filesystem.ts <wav-file> <language> [gguf]
6
6
  *
7
- * Loads a single GGUF checkpoint (`PARAKEET_INDIC_CONFORMER_CTC_Q8_0` by
7
+ * Loads a single GGUF checkpoint (`PARAKEET_INDIC_CONFORMER_600M_Q8_0` by
8
8
  * default) and transcribes with the batch `transcribe` API. `language` is
9
9
  * required (e.g. `hi`, `ta`) because Indic Conformer CTC masks the vocab
10
10
  * with `parakeet.ctc.lang_*` ranges. English Parakeet CTC ignores this field.
11
11
  *
12
12
  * Audio should be 16 kHz mono PCM in a WAV container.
13
13
  */
14
- import { loadModel, unloadModel, transcribe, PARAKEET_INDIC_CONFORMER_CTC_Q8_0 } from '@qvac/sdk';
14
+ import { loadModel, unloadModel, transcribe, PARAKEET_INDIC_CONFORMER_600M_Q8_0 } from '@qvac/sdk';
15
15
  const args = process.argv.slice(2);
16
16
  if (!args[0] || !args[1]) {
17
17
  console.error('Usage: bun run examples/asr/parakeet-indic-conformer-filesystem.ts ' +
@@ -22,7 +22,7 @@ if (!args[0] || !args[1]) {
22
22
  }
23
23
  const audioFilePath = args[0];
24
24
  const language = args[1];
25
- const parakeetModelSrc = args[2] ?? PARAKEET_INDIC_CONFORMER_CTC_Q8_0;
25
+ const parakeetModelSrc = args[2] ?? PARAKEET_INDIC_CONFORMER_600M_Q8_0;
26
26
  try {
27
27
  console.log('▸ Loading Indic Conformer CTC model...');
28
28
  const modelId = await loadModel({
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parakeet-unified-coreml.d.ts.map
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Transcribe a WAV file with Parakeet Unified and its Core ML encoder sidecar.
3
+ *
4
+ * From packages/sdk:
5
+ * bun run examples/asr/parakeet-unified-coreml.ts \
6
+ * /path/to/parakeet-unified-en-0.6b.q4_0.gguf [16-kHz-wav-file]
7
+ *
8
+ * The GGUF must be a regenerated Unified artifact. The five compiled Core ML
9
+ * files are downloaded through the SDK registry and staged beside the GGUF.
10
+ * The default audio is examples/audio/sample-16khz.wav.
11
+ */
12
+ import { downloadAsset, getModelInfo, loadModel, transcribe, unloadModel } from '@qvac/sdk';
13
+ import { link, mkdir, mkdtemp, open, rm, symlink } from 'node:fs/promises';
14
+ import { tmpdir } from 'node:os';
15
+ import { basename, join, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ const MODEL_STEM = 'parakeet-unified-en-0.6b';
18
+ const COREML_FILES = [
19
+ ['PARAKEET_UNIFIED_COREMLDATA', 'analytics/coremldata.bin'],
20
+ ['PARAKEET_UNIFIED_COREMLDATA_1', 'coremldata.bin'],
21
+ ['PARAKEET_UNIFIED_METADATA', 'metadata.json'],
22
+ ['PARAKEET_UNIFIED_MODEL', 'model.mil'],
23
+ ['PARAKEET_UNIFIED_WEIGHT', 'weights/weight.bin']
24
+ ];
25
+ const defaultAudioPath = fileURLToPath(new URL('../audio/sample-16khz.wav', import.meta.url));
26
+ const modelPath = process.argv[2];
27
+ const audioPath = process.argv[3] ?? defaultAudioPath;
28
+ async function checkModel(path) {
29
+ const name = basename(path);
30
+ if (!/^parakeet-unified-en-0\.6b\.(?:f16|q4_0|q8_0)\.gguf$/.test(name)) {
31
+ throw new Error(`Expected a regenerated Unified GGUF, got: ${name}`);
32
+ }
33
+ const file = await open(path, 'r');
34
+ try {
35
+ const header = Buffer.alloc(1024 * 1024);
36
+ const { bytesRead } = await file.read(header, 0, header.length, 0);
37
+ if (!header.subarray(0, bytesRead).includes('parakeet.unified.left_context_frames')) {
38
+ throw new Error(`Unified GGUF lacks the new streaming metadata: ${path}`);
39
+ }
40
+ }
41
+ finally {
42
+ await file.close();
43
+ }
44
+ }
45
+ async function stageModel(path) {
46
+ if (process.platform !== 'darwin')
47
+ throw new Error('Core ML requires macOS');
48
+ await checkModel(path);
49
+ const dir = await mkdtemp(join(tmpdir(), 'qvac-unified-coreml-'));
50
+ try {
51
+ const modelSrc = join(dir, basename(path));
52
+ await symlink(resolve(path), modelSrc);
53
+ const bundleDir = join(dir, `${MODEL_STEM}-encoder.mlmodelc`);
54
+ for (const [name, relativePath] of COREML_FILES) {
55
+ const info = await getModelInfo({ name });
56
+ if (!info.isCached) {
57
+ if (!info.registrySource || !info.registryPath) {
58
+ throw new Error(`Registry location missing for ${name}`);
59
+ }
60
+ console.log(`▸ Downloading Core ML ${relativePath}...`);
61
+ await downloadAsset({ assetSrc: `registry://${info.registrySource}/${info.registryPath}` });
62
+ }
63
+ const cached = await getModelInfo({ name });
64
+ const sourcePath = cached.cacheFiles.find((file) => file.isCached)?.path;
65
+ if (!sourcePath)
66
+ throw new Error(`Core ML file is missing from the SDK cache: ${name}`);
67
+ const targetPath = join(bundleDir, relativePath);
68
+ await mkdir(join(targetPath, '..'), { recursive: true });
69
+ await link(sourcePath, targetPath);
70
+ }
71
+ return { modelSrc, dir };
72
+ }
73
+ catch (error) {
74
+ await rm(dir, { recursive: true, force: true });
75
+ throw error;
76
+ }
77
+ }
78
+ async function main() {
79
+ if (!modelPath) {
80
+ throw new Error('Usage: bun run examples/asr/parakeet-unified-coreml.ts ' +
81
+ '<regenerated-unified-gguf> [16-kHz-wav-file]');
82
+ }
83
+ let staged;
84
+ let modelId;
85
+ try {
86
+ staged = await stageModel(modelPath);
87
+ console.log('▸ Loading Parakeet Unified with its Core ML encoder sidecar...');
88
+ modelId = await loadModel({
89
+ modelSrc: staged.modelSrc,
90
+ modelType: 'parakeet-transcription',
91
+ modelConfig: { useGPU: true }
92
+ });
93
+ console.log('▸ Transcribing audio...');
94
+ const text = await transcribe({ modelId, audioChunk: audioPath });
95
+ if (!text.trim())
96
+ throw new Error('Parakeet Unified returned an empty transcription');
97
+ console.log(text);
98
+ }
99
+ finally {
100
+ try {
101
+ if (modelId)
102
+ await unloadModel({ modelId });
103
+ }
104
+ finally {
105
+ if (staged)
106
+ await rm(staged.dir, { recursive: true, force: true });
107
+ }
108
+ }
109
+ }
110
+ main().catch((error) => {
111
+ console.error('✖', error);
112
+ process.exitCode = 1;
113
+ });
@@ -11,8 +11,9 @@
11
11
  * call either way. Show it as "can't say", never as "no".
12
12
  */
13
13
  import { assessModelFit, QWEN3_600M_INST_Q4, QWEN3_1_7B_INST_Q4, QWEN3_4B_INST_Q4_K_M, QWEN3_8B_INST_Q4_K_M, QWEN3_8_27B_MULTIMODAL_UD_Q8_K_XL } from '@qvac/sdk';
14
- // `as const` so `kind` stays the `'llm'` literal the input type asks for.
15
- const WORKLOAD = { kind: 'llm', contextTokens: 8192 };
14
+ // Sizes the assessment the way it sizes the load: the estimator reads the
15
+ // context out of the config `loadModel` would be given.
16
+ const CONTEXT_TOKENS = 8192;
16
17
  // A ladder ending well past what a laptop has, so one screen shows every verdict.
17
18
  const CANDIDATES = [
18
19
  QWEN3_600M_INST_Q4,
@@ -31,7 +32,11 @@ function gib(bytes) {
31
32
  }
32
33
  try {
33
34
  const result = await assessModelFit({
34
- models: CANDIDATES.map((model) => ({ model, workload: WORKLOAD })),
35
+ models: CANDIDATES.map((model) => ({
36
+ modelSrc: model,
37
+ modelType: 'llamacpp-completion',
38
+ modelConfig: { ctx_size: CONTEXT_TOKENS }
39
+ })),
35
40
  // Declared for aggregation only, not a scheduling instruction: 'sequential'
36
41
  // counts the largest operation peak, 'concurrent' counts one per model.
37
42
  execution: 'sequential',
@@ -49,7 +54,7 @@ try {
49
54
  const placement = result.assumptions.find((line) => line.includes('assumed to execute on'));
50
55
  if (placement)
51
56
  console.log(`▸ ${placement}`);
52
- console.log(`\n▸ At ${WORKLOAD.contextTokens} tokens of context`);
57
+ console.log(`\n▸ At ${CONTEXT_TOKENS} tokens of context`);
53
58
  for (const [index, model] of result.models.entries()) {
54
59
  const mark = VERDICT_MARK[model.verdict];
55
60
  const size = gib(CANDIDATES[index].expectedSize).padStart(9);
@@ -102,9 +102,12 @@ function sendJson(response, status, payload) {
102
102
  async function estimate(body) {
103
103
  const names = Array.isArray(body['names']) ? body['names'] : [];
104
104
  const contextTokens = Number(body['contextTokens'] ?? 8192);
105
- const workload = { kind: 'llm', contextTokens };
106
105
  return assessModelFit({
107
- models: names.map((name) => ({ model: byName(name), workload })),
106
+ models: names.map((name) => ({
107
+ modelSrc: byName(name),
108
+ modelType: 'llamacpp-completion',
109
+ modelConfig: { ctx_size: contextTokens }
110
+ })),
108
111
  // Declared for aggregation only. 'sequential' counts the largest operation
109
112
  // peak; 'concurrent' counts one per model.
110
113
  execution: 'sequential',
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rpc-inference.d.ts.map
@@ -0,0 +1,51 @@
1
+ // npx tsx rpc-inference.ts my-private-rpc-group
2
+ import { discoverRpcServers, getRpcDeviceMap, loadModel, completion, unloadModel, close, LLAMA_3_2_1B_INST_Q4_0 } from '@qvac/sdk';
3
+ try {
4
+ const topic = process.argv[2];
5
+ if (!topic)
6
+ throw new Error('Usage: rpc-inference.ts <topic>');
7
+ // Prefer server RDMA offers before assigning aliases. Transport is negotiated at load time.
8
+ const selected = (await discoverRpcServers({ topic, timeoutMs: 5000 }))
9
+ .sort((a, b) => Number(b.rdmaAvailable) - Number(a.rdmaAvailable) || a.url.localeCompare(b.url))
10
+ .slice(0, 2);
11
+ if (selected.length !== 2)
12
+ throw new Error('Two idle RPC servers are required');
13
+ const devices = getRpcDeviceMap(selected);
14
+ if (!devices.length)
15
+ throw new Error('The selected servers expose no devices');
16
+ console.error(`▸ Using ${devices.length} devices across ${selected.length} servers`);
17
+ const modelId = await loadModel({
18
+ modelSrc: LLAMA_3_2_1B_INST_Q4_0,
19
+ modelConfig: {
20
+ device: 'gpu',
21
+ 'rpc-servers': selected.map((server) => server.url).join(','),
22
+ devices: devices.map((device) => device.alias).join(','),
23
+ 'split-mode': 'layer',
24
+ 'tensor-split': devices.map(() => '1').join(',')
25
+ },
26
+ onProgress: (progress) => {
27
+ process.stderr.write(`\r▸ Downloading ${progress.percentage.toFixed(0)}%`);
28
+ }
29
+ });
30
+ try {
31
+ process.stderr.write('\n');
32
+ const run = completion({
33
+ modelId,
34
+ history: [{ role: 'user', content: 'Explain distributed inference in one sentence.' }],
35
+ stream: true
36
+ });
37
+ for await (const token of run.tokenStream)
38
+ process.stdout.write(token);
39
+ process.stdout.write('\n');
40
+ }
41
+ finally {
42
+ await unloadModel({ modelId });
43
+ }
44
+ }
45
+ catch (error) {
46
+ console.error('✖', error);
47
+ process.exitCode = 1;
48
+ }
49
+ finally {
50
+ await close();
51
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rpc-server.d.ts.map
@@ -0,0 +1,32 @@
1
+ // Rebuild the worker with
2
+ // rpcServerProvider: '@qvac/sdk/ggml-rpc-server/provider' in qvac.config.json.
3
+ // npx tsx rpc-server.ts 10.0.0.2 my-private-rpc-group
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { startRpcServer, stopRpcServer, close } from '@qvac/sdk';
6
+ try {
7
+ const host = process.argv[2];
8
+ const topic = process.argv[3];
9
+ if (!host || !topic)
10
+ throw new Error('Usage: rpc-server.ts <private-ipv4> <topic>');
11
+ const server = await startRpcServer({
12
+ host,
13
+ allowNonLoopbackHost: true,
14
+ discoveryTopic: topic
15
+ });
16
+ const input = createInterface({ input: process.stdin, output: process.stderr });
17
+ try {
18
+ console.error(`▸ Serving ${server.url}; RDMA capable: ${server.rdmaCapable}`);
19
+ await input.question('▸ Press Enter to stop after clients unload their models.\n');
20
+ }
21
+ finally {
22
+ input.close();
23
+ await stopRpcServer({ serverId: server.serverId });
24
+ }
25
+ }
26
+ catch (error) {
27
+ console.error('✖', error);
28
+ process.exitCode = 1;
29
+ }
30
+ finally {
31
+ await close();
32
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=llamacpp-deferred-tools.d.ts.map
@@ -0,0 +1,77 @@
1
+ // Deferred tool loading: a large tool set stays out of the prompt until the
2
+ // model asks for it. Only `get_weather` and the built-in `tool_search` (whose
3
+ // description carries a name-and-description catalog of the rest) are in the
4
+ // initial prompt; the deferred schemas arrive in the conversation when the
5
+ // model searches for them.
6
+ import { TOOL_SEARCH_NAME, completion, executeToolSearch, loadModel, unloadModel, QWEN3_1_7B_INST_Q4 } from '@qvac/sdk';
7
+ import { z } from 'zod';
8
+ import { weatherSchema, horoscopeSchema, mockExecute } from './shared.js';
9
+ const tools = [
10
+ {
11
+ name: 'get_weather',
12
+ description: 'Get current weather for a city',
13
+ parameters: weatherSchema
14
+ },
15
+ {
16
+ name: 'get_horoscope',
17
+ description: "Get today's horoscope for an astrological sign",
18
+ parameters: horoscopeSchema,
19
+ deferLoading: true,
20
+ group: 'astrology'
21
+ },
22
+ {
23
+ name: 'get_moon_phase',
24
+ description: 'Get the current phase of the moon',
25
+ parameters: z.object({ date: z.string().describe('ISO date').optional() }),
26
+ deferLoading: true,
27
+ group: 'astrology'
28
+ }
29
+ ];
30
+ try {
31
+ const modelId = await loadModel({
32
+ modelSrc: QWEN3_1_7B_INST_Q4,
33
+ modelConfig: { ctx_size: 4096, tools: true }
34
+ });
35
+ console.log(`▸ Model loaded. Model ID: ${modelId}`);
36
+ const history = [
37
+ {
38
+ role: 'system',
39
+ content: 'You are a helpful assistant. Search for a tool before calling it.'
40
+ },
41
+ { role: 'user', content: "What's my horoscope for Aquarius?" }
42
+ ];
43
+ console.log('\n▸ Turn 1 — only get_weather and tool_search are in the prompt\n');
44
+ const first = completion({ modelId, history, stream: true, tools });
45
+ for await (const token of first.tokenStream)
46
+ process.stdout.write(token);
47
+ const calls = await first.toolCalls;
48
+ const search = calls.find((call) => call.name === TOOL_SEARCH_NAME);
49
+ if (!search) {
50
+ console.log('\n\n▸ The model answered without searching; nothing more to show.');
51
+ }
52
+ else {
53
+ console.log(`\n\n▸ The model searched: ${JSON.stringify(search.arguments)}`);
54
+ // Running the search is the SDK's job, not a tool handler's. The result is
55
+ // an ordinary tool message: appending it is what makes the definitions
56
+ // callable on the next turn.
57
+ history.push({ role: 'assistant', content: await first.text });
58
+ history.push({
59
+ role: 'tool',
60
+ content: executeToolSearch(tools, search.arguments, history)
61
+ });
62
+ console.log('▸ Definitions appended. Turn 2 — the model can now call them directly.\n');
63
+ const second = completion({ modelId, history, stream: true, tools });
64
+ for await (const token of second.tokenStream)
65
+ process.stdout.write(token);
66
+ for (const call of await second.toolCalls) {
67
+ console.log(`\n\n▸ Tool call: ${call.name}(${JSON.stringify(call.arguments)})`);
68
+ console.log(`▸ Result: ${mockExecute(call.name, call.arguments)}`);
69
+ }
70
+ }
71
+ console.log('\n\n▸ Completed!');
72
+ await unloadModel({ modelId, clearStorage: false });
73
+ }
74
+ catch (error) {
75
+ console.error('✖', error);
76
+ process.exit(1);
77
+ }
@@ -4,6 +4,8 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
4
4
  // primary modelSrc is the LM GGUF; the codec decoder loads via modelConfig.
5
5
  // For zero-shot voice cloning also pass audio8CodecEncoderModelSrc plus
6
6
  // referenceAudioSrc/referenceText (a recording and its exact transcript).
7
+ // On macOS and iOS the decoder downloads with its Core ML bundle, which runs
8
+ // the codec's synthesis stack; `stats.codecOnCoreml` reports whether it did.
7
9
  // Only a fallback: the engine reports the rate it actually produced, and
8
10
  // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
9
11
  const AUDIO8_SAMPLE_RATE = 44100;
@@ -35,7 +37,9 @@ try {
35
37
  });
36
38
  const audioBuffer = await result.buffer;
37
39
  const sampleRate = (await result.sampleRate) ?? AUDIO8_SAMPLE_RATE;
40
+ const stats = await result.stats;
38
41
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
42
+ console.log(`▸ Codec ran on ${stats?.codecOnCoreml === 1 ? 'Core ML' : 'ggml'}`);
39
43
  console.log('▸ Saving audio to file...');
40
44
  createWav(audioBuffer, sampleRate, 'audio8-output.wav');
41
45
  console.log('▸ Audio saved to audio8-output.wav');
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=moss-dialogue.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { loadModel, textToSpeech, unloadModel, TTS_CODEC_DECODER_MOSS_TTS_F16, TTS_CODEC_ENCODER_MOSS_TTS_F16 } from '@qvac/sdk';
2
+ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
3
+ // MOSS-TTSD (GGML): multi-speaker dialogue in one pass, each voice cloned from
4
+ // a 24 kHz reference recording. The model continues the references, so the
5
+ // text opens with what each recording says under its speaker tag, followed by
6
+ // the lines to generate; only the new lines come out as audio. A dialogue is
7
+ // never split into sentences, so `stream: true` delivers the native chunks of
8
+ // the whole conversation (set `streamChunkTokens` to get them while it is
9
+ // still being generated).
10
+ //
11
+ // The MOSS-TTSD backbone is not in the QVAC Registry yet, so it is a local
12
+ // file; the codec halves it shares with MOSS-TTS download from the registry.
13
+ // Usage: node moss-dialogue.ts <moss-ttsd.gguf> <s1.wav> "<s1 transcript>" <s2.wav> "<s2 transcript>"
14
+ const [backbone, s1Audio, s1Text, s2Audio, s2Text] = process.argv.slice(2);
15
+ if (!backbone || !s1Audio || !s1Text || !s2Audio || !s2Text) {
16
+ console.error('Usage: node moss-dialogue.ts <moss-ttsd.gguf> <s1.wav> "<s1 transcript>" <s2.wav> "<s2 transcript>"');
17
+ process.exit(1);
18
+ }
19
+ // Only a fallback: the engine reports the rate it actually produced.
20
+ const MOSS_SAMPLE_RATE = 24000;
21
+ try {
22
+ const modelId = await loadModel({
23
+ modelSrc: backbone,
24
+ modelType: 'tts-ggml',
25
+ modelConfig: {
26
+ ttsEngine: 'moss',
27
+ language: 'en',
28
+ mossCodecDecoderModelSrc: TTS_CODEC_DECODER_MOSS_TTS_F16,
29
+ mossCodecEncoderModelSrc: TTS_CODEC_ENCODER_MOSS_TTS_F16,
30
+ // One recording per speaker, in the order the text tags them.
31
+ dialogueReferenceSrcs: [s1Audio, s2Audio],
32
+ streamChunkTokens: 25,
33
+ useGPU: true
34
+ }
35
+ });
36
+ console.log(`▸ Model loaded: ${modelId}`);
37
+ console.log('▸ Synthesizing the dialogue...');
38
+ const result = textToSpeech({
39
+ modelId,
40
+ text: `[S1] ${s1Text} [S2] ${s2Text} ` +
41
+ '[S1] Did the build finish? [S2] Yes, every test passed. [S1] Great, ship it.',
42
+ stream: true
43
+ });
44
+ const samples = [];
45
+ for await (const sample of result.bufferStream)
46
+ samples.push(sample);
47
+ const sampleRate = (await result.sampleRate) ?? MOSS_SAMPLE_RATE;
48
+ console.log(`▸ Dialogue complete: ${samples.length} samples`);
49
+ createWav(samples, sampleRate, 'moss-dialogue-output.wav');
50
+ console.log('▸ Audio saved to moss-dialogue-output.wav');
51
+ console.log('▸ Playing audio...');
52
+ const audioData = int16ArrayToBuffer(samples);
53
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
54
+ playAudio(wavBuffer);
55
+ console.log('▸ Audio playback complete');
56
+ await unloadModel({ modelId });
57
+ console.log('▸ Model unloaded');
58
+ process.exit(0);
59
+ }
60
+ catch (error) {
61
+ console.error('✖', error);
62
+ process.exit(1);
63
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=moss.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { loadModel, textToSpeech, unloadModel, TTS_DELAY_LLM_MOSS_TTS_F16, TTS_CODEC_DECODER_MOSS_TTS_F16, TTS_CODEC_ENCODER_MOSS_TTS_F16 } from '@qvac/sdk';
2
+ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
3
+ // MOSS (GGML): OpenMOSS MOSS-TTS v1.5 Delay, an 8B backbone (about 17 GB)
4
+ // plus a codec, so desktop only. The primary modelSrc is the backbone; the
5
+ // codec decoder loads via modelConfig, and the codec encoder is only needed to
6
+ // clone a voice from a 24 kHz reference recording (no transcript needed).
7
+ //
8
+ // MOSS streams natively: with `streamChunkTokens` set, `stream: true` emits a
9
+ // chunk every that-many codec frames (12.5 per second) while the backbone is
10
+ // still generating, and the text is synthesized as one utterance, so the
11
+ // `[pause 1.0s]` marker below and `durationTokens` apply to all of it.
12
+ //
13
+ // Uses registry model constants — downloads automatically from QVAC Registry.
14
+ // Usage: node moss.ts [referenceAudio.wav]
15
+ const [referenceAudioSrc] = process.argv.slice(2);
16
+ // Only a fallback: the engine reports the rate it actually produced.
17
+ const MOSS_SAMPLE_RATE = 24000;
18
+ try {
19
+ const modelId = await loadModel({
20
+ modelSrc: TTS_DELAY_LLM_MOSS_TTS_F16,
21
+ modelConfig: {
22
+ ttsEngine: 'moss',
23
+ language: 'en',
24
+ mossCodecDecoderModelSrc: TTS_CODEC_DECODER_MOSS_TTS_F16,
25
+ ...(referenceAudioSrc
26
+ ? { mossCodecEncoderModelSrc: TTS_CODEC_ENCODER_MOSS_TTS_F16, referenceAudioSrc }
27
+ : {}),
28
+ streamChunkTokens: 25,
29
+ useGPU: true,
30
+ seed: 7
31
+ },
32
+ onProgress: (p) => {
33
+ const mb = (n) => (n / 1e6).toFixed(1);
34
+ const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
35
+ process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
36
+ if (p.percentage >= 100)
37
+ process.stderr.write('\n');
38
+ }
39
+ });
40
+ console.log(`▸ Model loaded: ${modelId}`);
41
+ console.log('▸ Streaming MOSS Text-to-Speech...');
42
+ const started = Date.now();
43
+ const result = textToSpeech({
44
+ modelId,
45
+ text: 'Hold on [pause 1.0s] here it comes: speech that starts playing while the rest is still being generated.',
46
+ stream: true
47
+ });
48
+ const samples = [];
49
+ for await (const sample of result.bufferStream) {
50
+ if (samples.length === 0)
51
+ console.log(`▸ First audio after ${Date.now() - started} ms`);
52
+ samples.push(sample);
53
+ }
54
+ const sampleRate = (await result.sampleRate) ?? MOSS_SAMPLE_RATE;
55
+ const stats = await result.stats;
56
+ console.log(`▸ TTS complete: ${samples.length} samples, ${stats?.generatedFrames ?? '?'} codec frames, RTF ${stats?.realTimeFactor?.toFixed(2) ?? '?'}`);
57
+ createWav(samples, sampleRate, 'moss-output.wav');
58
+ console.log('▸ Audio saved to moss-output.wav');
59
+ console.log('▸ Playing audio...');
60
+ const audioData = int16ArrayToBuffer(samples);
61
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
62
+ playAudio(wavBuffer);
63
+ console.log('▸ Audio playback complete');
64
+ await unloadModel({ modelId });
65
+ console.log('▸ Model unloaded');
66
+ process.exit(0);
67
+ }
68
+ catch (error) {
69
+ console.error('✖', error);
70
+ process.exit(1);
71
+ }
@@ -20,7 +20,7 @@ export declare function buildContract(): {
20
20
  condition: string;
21
21
  responseSchema: string;
22
22
  };
23
- name: "audioGenStream" | "audioEditStream" | "audioUnderstand" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "vectorIndex" | "deleteCache" | "getModelInfo" | "getLoadedModelInfo" | "getSystemResources" | "assessModelFit" | "ocrStream" | "diffusionStream" | "videoStream" | "upscaleStream" | "worldSceneStream" | "worldStepStream" | "resume" | "pluginInvoke" | "pluginInvokeStream" | "modelRegistryList" | "modelRegistrySearch" | "modelRegistryGetModel" | "suspend" | "state" | "classify";
23
+ name: "audioGenStream" | "audioEditStream" | "audioUnderstand" | "startRpcServer" | "stopRpcServer" | "discoverRpcServers" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "vectorIndex" | "deleteCache" | "getModelInfo" | "getLoadedModelInfo" | "getSystemResources" | "assessModelFit" | "ocrStream" | "diffusionStream" | "videoStream" | "upscaleStream" | "worldSceneStream" | "worldStepStream" | "resume" | "pluginInvoke" | "pluginInvokeStream" | "modelRegistryList" | "modelRegistrySearch" | "modelRegistryGetModel" | "suspend" | "state" | "classify";
24
24
  callShape: "duplex" | "request-reply" | "server-stream";
25
25
  requestSchema: string;
26
26
  responseSchema: string;
@@ -33,6 +33,7 @@ export declare function renderContractFiles(): Promise<{
33
33
  'models.json': string;
34
34
  'model-type-maps.json': string;
35
35
  'error-codes.json': string;
36
+ 'numeric-constants.json': string;
36
37
  }>;
37
38
  export {};
38
39
  //# sourceMappingURL=build-contract.d.ts.map
@@ -7,6 +7,7 @@ import { constantsRegistry } from './constants-registry.js';
7
7
  import { buildModelsRegistry } from './build-models-registry.js';
8
8
  import { buildModelTypeMaps } from './build-model-type-maps.js';
9
9
  import { buildErrorCodes } from './build-error-codes.js';
10
+ import { buildNumericConstants } from './build-numeric-constants.js';
10
11
  export const contractDir = new URL('../../contract/', import.meta.url);
11
12
  export const callShapeByHandlerType = {
12
13
  reply: 'request-reply',
@@ -610,11 +611,13 @@ export async function renderContractFiles() {
610
611
  const modelsRegistry = buildModelsRegistry();
611
612
  const modelTypeMaps = buildModelTypeMaps();
612
613
  const errorCodes = buildErrorCodes();
614
+ const numericConstants = buildNumericConstants();
613
615
  return {
614
616
  'schema.json': await formatJson(schemaDocument, 'schema.json'),
615
617
  'manifest.json': await formatJson(manifest, 'manifest.json'),
616
618
  'models.json': await formatJson(modelsRegistry, 'models.json'),
617
619
  'model-type-maps.json': await formatJson(modelTypeMaps, 'model-type-maps.json'),
618
- 'error-codes.json': await formatJson(errorCodes, 'error-codes.json')
620
+ 'error-codes.json': await formatJson(errorCodes, 'error-codes.json'),
621
+ 'numeric-constants.json': await formatJson(numericConstants, 'numeric-constants.json')
619
622
  };
620
623
  }