@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
@@ -3,6 +3,7 @@ import { stream as streamRpc, duplex } from '../rpc/rpc-client.js';
3
3
  import { getClientLogger } from '../../logging/index.js';
4
4
  import { TextToSpeechStreamFailedError } from '../../utils/errors-client.js';
5
5
  import { parseClientInput } from '../parse-input.js';
6
+ import { generateClientRequestId } from './client-request-id.js';
6
7
  const logger = getClientLogger();
7
8
  /**
8
9
  * Fan-out queue that lets multiple consumers iterate the same TTS response
@@ -129,10 +130,11 @@ export class TtsMulticast {
129
130
  }
130
131
  }
131
132
  }
132
- function buildTtsRequest(params) {
133
+ function buildTtsRequest(params, requestId) {
133
134
  return {
134
135
  type: 'textToSpeech',
135
136
  modelId: params.modelId,
137
+ requestId,
136
138
  inputType: params.inputType,
137
139
  text: params.text,
138
140
  stream: params.stream,
@@ -155,10 +157,11 @@ function buildTtsRequest(params) {
155
157
  ...(params.quality !== undefined && { quality: params.quality })
156
158
  };
157
159
  }
158
- function buildTextToSpeechStreamRequest(params) {
160
+ function buildTextToSpeechStreamRequest(params, requestId) {
159
161
  return {
160
162
  type: 'textToSpeechStream',
161
163
  modelId: params.modelId,
164
+ requestId,
162
165
  inputType: params.inputType ?? 'text',
163
166
  ...(params.accumulateSentences !== undefined && {
164
167
  accumulateSentences: params.accumulateSentences
@@ -189,9 +192,9 @@ function buildTextToSpeechStreamRequest(params) {
189
192
  *
190
193
  * Three modes selected by `params.stream` and `params.sentenceStream`:
191
194
  *
192
- * - `stream: false` (default) — collect all PCM samples and resolve once via
195
+ * - `stream: false` — collect all PCM samples and resolve once via
193
196
  * `result.buffer` (`Promise<number[]>`). `bufferStream` is empty.
194
- * - `stream: true` — yield PCM samples through `result.bufferStream`
197
+ * - `stream: true` (default) — yield PCM samples through `result.bufferStream`
195
198
  * (`AsyncGenerator<number>`) as they arrive. `buffer` resolves to an empty
196
199
  * array.
197
200
  * - `stream: true, sentenceStream: true` — also exposes `result.chunkUpdates`
@@ -203,10 +206,21 @@ function buildTextToSpeechStreamRequest(params) {
203
206
  * if the consumer breaks out before the terminal frame, or rejects on a
204
207
  * pipeline error. Awaiting `done` is safe even when no stream is iterated.
205
208
  *
209
+ * `result.sampleRate` resolves from the first audio frame (so it can be awaited
210
+ * before draining the audio); `result.stats` and `result.stopReason` resolve
211
+ * from the terminal frame. All three resolve — with `undefined` when the run
212
+ * produced none — rather than reject. In `stream: true` mode (the default) the
213
+ * run starts lazily: nothing is sent until `bufferStream` is first iterated, so
214
+ * `done`, `sampleRate`, `stats` and `stopReason` stay pending until then. In
215
+ * `stream: false` and `sentenceStream: true` modes the run starts immediately.
216
+ * Read `sampleRate` rather than assuming the engine default: `outputSampleRate`
217
+ * and the LavaSR enhancer both change it.
218
+ *
206
219
  * @param params - TTS request parameters (see `TtsClientParamsInput`).
207
220
  * @param options - Optional RPC options (timeout, profiling, force new connection).
208
221
  * @returns A `TextToSpeechStreamResult` with `bufferStream`, `buffer`, `done`,
209
- * and (when `sentenceStream: true`) `chunkUpdates`.
222
+ * `sampleRate`, `stats`, and (when `sentenceStream: true`)
223
+ * `chunkUpdates`.
210
224
  * @throws {TextToSpeechStreamFailedError} When `sentenceStream: true` is paired
211
225
  * with `stream: false`, or when the underlying RPC stream errors.
212
226
  */
