@qvac/sdk 0.19.0 → 0.20.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 (79) hide show
  1. package/NOTICE +111 -82
  2. package/dist/examples/abot-world.js +3 -3
  3. package/dist/examples/advisory-model-fit.d.ts +59 -0
  4. package/dist/examples/advisory-model-fit.js +156 -0
  5. package/dist/examples/asr/parakeet-nemotron-filesystem.d.ts +2 -0
  6. package/dist/examples/asr/parakeet-nemotron-filesystem.js +170 -0
  7. package/dist/examples/assess-model-fit.js +6 -1
  8. package/dist/examples/audiogen/edit-music.d.ts +2 -0
  9. package/dist/examples/audiogen/edit-music.js +121 -0
  10. package/dist/examples/audiogen/understand-music.d.ts +2 -0
  11. package/dist/examples/audiogen/understand-music.js +73 -0
  12. package/dist/examples/diffusion-ic-lora-ltx.js +3 -3
  13. package/dist/examples/diffusion-img2vid.js +1 -1
  14. package/dist/examples/diffusion-txt2vid-minimax-h3.d.ts +2 -0
  15. package/dist/examples/diffusion-txt2vid-minimax-h3.js +64 -0
  16. package/dist/examples/diffusion-txt2vid.js +1 -1
  17. package/dist/examples/kv-cache-cleanup.js +5 -2
  18. package/dist/examples/model-fit-ui/server.js +33 -7
  19. package/dist/examples/rag/rag-turbovec.d.ts +2 -0
  20. package/dist/examples/rag/rag-turbovec.js +58 -0
  21. package/dist/examples/tts/audio8.js +5 -5
  22. package/dist/examples/tts/chatterbox-enhanced.js +4 -5
  23. package/dist/examples/tts/chatterbox-multilingual.js +5 -5
  24. package/dist/examples/tts/chatterbox.js +5 -5
  25. package/dist/examples/tts/cosyvoice3.js +5 -5
  26. package/dist/examples/tts/llm-to-tts-streaming.js +12 -2
  27. package/dist/examples/tts/parler.js +5 -5
  28. package/dist/examples/tts/supertonic-enhanced.js +4 -5
  29. package/dist/examples/tts/supertonic-multilingual.js +5 -5
  30. package/dist/examples/tts/supertonic.js +5 -5
  31. package/dist/scripts/contract/build-contract.d.ts +1 -1
  32. package/dist/scripts/contract/constants-registry.d.ts +31 -0
  33. package/dist/scripts/contract/constants-registry.js +13 -2
  34. package/dist/scripts/contract/method-shapes.d.ts +3 -0
  35. package/dist/scripts/contract/method-shapes.js +3 -0
  36. package/dist/src/client/api/assess-model-fit.js +2 -0
  37. package/dist/src/client/api/audio-edit.d.ts +36 -0
  38. package/dist/src/client/api/audio-edit.js +38 -0
  39. package/dist/src/client/api/audio-gen-result.d.ts +5 -1
  40. package/dist/src/client/api/audio-gen-result.js +105 -52
  41. package/dist/src/client/api/audio-gen.d.ts +38 -1
  42. package/dist/src/client/api/audio-gen.js +38 -1
  43. package/dist/src/client/api/audio-understand.d.ts +25 -0
  44. package/dist/src/client/api/audio-understand.js +27 -0
  45. package/dist/src/client/api/delete-cache.d.ts +7 -1
  46. package/dist/src/client/api/delete-cache.js +11 -1
  47. package/dist/src/client/api/index.d.ts +3 -0
  48. package/dist/src/client/api/index.js +3 -0
  49. package/dist/src/client/api/text-to-speech.d.ts +31 -4
  50. package/dist/src/client/api/text-to-speech.js +118 -21
  51. package/dist/src/client/api/vector-index.d.ts +143 -0
  52. package/dist/src/client/api/vector-index.js +168 -0
  53. package/dist/src/client/api/world.d.ts +2 -2
  54. package/dist/src/commands/bundle/manifest.d.ts +2 -0
  55. package/dist/src/commands/verify/addon-source.d.ts +6 -0
  56. package/dist/src/commands/verify/bundle-source.d.ts +2 -0
  57. package/dist/src/commands/verify/bundle-source.js +155 -1
  58. package/dist/src/commands/verify/index.js +1 -0
  59. package/dist/src/commands/verify/prebuilds.d.ts +23 -0
  60. package/dist/src/commands/verify/prebuilds.js +129 -13
  61. package/dist/src/commands/verify/resource-collectors.js +2 -1
  62. package/dist/src/expo/plugins/patches/qvac-platform-addons.d.ts +23 -0
  63. package/dist/src/expo/plugins/patches/qvac-platform-addons.js +94 -0
  64. package/dist/src/expo/plugins/withMobileBundle.d.ts +12 -2
  65. package/dist/src/expo/plugins/withMobileBundle.js +62 -31
  66. package/dist/src/index.d.ts +3 -3
  67. package/dist/src/index.js +7 -3
  68. package/dist/src/schemas/sdk-errors-server.d.ts +4 -0
  69. package/dist/src/schemas/sdk-errors-server.js +22 -0
  70. package/dist/src/utils/aggregate-events.d.ts +3 -1
  71. package/dist/src/utils/aggregate-events.js +7 -1
  72. package/dist/src/utils/errors-server.d.ts +12 -0
  73. package/dist/src/utils/errors-server.js +21 -0
  74. package/docs/system-resources-support-matrix.md +19 -19
  75. package/package.json +22 -15
  76. package/src/electron-forge/index.cjs +86 -14
  77. package/src/expo/plugins/patches/android-link.mjs +18 -9
  78. package/src/expo/plugins/patches/ios-link.mjs +18 -9
  79. package/src/expo/plugins/patches/qvac-platform-addons.ts +124 -0
