@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,5 +1,5 @@
1
- import { type TranscribeClientParams, type RPCOptions, type TranscribeSegment, type TranscribeStreamRequest, type TranscribeStreamClientParams, type TranscribeStreamSession, type TranscribeStreamMetadataSession, type TranscribeStreamConversationSession, type TranscribeStats } from '@qvac/inference/surface';
2
- import { duplex } from '../rpc/rpc-client.js';
1
+ import { type TranscribeClientParams, type RPCOptions, type TranscribeSegment, type TranscribeStreamRequest, type TranscribeStreamClientParams, type TranscribeStreamSession, type TranscribeStreamMetadataSession, type TranscribeStreamConversationSession, type TranscribeStreamEvent, type TranscribeStats, type InferenceBackendDiagnostics } from '@qvac/inference/surface';
2
+ import { stream, duplex } from '../rpc/rpc-client.js';
3
3
  type TranscribeDuplexFactory = (request: TranscribeStreamRequest, options?: RPCOptions) => ReturnType<typeof duplex>;
4
4
  /**
5
5
  * Transcribe audio and return the complete text. Accepts either a file
@@ -10,24 +10,27 @@ type TranscribeDuplexFactory = (request: TranscribeStreamRequest, options?: RPCO
10
10
  * @param params.audioChunk - Audio input as either a file path (string) or audio buffer
11
11
  * @param params.prompt - Optional initial prompt to guide the transcription
12
12
  * @param params.metadata - When true, resolves to an array of transcript
13
- * segments (`{ text, startMs, endMs, append, id }`)
14
- * instead of joined text. Whisper engine only.
13
+ * segments (`{ text, startMs, endMs, append, id }`,
14
+ * plus `isEndOfTurn` and `startsWord` on parakeet)
15
+ * instead of joined text.
15
16
  * @param options - Optional RPC options including per-call profiling
16
17
  * @returns A promise (decorated with `requestId`) resolving to the
17
18
  * complete transcribed text, or — when `metadata` is true —
18
19
  * the list of transcript segments in emission order. The
19
20
  * `requestId` is reachable synchronously so callers can target
20
21
  * this in-flight transcription with `cancel({ requestId })`
21
- * before `await` resolves.
22
+ * before `await` resolves. `stats` resolves to the terminal engine
23
+ * statistics, including Parakeet's per-run Core ML usage.
22
24
  */
