@qvac/sdk 0.10.2 → 0.11.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.
- package/NOTICE +92 -54
- package/dist/client/api/cancel.d.ts +24 -32
- package/dist/client/api/cancel.js +58 -31
- package/dist/client/api/client-request-id.d.ts +17 -0
- package/dist/client/api/client-request-id.js +27 -0
- package/dist/client/api/completion-stream.d.ts +1 -1
- package/dist/client/api/completion-stream.js +34 -3
- package/dist/client/api/diffusion.d.ts +46 -0
- package/dist/client/api/diffusion.js +50 -1
- package/dist/client/api/download-asset.d.ts +12 -2
- package/dist/client/api/download-asset.js +21 -3
- package/dist/client/api/embed.d.ts +8 -4
- package/dist/client/api/embed.js +14 -1
- package/dist/client/api/get-model-info.d.ts +1 -1
- package/dist/client/api/index.d.ts +1 -0
- package/dist/client/api/index.js +1 -0
- package/dist/client/api/load-model.d.ts +12 -4
- package/dist/client/api/load-model.js +25 -1
- package/dist/client/api/rag.d.ts +9 -3
- package/dist/client/api/rag.js +23 -3
- package/dist/client/api/transcribe.d.ts +20 -8
- package/dist/client/api/transcribe.js +58 -4
- package/dist/client/api/unload-model.d.ts +5 -2
- package/dist/client/api/unload-model.js +9 -4
- package/dist/client/api/upscale.d.ts +45 -0
- package/dist/client/api/upscale.js +99 -0
- package/dist/client/rpc/expo-rpc-client.js +2 -1
- package/dist/client/rpc/node-rpc-client.js +18 -7
- package/dist/client/rpc/rpc-client.d.ts +2 -2
- package/dist/client/rpc/rpc-client.js +10 -4
- package/dist/client/rpc/rpc-error.d.ts +9 -0
- package/dist/client/rpc/rpc-error.js +92 -0
- package/dist/examples/cancel-by-request-id.d.ts +38 -0
- package/dist/examples/cancel-by-request-id.js +103 -0
- package/dist/examples/diffusion-esrgan-upscale.d.ts +2 -0
- package/dist/examples/diffusion-esrgan-upscale.js +78 -0
- package/dist/examples/download-with-cancel.js +10 -10
- package/dist/examples/embed-multi-gpu.d.ts +2 -0
- package/dist/examples/embed-multi-gpu.js +46 -0
- package/dist/examples/llamacpp-multi-gpu.d.ts +2 -0
- package/dist/examples/llamacpp-multi-gpu.js +57 -0
- package/dist/examples/rag/rag-hyperdb/cancellation.js +20 -17
- package/dist/examples/tools/llamacpp-tools-gemma4.d.ts +2 -0
- package/dist/examples/tools/llamacpp-tools-gemma4.js +76 -0
- package/dist/examples/tools/llamacpp-tools-qwen35.d.ts +2 -0
- package/dist/examples/tools/llamacpp-tools-qwen35.js +70 -0
- package/dist/examples/transcription/whispercpp-microphone-conversation.d.ts +2 -0
- package/dist/examples/transcription/whispercpp-microphone-conversation.js +102 -0
- package/dist/examples/tts/supertonic.js +1 -1
- package/dist/expo/plugins/withMobileBundle.d.ts +20 -4
- package/dist/expo/plugins/withMobileBundle.js +44 -6
- package/dist/index.d.ts +5 -2
- package/dist/index.js +21 -1
- package/dist/models/registry/models.d.ts +186 -318
- package/dist/models/registry/models.js +1522 -1667
- package/dist/pear/pre.d.ts +3 -0
- package/dist/pear/pre.js +3 -0
- package/dist/schemas/cancel.d.ts +167 -28
- package/dist/schemas/cancel.js +119 -19
- package/dist/schemas/common.d.ts +427 -208
- package/dist/schemas/common.js +3 -1
- package/dist/schemas/completion-event.d.ts +14 -0
- package/dist/schemas/completion-event.js +11 -1
- package/dist/schemas/completion-stream.d.ts +15 -1
- package/dist/schemas/completion-stream.js +25 -7
- package/dist/schemas/download-asset.d.ts +15 -9
- package/dist/schemas/download-asset.js +17 -6
- package/dist/schemas/embed.d.ts +1 -0
- package/dist/schemas/embed.js +5 -0
- package/dist/schemas/error.d.ts +31 -0
- package/dist/schemas/error.js +29 -1
- package/dist/schemas/finetune.d.ts +2 -0
- package/dist/schemas/finetune.js +5 -0
- package/dist/schemas/get-model-info.d.ts +6 -6
- package/dist/schemas/llamacpp-config.d.ts +54 -8
- package/dist/schemas/llamacpp-config.js +12 -0
- package/dist/schemas/load-model.d.ts +1911 -1260
- package/dist/schemas/load-model.js +15 -0
- package/dist/schemas/model-src-utils.d.ts +14 -14
- package/dist/schemas/model-types.d.ts +10 -10
- package/dist/schemas/ocr.d.ts +3 -3
- package/dist/schemas/plugin.d.ts +40 -1
- package/dist/schemas/plugin.js +10 -1
- package/dist/schemas/rag.d.ts +9 -0
- package/dist/schemas/rag.js +17 -9
- package/dist/schemas/registry.d.ts +30 -30
- package/dist/schemas/reload-config.d.ts +14 -14
- package/dist/schemas/sdcpp-config.d.ts +131 -17
- package/dist/schemas/sdcpp-config.js +163 -1
- package/dist/schemas/sdk-config.d.ts +649 -271
- package/dist/schemas/sdk-errors-client.d.ts +1 -0
- package/dist/schemas/sdk-errors-client.js +5 -0
- package/dist/schemas/sdk-errors-server.d.ts +5 -0
- package/dist/schemas/sdk-errors-server.js +25 -0
- package/dist/schemas/text-to-speech.d.ts +78 -78
- package/dist/schemas/transcription-config.d.ts +24 -24
- package/dist/schemas/transcription.d.ts +56 -2
- package/dist/schemas/transcription.js +27 -0
- package/dist/schemas/translate.d.ts +2 -0
- package/dist/schemas/translate.js +13 -2
- package/dist/schemas/translation-config.d.ts +40 -40
- package/dist/schemas/unload-model.d.ts +2 -1
- package/dist/schemas/unload-model.js +4 -1
- package/dist/scripts/run-unit-tests.js +15 -2
- package/dist/server/bare/ops/cancel.d.ts +27 -1
- package/dist/server/bare/ops/cancel.js +36 -10
- package/dist/server/bare/ops/embed.d.ts +3 -3
- package/dist/server/bare/ops/embed.js +144 -28
- package/dist/server/bare/ops/kv-cache-utils.d.ts +0 -7
- package/dist/server/bare/ops/kv-cache-utils.js +8 -47
- package/dist/server/bare/ops/transcribe.d.ts +10 -5
- package/dist/server/bare/ops/transcribe.js +196 -41
- package/dist/server/bare/ops/translate.d.ts +1 -1
- package/dist/server/bare/ops/translate.js +197 -88
- package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.d.ts +12 -3
- package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +170 -172
- package/dist/server/bare/plugins/llamacpp-completion/ops/finetune.js +125 -21
- package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.d.ts +154 -0
- package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.js +448 -0
- package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.d.ts +21 -73
- package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.js +18 -101
- package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +30 -3
- package/dist/server/bare/plugins/llamacpp-completion/plugin.js +157 -85
- package/dist/server/bare/plugins/llamacpp-completion/transform.d.ts +10 -0
- package/dist/server/bare/plugins/llamacpp-completion/transform.js +34 -0
- package/dist/server/bare/plugins/llamacpp-embedding/plugin.d.ts +7 -0
- package/dist/server/bare/plugins/llamacpp-embedding/plugin.js +10 -1
- package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +17 -15
- package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +5 -1
- package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +3 -3
- package/dist/server/bare/plugins/onnx-ocr/plugin.js +3 -0
- package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +39 -39
- package/dist/server/bare/plugins/onnx-tts/plugin.js +6 -0
- package/dist/server/bare/plugins/parakeet-transcription/plugin.d.ts +29 -21
- package/dist/server/bare/plugins/parakeet-transcription/plugin.js +4 -1
- package/dist/server/bare/plugins/sdcpp-generation/ops/diffusion.js +8 -0
- package/dist/server/bare/plugins/sdcpp-generation/ops/upscale.d.ts +3 -0
- package/dist/server/bare/plugins/sdcpp-generation/ops/upscale.js +40 -0
- package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +91 -16
- package/dist/server/bare/plugins/sdcpp-generation/plugin.js +97 -7
- package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +22 -3
- package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +50 -14
- package/dist/server/bare/rag-hyperdb/index.d.ts +1 -1
- package/dist/server/bare/rag-hyperdb/index.js +1 -1
- package/dist/server/bare/rag-hyperdb/rag-operation-manager.d.ts +26 -4
- package/dist/server/bare/rag-hyperdb/rag-operation-manager.js +65 -28
- package/dist/server/bare/runtime/disposable-scope.d.ts +36 -0
- package/dist/server/bare/runtime/disposable-scope.js +69 -0
- package/dist/server/bare/runtime/index.d.ts +8 -0
- package/dist/server/bare/runtime/index.js +3 -0
- package/dist/server/bare/runtime/request-context.d.ts +49 -0
- package/dist/server/bare/runtime/request-context.js +1 -0
- package/dist/server/bare/runtime/request-id.d.ts +12 -0
- package/dist/server/bare/runtime/request-id.js +14 -0
- package/dist/server/bare/runtime/request-registry-singleton.d.ts +4 -0
- package/dist/server/bare/runtime/request-registry-singleton.js +27 -0
- package/dist/server/bare/runtime/request-registry.d.ts +136 -0
- package/dist/server/bare/runtime/request-registry.js +297 -0
- package/dist/server/bare/runtime/with-request-context.d.ts +22 -0
- package/dist/server/bare/runtime/with-request-context.js +53 -0
- package/dist/server/bare/types/addon-responses.d.ts +11 -1
- package/dist/server/rpc/handler-registry.js +26 -7
- package/dist/server/rpc/handlers/cancel-delegated.js +28 -31
- package/dist/server/rpc/handlers/cancelHandler.d.ts +30 -1
- package/dist/server/rpc/handlers/cancelHandler.js +54 -22
- package/dist/server/rpc/handlers/delete-cache.d.ts +9 -0
- package/dist/server/rpc/handlers/delete-cache.js +13 -15
- package/dist/server/rpc/handlers/download-asset.js +133 -40
- package/dist/server/rpc/handlers/index.d.ts +2 -0
- package/dist/server/rpc/handlers/index.js +2 -0
- package/dist/server/rpc/handlers/load-model/download-manager.d.ts +60 -2
- package/dist/server/rpc/handlers/load-model/download-manager.js +141 -8
- package/dist/server/rpc/handlers/load-model/handler.js +213 -98
- package/dist/server/rpc/handlers/load-model/http.js +3 -3
- package/dist/server/rpc/handlers/load-model/hyperdrive.js +1 -1
- package/dist/server/rpc/handlers/load-model/registry.js +1 -1
- package/dist/server/rpc/handlers/load-model/resolve-session.d.ts +20 -1
- package/dist/server/rpc/handlers/load-model/resolve-session.js +6 -5
- package/dist/server/rpc/handlers/load-model/resolve.d.ts +3 -2
- package/dist/server/rpc/handlers/load-model/resolve.js +9 -6
- package/dist/server/rpc/handlers/load-model/types.d.ts +24 -0
- package/dist/server/rpc/handlers/rag.js +133 -15
- package/dist/server/rpc/handlers/upscale-stream.d.ts +3 -0
- package/dist/server/rpc/handlers/upscale-stream.js +4 -0
- package/dist/server/utils/completion-normalizer.js +8 -0
- package/dist/server/utils/tools/dialect.js +4 -0
- package/dist/server/utils/tools/parser.js +16 -1
- package/dist/server/utils/tools/parsers/gemma4native.d.ts +4 -0
- package/dist/server/utils/tools/parsers/gemma4native.js +60 -0
- package/dist/server/utils/tools/parsers/qwen35.d.ts +4 -0
- package/dist/server/utils/tools/parsers/qwen35.js +120 -0
- package/dist/utils/aggregate-events.d.ts +9 -0
- package/dist/utils/aggregate-events.js +18 -3
- package/dist/utils/decorate-promise.d.ts +34 -0
- package/dist/utils/decorate-promise.js +35 -0
- package/dist/utils/errors-client.d.ts +3 -0
- package/dist/utils/errors-client.js +5 -0
- package/dist/utils/errors-server.d.ts +85 -0
- package/dist/utils/errors-server.js +97 -0
- package/package.json +20 -15
|
@@ -2,8 +2,10 @@ import { transcribeResponseSchema, transcribeStreamResponseSchema, } from "../..
|
|
|
2
2
|
import { stream, duplex } from "../rpc/rpc-client.js";
|
|
3
3
|
import { getClientLogger } from "../../logging/index.js";
|
|
4
4
|
import { TranscriptionFailedError } from "../../utils/errors-client.js";
|
|
5
|
+
import { decoratePromise } from "../../utils/decorate-promise.js";
|
|
6
|
+
import { generateClientRequestId } from "./client-request-id.js";
|
|
5
7
|
const logger = getClientLogger();
|
|
6
|
-
function buildTranscribeRequest(params) {
|
|
8
|
+
function buildTranscribeRequest(params, requestId) {
|
|
7
9
|
return {
|
|
8
10
|
type: "transcribe",
|
|
9
11
|
modelId: params.modelId,
|
|
@@ -12,10 +14,21 @@ function buildTranscribeRequest(params) {
|
|
|
12
14
|
: { type: "base64", value: params.audioChunk.toString("base64") },
|
|
13
15
|
...(params.prompt && { prompt: params.prompt }),
|
|
14
16
|
...(params.metadata === true && { metadata: true }),
|
|
17
|
+
requestId,
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
|
-
export
|
|
18
|
-
|
|
20
|
+
export function transcribe(params, options) {
|
|
21
|
+
// Client-generated id surfaced synchronously on the returned promise
|
|
22
|
+
// — same shape as `loadModel` / `downloadAsset` / `completion`. The
|
|
23
|
+
// CLI cancel bridge in `qvac serve` binds `req.on('close')` to
|
|
24
|
+
// `cancel({ requestId })` immediately after the call returns so a
|
|
25
|
+
// client disconnect aborts the in-flight transcription.
|
|
26
|
+
const requestId = generateClientRequestId();
|
|
27
|
+
const inner = runTranscribe(params, requestId, options);
|
|
28
|
+
return decoratePromise(inner, { requestId });
|
|
29
|
+
}
|
|
30
|
+
async function runTranscribe(params, requestId, options) {
|
|
31
|
+
const request = buildTranscribeRequest(params, requestId);
|
|
19
32
|
if (params.metadata === true) {
|
|
20
33
|
const segments = [];
|
|
21
34
|
for await (const response of stream(request, options)) {
|
|
@@ -54,6 +67,9 @@ export function transcribeStream(params, options) {
|
|
|
54
67
|
return transcribeStreamWithAudio(params, options);
|
|
55
68
|
}
|
|
56
69
|
const streamParams = params;
|
|
70
|
+
if (streamParams.emitVadEvents === true) {
|
|
71
|
+
return transcribeStreamDuplexConversation(streamParams, options);
|
|
72
|
+
}
|
|
57
73
|
if (streamParams.metadata === true) {
|
|
58
74
|
return transcribeStreamDuplexMetadata(streamParams, options);
|
|
59
75
|
}
|
|
@@ -64,7 +80,7 @@ export function transcribeStream(params, options) {
|
|
|
64
80
|
* the value extracted from each response frame until `done` is seen.
|
|
65
81
|
*/
|
|
66
82
|
async function* streamTranscribeValues(params, options, extract) {
|
|
67
|
-
const request = buildTranscribeRequest(params);
|
|
83
|
+
const request = buildTranscribeRequest(params, generateClientRequestId());
|
|
68
84
|
for await (const response of stream(request, options)) {
|
|
69
85
|
if (response.type === "transcribe") {
|
|
70
86
|
const parsed = transcribeResponseSchema.parse(response);
|
|
@@ -88,6 +104,13 @@ function buildTranscribeStreamRequest(params) {
|
|
|
88
104
|
modelId: params.modelId,
|
|
89
105
|
...(params.prompt && { prompt: params.prompt }),
|
|
90
106
|
...(params.metadata === true && { metadata: true }),
|
|
107
|
+
...(params.emitVadEvents === true && { emitVadEvents: true }),
|
|
108
|
+
...(params.endOfTurnSilenceMs !== undefined && {
|
|
109
|
+
endOfTurnSilenceMs: params.endOfTurnSilenceMs,
|
|
110
|
+
}),
|
|
111
|
+
...(params.vadRunIntervalMs !== undefined && {
|
|
112
|
+
vadRunIntervalMs: params.vadRunIntervalMs,
|
|
113
|
+
}),
|
|
91
114
|
};
|
|
92
115
|
}
|
|
93
116
|
/**
|
|
@@ -126,6 +149,10 @@ function transcribeStreamDuplex(params, options) {
|
|
|
126
149
|
function transcribeStreamDuplexMetadata(params, options) {
|
|
127
150
|
return createTranscribeStreamSession(params, options, processLineMetadata, "TranscribeStreamMetadataSession");
|
|
128
151
|
}
|
|
152
|
+
function transcribeStreamDuplexConversation(params, options) {
|
|
153
|
+
const wantsMetadata = params.metadata === true;
|
|
154
|
+
return createTranscribeStreamSession(params, options, (line) => processLineConversation(line, wantsMetadata), "TranscribeStreamConversationSession");
|
|
155
|
+
}
|
|
129
156
|
/**
|
|
130
157
|
* Line-delimited parser: reads newline-separated frames from a duplex
|
|
131
158
|
* response stream, passes each non-empty line through `process`, and yields
|
|
@@ -190,3 +217,30 @@ function processLine(line) {
|
|
|
190
217
|
function processLineMetadata(line) {
|
|
191
218
|
return processWith(line, (response) => response.segment);
|
|
192
219
|
}
|
|
220
|
+
function processLineConversation(line, wantsMetadata) {
|
|
221
|
+
return processWith(line, (response) => {
|
|
222
|
+
if (response.vad) {
|
|
223
|
+
return {
|
|
224
|
+
type: "vad",
|
|
225
|
+
speaking: response.vad.speaking,
|
|
226
|
+
probability: response.vad.probability,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (response.endOfTurn) {
|
|
230
|
+
return {
|
|
231
|
+
type: "endOfTurn",
|
|
232
|
+
silenceDurationMs: response.endOfTurn.silenceDurationMs,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (wantsMetadata) {
|
|
236
|
+
if (response.segment) {
|
|
237
|
+
return { type: "segment", segment: response.segment };
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
if (response.text && response.text.trim()) {
|
|
242
|
+
return { type: "text", text: response.text };
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
@@ -3,12 +3,15 @@ import { type UnloadModelParams } from "../../schemas/index";
|
|
|
3
3
|
* Unloads a previously loaded model from the server.
|
|
4
4
|
*
|
|
5
5
|
* When the last model is unloaded (no more models remain), this function
|
|
6
|
-
* automatically closes the RPC connection, allowing the
|
|
7
|
-
* naturally without requiring manual cleanup.
|
|
6
|
+
* automatically closes the RPC connection on Node/Electron, allowing the
|
|
7
|
+
* process to exit naturally without requiring manual cleanup. On Bare the
|
|
8
|
+
* connection is left open by default so long-lived workers survive a routine
|
|
9
|
+
* unload; pass `autoClose: true` to opt in to closing.
|
|
8
10
|
*
|
|
9
11
|
* @param params - The parameters for unloading the model
|
|
10
12
|
* @param params.modelId - The unique identifier of the model to unload
|
|
11
13
|
* @param params.clearStorage - Whether to clear the storage for the model
|
|
14
|
+
* @param params.autoClose - Override the runtime-default auto-close behavior
|
|
12
15
|
* @throws {QvacErrorBase} When the response type is invalid or when the unload operation fails
|
|
13
16
|
*/
|
|
14
17
|
export declare function unloadModel(params: UnloadModelParams): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBare } from "which-runtime";
|
|
1
2
|
import {} from "../../schemas/index.js";
|
|
2
3
|
import { send, close } from "../rpc/rpc-client.js";
|
|
3
4
|
import { stopLoggingStreamForModel } from "../logging-stream-registry.js";
|
|
@@ -8,12 +9,15 @@ const logger = getClientLogger();
|
|
|
8
9
|
* Unloads a previously loaded model from the server.
|
|
9
10
|
*
|
|
10
11
|
* When the last model is unloaded (no more models remain), this function
|
|
11
|
-
* automatically closes the RPC connection, allowing the
|
|
12
|
-
* naturally without requiring manual cleanup.
|
|
12
|
+
* automatically closes the RPC connection on Node/Electron, allowing the
|
|
13
|
+
* process to exit naturally without requiring manual cleanup. On Bare the
|
|
14
|
+
* connection is left open by default so long-lived workers survive a routine
|
|
15
|
+
* unload; pass `autoClose: true` to opt in to closing.
|
|
13
16
|
*
|
|
14
17
|
* @param params - The parameters for unloading the model
|
|
15
18
|
* @param params.modelId - The unique identifier of the model to unload
|
|
16
19
|
* @param params.clearStorage - Whether to clear the storage for the model
|
|
20
|
+
* @param params.autoClose - Override the runtime-default auto-close behavior
|
|
17
21
|
* @throws {QvacErrorBase} When the response type is invalid or when the unload operation fails
|
|
18
22
|
*/
|
|
19
23
|
export async function unloadModel(params) {
|
|
@@ -30,8 +34,9 @@ export async function unloadModel(params) {
|
|
|
30
34
|
throw new ModelUnloadFailedError(params.modelId);
|
|
31
35
|
}
|
|
32
36
|
stopLoggingStreamForModel(params.modelId);
|
|
33
|
-
|
|
34
|
-
if (
|
|
37
|
+
const shouldAutoClose = params.autoClose ?? !isBare;
|
|
38
|
+
if (shouldAutoClose &&
|
|
39
|
+
response.hasActiveModels === false &&
|
|
35
40
|
response.hasActiveProviders === false) {
|
|
36
41
|
logger.info("🧹 No models or providers active, automatically closing RPC connection...");
|
|
37
42
|
await close();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type UpscaleClientParams, type UpscaleStats } from "../../schemas/sdcpp-config";
|
|
2
|
+
interface UpscaleResult {
|
|
3
|
+
outputs: Promise<Uint8Array[]>;
|
|
4
|
+
stats: Promise<UpscaleStats | undefined>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Runs standalone ESRGAN upscaling on an arbitrary PNG/JPEG image.
|
|
8
|
+
*
|
|
9
|
+
* The model must have been loaded with `modelType: "diffusion"` and
|
|
10
|
+
* `modelConfig.mode: "upscale"` — calling `upscale()` against a model
|
|
11
|
+
* loaded in default (`mode: "diffusion"`) mode throws
|
|
12
|
+
* `ModelOperationNotSupportedError` upfront.
|
|
13
|
+
*
|
|
14
|
+
* `outputs` always resolves to length 1: `repeats` runs N passes
|
|
15
|
+
* internally and emits a single final image at `source * scale^repeats`
|
|
16
|
+
* dimensions. The `Uint8Array[]` shape reserves headroom for future
|
|
17
|
+
* multi-output variants.
|
|
18
|
+
*
|
|
19
|
+
* @param params - `{ modelId, image, repeats? }`. `image` is raw PNG/JPEG
|
|
20
|
+
* bytes; the client base64-encodes them on the wire.
|
|
21
|
+
* @returns `{ outputs, stats }` — `outputs` resolves to a single-element
|
|
22
|
+
* array containing the final upscaled PNG; `stats` resolves to
|
|
23
|
+
* addon-side stats (load/upscale ms, final width/height, repeats
|
|
24
|
+
* actually executed, etc.).
|
|
25
|
+
* @throws {ModelOperationNotSupportedError} If the model was not loaded
|
|
26
|
+
* with `mode: "upscale"`.
|
|
27
|
+
* @throws {StreamEndedError} If the RPC stream closes without emitting a
|
|
28
|
+
* terminal `done` chunk.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const modelId = await loadModel(REALESRGAN_X4PLUS_ANIME_6B, {
|
|
33
|
+
* modelType: "diffusion",
|
|
34
|
+
* modelConfig: { mode: "upscale", upscaler: { tile_size: 128 } },
|
|
35
|
+
* });
|
|
36
|
+
* const pngBytes = fs.readFileSync("input.png");
|
|
37
|
+
* const { outputs, stats } = upscale({ modelId, image: pngBytes, repeats: 2 });
|
|
38
|
+
* const [upscaledPng] = await outputs;
|
|
39
|
+
* fs.writeFileSync("upscaled.png", upscaledPng);
|
|
40
|
+
* console.log(await stats);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function upscale(params: UpscaleClientParams): UpscaleResult;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=upscale.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { upscaleStreamResponseSchema, } from "../../schemas/sdcpp-config.js";
|
|
2
|
+
import { stream as streamRpc } from "../rpc/rpc-client.js";
|
|
3
|
+
import { decodeBase64, encodeBase64 } from "../../utils/encoding.js";
|
|
4
|
+
import { StreamEndedError } from "../../utils/errors-client.js";
|
|
5
|
+
/**
|
|
6
|
+
* Runs standalone ESRGAN upscaling on an arbitrary PNG/JPEG image.
|
|
7
|
+
*
|
|
8
|
+
* The model must have been loaded with `modelType: "diffusion"` and
|
|
9
|
+
* `modelConfig.mode: "upscale"` — calling `upscale()` against a model
|
|
10
|
+
* loaded in default (`mode: "diffusion"`) mode throws
|
|
11
|
+
* `ModelOperationNotSupportedError` upfront.
|
|
12
|
+
*
|
|
13
|
+
* `outputs` always resolves to length 1: `repeats` runs N passes
|
|
14
|
+
* internally and emits a single final image at `source * scale^repeats`
|
|
15
|
+
* dimensions. The `Uint8Array[]` shape reserves headroom for future
|
|
16
|
+
* multi-output variants.
|
|
17
|
+
*
|
|
18
|
+
* @param params - `{ modelId, image, repeats? }`. `image` is raw PNG/JPEG
|
|
19
|
+
* bytes; the client base64-encodes them on the wire.
|
|
20
|
+
* @returns `{ outputs, stats }` — `outputs` resolves to a single-element
|
|
21
|
+
* array containing the final upscaled PNG; `stats` resolves to
|
|
22
|
+
* addon-side stats (load/upscale ms, final width/height, repeats
|
|
23
|
+
* actually executed, etc.).
|
|
24
|
+
* @throws {ModelOperationNotSupportedError} If the model was not loaded
|
|
25
|
+
* with `mode: "upscale"`.
|
|
26
|
+
* @throws {StreamEndedError} If the RPC stream closes without emitting a
|
|
27
|
+
* terminal `done` chunk.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const modelId = await loadModel(REALESRGAN_X4PLUS_ANIME_6B, {
|
|
32
|
+
* modelType: "diffusion",
|
|
33
|
+
* modelConfig: { mode: "upscale", upscaler: { tile_size: 128 } },
|
|
34
|
+
* });
|
|
35
|
+
* const pngBytes = fs.readFileSync("input.png");
|
|
36
|
+
* const { outputs, stats } = upscale({ modelId, image: pngBytes, repeats: 2 });
|
|
37
|
+
* const [upscaledPng] = await outputs;
|
|
38
|
+
* fs.writeFileSync("upscaled.png", upscaledPng);
|
|
39
|
+
* console.log(await stats);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function upscale(params) {
|
|
43
|
+
const request = {
|
|
44
|
+
modelId: params.modelId,
|
|
45
|
+
image: encodeBase64(params.image),
|
|
46
|
+
...(params.repeats !== undefined && { repeats: params.repeats }),
|
|
47
|
+
type: "upscaleStream",
|
|
48
|
+
};
|
|
49
|
+
let statsResolver = () => { };
|
|
50
|
+
let statsRejecter = () => { };
|
|
51
|
+
const statsPromise = new Promise((resolve, reject) => {
|
|
52
|
+
statsResolver = resolve;
|
|
53
|
+
statsRejecter = reject;
|
|
54
|
+
});
|
|
55
|
+
statsPromise.catch(() => { });
|
|
56
|
+
let outputsResolver = () => { };
|
|
57
|
+
let outputsRejecter = () => { };
|
|
58
|
+
const outputsPromise = new Promise((resolve, reject) => {
|
|
59
|
+
outputsResolver = resolve;
|
|
60
|
+
outputsRejecter = reject;
|
|
61
|
+
});
|
|
62
|
+
outputsPromise.catch(() => { });
|
|
63
|
+
const collectedBuffers = [];
|
|
64
|
+
async function processResponses() {
|
|
65
|
+
let sawDone = false;
|
|
66
|
+
try {
|
|
67
|
+
for await (const response of streamRpc(request)) {
|
|
68
|
+
if (response &&
|
|
69
|
+
typeof response === "object" &&
|
|
70
|
+
"type" in response &&
|
|
71
|
+
response.type === "upscaleStream") {
|
|
72
|
+
const parsed = upscaleStreamResponseSchema.parse(response);
|
|
73
|
+
if (parsed.data) {
|
|
74
|
+
collectedBuffers.push(decodeBase64(parsed.data));
|
|
75
|
+
}
|
|
76
|
+
if (parsed.done) {
|
|
77
|
+
sawDone = true;
|
|
78
|
+
statsResolver(parsed.stats);
|
|
79
|
+
outputsResolver(collectedBuffers);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!sawDone) {
|
|
84
|
+
const error = new StreamEndedError();
|
|
85
|
+
statsRejecter(error);
|
|
86
|
+
outputsRejecter(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
statsRejecter(error);
|
|
91
|
+
outputsRejecter(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
void processResponses();
|
|
95
|
+
return {
|
|
96
|
+
outputs: outputsPromise,
|
|
97
|
+
stats: statsPromise,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -195,6 +195,7 @@ export async function createDuplexSession(payload, commandId) {
|
|
|
195
195
|
const req = rpc.request(commandId);
|
|
196
196
|
const requestStream = req.createRequestStream();
|
|
197
197
|
const responseStream = req.createResponseStream({ encoding: "utf-8" });
|
|
198
|
-
|
|
198
|
+
// Pre-encode payload — RN/Hermes lacks a global `Buffer`, so `write(string, "utf-8")` would throw.
|
|
199
|
+
requestStream.write(new TextEncoder().encode(payload));
|
|
199
200
|
return { requestStream, responseStream };
|
|
200
201
|
}
|
|
@@ -13,8 +13,6 @@ import { getClientLogger } from "../../logging/index.js";
|
|
|
13
13
|
import { RPCInitTimeoutError } from "../../utils/errors-client.js";
|
|
14
14
|
const RPC_INIT_TIMEOUT_MS = 30_000;
|
|
15
15
|
const logger = getClientLogger();
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = path.dirname(__filename);
|
|
18
16
|
let rpcInstance = null;
|
|
19
17
|
let rpcPromise = null;
|
|
20
18
|
let bareWorkerProc = null;
|
|
@@ -56,14 +54,27 @@ function resolvePackagedWorkerPath() {
|
|
|
56
54
|
return undefined;
|
|
57
55
|
}
|
|
58
56
|
/**
|
|
59
|
-
*
|
|
57
|
+
* Resolve the SDK's default worker entry via bundler-visible asset references.
|
|
58
|
+
*
|
|
59
|
+
* `path.resolve(__dirname, ...)` is invisible to static analysis, so packaged
|
|
60
|
+
* consumers ship without worker.js. We use `import.meta.asset(<literal>)` on
|
|
61
|
+
* Bare (detected by bare-module-lexer) and fall back to
|
|
62
|
+
* `new URL(<literal>, import.meta.url)` elsewhere. Specs must be string
|
|
63
|
+
* literals at the call site.
|
|
60
64
|
*/
|
|
61
65
|
function getDefaultWorkerPath() {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
const hasAsset = typeof import.meta.asset === "function";
|
|
67
|
+
const packagedUrl = hasAsset
|
|
68
|
+
? new URL(import.meta.asset("../../server/worker.js"))
|
|
69
|
+
: new URL("../../server/worker.js", import.meta.url);
|
|
70
|
+
const packaged = fileURLToPath(packagedUrl);
|
|
71
|
+
if (fs.existsSync(packaged))
|
|
72
|
+
return packaged;
|
|
65
73
|
// Dev/source layout fallback
|
|
66
|
-
|
|
74
|
+
const devUrl = hasAsset
|
|
75
|
+
? new URL(import.meta.asset("../../dist/server/worker.js"))
|
|
76
|
+
: new URL("../../dist/server/worker.js", import.meta.url);
|
|
77
|
+
return fileURLToPath(devUrl);
|
|
67
78
|
}
|
|
68
79
|
/**
|
|
69
80
|
* Resolve worker path with priority:
|
|
@@ -3,11 +3,11 @@ import { type Request, type Response, type RPCOptions } from "../../schemas/inde
|
|
|
3
3
|
export declare function send<T extends Request>(request: T, options?: RPCOptions, rpc?: RPC): Promise<Response>;
|
|
4
4
|
export declare function stream<T extends Request>(request: T, options?: RPCOptions, rpc?: RPC): AsyncGenerator<Response>;
|
|
5
5
|
export interface DuplexWritable {
|
|
6
|
-
write(chunk:
|
|
6
|
+
write(chunk: Uint8Array): void;
|
|
7
7
|
end(): void;
|
|
8
8
|
destroy(): void;
|
|
9
9
|
}
|
|
10
|
-
export interface DuplexReadable extends AsyncIterable<Buffer> {
|
|
10
|
+
export interface DuplexReadable extends AsyncIterable<Buffer | string> {
|
|
11
11
|
destroy(): void;
|
|
12
12
|
}
|
|
13
13
|
export interface DuplexSession {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { requestSchema, responseSchema, PROFILING_TRAILER_KEY, } from "../../schemas/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { reconstructError } from "./rpc-error.js";
|
|
3
3
|
import { withTimeout, withTimeoutStream } from "../../utils/withTimeout.js";
|
|
4
4
|
import { getClientLogger, summarizeRequest } from "../../logging/index.js";
|
|
5
5
|
import { getRPC, close as closeRPC, createDuplexSession } from "#rpc";
|
|
@@ -15,7 +15,12 @@ function getNextCommandId() {
|
|
|
15
15
|
}
|
|
16
16
|
function checkAndThrowError(response) {
|
|
17
17
|
if (response.type === "error") {
|
|
18
|
-
|
|
18
|
+
// Use the typed-error reconstructor map in `rpc-error.ts` so the
|
|
19
|
+
// original class (e.g. `RequestRejectedByPolicyError`) survives
|
|
20
|
+
// the RPC boundary intact and `err instanceof <Class>` works in
|
|
21
|
+
// consumer `catch` blocks. Unknown error names fall back to a
|
|
22
|
+
// plain `RPCError` wrapper inside `reconstructError`.
|
|
23
|
+
throw reconstructError(response);
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
async function getRPCInstance() {
|
|
@@ -311,12 +316,13 @@ async function duplexProfiled(request, options = {}) {
|
|
|
311
316
|
}
|
|
312
317
|
if (outputParts.length > 0) {
|
|
313
318
|
timings.chunkCount += outputParts.length;
|
|
314
|
-
|
|
319
|
+
// Yield strings (not Buffer) — RN/Hermes has no global `Buffer`; consumers call `.toString()` either way.
|
|
320
|
+
yield outputParts.join("\n") + "\n";
|
|
315
321
|
}
|
|
316
322
|
}
|
|
317
323
|
if (lineBuffer.trim()) {
|
|
318
324
|
timings.chunkCount++;
|
|
319
|
-
yield
|
|
325
|
+
yield lineBuffer + "\n";
|
|
320
326
|
}
|
|
321
327
|
}
|
|
322
328
|
catch (error) {
|
|
@@ -16,4 +16,13 @@ export declare class RPCError extends Error {
|
|
|
16
16
|
isQvacError: boolean;
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Rebuild the original server-thrown typed error from its serialised
|
|
21
|
+
* envelope so consumer code can do
|
|
22
|
+
* `if (err instanceof RequestRejectedByPolicyError) { ... }` across
|
|
23
|
+
* the RPC boundary. Unknown error names fall through to the legacy
|
|
24
|
+
* `RPCError` wrapper, which preserves `name`/`code`/`message` for
|
|
25
|
+
* code-based predicates.
|
|
26
|
+
*/
|
|
27
|
+
export declare function reconstructError(response: ErrorResponse): Error;
|
|
19
28
|
//# sourceMappingURL=rpc-error.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RequestIdConflictError, RequestNotFoundError, RequestRejectedByPolicyError, } from "../../utils/errors-server.js";
|
|
1
2
|
export class RPCError extends Error {
|
|
2
3
|
timestamp;
|
|
3
4
|
remoteStack;
|
|
@@ -37,3 +38,94 @@ export class RPCError extends Error {
|
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Attach the remote stack onto a reconstructed typed error so the
|
|
43
|
+
* client-side trace points at the consumer call site and the worker-
|
|
44
|
+
* side trace is preserved for debugging. Mirrors the behaviour
|
|
45
|
+
* `RPCError`'s constructor applies in the fall-through path.
|
|
46
|
+
*/
|
|
47
|
+
function attachRemoteContext(err, response) {
|
|
48
|
+
if (response.stack) {
|
|
49
|
+
err.remoteStack = response.stack;
|
|
50
|
+
err.stack = `${err.stack}\n--- Worker Stack ---\n${response.stack}`;
|
|
51
|
+
}
|
|
52
|
+
if (response.timestamp) {
|
|
53
|
+
err.timestamp = response.timestamp;
|
|
54
|
+
}
|
|
55
|
+
return err;
|
|
56
|
+
}
|
|
57
|
+
/** Read a string field from the `typedFields` envelope, defaulting to `fallback` if missing or non-string. */
|
|
58
|
+
function readStringField(fields, key, fallback) {
|
|
59
|
+
const value = fields?.[key];
|
|
60
|
+
return typeof value === "string" ? value : fallback;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Map of server-thrown `QvacErrorBase` subclasses that need to survive
|
|
64
|
+
* the RPC boundary as their original class — `err instanceof
|
|
65
|
+
* RequestRejectedByPolicyError` on the consumer side must match the
|
|
66
|
+
* class re-exported from `@qvac/sdk`.
|
|
67
|
+
*
|
|
68
|
+
* **Key shape.** The key is the `name` value `QvacErrorBase` sets at
|
|
69
|
+
* construction time, which `createErrorResponse` forwards onto
|
|
70
|
+
* `response.name`. That value is the SCREAMING_SNAKE_CASE error-code
|
|
71
|
+
* name from `sdk-errors-server.ts` (`"REQUEST_REJECTED_BY_POLICY"`),
|
|
72
|
+
* **not** the JS class name (`"RequestRejectedByPolicyError"`). The
|
|
73
|
+
* mismatch was a 0.11.0 bring-up bug fixed alongside the reconstructor
|
|
74
|
+
* unit tests in `rpc-error-reconstruct.test.ts`.
|
|
75
|
+
*
|
|
76
|
+
* **Maintenance contract.** Every new cross-RPC server-thrown typed
|
|
77
|
+
* error class added in `errors-server.ts` adds a row here in the same
|
|
78
|
+
* PR, keyed by its `SDK_SERVER_ERROR_CODES.<NAME>` constant. The
|
|
79
|
+
* class must (a) implement `toErrorResponseFields(): Record<…>` so
|
|
80
|
+
* the wire envelope carries its named constructor arguments and (b)
|
|
81
|
+
* be re-exported from `@qvac/sdk` so consumers can `import` it.
|
|
82
|
+
* Forgetting either side means `instanceof` regresses for that class.
|
|
83
|
+
*
|
|
84
|
+
* Client-constructed typed errors (e.g. `InferenceCancelledError`
|
|
85
|
+
* built in `client/api/completion-stream.ts` from the aggregated
|
|
86
|
+
* partial state) are NOT registered here — they never round-trip the
|
|
87
|
+
* envelope, and adding a reconstructor for one would create a
|
|
88
|
+
* parallel construction path that fires whenever the server happens
|
|
89
|
+
* to throw the same class name.
|
|
90
|
+
*/
|
|
91
|
+
const RECONSTRUCTORS = {
|
|
92
|
+
REQUEST_ID_CONFLICT: (response) => {
|
|
93
|
+
return new RequestIdConflictError(readStringField(response.typedFields, "requestId", ""), response.cause);
|
|
94
|
+
},
|
|
95
|
+
REQUEST_NOT_FOUND: (response) => {
|
|
96
|
+
return new RequestNotFoundError(readStringField(response.typedFields, "requestId", ""), response.cause);
|
|
97
|
+
},
|
|
98
|
+
REQUEST_REJECTED_BY_POLICY: (response) => {
|
|
99
|
+
return new RequestRejectedByPolicyError(readStringField(response.typedFields, "requestId", ""), readStringField(response.typedFields, "kind", ""), readStringField(response.typedFields, "modelId", ""), readStringField(response.typedFields, "reason", response.message), response.cause);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Rebuild the original server-thrown typed error from its serialised
|
|
104
|
+
* envelope so consumer code can do
|
|
105
|
+
* `if (err instanceof RequestRejectedByPolicyError) { ... }` across
|
|
106
|
+
* the RPC boundary. Unknown error names fall through to the legacy
|
|
107
|
+
* `RPCError` wrapper, which preserves `name`/`code`/`message` for
|
|
108
|
+
* code-based predicates.
|
|
109
|
+
*/
|
|
110
|
+
export function reconstructError(response) {
|
|
111
|
+
const reconstructor = response.name
|
|
112
|
+
? RECONSTRUCTORS[response.name]
|
|
113
|
+
: undefined;
|
|
114
|
+
if (!reconstructor) {
|
|
115
|
+
return new RPCError(response);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return attachRemoteContext(reconstructor(response), response);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Defensive fall-through: if a reconstructor throws (e.g. a
|
|
122
|
+
// future class adds a required constructor field and an older
|
|
123
|
+
// server doesn't ship `typedFields`), surface the original error
|
|
124
|
+
// via `RPCError` so consumers never see "couldn't reconstruct
|
|
125
|
+
// error" obscuring the real one. The reconstructors here are
|
|
126
|
+
// intentionally written to coerce missing fields to defensible
|
|
127
|
+
// defaults (`String(x ?? "")`), so reaching this branch is the
|
|
128
|
+
// edge case, not the norm.
|
|
129
|
+
return new RPCError(response);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cancel a specific in-flight completion by `requestId`.
|
|
3
|
+
*
|
|
4
|
+
* `completion(...)` exposes a stable `requestId` (UUIDv4, generated
|
|
5
|
+
* client-side) on the returned `CompletionRun`. Pass it to
|
|
6
|
+
* `cancel({ requestId })` to abort that exact run without affecting any
|
|
7
|
+
* other inference happening on the same model.
|
|
8
|
+
*
|
|
9
|
+
* Two cancel paths exist:
|
|
10
|
+
*
|
|
11
|
+
* 1. `cancel({ requestId })` — targeted cancel, the primary path
|
|
12
|
+
* introduced in 0.11.0. The `requestId` is available synchronously
|
|
13
|
+
* on the `CompletionRun`. Same-tick cancels (issued before the
|
|
14
|
+
* server has registered the request) are recorded and applied
|
|
15
|
+
* retroactively when `begin(...)` arrives, so they aren't silently
|
|
16
|
+
* dropped.
|
|
17
|
+
* 2. `cancel({ operation: "inference", modelId })` — broad cancel
|
|
18
|
+
* (escape hatch, kept indefinitely). Cancels every inference running
|
|
19
|
+
* on the model. Useful for unload, app shutdown, admin sweeps when
|
|
20
|
+
* the caller doesn't have a `requestId` to hand.
|
|
21
|
+
*
|
|
22
|
+
* --- Cancel outcomes (0.11.0+) ---
|
|
23
|
+
*
|
|
24
|
+
* A cancel surfaces on two channels:
|
|
25
|
+
*
|
|
26
|
+
* - `run.events` ends *normally* with a `completionDone` event carrying
|
|
27
|
+
* `stopReason: "cancelled"`. The loop exits cleanly, no thrown error.
|
|
28
|
+
* - `run.text` / `run.final` / `run.stats` / `run.toolCalls` reject
|
|
29
|
+
* with `InferenceCancelledError(requestId, partial)`, where `partial`
|
|
30
|
+
* holds whatever the model produced before the cancel landed
|
|
31
|
+
* (accumulated `text`, completed `toolCalls`, last-known `stats`).
|
|
32
|
+
*
|
|
33
|
+
* Pick the channel that matches how you consume the run: event-loop
|
|
34
|
+
* consumers don't need to catch anything; promise-aggregate consumers
|
|
35
|
+
* pattern-match on `instanceof InferenceCancelledError`.
|
|
36
|
+
*/
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=cancel-by-request-id.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cancel a specific in-flight completion by `requestId`.
|
|
3
|
+
*
|
|
4
|
+
* `completion(...)` exposes a stable `requestId` (UUIDv4, generated
|
|
5
|
+
* client-side) on the returned `CompletionRun`. Pass it to
|
|
6
|
+
* `cancel({ requestId })` to abort that exact run without affecting any
|
|
7
|
+
* other inference happening on the same model.
|
|
8
|
+
*
|
|
9
|
+
* Two cancel paths exist:
|
|
10
|
+
*
|
|
11
|
+
* 1. `cancel({ requestId })` — targeted cancel, the primary path
|
|
12
|
+
* introduced in 0.11.0. The `requestId` is available synchronously
|
|
13
|
+
* on the `CompletionRun`. Same-tick cancels (issued before the
|
|
14
|
+
* server has registered the request) are recorded and applied
|
|
15
|
+
* retroactively when `begin(...)` arrives, so they aren't silently
|
|
16
|
+
* dropped.
|
|
17
|
+
* 2. `cancel({ operation: "inference", modelId })` — broad cancel
|
|
18
|
+
* (escape hatch, kept indefinitely). Cancels every inference running
|
|
19
|
+
* on the model. Useful for unload, app shutdown, admin sweeps when
|
|
20
|
+
* the caller doesn't have a `requestId` to hand.
|
|
21
|
+
*
|
|
22
|
+
* --- Cancel outcomes (0.11.0+) ---
|
|
23
|
+
*
|
|
24
|
+
* A cancel surfaces on two channels:
|
|
25
|
+
*
|
|
26
|
+
* - `run.events` ends *normally* with a `completionDone` event carrying
|
|
27
|
+
* `stopReason: "cancelled"`. The loop exits cleanly, no thrown error.
|
|
28
|
+
* - `run.text` / `run.final` / `run.stats` / `run.toolCalls` reject
|
|
29
|
+
* with `InferenceCancelledError(requestId, partial)`, where `partial`
|
|
30
|
+
* holds whatever the model produced before the cancel landed
|
|
31
|
+
* (accumulated `text`, completed `toolCalls`, last-known `stats`).
|
|
32
|
+
*
|
|
33
|
+
* Pick the channel that matches how you consume the run: event-loop
|
|
34
|
+
* consumers don't need to catch anything; promise-aggregate consumers
|
|
35
|
+
* pattern-match on `instanceof InferenceCancelledError`.
|
|
36
|
+
*/
|
|
37
|
+
import { cancel, completion, loadModel, unloadModel, InferenceCancelledError, QWEN3_600M_INST_Q4, } from "@qvac/sdk";
|
|
38
|
+
try {
|
|
39
|
+
const modelId = await loadModel({
|
|
40
|
+
modelSrc: QWEN3_600M_INST_Q4,
|
|
41
|
+
modelType: "llm",
|
|
42
|
+
modelConfig: { ctx_size: 4096 },
|
|
43
|
+
});
|
|
44
|
+
const run = completion({
|
|
45
|
+
modelId,
|
|
46
|
+
history: [
|
|
47
|
+
{
|
|
48
|
+
role: "user",
|
|
49
|
+
content: "Write a long, detailed essay about the history of the Roman Empire.",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
stream: true,
|
|
53
|
+
});
|
|
54
|
+
console.log(`requestId: ${run.requestId}`);
|
|
55
|
+
// Cancel after a short delay so we exercise the cancel-mid-decode path.
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
void cancel({ requestId: run.requestId });
|
|
58
|
+
console.log("(cancel issued)");
|
|
59
|
+
}, 250);
|
|
60
|
+
// Channel 1: the events stream ends normally on cancel. The
|
|
61
|
+
// `completionDone` event's `stopReason` tells you why the loop is
|
|
62
|
+
// about to exit ("eos" | "length" | "cancelled" | "error" | ...).
|
|
63
|
+
let tokenCount = 0;
|
|
64
|
+
let endReason;
|
|
65
|
+
for await (const event of run.events) {
|
|
66
|
+
if (event.type === "contentDelta") {
|
|
67
|
+
tokenCount++;
|
|
68
|
+
process.stdout.write(event.text);
|
|
69
|
+
}
|
|
70
|
+
else if (event.type === "completionDone") {
|
|
71
|
+
endReason = event.stopReason;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log(`\n\nstreamed ${tokenCount} content deltas, stopReason=${endReason}.`);
|
|
75
|
+
// Channel 2: promise-aggregates reject with InferenceCancelledError
|
|
76
|
+
// on cancel. The accumulated state up to the cancel point is preserved
|
|
77
|
+
// on `err.partial`.
|
|
78
|
+
try {
|
|
79
|
+
const text = await run.text;
|
|
80
|
+
console.log(`completed normally (${text.length} chars).`);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (err instanceof InferenceCancelledError) {
|
|
84
|
+
console.log(`run.text rejected: cancelled (requestId=${err.requestId})`);
|
|
85
|
+
console.log(`partial text length: ${(err.partial.text ?? "").length}`);
|
|
86
|
+
if (err.partial.stats?.tokensPerSecond !== undefined) {
|
|
87
|
+
console.log(`partial stats: ${err.partial.stats.tokensPerSecond.toFixed(1)} tok/s`);
|
|
88
|
+
}
|
|
89
|
+
if (err.partial.toolCalls && err.partial.toolCalls.length > 0) {
|
|
90
|
+
console.log(`partial tool calls: ${err.partial.toolCalls.length}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
await unloadModel({ modelId });
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error("Error:", error);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|