@@ -69,13 +69,16 @@ try {
69
69
  console.log('\n▸ 2. Delete specific model within cache key:');
70
70
  console.log('▸ await deleteCache({ kvCacheKey: "my-session", modelId: "model-abc" })');
71
71
  console.log('▸ └─ Removes: my-session/model-abc/ (specific model directory)');
72
- console.log('\n▸ 3. Delete ALL caches (entire cache folder):');
72
+ console.log('\n▸ 3. Reclaim automatic caches only (named keys survive):');
73
+ console.log('▸ await deleteCache({ auto: true })');
74
+ console.log('▸ └─ Removes: every kvCache:true cache no request is using');
75
+ console.log('\n▸ 4. Delete ALL caches (entire cache folder):');
73
76
  console.log('▸ await deleteCache({ all: true })');
74
77
  console.log('▸ └─ Removes: ALL cache files from all sessions and models');
75
78
  console.log('\n▸ Use Cases:');
76
79
  console.log("▸ - User logout → Delete user's cache key");
77
80
  console.log('▸ - Model updated → Delete old model caches');
78
- console.log('▸ - Disk space cleanup → Delete old cache keys or all caches');
81
+ console.log('▸ - Disk space cleanup → Reclaim with { auto: true }');
79
82
  console.log('▸ - Privacy → Clear conversation history');
80
83
  console.log('▸ - App reset → Delete all caches with { all: true }');
81
84
  await unloadModel({ modelId, clearStorage: false });
@@ -21,17 +21,20 @@
21
21
  * node dist/examples/model-fit-ui/server.js
22
22
  */
23
23
  import { createServer } from 'node:http';
24
- import { readFileSync } from 'node:fs';
25
- import { assessModelFit, getSystemResources, loadModel, unloadModel, completion, close, 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';
24
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
25
+ import { homedir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { assessModelFit, getSystemResources, loadModel, unloadModel, completion, close, QWEN3_600M_INST_Q4, QWEN3_1_7B_INST_Q4, QWEN3_4B_INST_Q4_K_M, QWEN3_8B_INST_Q4_K_M, GEMMA4_31B_MULTIMODAL_Q4_K_M } from '@qvac/sdk';
26
28
  const PORT = 8712;
27
- // A ladder ending well past what a laptop has, so one screen shows every
28
- // verdict. The 27B at Q8 is here to be refused.
29
+ // A ladder ending past what a laptop has, so one screen shows every verdict.
30
+ // The 31B is here to be refused: 18 GiB against a 24 GiB machine leaves the OS
31
+ // no way to satisfy it without paging.
29
32
  const CATALOG = [
30
33
  QWEN3_600M_INST_Q4,
31
34
  QWEN3_1_7B_INST_Q4,
32
35
  QWEN3_4B_INST_Q4_K_M,
33
36
  QWEN3_8B_INST_Q4_K_M,
34
- QWEN3_8_27B_MULTIMODAL_UD_Q8_K_XL
37
+ GEMMA4_31B_MULTIMODAL_Q4_K_M
35
38
  ];
36
39
  const PAGE_PATH = new URL('index.html', import.meta.url);
37
40
  // Read per request, not once at startup: editing the page and reloading is the
@@ -43,6 +46,18 @@ function page() {
43
46
  // `onProgress` is passed, and without it a 29 GiB download, a stall and a dead
44
47
  // worker all look identical from the browser: "loading…" forever.
45
48
  const progress = new Map();
49
+ // Whether the weights are already on disk, so a Run costs no download. The
50
+ // cache layout is not API, so this matches on file size — enough to label a
51
+ // button, and it errs towards promising a download that may not happen.
52
+ function cachedSizes() {
53
+ try {
54
+ const dir = join(homedir(), '.qvac', 'models');
55
+ return new Set(readdirSync(dir).map((file) => statSync(join(dir, file)).size));
56
+ }
57
+ catch {
58
+ return new Set();
59
+ }
60
+ }
46
61
  function byName(name) {
47
62
  const model = CATALOG.find((entry) => entry.name === name);
48
63
  if (!model)
@@ -131,7 +146,13 @@ async function run(body) {
131
146
  systemUsedBeforeBytes: before,
132
147
  systemUsedAfterLoadBytes: afterLoad,
133
148
  backendDevice: final.stats?.backendDevice,
134
- sample: final.contentText.trim().slice(0, 120)
149
+ // The engine's own numbers for the run: tokens/s, time to first token,
150
+ // token counts. Worth showing next to the memory figures.
151
+ stats: final.stats,
152
+ // `cacheableAssistantContent` is the same text with <think> blocks
153
+ // stripped; a reasoning model otherwise leads with its scratchpad.
154
+ sample: (final.cacheableAssistantContent ?? final.contentText).trim().slice(0, 160) ||
155
+ '(no plain text — the model returned reasoning only)'
135
156
  };
136
157
  }
137
158
  catch (error) {
@@ -164,7 +185,12 @@ const server = createServer((request, response) => {
164
185
  return;
165
186
  }
166
187
  if (request.method === 'GET' && url === '/api/models') {
167
- sendJson(response, 200, CATALOG.map((model) => ({ name: model.name, expectedSize: model.expectedSize })));
188
+ const sizes = cachedSizes();
189
+ sendJson(response, 200, CATALOG.map((model) => ({
190
+ name: model.name,
191
+ expectedSize: model.expectedSize,
192
+ cached: sizes.has(model.expectedSize)
193
+ })));
168
194
  return;
169
195
  }
170
196
  if (request.method === 'POST' && (url === '/api/assess' || url === '/api/run')) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rag-turbovec.d.ts.map
@@ -0,0 +1,58 @@
1
+ import { createVectorIndex, embed, loadModel, loadVectorIndex, unloadModel, GTE_LARGE_FP16, VectorIndexStorage } from '@qvac/sdk';
2
+ // Retrieval over documents kept in your own store. The SDK holds only the
3
+ // vectors, in a TurboVec index inside the worker; the documents stay in this
4
+ // Map (or any database you choose) and result ids map back to them.
5
+ try {
6
+ const query = process.argv[2] || 'Which moon has methane rain and lakes?';
7
+ console.log(`▸ Query: "${query}"`);
8
+ const documents = new Map([
9
+ ['1', 'Saturn moon Titan has lakes, clouds, and rain made of liquid methane.'],
10
+ ['2', 'Solar panels convert sunlight into electricity using photovoltaic cells.'],
11
+ ['3', 'Honeybees communicate the location of flowers through a waggle dance.'],
12
+ ['4', 'The Pacific Ocean is the largest and deepest ocean on Earth.']
13
+ ]);
14
+ const modelId = await loadModel({
15
+ modelSrc: GTE_LARGE_FP16,
16
+ onProgress: (p) => {
17
+ const mb = (n) => (n / 1e6).toFixed(1);
18
+ const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
19
+ process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
20
+ if (p.percentage >= 100)
21
+ process.stderr.write('\n');
22
+ }
23
+ });
24
+ console.log('▸ Embedding documents...');
25
+ const { embedding: vectors } = await embed({ modelId, text: [...documents.values()] });
26
+ console.log('▸ Building the vector index...');
27
+ const index = await createVectorIndex({
28
+ dim: vectors[0].length,
29
+ storage: VectorIndexStorage.TURBOVEC_Q4
30
+ });
31
+ await index.add({ ids: [...documents.keys()], vectors });
32
+ console.log(`▸ Indexed ${index.length} vectors of dimension ${index.dim}`);
33
+ console.log('▸ Searching...');
34
+ const { embedding: queryVector } = await embed({ modelId, text: query });
35
+ const hits = await index.search({ query: queryVector, k: 2 });
36
+ console.log('▸ Top matches:');
37
+ for (const hit of hits) {
38
+ console.log(` score=${hit.score.toFixed(4)} id=${hit.id}: ${documents.get(hit.id)}`);
39
+ }
40
+ // Snapshots persist the vectors only. A relative path resolves under the
41
+ // QVAC data directory; pass an absolute path to store it elsewhere.
42
+ const snapshotPath = 'examples/rag-turbovec.qvi';
43
+ const { path: writtenPath } = await index.write({ path: snapshotPath });
44
+ await index.dispose();
45
+ console.log(`▸ Snapshot written to ${writtenPath}`);
46
+ const reloaded = await loadVectorIndex({ path: snapshotPath });
47
+ const [reloadedBest] = await reloaded.search({ query: queryVector, k: 1 });
48
+ await reloaded.dispose();
49
+ if (!reloadedBest || reloadedBest.id !== hits[0]?.id) {
50
+ throw new Error(`Reloaded index returned id=${reloadedBest?.id} but the original returned id=${hits[0]?.id}`);
51
+ }
52
+ console.log(`▸ Reloaded index agrees: best match id=${reloadedBest.id}`);
53
+ await unloadModel({ modelId });
54
+ }
55
+ catch (error) {
56
+ console.error('✖', error);
57
+ process.exit(1);
58
+ }
@@ -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
+ // Only a fallback: the engine reports the rate it actually produced, and
8
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
7
9
  const AUDIO8_SAMPLE_RATE = 44100;
8
10
  try {
9
11
  const modelId = await loadModel({
@@ -32,16 +34,14 @@ try {
32
34
  stream: false
33
35
  });
34
36
  const audioBuffer = await result.buffer;
37
+ const sampleRate = (await result.sampleRate) ?? AUDIO8_SAMPLE_RATE;
35
38
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
36
39
  console.log('▸ Saving audio to file...');
37
- createWav(audioBuffer, AUDIO8_SAMPLE_RATE, 'audio8-output.wav');
40
+ createWav(audioBuffer, sampleRate, 'audio8-output.wav');
38
41
  console.log('▸ Audio saved to audio8-output.wav');
39
42
  console.log('▸ Playing audio...');
40
43
  const audioData = int16ArrayToBuffer(audioBuffer);
41
- const wavBuffer = Buffer.concat([
42
- createWavHeader(audioData.length, AUDIO8_SAMPLE_RATE),
43
- audioData
44
- ]);
44
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
45
45
  playAudio(wavBuffer);
46
46
  console.log('▸ Audio playback complete');
47
47
  await unloadModel({ modelId });
@@ -5,6 +5,7 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
5
5
  // enhancement — there is no on/off flag — and it forces the output to 48 kHz.
6
6
  // Usage: node chatterbox-enhanced.ts [referenceAudioSrc]
7
7
  const [referenceAudioSrc] = process.argv.slice(2);
8
+ // Only a fallback; the engine reports the rate it actually produced.
8
9
  const ENHANCED_SAMPLE_RATE = 48000;
9
10
  try {
10
11
  const modelId = await loadModel({
@@ -33,16 +34,14 @@ try {
33
34
  stream: false
34
35
  });
35
36
  const audioBuffer = await result.buffer;
37
+ const sampleRate = (await result.sampleRate) ?? ENHANCED_SAMPLE_RATE;
36
38
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
37
39
  console.log('▸ Saving audio to file...');
38
- createWav(audioBuffer, ENHANCED_SAMPLE_RATE, 'chatterbox-enhanced-output.wav');
40
+ createWav(audioBuffer, sampleRate, 'chatterbox-enhanced-output.wav');
39
41
  console.log('▸ Audio saved to chatterbox-enhanced-output.wav');
40
42
  console.log('▸ Playing audio...');
41
43
  const audioData = int16ArrayToBuffer(audioBuffer);
42
- const wavBuffer = Buffer.concat([
43
- createWavHeader(audioData.length, ENHANCED_SAMPLE_RATE),
44
- audioData
45
- ]);
44
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
46
45
  playAudio(wavBuffer);
47
46
  console.log('▸ Audio playback complete');
48
47
  await unloadModel({ modelId });
@@ -2,6 +2,8 @@ import { loadModel, textToSpeech, unloadModel, TTS_T3_MULTILINGUAL_CHATTERBOX_Q4
2
2
  import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
3
3
  // Chatterbox multilingual TTS (GGML): Japanese synthesis with MeCab/IPAdic.
4
4
  // Uses registry model constants — downloads automatically from QVAC Registry.
5
+ // Only a fallback: the engine reports the rate it actually produced, and
6
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
5
7
  const CHATTERBOX_SAMPLE_RATE = 24000;
6
8
  const OUTPUT_FILE = 'chatterbox-multilingual-output.wav';
7
9
  try {
@@ -32,16 +34,14 @@ try {
32
34
  stream: false
33
35
  });
34
36
  const audioBuffer = await result.buffer;
37
+ const sampleRate = (await result.sampleRate) ?? CHATTERBOX_SAMPLE_RATE;
35
38
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
36
39
  console.log('▸ Saving audio to file...');
37
- createWav(audioBuffer, CHATTERBOX_SAMPLE_RATE, OUTPUT_FILE);
40
+ createWav(audioBuffer, sampleRate, OUTPUT_FILE);
38
41
  console.log(`▸ Audio saved to ${OUTPUT_FILE}`);
39
42
  console.log('▸ Playing audio...');
40
43
  const audioData = int16ArrayToBuffer(audioBuffer);
41
- const wavBuffer = Buffer.concat([
42
- createWavHeader(audioData.length, CHATTERBOX_SAMPLE_RATE),
43
- audioData
44
- ]);
44
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
45
45
  playAudio(wavBuffer);
46
46
  console.log('▸ Audio playback complete');
47
47
  await unloadModel({ modelId });
@@ -4,6 +4,8 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
4
4
  // Uses registry model constants — downloads automatically from QVAC Registry.
5
5
  // Usage: node chatterbox.ts [referenceAudioSrc]
6
6
  const [referenceAudioSrc] = process.argv.slice(2);
7
+ // Only a fallback: the engine reports the rate it actually produced, and
8
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
7
9
  const CHATTERBOX_SAMPLE_RATE = 24000;
8
10
  try {
9
11
  const modelId = await loadModel({
@@ -35,16 +37,14 @@ try {
35
37
  stream: false
36
38
  });
37
39
  const audioBuffer = await result.buffer;
40
+ const sampleRate = (await result.sampleRate) ?? CHATTERBOX_SAMPLE_RATE;
38
41
  console.log(`▸ TTS complete. Total bytes: ${audioBuffer.length}`);
39
42
  console.log('▸ Saving audio to file...');
40
- createWav(audioBuffer, CHATTERBOX_SAMPLE_RATE, 'tts-output.wav');
43
+ createWav(audioBuffer, sampleRate, 'tts-output.wav');
41
44
  console.log('▸ Audio saved to tts-output.wav');
42
45
  console.log('▸ Playing audio...');
43
46
  const audioData = int16ArrayToBuffer(audioBuffer);
44
- const wavBuffer = Buffer.concat([
45
- createWavHeader(audioData.length, CHATTERBOX_SAMPLE_RATE),
46
- audioData
47
- ]);
47
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
48
48
  playAudio(wavBuffer);
49
49
  console.log('▸ Audio playback complete');
50
50
  await unloadModel({ modelId });
@@ -4,6 +4,8 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
4
4
  // synthesis (emotion, pace, or instruct). The LLM GGUF's registry companion
5
5
  // set downloads the flow/HiFT models, baked voice and tokenizer alongside it.
6
6
  // Native output is 24 kHz.
7
+ // Only a fallback: the engine reports the rate it actually produced, and
8
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
7
9
  const COSYVOICE3_SAMPLE_RATE = 24000;
8
10
  try {
9
11
  const modelId = await loadModel({
@@ -30,16 +32,14 @@ try {
30
32
  emotion: 'happy'
31
33
  });
32
34
  const audioBuffer = await result.buffer;
35
+ const sampleRate = (await result.sampleRate) ?? COSYVOICE3_SAMPLE_RATE;
33
36
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
34
37
  console.log('▸ Saving audio to file...');
35
- createWav(audioBuffer, COSYVOICE3_SAMPLE_RATE, 'cosyvoice3-output.wav');
38
+ createWav(audioBuffer, sampleRate, 'cosyvoice3-output.wav');
36
39
  console.log('▸ Audio saved to cosyvoice3-output.wav');
37
40
  console.log('▸ Playing audio...');
38
41
  const audioData = int16ArrayToBuffer(audioBuffer);
39
- const wavBuffer = Buffer.concat([
40
- createWavHeader(audioData.length, COSYVOICE3_SAMPLE_RATE),
41
- audioData
42
- ]);
42
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
43
43
  playAudio(wavBuffer);
44
44
  console.log('▸ Audio playback complete');
45
45
  await unloadModel({ modelId });
@@ -14,6 +14,9 @@
14
14
  */
15
15
  import { completion, loadModel, textToSpeechStream, unloadModel, LLAMA_3_2_1B_INST_Q4_0, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0 } from '@qvac/sdk';
16
16
  import { createWav, playPcmInt16Chunk } from './utils.js';
17
+ // Only a fallback, for the case where no frame ever reports a rate: each
18
+ // audio frame carries the rate the engine actually produced, and
19
+ // `outputSampleRate` can move it off this default.
17
20
  const SUPERTONIC_SAMPLE_RATE = 44100;
18
21
  /**
19
22
  * Drop angle-bracket spans (e.g. `<think>…</think>`, `<|foo|>`) even when split across tokens,
@@ -107,16 +110,23 @@ try {
107
110
  });
108
111
  let phraseIndex = 0;
109
112
  const filterChunk = createAngleBracketAndStarFilter();
113
+ // Playback and the WAV header both follow what the engine reports rather
114
+ // than the engine's documented default, so a configured `outputSampleRate`
115
+ // cannot leave the audio playing back at the wrong speed.
116
+ let sampleRate = SUPERTONIC_SAMPLE_RATE;
110
117
  const drainPcm = (async () => {
111
118
  for await (const m of ttsSession) {
112
119
  if (m.buffer.length > 0) {
120
+ if (m.sampleRate !== undefined) {
121
+ sampleRate = m.sampleRate;
122
+ }
113
123
  appendPcmSamples(combinedPcm, m.buffer);
114
124
  phraseIndex += 1;
115
125
  const preview = typeof m.sentenceChunk === 'string'
116
126
  ? m.sentenceChunk.replace(/\s+/g, ' ').trim().slice(0, 72)
117
127
  : '';
118
128
  console.log(`\n▸ [TTS phrase ${phraseIndex}] ${m.buffer.length} samples${preview ? ` — "${preview}${preview.length >= 72 ? '...' : ''}"` : ''}`);
119
- await playPcmInt16Chunk(m.buffer, SUPERTONIC_SAMPLE_RATE);
129
+ await playPcmInt16Chunk(m.buffer, sampleRate);
120
130
  }
121
131
  }
122
132
  })();
@@ -135,7 +145,7 @@ try {
135
145
  }
136
146
  const outWav = 'llm-to-tts-streaming-output.wav';
137
147
  console.log(`\n▸ Writing ${combinedPcm.length} samples to ${outWav} (full utterance; phrases were already played above).`);
138
- createWav(combinedPcm, SUPERTONIC_SAMPLE_RATE, outWav);
148
+ createWav(combinedPcm, sampleRate, outWav);
139
149
  await unloadModel({ modelId: llmModelId, clearStorage: false });
140
150
  await unloadModel({ modelId: ttsModelId, clearStorage: false });
141
151
  console.log('▸ Models unloaded.');
@@ -2,6 +2,8 @@ import { loadModel, textToSpeech, unloadModel, TTS_MINI_V1_EN_PARLER_TTS_Q8_0 }
2
2
  import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
3
3
  // Parler-TTS (GGML): description-conditioned speech with per-call voice controls.
4
4
  // Uses the registry-hosted Mini v1 Q8_0 model and its native 44.1 kHz output.
5
+ // Only a fallback: the engine reports the rate it actually produced, and
6
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
5
7
  const PARLER_SAMPLE_RATE = 44100;
6
8
  try {
7
9
  const modelId = await loadModel({
@@ -29,16 +31,14 @@ try {
29
31
  emotion: 'happy'
30
32
  });
31
33
  const audioBuffer = await result.buffer;
34
+ const sampleRate = (await result.sampleRate) ?? PARLER_SAMPLE_RATE;
32
35
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
33
36
  console.log('▸ Saving audio to file...');
34
- createWav(audioBuffer, PARLER_SAMPLE_RATE, 'parler-output.wav');
37
+ createWav(audioBuffer, sampleRate, 'parler-output.wav');
35
38
  console.log('▸ Audio saved to parler-output.wav');
36
39
  console.log('▸ Playing audio...');
37
40
  const audioData = int16ArrayToBuffer(audioBuffer);
38
- const wavBuffer = Buffer.concat([
39
- createWavHeader(audioData.length, PARLER_SAMPLE_RATE),
40
- audioData
41
- ]);
41
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
42
42
  playAudio(wavBuffer);
43
43
  console.log('▸ Audio playback complete');
44
44
  await unloadModel({ modelId });
@@ -5,6 +5,7 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
5
5
  // Supplying the enhancer GGUF is what enables enhancement — there is no on/off
6
6
  // flag — and it forces the output to 48 kHz regardless of the engine's native
7
7
  // rate.
8
+ // Only a fallback; the engine reports the rate it actually produced.
8
9
  const ENHANCED_SAMPLE_RATE = 48000;
9
10
  try {
10
11
  const modelId = await loadModel({
@@ -35,16 +36,14 @@ try {
35
36
  stream: false
36
37
  });
37
38
  const audioBuffer = await result.buffer;
39
+ const sampleRate = (await result.sampleRate) ?? ENHANCED_SAMPLE_RATE;
38
40
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
39
41
  console.log('▸ Saving audio to file...');
40
- createWav(audioBuffer, ENHANCED_SAMPLE_RATE, 'supertonic-enhanced-output.wav');
42
+ createWav(audioBuffer, sampleRate, 'supertonic-enhanced-output.wav');
41
43
  console.log('▸ Audio saved to supertonic-enhanced-output.wav');
42
44
  console.log('▸ Playing audio...');
43
45
  const audioData = int16ArrayToBuffer(audioBuffer);
44
- const wavBuffer = Buffer.concat([
45
- createWavHeader(audioData.length, ENHANCED_SAMPLE_RATE),
46
- audioData
47
- ]);
46
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
48
47
  playAudio(wavBuffer);
49
48
  console.log('▸ Audio playback complete');
50
49
  await unloadModel({ modelId });
@@ -1,6 +1,8 @@
1
1
  import { loadModel, textToSpeech, unloadModel, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0 } from '@qvac/sdk';
2
2
  import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
3
3
  // Supertonic 3 TTS (GGML): multilingual synthesis across 31 languages.
4
+ // Only a fallback: the engine reports the rate it actually produced, and
5
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
4
6
  const SUPERTONIC_SAMPLE_RATE = 44100;
5
7
  try {
6
8
  const modelId = await loadModel({
@@ -29,16 +31,14 @@ try {
29
31
  stream: false
30
32
  });
31
33
  const audioBuffer = await result.buffer;
34
+ const sampleRate = (await result.sampleRate) ?? SUPERTONIC_SAMPLE_RATE;
32
35
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
33
36
  console.log('▸ Saving audio to file...');
34
- createWav(audioBuffer, SUPERTONIC_SAMPLE_RATE, 'supertonic-multilingual-output.wav');
37
+ createWav(audioBuffer, sampleRate, 'supertonic-multilingual-output.wav');
35
38
  console.log('▸ Audio saved to supertonic-multilingual-output.wav');
36
39
  console.log('▸ Playing audio...');
37
40
  const audioData = int16ArrayToBuffer(audioBuffer);
38
- const wavBuffer = Buffer.concat([
39
- createWavHeader(audioData.length, SUPERTONIC_SAMPLE_RATE),
40
- audioData
41
- ]);
41
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
42
42
  playAudio(wavBuffer);
43
43
  console.log('▸ Audio playback complete');
44
44
  await unloadModel({ modelId });
@@ -2,6 +2,8 @@ import { loadModel, textToSpeech, unloadModel, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0
2
2
  import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
3
3
  // Supertonic 3 TTS (GGML): fast multilingual synthesis with baked-in voices.
4
4
  // Uses registry model constants — downloads automatically from QVAC Registry.
5
+ // Only a fallback: the engine reports the rate it actually produced, and
6
+ // `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
5
7
  const SUPERTONIC_SAMPLE_RATE = 44100;
6
8
  try {
7
9
  const modelId = await loadModel({
@@ -30,16 +32,14 @@ try {
30
32
  stream: false
31
33
  });
32
34
  const audioBuffer = await result.buffer;
35
+ const sampleRate = (await result.sampleRate) ?? SUPERTONIC_SAMPLE_RATE;
33
36
  console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
34
37
  console.log('▸ Saving audio to file...');
35
- createWav(audioBuffer, SUPERTONIC_SAMPLE_RATE, 'supertonic-output.wav');
38
+ createWav(audioBuffer, sampleRate, 'supertonic-output.wav');
36
39
  console.log('▸ Audio saved to supertonic-output.wav');
37
40
  console.log('▸ Playing audio...');
38
41
  const audioData = int16ArrayToBuffer(audioBuffer);
39
- const wavBuffer = Buffer.concat([
40
- createWavHeader(audioData.length, SUPERTONIC_SAMPLE_RATE),
41
- audioData
42
- ]);
42
+ const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
43
43
  playAudio(wavBuffer);
44
44
  console.log('▸ Audio playback complete');
45
45
  await unloadModel({ modelId });
@@ -20,7 +20,7 @@ export declare function buildContract(): {
20
20
  condition: string;
21
21
  responseSchema: string;
22
22
  };
23
- name: "audioGenStream" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "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" | "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;
@@ -61,9 +61,24 @@ export declare const constantsRegistry: {
61
61
  readonly SupportedAudioFormat: z.ZodEnum<{
62
62
  [k: string]: string;
63
63
  }>;
64
+ readonly TtsEngine: z.ZodEnum<{
65
+ [k: string]: string;
66
+ }>;
64
67
  readonly TtsPace: z.ZodEnum<{
65
68
  [k: string]: string;
66
69
  }>;
70
+ readonly TtsParlerEmotion: z.ZodEnum<{
71
+ [k: string]: "command" | "anger" | "narration" | "conversation" | "disgust" | "fear" | "happy" | "neutral" | "proper noun" | "news" | "sad" | "surprise";
72
+ }>;
73
+ readonly TtsSentenceDelimiterPreset: z.ZodEnum<{
74
+ [k: string]: string;
75
+ }>;
76
+ readonly TtsChatterboxLanguage: z.ZodEnum<{
77
+ [k: string]: string;
78
+ }>;
79
+ readonly TtsSupertonicLanguage: z.ZodEnum<{
80
+ [k: string]: string;
81
+ }>;
67
82
  readonly TtsCosyvoice3Emotion: z.ZodEnum<{
68
83
  [k: string]: string;
69
84
  }>;
@@ -82,5 +97,21 @@ export declare const constantsRegistry: {
82
97
  readonly AudioGenTaskType: z.ZodEnum<{
83
98
  [k: string]: string;
84
99
  }>;
100
+ readonly AudioGenEditOperation: z.ZodEnum<{
101
+ [k: string]: string;
102
+ }>;
103
+ readonly AudioGenRepaintMode: z.ZodEnum<{
104
+ [k: string]: string;
105
+ }>;
106
+ readonly AudioGenTrack: z.ZodEnum<{
107
+ [k: string]: string;
108
+ }>;
109
+ readonly VectorIndexStorage: z.ZodEnum<{
110
+ readonly F32: "f32";
111
+ readonly Q8: "q8";
112
+ readonly Q4: "q4";
113
+ readonly TURBOVEC_Q4: "turbovec-q4";
114
+ readonly TURBOVEC_Q2: "turbovec-q2";
115
+ }>;
85
116
  };
86
117
  //# sourceMappingURL=constants-registry.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { ModelType, VERBOSITY, SUPPORTED_AUDIO_FORMATS, AUDIOGEN_ENGINES, AUDIOGEN_TASK_TYPES, TTS_PACES, TTS_COSYVOICE3_EMOTIONS, TTS_COSYVOICE3_INSTRUCT_DIALECTS, TTS_COSYVOICE3_INSTRUCT_VOLUMES, TTS_COSYVOICE3_INSTRUCT_STYLES } from '@qvac/inference/surface';
2
+ import { ModelType, VERBOSITY, SUPPORTED_AUDIO_FORMATS, AUDIOGEN_ENGINES, AUDIOGEN_TASK_TYPES, AUDIOGEN_EDIT_OPERATIONS, AUDIOGEN_REPAINT_MODES, AUDIOGEN_TRACKS, TTS_ENGINES, TTS_PACES, TTS_PARLER_EMOTIONS, TTS_SENTENCE_DELIMITER_PRESETS, TTS_CHATTERBOX_LANGUAGES, TTS_SUPERTONIC_LANGUAGES, TTS_COSYVOICE3_EMOTIONS, TTS_COSYVOICE3_INSTRUCT_DIALECTS, TTS_COSYVOICE3_INSTRUCT_VOLUMES, TTS_COSYVOICE3_INSTRUCT_STYLES, VectorIndexStorage } from '@qvac/inference/surface';
3
3
  import { PLUGIN_LLM, PLUGIN_EMBEDDING, PLUGIN_WHISPER, PLUGIN_BCI, PLUGIN_NMT, PLUGIN_TTS, PLUGIN_OCR, PLUGIN_DIFFUSION, PLUGIN_AUDIOGEN, PLUGIN_VLA, PLUGIN_CLASSIFICATION } from '../../src/plugin-ids.js';
4
4
  // Uppercases each vocabulary value into an identifier-safe varname (e.g.
5
5
  // 'slow' -> 'SLOW', 'cover-nofsq' -> 'COVER_NOFSQ'), mirroring the
@@ -52,11 +52,22 @@ export const constantsRegistry = {
52
52
  // than a hand-written record, so the varnames can't drift from the one
53
53
  // source of truth every other consumer of SUPPORTED_AUDIO_FORMATS shares.
54
54
  SupportedAudioFormat: z.enum(Object.fromEntries(SUPPORTED_AUDIO_FORMATS.map((ext) => [ext.slice(1).toUpperCase(), ext]))),
55
+ TtsEngine: enumFromVocabulary(TTS_ENGINES),
55
56
  TtsPace: enumFromVocabulary(TTS_PACES),
57
+ // 'proper noun' carries a space; enumFromVocabulary only rewrites '-', so
58
+ // map it explicitly to keep the varname identifier-safe.
59
+ TtsParlerEmotion: z.enum(Object.fromEntries(TTS_PARLER_EMOTIONS.map((value) => [value.toUpperCase().replace(/[\s-]/g, '_'), value]))),
60
+ TtsSentenceDelimiterPreset: enumFromVocabulary(TTS_SENTENCE_DELIMITER_PRESETS),
61
+ TtsChatterboxLanguage: enumFromVocabulary(TTS_CHATTERBOX_LANGUAGES),
62
+ TtsSupertonicLanguage: enumFromVocabulary(TTS_SUPERTONIC_LANGUAGES),
56
63
  TtsCosyvoice3Emotion: enumFromVocabulary(TTS_COSYVOICE3_EMOTIONS),
57
64
  TtsCosyvoice3InstructDialect: enumFromVocabulary(TTS_COSYVOICE3_INSTRUCT_DIALECTS),
58
65
  TtsCosyvoice3InstructVolume: enumFromVocabulary(TTS_COSYVOICE3_INSTRUCT_VOLUMES),
59
66
  TtsCosyvoice3InstructStyle: enumFromVocabulary(TTS_COSYVOICE3_INSTRUCT_STYLES),
60
67
  AudioGenEngine: enumFromVocabulary(AUDIOGEN_ENGINES),
61
- AudioGenTaskType: enumFromVocabulary(AUDIOGEN_TASK_TYPES)
68
+ AudioGenTaskType: enumFromVocabulary(AUDIOGEN_TASK_TYPES),
69
+ AudioGenEditOperation: enumFromVocabulary(AUDIOGEN_EDIT_OPERATIONS),
70
+ AudioGenRepaintMode: enumFromVocabulary(AUDIOGEN_REPAINT_MODES),
71
+ AudioGenTrack: enumFromVocabulary(AUDIOGEN_TRACKS),
72
+ VectorIndexStorage: z.enum(VectorIndexStorage)
62
73
  };
@@ -8,7 +8,9 @@
8
8
  */
9
9
  export declare const methodShapes: {
10
10
  readonly assessModelFit: "reply";
11
+ readonly audioEditStream: "stream";
11
12
  readonly audioGenStream: "stream";
13
+ readonly audioUnderstand: "stream";
12
14
  readonly batchCompletionStream: "stream";
13
15
  readonly bciTranscribe: "stream";
14
16
  readonly bciTranscribeStream: "duplex";
@@ -44,6 +46,7 @@ export declare const methodShapes: {
44
46
  readonly translate: "stream";
45
47
  readonly unloadModel: "reply";
46
48
  readonly upscaleStream: "stream";
49
+ readonly vectorIndex: "reply";
47
50
  readonly videoStream: "stream";
48
51
  readonly worldSceneStream: "stream";
49
52
  readonly worldStepStream: "stream";
@@ -8,7 +8,9 @@
8
8
  */
9
9
  export const methodShapes = {
10
10
  assessModelFit: 'reply',
11
+ audioEditStream: 'stream',
11
12
  audioGenStream: 'stream',
13
+ audioUnderstand: 'stream',
12
14
  batchCompletionStream: 'stream',
13
15
  bciTranscribe: 'stream',
14
16
  bciTranscribeStream: 'duplex',
@@ -44,6 +46,7 @@ export const methodShapes = {
44
46
  translate: 'stream',
45
47
  unloadModel: 'reply',
46
48
  upscaleStream: 'stream',
49
+ vectorIndex: 'reply',
47
50
  videoStream: 'stream',
48
51
  worldSceneStream: 'stream',
49
52
  worldStepStream: 'stream'
@@ -29,8 +29,10 @@ export async function assessModelFit(input) {
29
29
  verdict: response.verdict,
30
30
  basis: response.basis,
31
31
  execution: response.execution,
32
+ ...(response.evidence && { evidence: response.evidence }),
32
33
  ...(response.budget && { budget: response.budget }),
33
34
  ...(response.estimate && { estimate: response.estimate }),
35
+ ...(response.floorBytes !== undefined && { floorBytes: response.floorBytes }),
34
36
  models: response.models,
35
37
  reasons: response.reasons,
36
38
  assumptions: response.assumptions
@@ -0,0 +1,36 @@
1
+ import type { AudioEditClientParams, AudioGenResult } from '@qvac/inference/surface';
2
+ /**
3
+ * Edits a source recording with a loaded ACE-Step AudioGen model: an ordered
4
+ * pipeline of `flow-edit` and `repaint` operations, executed in array order.
5
+ *
6
+ * @param params - Loaded model ID, the source audio, the ordered `operations`, and an optional `seed`.
7
+ * @param params.sourceAudio - The recording to edit: a file path (decoded server-side) or raw
8
+ * interleaved stereo 48 kHz Float32 LE PCM bytes in `[-1, 1]`.
9
+ * @param params.operations - `flow-edit` re-conditions the whole clip from a `from` prompt to a
10
+ * `to` prompt over an optional `nMin`/`nMax` diffusion window (turbo DiT variants only);
11
+ * `repaint` regenerates the `start`..`end` range (seconds) against a new `caption`, keeping the
12
+ * rest of the clip. Operations may repeat or mix.
13
+ * @param params.seed - Seeds the first operation; each following operation uses `seed + index`.
14
+ * @returns The same run shape as `audioGen()`: a synchronous request ID, generation progress stream, PCM audio promise, stats promise, and diagnostics promise.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const run = audioEdit({
19
+ * modelId,
20
+ * sourceAudio: "/path/to/song.wav",
21
+ * operations: [
22
+ * {
23
+ * type: "flow-edit",
24
+ * from: { caption: "original pop song", lyrics: originalLyrics },
25
+ * to: { caption: "guitar pop-rock", lyrics: newLyrics },
26
+ * },
27
+ * { type: "repaint", caption: "analog synth solo", start: 10, end: 20, mode: "balanced" },
28
+ * ],
29
+ * seed: 22883,
30
+ * });
31
+ * stopButton.onclick = () => cancel({ requestId: run.requestId });
32
+ * const { pcm, sampleRate, channels, bitsPerSample } = await run.audio;
33
+ * ```
34
+ */
35
+ export declare function audioEdit(params: AudioEditClientParams): AudioGenResult;
36
+ //# sourceMappingURL=audio-edit.d.ts.map