23
- export declare function transcribe(params: TranscribeClientParams & {
24
- metadata: true;
25
- }, options?: RPCOptions): Promise<TranscribeSegment[]> & {
26
- requestId: string;
27
- };
28
- export declare function transcribe(params: TranscribeClientParams, options?: RPCOptions): Promise<string> & {
25
+ type TranscribeCall<T> = Promise<T> & {
29
26
  requestId: string;
27
+ stats: Promise<TranscribeStats | undefined>;
30
28
  };
29
+ export declare function transcribe(params: TranscribeClientParams & {
30
+ metadata: true;
31
+ }, options?: RPCOptions): TranscribeCall<TranscribeSegment[]>;
32
+ export declare function transcribe(params: TranscribeClientParams, options?: RPCOptions): TranscribeCall<string>;
33
+ export declare function createTranscribeCall(params: TranscribeClientParams, options?: RPCOptions, responseStream?: typeof stream): TranscribeCall<string | TranscribeSegment[]>;
31
34
  /**
32
35
  * @deprecated Pass audio via `transcribe()` instead. This overload will be
33
36
  * removed in the next major version.
@@ -57,8 +60,9 @@ export declare function transcribeStream(params: TranscribeClientParams, options
57
60
  * @param params.modelId - The loaded transcription model to use
58
61
  * @param params.prompt - Optional initial prompt to guide transcription
59
62
  * @param params.metadata - When true, the session yields transcript segment
60
- * objects (`{ text, startMs, endMs, append, id }`)
61
- * instead of plain text. Whisper engine only.
63
+ * objects (`{ text, startMs, endMs, append, id }`,
64
+ * plus `isEndOfTurn` and `startsWord` on parakeet)
65
+ * instead of plain text.
62
66
  * @param options - Optional RPC options including per-call profiling.
63
67
  * @returns A session object: call `write(audioChunk)` with a `Uint8Array`
64
68
  * (Node `Buffer` is a `Uint8Array` subtype) to feed audio,
@@ -81,13 +85,15 @@ export declare function transcribeStream(params: TranscribeStreamClientParams, o
81
85
  * to surface strings or segments; `sessionName` is only used to label the
82
86
  * "already iterated" error so callers see the correct session type.
83
87
  */
84
- export declare function createTranscribeStreamSession<T>(params: TranscribeStreamClientParams, options: RPCOptions | undefined, process: (line: string, onStats: (stats: TranscribeStats | undefined) => void) => T | undefined | null, sessionName: string, duplexFactory?: TranscribeDuplexFactory): Promise<{
88
+ export declare function createTranscribeStreamSession<T>(params: TranscribeStreamClientParams, options: RPCOptions | undefined, process: (line: string, onTerminal: (stats: TranscribeStats | undefined, diagnostics: InferenceBackendDiagnostics | undefined) => void) => T | undefined | null, sessionName: string, duplexFactory?: TranscribeDuplexFactory): Promise<{
85
89
  stats: Promise<TranscribeStats | undefined>;
90
+ diagnostics: Promise<InferenceBackendDiagnostics | undefined>;
86
91
  write(audioChunk: Uint8Array): void;
87
92
  end(): void;
88
93
  destroy(): void;
89
94
  [Symbol.asyncIterator](): AsyncIterator<T>;
90
95
  }>;
91
- export declare function processLine(line: string, onStats: (stats: TranscribeStats | undefined) => void): string | undefined | null;
96
+ export declare function processLine(line: string, onTerminal: (stats: TranscribeStats | undefined, diagnostics: InferenceBackendDiagnostics | undefined) => void): string | undefined | null;
97
+ export declare function processLineConversation(line: string, wantsMetadata: boolean, onTerminal: (stats: TranscribeStats | undefined, diagnostics: InferenceBackendDiagnostics | undefined) => void): TranscribeStreamEvent | undefined | null;
92
98
  export {};
93
99
  //# sourceMappingURL=transcribe.d.ts.map
@@ -18,26 +18,38 @@ function buildTranscribeRequest(params, requestId) {
18
18
  };
19
19
  }
20
20
  export function transcribe(params, options) {
21
+ return createTranscribeCall(params, options);
22
+ }
23
+ export function createTranscribeCall(params, options, responseStream = stream) {
21
24
  // Client-generated id surfaced synchronously on the returned promise
22
25
  // — same shape as `loadModel` / `downloadAsset` / `completion`. The
23
26
  // CLI cancel bridge in `qvac serve` binds `req.on('close')` to
24
27
  // `cancel({ requestId })` immediately after the call returns so a
25
28
  // client disconnect aborts the in-flight transcription.
26
29
  const requestId = generateClientRequestId();
27
- const inner = runTranscribe(params, requestId, options);
28
- return decoratePromise(inner, { requestId });
30
+ let resolveStats = () => { };
31
+ let rejectStats = () => { };
32
+ const stats = new Promise((resolve, reject) => {
33
+ resolveStats = resolve;
34
+ rejectStats = reject;
35
+ });
36
+ stats.catch(() => { });
37
+ const inner = runTranscribe(params, requestId, options, resolveStats, responseStream);
38
+ inner.then(() => resolveStats(undefined), rejectStats);
39
+ return decoratePromise(inner, { requestId, stats });
29
40
  }
30
- async function runTranscribe(params, requestId, options) {
41
+ async function runTranscribe(params, requestId, options, onStats, responseStream) {
31
42
  const request = buildTranscribeRequest(params, requestId);
32
43
  if (params.metadata === true) {
33
44
  const segments = [];
34
- for await (const response of stream(request, options)) {
45
+ for await (const response of responseStream(request, options)) {
35
46
  if (response.type === 'transcribe') {
36
47
  const parsed = transcribeResponseSchema.parse(response);
37
48
  if (parsed.segment) {
38
49
  segments.push(parsed.segment);
39
50
  }
40
51
  if (parsed.done) {
52
+ onStats(parsed.stats);
41
53
  break;
42
54
  }
43
55
  }
@@ -45,13 +57,14 @@ async function runTranscribe(params, requestId, options) {
45
57
  return segments;
46
58
  }
47
59
  let fullText = '';
48
- for await (const response of stream(request, options)) {
60
+ for await (const response of responseStream(request, options)) {
49
61
  if (response.type === 'transcribe') {
50
62
  const parsed = transcribeResponseSchema.parse(response);
51
63
  if (parsed.text) {
52
64
  fullText += parsed.text;
53
65
  }
54
66
  if (parsed.done) {
67
+ onStats(parsed.stats);
55
68
  break;
56
69
  }
57
70
  }
@@ -144,16 +157,47 @@ export async function createTranscribeStreamSession(params, options, process, se
144
157
  statsSettled = true;
145
158
  rejectStatsPromise(error);
146
159
  }
147
- function resolveEmptyStats() {
148
- resolveStats(undefined);
160
+ // Settled from the same terminal frame as `stats`, and on the same paths:
161
+ // resolved empty when the stream closes without one, rejected on error.
162
+ let diagnosticsSettled = false;
163
+ let resolveDiagnosticsPromise = () => { };
164
+ let rejectDiagnosticsPromise = () => { };
165
+ const diagnostics = new Promise((resolve, reject) => {
166
+ resolveDiagnosticsPromise = resolve;
167
+ rejectDiagnosticsPromise = reject;
168
+ });
169
+ diagnostics.catch(() => { });
170
+ function resolveDiagnostics(value) {
171
+ if (diagnosticsSettled)
172
+ return;
173
+ diagnosticsSettled = true;
174
+ resolveDiagnosticsPromise(value);
175
+ }
176
+ function rejectDiagnostics(error) {
177
+ if (diagnosticsSettled)
178
+ return;
179
+ diagnosticsSettled = true;
180
+ rejectDiagnosticsPromise(error);
181
+ }
182
+ function resolveTerminal(statsValue, diagnosticsValue) {
183
+ resolveStats(statsValue);
184
+ resolveDiagnostics(diagnosticsValue);
185
+ }
186
+ function resolveEmptyTerminal() {
187
+ resolveTerminal(undefined, undefined);
188
+ }
189
+ function rejectTerminal(error) {
190
+ rejectStats(error);
191
+ rejectDiagnostics(error);
149
192
  }
150
193
  function processResponse(line) {
151
- return process(line, resolveStats);
194
+ return process(line, resolveTerminal);
152
195
  }
153
- const responses = parseLines(responseStream, processResponse, resolveEmptyStats, rejectStats);
196
+ const responses = parseLines(responseStream, processResponse, resolveEmptyTerminal, rejectTerminal);
154
197
  let consumed = false;
155
198
  return {
156
199
  stats,
200
+ diagnostics,
157
201
  write(audioChunk) {
158
202
  requestStream.write(audioChunk);
159
203
  },
@@ -161,7 +205,7 @@ export async function createTranscribeStreamSession(params, options, process, se
161
205
  requestStream.end();
162
206
  },
163
207
  destroy() {
164
- resolveStats(undefined);
208
+ resolveEmptyTerminal();
165
209
  requestStream.destroy();
166
210
  responseStream.destroy();
167
211
  },
@@ -182,7 +226,7 @@ function transcribeStreamDuplexMetadata(params, options) {
182
226
  }
183
227
  function transcribeStreamDuplexConversation(params, options) {
184
228
  const wantsMetadata = params.metadata === true;
185
- return createTranscribeStreamSession(params, options, (line, onStats) => processLineConversation(line, wantsMetadata, onStats), 'TranscribeStreamConversationSession');
229
+ return createTranscribeStreamSession(params, options, (line, onTerminal) => processLineConversation(line, wantsMetadata, onTerminal), 'TranscribeStreamConversationSession');
186
230
  }
187
231
  /**
188
232
  * Line-delimited parser: reads newline-separated frames from a duplex
@@ -241,31 +285,32 @@ function parseResponseLine(line) {
241
285
  * `undefined` for frames the caller should skip, or the value extracted from
242
286
  * the frame.
243
287
  */
244
- function processWith(line, extract, onStats) {
288
+ function processWith(line, extract, onTerminal) {
245
289
  const response = parseResponseLine(line);
246
290
  if (response === null)
247
291
  return undefined;
248
292
  if (response.error)
249
293
  throw new TranscriptionFailedError(response.error);
250
294
  if (response.done) {
251
- onStats(response.stats);
295
+ onTerminal(response.stats, response.diagnostics);
252
296
  return null;
253
297
  }
254
298
  return extract(response);
255
299
  }
256
- export function processLine(line, onStats) {
257
- return processWith(line, (response) => (response.text?.trim() ? response.text : undefined), onStats);
300
+ export function processLine(line, onTerminal) {
301
+ return processWith(line, (response) => (response.text?.trim() ? response.text : undefined), onTerminal);
258
302
  }
259
- function processLineMetadata(line, onStats) {
260
- return processWith(line, (response) => response.segment, onStats);
303
+ function processLineMetadata(line, onTerminal) {
304
+ return processWith(line, (response) => response.segment, onTerminal);
261
305
  }
262
- function processLineConversation(line, wantsMetadata, onStats) {
306
+ export function processLineConversation(line, wantsMetadata, onTerminal) {
263
307
  return processWith(line, (response) => {
264
308
  if (response.vad) {
265
309
  return {
266
310
  type: 'vad',
267
311
  speaking: response.vad.speaking,
268
- probability: response.vad.probability
312
+ probability: response.vad.probability,
313
+ ...(response.vad.source && { source: response.vad.source })
269
314
  };
270
315
  }
271
316
  if (response.endOfTurn) {
@@ -291,5 +336,5 @@ function processLineConversation(line, wantsMetadata, onStats) {
291
336
  return { type: 'text', text: response.text };
292
337
  }
293
338
  return undefined;
294
- }, onStats);
339
+ }, onTerminal);
295
340
  }
@@ -1,4 +1,4 @@
1
- import { ContextOverflowError, RequestIdConflictError, RequestNotFoundError, RequestRejectedByPolicyError, TranslationFailedError } from '../../utils/errors-server.js';
1
+ import { RpcServerOperationError, InferenceCancelledError, ContextOverflowError, RequestIdConflictError, RequestNotFoundError, RequestRejectedByPolicyError, TranslationFailedError } from '../../utils/errors-server.js';
2
2
  export class RPCError extends Error {
3
3
  timestamp;
4
4
  remoteStack;
@@ -93,14 +93,18 @@ function readOptionalStringField(fields, key) {
93
93
  * be re-exported from `@qvac/sdk` so consumers can `import` it.
94
94
  * Forgetting either side means `instanceof` regresses for that class.
95
95
  *
96
- * Client-constructed typed errors (e.g. `InferenceCancelledError`
97
- * built in `client/api/completion-stream.ts` from the aggregated
98
- * partial state) are NOT registered here — they never round-trip the
99
- * envelope, and adding a reconstructor for one would create a
100
- * parallel construction path that fires whenever the server happens
101
- * to throw the same class name.
96
+ * Completion aggregators construct cancellation errors locally with partial
97
+ * output. RPC control operations also throw them in the worker, so cancellation
98
+ * errors support both paths.
102
99
  */
103
100
  const RECONSTRUCTORS = {
101
+ INFERENCE_CANCELLED: (response) => {
102
+ const partial = response.typedFields?.['partial'];
103
+ return new InferenceCancelledError(readStringField(response.typedFields, 'requestId', ''), partial && typeof partial === 'object' && !Array.isArray(partial)
104
+ ? partial
105
+ : {}, response.cause);
106
+ },
107
+ RPC_SERVER_OPERATION_FAILED: (response) => new RpcServerOperationError(readStringField(response.typedFields, 'operation', 'rpcServer'), readStringField(response.typedFields, 'details', response.message), response.cause),
104
108
  REQUEST_ID_CONFLICT: (response) => {
105
109
  return new RequestIdConflictError(readStringField(response.typedFields, 'requestId', ''), response.cause);
106
110
  },
@@ -1,5 +1,5 @@
1
- export declare function generateWorkerEntry(pluginSpecifiers: string[], sdkName: string, resolveImport?: (specifier: string) => string): string;
2
- export declare function generateWorkerEntries(pluginSpecifiers: string[], sdkName: string, resolveBundleImport: (specifier: string) => string): {
1
+ export declare function generateWorkerEntry(pluginSpecifiers: string[], sdkName: string, resolveImport?: (specifier: string) => string, rpcServerProvider?: string): string;
2
+ export declare function generateWorkerEntries(pluginSpecifiers: string[], sdkName: string, resolveBundleImport: (specifier: string) => string, rpcServerProvider?: string): {
3
3
  runtimeEntry: string;
4
4
  bundleEntry: string;
5
5
  };
@@ -1,8 +1,12 @@
1
1
  import { parseBuiltinSpecifier } from './plugins.js';
2
- export function generateWorkerEntry(pluginSpecifiers, sdkName, resolveImport = (specifier) => specifier) {
2
+ export function generateWorkerEntry(pluginSpecifiers, sdkName, resolveImport = (specifier) => specifier, rpcServerProvider) {
3
3
  const imports = [];
4
4
  const registrations = [];
5
5
  let varIndex = 0;
6
+ if (rpcServerProvider) {
7
+ imports.push(`import { registerRpcServerProvider } from ${JSON.stringify(resolveImport(`${sdkName}/rpc-server-provider`))};`, `import rpcServerProvider from ${JSON.stringify(resolveImport(rpcServerProvider))};`);
8
+ registrations.push('registerRpcServerProvider(rpcServerProvider);');
9
+ }
6
10
  for (const specifier of pluginSpecifiers) {
7
11
  const builtin = parseBuiltinSpecifier(specifier, sdkName);
8
12
  if (builtin) {
@@ -46,9 +50,9 @@ if (hasRPCConfig) {
46
50
  }
47
51
  `;
48
52
  }
49
- export function generateWorkerEntries(pluginSpecifiers, sdkName, resolveBundleImport) {
53
+ export function generateWorkerEntries(pluginSpecifiers, sdkName, resolveBundleImport, rpcServerProvider) {
50
54
  return {
51
- runtimeEntry: generateWorkerEntry(pluginSpecifiers, sdkName),
52
- bundleEntry: generateWorkerEntry(pluginSpecifiers, sdkName, resolveBundleImport)
55
+ runtimeEntry: generateWorkerEntry(pluginSpecifiers, sdkName, undefined, rpcServerProvider),
56
+ bundleEntry: generateWorkerEntry(pluginSpecifiers, sdkName, resolveBundleImport, rpcServerProvider)
53
57
  };
54
58
  }
@@ -1,3 +1,4 @@
1
+ import { type HostPrebuildPackage } from '../host-prebuilds/index.js';
1
2
  export interface BundleSdkOptions {
2
3
  projectRoot?: string | undefined;
3
4
  configPath?: string | undefined;
@@ -6,6 +7,23 @@ export interface BundleSdkOptions {
6
7
  defer?: string[] | undefined;
7
8
  quiet?: boolean | undefined;
8
9
  verbose?: boolean | undefined;
10
+ /**
11
+ * Install the addon platform packages the bundle needs for its mobile hosts
12
+ * with the project's package manager (see `ensureHostPrebuilds`), then
13
+ * bundle again. Off by default: without it, bundling never changes
14
+ * package.json or node_modules. When the install is refused, the bundle and
15
+ * its manifest are still written before `HostPrebuildsInstallRefusedError`
16
+ * is thrown.
17
+ */
18
+ installMissingPrebuilds?: boolean | undefined;
19
+ /**
20
+ * Check the bundled packages' engines.bare against the Bare runtime of each
21
+ * host and warn on a mismatch. Defaults to true; callers that run
22
+ * `verifyBundle` on the result can turn it off.
23
+ */
24
+ checkEngines?: boolean | undefined;
25
+ /** Allow network lookups during the engines check. Defaults to true. */
26
+ network?: boolean | undefined;
9
27
  }
10
28
  export interface BundleSdkResult {
11
29
  bundlePath: string;
@@ -15,6 +33,8 @@ export interface BundleSdkResult {
15
33
  worker: string;
16
34
  };
17
35
  manifestPath: string;
36
+ /** Platform packages `installMissingPrebuilds` installed; empty when it is off. */
37
+ installedPrebuilds: HostPrebuildPackage[];
18
38
  }
19
39
  export declare function bundleSdk(options?: BundleSdkOptions): Promise<BundleSdkResult>;
20
40
  //# sourceMappingURL=index.d.ts.map
@@ -3,13 +3,18 @@ import path from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
4
  import { DEFAULT_HOSTS, DEFAULT_SDK_NAME } from './constants.js';
5
5
  import { CONFIG_CANDIDATES, resolveConfigForProject } from '../../client/config-loader/resolve-config.node.js';
6
- import { getClientLogger } from '../../logging/index.js';
7
- import { BareImportsMapNotFoundError } from '../../utils/errors-client.js';
6
+ import { createCommandLogger } from '../command-logger.js';
7
+ import { BareImportsMapNotFoundError, HostPrebuildsInstallRefusedError } from '../../utils/errors-client.js';
8
8
  import { resolvePluginSpecifiers, parseBuiltinSpecifier } from './plugins.js';
9
9
  import { generateWorkerEntries } from './entry-gen.js';
10
10
  import { runBarePack } from './bare-pack.js';
11
11
  import { generateAddonsManifest } from './manifest.js';
12
12
  import { createSdkImportResolver } from './resolve-sdk-import.js';
13
+ import { installMissingHostPrebuilds } from '../host-prebuilds/index.js';
14
+ import { collectAddonsFromBundle } from '../verify/bundle-source.js';
15
+ import { verifyBundle } from '../verify/index.js';
16
+ import { formatRuntimeSource } from '../verify/abi.js';
17
+ import { formatEnginesAdvice } from '../verify/engines-advice.js';
13
18
  const require = createRequire(import.meta.url);
14
19
  function resolveSdkPath(projectRoot, explicitSdkPath) {
15
20
  if (explicitSdkPath) {
@@ -49,14 +54,43 @@ function resolveImportsMapPath(sdkPath, sdkName) {
49
54
  }
50
55
  throw new BareImportsMapNotFoundError(sdkName, importsMapPath);
51
56
  }
52
- function createCommandLogger(options) {
53
- if (options.quiet) {
54
- return getClientLogger({ level: 'error', enableConsole: false });
57
+ const ENGINES_ISSUE_CODES = new Set(['abi-mismatch', 'engines-mismatch']);
58
+ async function checkBundleEngines(options) {
59
+ const { projectRoot, bundlePath, hosts, configPath, network, logger } = options;
60
+ logger.info('\nšŸ”Ž Checking engines.bare against the Bare runtime of each host...');
61
+ const result = await verifyBundle({
62
+ projectRoot,
63
+ addonsSource: bundlePath,
64
+ hosts,
65
+ ...(configPath !== undefined ? { configPath } : {}),
66
+ ...(network !== undefined ? { network } : {}),
67
+ onProgress: (message) => logger.info(` ${message}`)
68
+ });
69
+ const checkedHosts = [];
70
+ for (const group of result.runtimes ?? []) {
71
+ const label = group.hosts.join(', ');
72
+ if (group.resolution.resolved) {
73
+ checkedHosts.push(...group.hosts);
74
+ logger.info(` ${label}: Bare ${group.resolution.runtime.version} (from ${formatRuntimeSource(group.resolution.runtime)})`);
75
+ }
76
+ else {
77
+ logger.warn(`engines.bare not checked for ${label}: ${group.resolution.error.reason}`);
78
+ }
55
79
  }
56
- if (options.verbose) {
57
- return getClientLogger({ level: 'debug' });
80
+ const mismatches = result.issues.filter((issue) => ENGINES_ISSUE_CODES.has(issue.code) && issue.level === 'error');
81
+ if (mismatches.length === 0) {
82
+ if (checkedHosts.length > 0) {
83
+ logger.info(` No engines.bare mismatches for ${checkedHosts.join(', ')}`);
84
+ }
85
+ return;
58
86
  }
59
- return getClientLogger();
87
+ const lines = ['The bundle contains packages that require a newer Bare runtime:'];
88
+ for (const issue of mismatches)
89
+ lines.push(` - ${issue.message}`);
90
+ lines.push('');
91
+ for (const advice of result.advice ?? [])
92
+ lines.push(...formatEnginesAdvice(advice));
93
+ logger.warn(lines.join('\n').trimEnd());
60
94
  }
61
95
  export async function bundleSdk(options = {}) {
62
96
  const startTime = Date.now();
@@ -94,7 +128,7 @@ export async function bundleSdk(options = {}) {
94
128
  await fsp.mkdir(outputDir, { recursive: true });
95
129
  logger.info('\nšŸ“ Generating worker entry...');
96
130
  const resolveSdkImport = createSdkImportResolver(sdkPath, sdkName);
97
- const { runtimeEntry, bundleEntry } = generateWorkerEntries(pluginSpecifiers, sdkName, resolveSdkImport);
131
+ const { runtimeEntry, bundleEntry } = generateWorkerEntries(pluginSpecifiers, sdkName, resolveSdkImport, config.rpcServerProvider);
98
132
  await fsp.writeFile(entryPath, runtimeEntry, 'utf8');
99
133
  logger.info(` Created: ${path.relative(projectRoot, entryPath)}`);
100
134
  logger.info(` Using: ${path.relative(projectRoot, importsMapPath)}`);
@@ -103,9 +137,11 @@ export async function bundleSdk(options = {}) {
103
137
  if (deferModules.length > 0) {
104
138
  logger.debug(` Deferred: ${deferModules.join(', ')}`);
105
139
  }
140
+ let installedPrebuilds = [];
141
+ let installRefused;
106
142
  try {
107
143
  await fsp.writeFile(bundleEntryPath, bundleEntry, 'utf8');
108
- await runBarePack({
144
+ const barePackOptions = {
109
145
  entryPath: bundleEntryPath,
110
146
  outputPath: bundlePath,
111
147
  hosts,
@@ -113,7 +149,34 @@ export async function bundleSdk(options = {}) {
113
149
  deferModules,
114
150
  quiet: options.quiet === true,
115
151
  logger
116
- });
152
+ };
153
+ await runBarePack(barePackOptions);
154
+ if (options.installMissingPrebuilds === true) {
155
+ try {
156
+ const { installed } = await installMissingHostPrebuilds({
157
+ projectRoot,
158
+ hosts,
159
+ addons: await collectAddonsFromBundle({ bundlePath, projectRoot, hosts }),
160
+ quiet: options.quiet === true,
161
+ logger
162
+ });
163
+ installedPrebuilds = installed;
164
+ // Where a platform package was missing, bare-pack resolved the addon's
165
+ // `#host-addon` import to its fallback module; bundle again to pick up
166
+ // the installed package.
167
+ if (installed.length > 0) {
168
+ logger.info('\nšŸ”Ø Bundling again with the installed platform packages...');
169
+ await runBarePack(barePackOptions);
170
+ }
171
+ }
172
+ catch (error) {
173
+ if (!(error instanceof HostPrebuildsInstallRefusedError))
174
+ throw error;
175
+ // A refusal happens before anything is installed, so the bundle
176
+ // already written is final and only its manifest is left to write.
177
+ installRefused = error;
178
+ }
179
+ }
117
180
  }
118
181
  finally {
119
182
  await fsp.rm(bundleEntryPath, { force: true });
@@ -128,6 +191,18 @@ export async function bundleSdk(options = {}) {
128
191
  projectRoot,
129
192
  logger
130
193
  });
194
+ if (options.checkEngines !== false) {
195
+ await checkBundleEngines({
196
+ projectRoot,
197
+ bundlePath,
198
+ hosts,
199
+ configPath: configPath ?? undefined,
200
+ network: options.network,
201
+ logger
202
+ });
203
+ }
204
+ if (installRefused !== undefined)
205
+ throw installRefused;
131
206
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
132
207
  logger.info(`\nšŸŽ‰ Done in ${elapsed}s!\n`);
133
208
  logger.info('Generated files:');
@@ -143,6 +218,7 @@ export async function bundleSdk(options = {}) {
143
218
  entryPaths: {
144
219
  worker: entryPath
145
220
  },
146
- manifestPath: manifestResult.manifestPath
221
+ manifestPath: manifestResult.manifestPath,
222
+ installedPrebuilds
147
223
  };
148
224
  }
@@ -17,7 +17,7 @@ export function parseBuiltinSpecifier(specifier, sdkName) {
17
17
  }
18
18
  export function resolvePluginSpecifiers(config, sdkName, logger) {
19
19
  let plugins = config.plugins ?? [];
20
- if (plugins.length === 0) {
20
+ if (plugins.length === 0 && !(config.rpcServerProvider && config.plugins !== undefined)) {
21
21
  const allBuiltins = BUILTIN_SUFFIXES.map((suffix) => buildBuiltinSpecifier(sdkName, suffix));
22
22
  logger.warn('No plugins specified — bundling ALL built-in plugins.');
23
23
  logger.info(" For smaller bundles, add a 'plugins' array to qvac.config.*\n");
@@ -0,0 +1,6 @@
1
+ export interface CommandLoggerOptions {
2
+ quiet?: boolean | undefined;
3
+ verbose?: boolean | undefined;
4
+ }
5
+ export declare function createCommandLogger(options: CommandLoggerOptions): import("../logging/index.js").Logger;
6
+ //# sourceMappingURL=command-logger.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { getClientLogger } from '../logging/index.js';
2
+ export function createCommandLogger(options) {
3
+ if (options.quiet) {
4
+ return getClientLogger({ level: 'error', enableConsole: false });
5
+ }
6
+ // The client logger keeps console output off unless asked; a command's
7
+ // progress and warnings are meant for the person running it.
8
+ if (options.verbose) {
9
+ return getClientLogger({ level: 'debug', enableConsole: true });
10
+ }
11
+ return getClientLogger({ enableConsole: true });
12
+ }
@@ -0,0 +1,71 @@
1
+ import type { Logger } from '../../logging/types.js';
2
+ import { type NativeAddon } from '../verify/addon-source.js';
3
+ import { type PackageManagerName } from './package-manager.js';
4
+ export type { PackageManagerName } from './package-manager.js';
5
+ export interface EnsureHostPrebuildsOptions {
6
+ /** The package whose package.json receives the pins; its package manager installs them. */
7
+ projectRoot: string;
8
+ /** Bare hosts to cover. Only mobile hosts (`android-*`, `ios-*`) can need an install. */
9
+ hosts: string[];
10
+ /**
11
+ * Addon package names to cover. Defaults to every addon installed for the
12
+ * project: in its own node_modules or in that of a workspace root listing it.
13
+ */
14
+ addons?: string[] | undefined;
15
+ /** Install with this package manager instead of detecting the project's own. */
16
+ packageManager?: PackageManagerName | undefined;
17
+ quiet?: boolean | undefined;
18
+ verbose?: boolean | undefined;
19
+ }
20
+ export interface HostPrebuildPackage {
21
+ /** The platform package, e.g. `@qvac/tts-ggml-android-arm64`. */
22
+ name: string;
23
+ /** Always the addon's own version: platform packages are version-locked to it. */
24
+ version: string;
25
+ /** The addon the package ships prebuilds for. */
26
+ addon: string;
27
+ /** The requested hosts the package covers. */
28
+ hosts: string[];
29
+ }
30
+ export interface EnsureHostPrebuildsResult {
31
+ /** The packages this call installed; empty when every prebuild already resolved. */
32
+ installed: HostPrebuildPackage[];
33
+ /** The package manager that installed them, or null when nothing was missing. */
34
+ packageManager: PackageManagerName | null;
35
+ }
36
+ /**
37
+ * Installs the per-platform prebuild packages that addons need for mobile
38
+ * hosts and that the project does not have yet.
39
+ *
40
+ * A split addon names one platform package per host in the `#host-addon` map
41
+ * of its package.json (`<addon>-ios` covers every iOS host). Desktop platform
42
+ * packages install as the addon's os/cpu-filtered optionalDependencies, but a
43
+ * build host never reports a mobile os, so mobile ones have to be declared.
44
+ * This declares the missing ones in package.json, pinned to each addon's
45
+ * exact version, using the project's own package manager.
46
+ *
47
+ * A host whose prebuild already resolves is skipped: a local
48
+ * `prebuilds/<host>` in the addon (source builds) or its platform package at
49
+ * the addon's version. Throws `HostPrebuildsInstallRefusedError` when no
50
+ * supported package manager (npm 7+, pnpm, bun, Yarn Berry) is found, and
51
+ * `HostPrebuildsInstallFailedError` when the install does not produce the
52
+ * packages.
53
+ */
54
+ export declare function ensureHostPrebuilds(options: EnsureHostPrebuildsOptions): Promise<EnsureHostPrebuildsResult>;
55
+ export interface InstallMissingHostPrebuildsOptions {
56
+ projectRoot: string;
57
+ hosts: string[];
58
+ addons: NativeAddon[];
59
+ packageManager?: PackageManagerName | undefined;
60
+ quiet: boolean;
61
+ logger: Logger;
62
+ }
63
+ /** `ensureHostPrebuilds` for addons the caller has already located. */
64
+ export declare function installMissingHostPrebuilds(options: InstallMissingHostPrebuildsOptions): Promise<EnsureHostPrebuildsResult>;
65
+ /**
66
+ * The platform packages `addons` still need for the mobile hosts in `hosts`,
67
+ * one entry per package. Throws when two installed versions of one addon need
68
+ * the same platform package, since only one version of it can be installed.
69
+ */
70
+ export declare function findMissingHostPrebuilds(addons: NativeAddon[], hosts: string[]): Promise<HostPrebuildPackage[]>;
71
+ //# sourceMappingURL=index.d.ts.map