@@ -215,7 +229,11 @@ export function textToSpeech(params, options) {
215
229
  if (parsed.sentenceStream && !parsed.stream) {
216
230
  throw new TextToSpeechStreamFailedError('textToSpeech: `sentenceStream: true` requires `stream: true`');
217
231
  }
218
- const request = buildTtsRequest(parsed);
232
+ // Minted client-side and surfaced synchronously on the result, the same way
233
+ // `transcribe()` / `completion()` do, so a caller can cancel a run it has
234
+ // only just started.
235
+ const requestId = parsed.requestId ?? generateClientRequestId();
236
+ const request = buildTtsRequest(parsed, requestId);
219
237
  if (parsed.stream && parsed.sentenceStream) {
220
238
  return sentenceStreamTts(request, options);
221
239
  }
@@ -227,15 +245,70 @@ export function textToSpeech(params, options) {
227
245
  // Adapts the raw RPC stream into the filtered `TtsResponse` source the
228
246
  // multicast expects. Kept here (not inlined) so the multicast can be
229
247
  // constructed in tests with a hand-rolled source instead.
230
- async function* ttsResponseSource(request, options) {
231
- for await (const response of streamRpc(request, options)) {
232
- if (response.type !== 'textToSpeech')
233
- continue;
234
- yield response;
248
+ async function* ttsResponseSource(request, options, side) {
249
+ try {
250
+ for await (const response of streamRpc(request, options)) {
251
+ if (response.type !== 'textToSpeech')
252
+ continue;
253
+ side.observe(response);
254
+ yield response;
255
+ }
235
256
  }
257
+ finally {
258
+ // Reached on normal end, on error, and when the multicast's pump breaks
259
+ // out on the terminal frame.
260
+ side.settle();
261
+ }
262
+ }
263
+ /** Exported for test use; not part of the public SDK surface. */
264
+ export function createTtsSideChannel() {
265
+ let resolveSampleRate;
266
+ let resolveStats;
267
+ let resolveStopReason;
268
+ const sampleRate = new Promise((resolve) => {
269
+ resolveSampleRate = resolve;
270
+ });
271
+ const stats = new Promise((resolve) => {
272
+ resolveStats = resolve;
273
+ });
274
+ const stopReason = new Promise((resolve) => {
275
+ resolveStopReason = resolve;
276
+ });
277
+ let seenSampleRate;
278
+ let latestStats;
279
+ let seenStopReason;
280
+ let settled = false;
281
+ return {
282
+ sampleRate,
283
+ stats,
284
+ stopReason,
285
+ observe(response) {
286
+ // Resolve on the FIRST frame that carries a rate, not at end of stream:
287
+ // a caller opening an audio device wants it before draining the audio.
288
+ // Promise resolution is idempotent, so settle() can still resolve the
289
+ // undefined fallback for a run that never reported one.
290
+ if (seenSampleRate === undefined && response.sampleRate !== undefined) {
291
+ seenSampleRate = response.sampleRate;
292
+ resolveSampleRate(seenSampleRate);
293
+ }
294
+ if (response.stats !== undefined)
295
+ latestStats = response.stats;
296
+ if (response.stopReason !== undefined)
297
+ seenStopReason = response.stopReason;
298
+ },
299
+ settle() {
300
+ if (settled)
301
+ return;
302
+ settled = true;
303
+ resolveSampleRate(seenSampleRate);
304
+ resolveStats(latestStats);
305
+ resolveStopReason(seenStopReason);
306
+ }
307
+ };
236
308
  }
237
309
  function sentenceStreamTts(request, options) {
238
- const multicast = new TtsMulticast(ttsResponseSource(request, options));
310
+ const side = createTtsSideChannel();
311
+ const multicast = new TtsMulticast(ttsResponseSource(request, options, side));
239
312
  // Subscribe eagerly, synchronously — before pump() can push its first
240
313
  // item — so both subscribers see the full queue from index 0. If we
241
314
  // deferred subscribing until the generators were iterated, the first
@@ -247,7 +320,11 @@ function sentenceStreamTts(request, options) {
247
320
  bufferStream: sentenceBufferStream(bufferSubscription),
248
321
  chunkUpdates: sentenceChunkUpdates(chunkSubscription),
249
322
  buffer: Promise.resolve([]),
250
- done: multicast.done
323
+ done: multicast.done,
324
+ requestId: request.requestId,
325
+ sampleRate: side.sampleRate,
326
+ stats: side.stats,
327
+ stopReason: side.stopReason
251
328
  };
252
329
  }
253
330
  async function* sentenceBufferStream(source) {
@@ -267,10 +344,12 @@ async function* sentenceChunkUpdates(source) {
267
344
  if (hasAudio || hasMeta) {
268
345
  yield {
269
346
  buffer: hasAudio ? [...m.buffer] : [],
347
+ ...(m.sampleRate !== undefined ? { sampleRate: m.sampleRate } : {}),
270
348
  ...(m.chunkIndex !== undefined ? { chunkIndex: m.chunkIndex } : {}),
271
349
  ...(typeof m.sentenceChunk === 'string' && m.sentenceChunk.length > 0
272
350
  ? { sentenceChunk: m.sentenceChunk }
273
- : {})
351
+ : {}),
352
+ ...(m.isLast !== undefined ? { isLast: m.isLast } : {})
274
353
  };
275
354
  }
276
355
  if (m.done)
@@ -285,13 +364,18 @@ function plainStreamTts(request, options) {
285
364
  rejectDone = reject;
286
365
  });
287
366
  done.catch(() => { });
367
+ const side = createTtsSideChannel();
288
368
  return {
289
- bufferStream: plainTtsBufferStream(request, options, resolveDone, rejectDone),
369
+ bufferStream: plainTtsBufferStream(request, options, resolveDone, rejectDone, side),
290
370
  buffer: Promise.resolve([]),
291
- done
371
+ done,
372
+ requestId: request.requestId,
373
+ sampleRate: side.sampleRate,
374
+ stats: side.stats,
375
+ stopReason: side.stopReason
292
376
  };
293
377
  }
294
- async function* plainTtsBufferStream(request, options, resolveDone, rejectDone) {
378
+ async function* plainTtsBufferStream(request, options, resolveDone, rejectDone, side) {
295
379
  let settled = false;
296
380
  try {
297
381
  for await (const response of streamRpc(request, options)) {
@@ -299,6 +383,7 @@ async function* plainTtsBufferStream(request, options, resolveDone, rejectDone)
299
383
  continue;
300
384
  // See TtsMulticast.pump — skip per-frame Zod validation; the server is
301
385
  // the source of truth for this wire shape.
386
+ side.observe(response);
302
387
  if (response.buffer.length > 0) {
303
388
  yield* response.buffer;
304
389
  }
@@ -320,6 +405,7 @@ async function* plainTtsBufferStream(request, options, resolveDone, rejectDone)
320
405
  // with `false` so `await result.done` never hangs.
321
406
  if (!settled)
322
407
  resolveDone(false);
408
+ side.settle();
323
409
  }
324
410
  }
325
411
  function collectTts(request, options) {
@@ -330,21 +416,27 @@ function collectTts(request, options) {
330
416
  rejectDone = reject;
331
417
  });
332
418
  done.catch(() => { });
419
+ const side = createTtsSideChannel();
333
420
  return {
334
421
  bufferStream: emptyBufferStream(),
335
- buffer: collectTtsBuffer(request, options, resolveDone, rejectDone),
336
- done
422
+ buffer: collectTtsBuffer(request, options, resolveDone, rejectDone, side),
423
+ done,
424
+ requestId: request.requestId,
425
+ sampleRate: side.sampleRate,
426
+ stats: side.stats,
427
+ stopReason: side.stopReason
337
428
  };
338
429
  }
339
430
  async function* emptyBufferStream() {
340
431
  // Non-streaming mode exposes no incremental buffer stream.
341
432
  }
342
- async function collectTtsBuffer(request, options, resolveDone, rejectDone) {
433
+ async function collectTtsBuffer(request, options, resolveDone, rejectDone, side) {
343
434
  let buffer = [];
344
435
  try {
345
436
  for await (const response of streamRpc(request, options)) {
346
437
  if (response.type !== 'textToSpeech')
347
438
  continue;
439
+ side.observe(response);
348
440
  buffer = buffer.concat(response.buffer);
349
441
  if (response.done) {
350
442
  resolveDone(true);
@@ -356,6 +448,9 @@ async function collectTtsBuffer(request, options, resolveDone, rejectDone) {
356
448
  rejectDone(e);
357
449
  throw e;
358
450
  }
451
+ finally {
452
+ side.settle();
453
+ }
359
454
  }
360
455
  export function encodeTextToSpeechFragment(textFragment) {
361
456
  return typeof textFragment === 'string' ? new TextEncoder().encode(textFragment) : textFragment;
@@ -375,7 +470,8 @@ export function encodeTextToSpeechFragment(textFragment) {
375
470
  * than once, receives a server error, or is written after closing.
376
471
  */
377
472
  export async function textToSpeechStream(params, options) {
378
- const request = parseClientInput(textToSpeechStreamRequestSchema, buildTextToSpeechStreamRequest(params));
473
+ const requestId = params.requestId ?? generateClientRequestId();
474
+ const request = parseClientInput(textToSpeechStreamRequestSchema, buildTextToSpeechStreamRequest(params, requestId));
379
475
  const { requestStream, responseStream } = await duplex(request, options);
380
476
  const responses = parseTextToSpeechStreamLines(responseStream);
381
477
  let consumed = false;
@@ -385,6 +481,7 @@ export async function textToSpeechStream(params, options) {
385
481
  // surface predictable.
386
482
  let closed = false;
387
483
  return {
484
+ requestId,
388
485
  write(textFragment) {
389
486
  if (closed) {
390
487
  throw new TextToSpeechStreamFailedError('TextToSpeechStreamSession.write() called after end()/destroy()');
@@ -0,0 +1,143 @@
1
+ import { send } from '../rpc/rpc-client.js';
2
+ import { type CreateVectorIndexParams, type LoadVectorIndexParams, type RPCOptions, type VectorIndexAddParams, type VectorIndexHit, type VectorIndexIdsParams, type VectorIndexStorage, type VectorIndexWriteParams } from '@qvac/inference/surface';
3
+ /**
4
+ * A handle to a vector index that lives in worker memory.
5
+ *
6
+ * Vectors are indexed under stable unsigned 64-bit ids and searched by
7
+ * dot-product similarity, so L2-normalize vectors before adding and queries
8
+ * before searching when cosine similarity is wanted. The index holds vectors
9
+ * only; keep the documents they describe wherever suits the app and map
10
+ * result ids back to them.
11
+ *
12
+ * The index is released by `dispose()` (or `await using`) and on worker
13
+ * shutdown. After a worker crash or restart every method rejects with
14
+ * `VECTOR_INDEX_NOT_FOUND`; recover by creating a new index or loading a
15
+ * snapshot written earlier with `write()`.
16
+ */
17
+ export interface VectorIndex {
18
+ /** Opaque id of the worker-side index; useful in logs, not needed for any call. */
19
+ readonly indexId: string;
20
+ /** Vector dimensionality every added vector and query must match. */
21
+ readonly dim: number;
22
+ /**
23
+ * Storage mode. Known after `createVectorIndex`. After `loadVectorIndex` it
24
+ * is derived from the snapshot's bit width, so 4-bit snapshots (`q4` or
25
+ * `turbovec-q4`) report `undefined`.
26
+ */
27
+ readonly storage: VectorIndexStorage | undefined;
28
+ /** Number of live entries as of the last call that changed or reported it. */
29
+ readonly length: number;
30
+ /**
31
+ * Adds vectors under the given ids in one atomic batch. Rejects duplicate
32
+ * ids, ids equal to 2^64 - 1, and rows whose length differs from `dim`.
33
+ * @returns The number of live entries after the add.
34
+ */
35
+ add(params: VectorIndexAddParams, options?: RPCOptions): Promise<{
36
+ length: number;
37
+ }>;
38
+ /**
39
+ * Finds the `k` nearest entries to one query. Hits are ordered by
40
+ * descending score, then ascending id, and fewer than `k` are returned
41
+ * when the index holds fewer entries.
42
+ * @overloadLabel "Single query"
43
+ */
44
+ search(params: {
45
+ query: number[];
46
+ k: number;
47
+ }, options?: RPCOptions): Promise<VectorIndexHit[]>;
48
+ /**
49
+ * Finds the `k` nearest entries to each query row; one hit list per row.
50
+ * @overloadLabel "Multiple queries"
51
+ */
52
+ search(params: {
53
+ queries: number[][];
54
+ k: number;
55
+ }, options?: RPCOptions): Promise<VectorIndexHit[][]>;
56
+ /**
57
+ * Removes ids; each boolean tells whether that id was present. Ids are
58
+ * applied in order, so a rejection leaves the ids before the failure
59
+ * removed and `length` reporting the last successful call.
60
+ */
61
+ remove(params: VectorIndexIdsParams, options?: RPCOptions): Promise<boolean[]>;
62
+ /** Reports, per id, whether the index holds it. */
63
+ contains(params: VectorIndexIdsParams, options?: RPCOptions): Promise<boolean[]>;
64
+ /**
65
+ * Writes a checksummed snapshot to `path`, replacing any existing file.
66
+ * An absolute path is used as given; a relative path resolves under the
67
+ * QVAC data directory. Load it later with `loadVectorIndex`.
68
+ * @returns The resolved absolute path.
69
+ */
70
+ write(params: VectorIndexWriteParams, options?: RPCOptions): Promise<{
71
+ path: string;
72
+ }>;
73
+ /** Releases worker memory. Safe to call more than once. */
74
+ dispose(options?: RPCOptions): Promise<void>;
75
+ [Symbol.asyncDispose](): Promise<void>;
76
+ }
77
+ /**
78
+ * Builds the two public functions over a transport. The default export pair
79
+ * below binds them to the RPC client; tests bind a fake instead.
80
+ */
81
+ export declare function createVectorIndexClient(transport: typeof send): {
82
+ createVectorIndex(params: CreateVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
83
+ loadVectorIndex(params: LoadVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
84
+ };
85
+ /**
86
+ * Creates an empty vector index in worker memory and returns a handle to it.
87
+ *
88
+ * The index is backed by the native TurboVec engine of the registered
89
+ * embedding plugin (the built-in llama.cpp embedding plugin provides it).
90
+ * Pair it with `embed()` to build retrieval over documents kept in any store:
91
+ * add each document's embedding under an id of your choosing, then search
92
+ * with the embedding of a query and map the returned ids back to your
93
+ * documents.
94
+ *
95
+ * @param params - Index parameters
96
+ * @param params.dim - Vector dimensionality, for example 1024 for GTE Large. TurboVec storage needs a multiple of 8 no greater than 1024.
97
+ * @param params.storage - Storage mode from `VectorIndexStorage`; defaults to `TURBOVEC_Q4`. `TURBOVEC_Q2` halves memory again at lower recall; `F32`, `Q8`, and `Q4` are exact or generically quantised stores for dimensions TurboVec cannot hold.
98
+ * @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
99
+ * @returns A handle whose methods add, search, remove, snapshot, and dispose the index.
100
+ * @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
101
+ * @throws {VectorIndexFailedError} When the native index rejects the dimension or storage mode
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const documents = new Map([
106
+ * ['1', 'Saturn moon Titan has lakes of liquid methane.'],
107
+ * ['2', 'Solar panels turn sunlight into electricity.']
108
+ * ])
109
+ * const { embedding } = await embed({ modelId, text: [...documents.values()] })
110
+ *
111
+ * await using index = await createVectorIndex({ dim: embedding[0].length })
112
+ * await index.add({ ids: [...documents.keys()], vectors: embedding })
113
+ *
114
+ * const { embedding: query } = await embed({ modelId, text: 'Which moon has methane lakes?' })
115
+ * const hits = await index.search({ query, k: 1 })
116
+ * console.log(documents.get(hits[0].id))
117
+ * ```
118
+ */
119
+ export declare function createVectorIndex(params: CreateVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
120
+ /**
121
+ * Loads a vector index snapshot written by `VectorIndex.write()` into worker
122
+ * memory and returns a handle to it.
123
+ *
124
+ * The snapshot records its dimension, storage mode, and entries, so nothing
125
+ * else needs to be supplied. Loading copies the file into memory; later
126
+ * changes are not written back until `write()` is called again.
127
+ *
128
+ * @param params - Load parameters
129
+ * @param params.path - Snapshot path. An absolute path is used as given; a relative path resolves under the QVAC data directory, the same rule `write()` applies.
130
+ * @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
131
+ * @returns A handle to the loaded index.
132
+ * @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
133
+ * @throws {VectorIndexFailedError} When the file is missing, corrupt, or not a vector index snapshot
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const index = await loadVectorIndex({ path: 'indexes/articles.qvi' })
138
+ * const hits = await index.search({ query, k: 5 })
139
+ * await index.dispose()
140
+ * ```
141
+ */
142
+ export declare function loadVectorIndex(params: LoadVectorIndexParams, options?: RPCOptions): Promise<VectorIndex>;
143
+ //# sourceMappingURL=vector-index.d.ts.map
@@ -0,0 +1,168 @@
1
+ import { send } from '../rpc/rpc-client.js';
2
+ import { DEFAULT_VECTOR_INDEX_STORAGE, findVectorRowLengthMismatch, toWireVectorIds } from '@qvac/inference/surface';
3
+ import { InvalidOperationError, InvalidResponseError, RequestValidationFailedError } from '../../utils/errors-client.js';
4
+ /**
5
+ * Builds the two public functions over a transport. The default export pair
6
+ * below binds them to the RPC client; tests bind a fake instead.
7
+ */
8
+ export function createVectorIndexClient(transport) {
9
+ async function call(request, options) {
10
+ const response = await transport(request, options);
11
+ if (response.type !== 'vectorIndex') {
12
+ throw new InvalidResponseError('vectorIndex');
13
+ }
14
+ if (response.operation !== request.operation) {
15
+ throw new InvalidOperationError();
16
+ }
17
+ return response;
18
+ }
19
+ function createHandle(opened) {
20
+ const { indexId, dim, storage } = opened;
21
+ let length = opened.length;
22
+ let disposed = false;
23
+ // Row lengths are checked here so a mismatch fails before any request is
24
+ // sent and before the worker touches native memory.
25
+ function assertRows(rows, label) {
26
+ const mismatch = findVectorRowLengthMismatch(rows, dim);
27
+ if (mismatch === -1)
28
+ return;
29
+ throw new RequestValidationFailedError(`${label}[${mismatch}] has ${rows[mismatch]?.length ?? 0} components, expected ${dim}`);
30
+ }
31
+ // The flag is set only after the worker confirms, so a failed dispose
32
+ // can be retried instead of leaking the index until worker shutdown.
33
+ async function dispose(options) {
34
+ if (disposed)
35
+ return;
36
+ await call({ type: 'vectorIndex', operation: 'dispose', indexId }, options);
37
+ disposed = true;
38
+ }
39
+ async function search(params, options) {
40
+ const queries = 'queries' in params ? params.queries : [params.query];
41
+ assertRows(queries, 'queries');
42
+ const response = await call({ type: 'vectorIndex', operation: 'search', indexId, queries, k: params.k }, options);
43
+ return 'queries' in params ? response.results : (response.results[0] ?? []);
44
+ }
45
+ const handle = {
46
+ indexId,
47
+ dim,
48
+ storage,
49
+ get length() {
50
+ return length;
51
+ },
52
+ async add(params, options) {
53
+ assertRows(params.vectors, 'vectors');
54
+ const response = await call({
55
+ type: 'vectorIndex',
56
+ operation: 'add',
57
+ indexId,
58
+ ids: toWireVectorIds(params.ids),
59
+ vectors: params.vectors
60
+ }, options);
61
+ length = response.length;
62
+ return { length };
63
+ },
64
+ search,
65
+ async remove(params, options) {
66
+ const response = await call({ type: 'vectorIndex', operation: 'remove', indexId, ids: toWireVectorIds(params.ids) }, options);
67
+ length = response.length;
68
+ return response.removed;
69
+ },
70
+ async contains(params, options) {
71
+ const response = await call({ type: 'vectorIndex', operation: 'contains', indexId, ids: toWireVectorIds(params.ids) }, options);
72
+ return response.present;
73
+ },
74
+ async write(params, options) {
75
+ const response = await call({ type: 'vectorIndex', operation: 'write', indexId, path: params.path }, options);
76
+ return { path: response.path };
77
+ },
78
+ dispose
79
+ };
80
+ // Older runtimes without explicit resource management have no symbol to
81
+ // key the method on; `dispose()` remains available there.
82
+ if (typeof Symbol.asyncDispose === 'symbol') {
83
+ Object.defineProperty(handle, Symbol.asyncDispose, {
84
+ value: () => dispose(),
85
+ enumerable: false
86
+ });
87
+ }
88
+ return handle;
89
+ }
90
+ return {
91
+ async createVectorIndex(params, options) {
92
+ const response = await call({
93
+ type: 'vectorIndex',
94
+ operation: 'create',
95
+ dim: params.dim,
96
+ storage: params.storage ?? DEFAULT_VECTOR_INDEX_STORAGE
97
+ }, options);
98
+ return createHandle(response);
99
+ },
100
+ async loadVectorIndex(params, options) {
101
+ const response = await call({ type: 'vectorIndex', operation: 'load', path: params.path }, options);
102
+ return createHandle(response);
103
+ }
104
+ };
105
+ }
106
+ const client = createVectorIndexClient(send);
107
+ /**
108
+ * Creates an empty vector index in worker memory and returns a handle to it.
109
+ *
110
+ * The index is backed by the native TurboVec engine of the registered
111
+ * embedding plugin (the built-in llama.cpp embedding plugin provides it).
112
+ * Pair it with `embed()` to build retrieval over documents kept in any store:
113
+ * add each document's embedding under an id of your choosing, then search
114
+ * with the embedding of a query and map the returned ids back to your
115
+ * documents.
116
+ *
117
+ * @param params - Index parameters
118
+ * @param params.dim - Vector dimensionality, for example 1024 for GTE Large. TurboVec storage needs a multiple of 8 no greater than 1024.
119
+ * @param params.storage - Storage mode from `VectorIndexStorage`; defaults to `TURBOVEC_Q4`. `TURBOVEC_Q2` halves memory again at lower recall; `F32`, `Q8`, and `Q4` are exact or generically quantised stores for dimensions TurboVec cannot hold.
120
+ * @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
121
+ * @returns A handle whose methods add, search, remove, snapshot, and dispose the index.
122
+ * @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
123
+ * @throws {VectorIndexFailedError} When the native index rejects the dimension or storage mode
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const documents = new Map([
128
+ * ['1', 'Saturn moon Titan has lakes of liquid methane.'],
129
+ * ['2', 'Solar panels turn sunlight into electricity.']
130
+ * ])
131
+ * const { embedding } = await embed({ modelId, text: [...documents.values()] })
132
+ *
133
+ * await using index = await createVectorIndex({ dim: embedding[0].length })
134
+ * await index.add({ ids: [...documents.keys()], vectors: embedding })
135
+ *
136
+ * const { embedding: query } = await embed({ modelId, text: 'Which moon has methane lakes?' })
137
+ * const hits = await index.search({ query, k: 1 })
138
+ * console.log(documents.get(hits[0].id))
139
+ * ```
140
+ */
141
+ export function createVectorIndex(params, options) {
142
+ return client.createVectorIndex(params, options);
143
+ }
144
+ /**
145
+ * Loads a vector index snapshot written by `VectorIndex.write()` into worker
146
+ * memory and returns a handle to it.
147
+ *
148
+ * The snapshot records its dimension, storage mode, and entries, so nothing
149
+ * else needs to be supplied. Loading copies the file into memory; later
150
+ * changes are not written back until `write()` is called again.
151
+ *
152
+ * @param params - Load parameters
153
+ * @param params.path - Snapshot path. An absolute path is used as given; a relative path resolves under the QVAC data directory, the same rule `write()` applies.
154
+ * @param options - Optional RPC options (timeout, profiling, force new connection, etc.).
155
+ * @returns A handle to the loaded index.
156
+ * @throws {VectorIndexProviderUnavailableError} When no registered plugin exposes a vector index provider
157
+ * @throws {VectorIndexFailedError} When the file is missing, corrupt, or not a vector index snapshot
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const index = await loadVectorIndex({ path: 'indexes/articles.qvi' })
162
+ * const hits = await index.search({ query, k: 5 })
163
+ * await index.dispose()
164
+ * ```
165
+ */
166
+ export function loadVectorIndex(params, options) {
167
+ return client.loadVectorIndex(params, options);
168
+ }
@@ -45,9 +45,9 @@ import { type WorldSceneResult, type WorldSceneResultWithPack, type WorldStepRes
45
45
  * modelSrc: ABOT_WORLD_0_5B_Q8_0,
46
46
  * modelConfig: {
47
47
  * mode: "world",
48
- * taehvModelSrc: ABOT_WORLD_0_5B_LF_VAE,
48
+ * taehvModelSrc: ABOT_WORLD_0_5B_LF_TAEHV_VAE,
49
49
  * t5XxlModelSrc: UMT5_XXL_ENC_Q8_0,
50
- * vaeModelSrc: ABOT_WORLD_0_5B_LF_VAE_F16,
50
+ * vaeModelSrc: ABOT_WORLD_0_5B_LF_WAN_VAE,
51
51
  * world: { seed: 42, kvCache: true },
52
52
  * },
53
53
  * });
@@ -1,6 +1,8 @@
1
1
  import type { Logger } from '../../logging/types.js';
2
2
  interface BarePackHeader {
3
3
  id?: string;
4
+ main?: string;
5
+ imports?: Record<string, unknown>;
4
6
  resolutions?: Record<string, unknown>;
5
7
  }
6
8
  interface GenerateAddonsManifestOptions {
@@ -5,6 +5,12 @@ export interface NativeAddon {
5
5
  packageJsonPath: string;
6
6
  packageRoot: string;
7
7
  enginesBare?: string;
8
+ /**
9
+ * Hosts whose bundle graph links this addon, and so the only ones needing a
10
+ * prebuild; empty means the bundle links it nowhere. Absent when the source
11
+ * carries no link information, in which case every host is checked.
12
+ */
13
+ linkedHosts?: string[];
8
14
  }
9
15
  export interface AddonPackageJson {
10
16
  name?: string;
@@ -6,6 +6,8 @@ export declare class InvalidBundleSourceError extends Error {
6
6
  export interface CollectAddonsFromBundleOptions {
7
7
  bundlePath: string;
8
8
  projectRoot: string;
9
+ /** Omitting these leaves `linkedHosts` unset, so every host stays checked. */
10
+ hosts?: string[];
9
11
  diagnostics?: CollectDiagnostics;
10
12
  }
11
13
  export declare function collectAddonsFromBundle(options: CollectAddonsFromBundleOptions): Promise<NativeAddon[]>;