@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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { TOOLS_MODE } from "../../../../../schemas/tools.js";
|
|
2
|
-
import { logCacheDisabled, logCacheInit, logCacheSave,
|
|
3
|
-
import {
|
|
2
|
+
import { logCacheDisabled, logCacheInit, logCacheSave, logMessagesToAddon, } from "./cache-logger.js";
|
|
3
|
+
import { extractSystemPrompt, getCurrentCacheInfo, } from "../../../ops/kv-cache-utils.js";
|
|
4
4
|
import { getModel, getModelConfig, } from "../../../registry/model-registry.js";
|
|
5
|
-
import {
|
|
5
|
+
import { decideCachedHistorySlice } from "./kv-cache-state.js";
|
|
6
|
+
import { createKvCacheSession, generateConfigHash, } from "./kv-cache-session.js";
|
|
6
7
|
import { appendToolsToHistory, detectToolDialect, prependToolsToHistory, } from "../../../../utils/tool-integration.js";
|
|
7
8
|
import { parseToolCalls } from "../../../../utils/tools/index.js";
|
|
8
9
|
import { getResponseFormatJsonSchema } from "../../../../utils/response-format.js";
|
|
@@ -11,37 +12,23 @@ import { getServerLogger } from "../../../../../logging/index.js";
|
|
|
11
12
|
import { AttachmentNotFoundError } from "../../../../../utils/errors-server.js";
|
|
12
13
|
import { nowMs } from "../../../../../profiling/index.js";
|
|
13
14
|
import { buildStreamResult, hasDefinedValues, } from "../../../../../profiling/model-execution.js";
|
|
14
|
-
import fs
|
|
15
|
-
import path from "bare-path";
|
|
15
|
+
import fs from "bare-fs";
|
|
16
16
|
const logger = getServerLogger();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// drop the `access()` probe and wrap the `model.run()` call in a real
|
|
33
|
-
// try/catch that forwards the error to `logCacheSaveError`.
|
|
34
|
-
async function recordCacheSaveCount(cachePath, messageCount) {
|
|
35
|
-
try {
|
|
36
|
-
await fsPromises.access(cachePath);
|
|
37
|
-
cachedMessageCounts.set(cachePath, messageCount);
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
cachedMessageCounts.delete(cachePath);
|
|
42
|
-
logCacheSaveError(cachePath, err);
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
17
|
+
/**
|
|
18
|
+
* Decide whether a completed turn earned the right to record its kv-cache
|
|
19
|
+
* boundary. A `savedCount` is only safe to write when the turn ran to
|
|
20
|
+
* completion AND produced at least one token — anything else (cancelled
|
|
21
|
+
* mid-decode, zero-token reply, early EOS) leaves the on-disk cache file
|
|
22
|
+
* in an unknown state relative to `history.length + 1`, and a stale entry
|
|
23
|
+
* would slice the next turn's history down to an empty payload.
|
|
24
|
+
*
|
|
25
|
+
* Replaces the pre-0.11.0 `shouldRecordSavedCount(wasCancelled, ...)` with
|
|
26
|
+
* a signal-driven check that reads directly from the request's
|
|
27
|
+
* `AbortSignal`. The local helper keeps the call sites in
|
|
28
|
+
* `completion-stream.ts` honest without importing the registry every time.
|
|
29
|
+
*/
|
|
30
|
+
function shouldCommitTurn(signal, producedTokens) {
|
|
31
|
+
return !signal.aborted && producedTokens;
|
|
45
32
|
}
|
|
46
33
|
function transformMessage(message) {
|
|
47
34
|
const transformed = [];
|
|
@@ -100,9 +87,7 @@ async function initSystemPromptCache(model, cachePathToUse, systemPromptToUse, c
|
|
|
100
87
|
const primeResponse = await runModel(model, primeMessages, {
|
|
101
88
|
cacheKey: cachePathToUse,
|
|
102
89
|
saveCacheToDisk: true,
|
|
103
|
-
|
|
104
|
-
primeResponse.once("output", () => {
|
|
105
|
-
void primeResponse.cancel();
|
|
90
|
+
prefill: true,
|
|
106
91
|
});
|
|
107
92
|
await primeResponse.await();
|
|
108
93
|
}
|
|
@@ -117,7 +102,8 @@ async function initSystemPromptCache(model, cachePathToUse, systemPromptToUse, c
|
|
|
117
102
|
* consumer pushing both an assistant transcript and a follow-up user
|
|
118
103
|
* message between completions) all reaches the model.
|
|
119
104
|
* - Cache hit with a stale/missing `savedCount`: fall back to the full
|
|
120
|
-
* non-system history.
|
|
105
|
+
* non-system history. The session is told (`dropStaleSavedCount`) so
|
|
106
|
+
* the bad boundary doesn't propagate into the next turn.
|
|
121
107
|
*
|
|
122
108
|
* Dynamic mode (`tools` argument set):
|
|
123
109
|
* - The addon anchors the tool block after the last user message and
|
|
@@ -133,7 +119,7 @@ async function initSystemPromptCache(model, cachePathToUse, systemPromptToUse, c
|
|
|
133
119
|
* the new prompt, then re-anchor the tool block.
|
|
134
120
|
* * otherwise: send just the last message + tool block.
|
|
135
121
|
*/
|
|
136
|
-
function prepareMessagesForCache(
|
|
122
|
+
function prepareMessagesForCache(session, turn, cacheExists, history, tools) {
|
|
137
123
|
const addTools = tools?.length ? transformMessages(tools) : [];
|
|
138
124
|
const dynamic = addTools.length > 0;
|
|
139
125
|
if (!(cacheExists && history.length > 0)) {
|
|
@@ -141,16 +127,18 @@ function prepareMessagesForCache(cachePathToUse, cacheExists, history, tools) {
|
|
|
141
127
|
return [...transformMessages(historyWithoutSystem), ...addTools];
|
|
142
128
|
}
|
|
143
129
|
if (!dynamic) {
|
|
144
|
-
// Static path — slice from the
|
|
130
|
+
// Static path — slice from the turn's `savedCount` so callers can
|
|
145
131
|
// stage multiple messages between completions. `decideCachedHistorySlice`
|
|
146
132
|
// also guards against the QVAC-17780 stale-count regression: if the
|
|
147
133
|
// saved boundary would slice the history down to an empty payload
|
|
148
134
|
// (e.g. after a cancelled mid-decode), it falls back to the full
|
|
149
135
|
// non-system history and signals the caller to drop the bad entry.
|
|
150
|
-
|
|
151
|
-
|
|
136
|
+
// The session owns the entry; `dropStaleSavedCount` clears it
|
|
137
|
+
// without touching the on-disk file (the file is still trustworthy
|
|
138
|
+
// — only the boundary count is wrong).
|
|
139
|
+
const { messages, clearStaleCount } = decideCachedHistorySlice(turn.savedCount, cacheExists, history);
|
|
152
140
|
if (clearStaleCount) {
|
|
153
|
-
|
|
141
|
+
session.dropStaleSavedCount(turn);
|
|
154
142
|
}
|
|
155
143
|
return transformMessages(messages);
|
|
156
144
|
}
|
|
@@ -231,8 +219,10 @@ async function* processModelResponse(model, messagesToSend, tools, generationPar
|
|
|
231
219
|
producedTokens,
|
|
232
220
|
};
|
|
233
221
|
}
|
|
234
|
-
export async function* completion(params) {
|
|
222
|
+
export async function* completion(params, opts) {
|
|
235
223
|
const { history, modelId, kvCache, tools, generationParams, responseFormat } = params;
|
|
224
|
+
const { signal, scope } = opts;
|
|
225
|
+
const requestLogger = opts.logger ?? logger;
|
|
236
226
|
const modelConfig = getModelConfig(modelId);
|
|
237
227
|
const toolsEnabled = modelConfig.tools === true;
|
|
238
228
|
const toolsMode = modelConfig.toolsMode;
|
|
@@ -259,136 +249,45 @@ export async function* completion(params) {
|
|
|
259
249
|
}
|
|
260
250
|
}
|
|
261
251
|
const model = getModel(modelId);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// to a per-turn anchor and must not enter the system cache.
|
|
281
|
-
staticTools ? tools : undefined);
|
|
282
|
-
markCacheInitialized(modelId, configHash, kvCache);
|
|
283
|
-
cacheExists = true;
|
|
284
|
-
}
|
|
285
|
-
const messagesToSend = prepareMessagesForCache(cachePathToUse, cacheExists, history, dynamicTools ? tools : undefined);
|
|
286
|
-
logMessagesToAddon(messagesToSend, "PROMPT_SEND");
|
|
287
|
-
const cancelCountBefore = snapshotCancelCount(modelId);
|
|
288
|
-
const result = yield* processModelResponse(model, messagesToSend, tools, mergedGenerationParams, { cacheKey: cachePathToUse, saveCacheToDisk: true }, dialect);
|
|
289
|
-
const wasCancelled = snapshotCancelCount(modelId) > cancelCountBefore;
|
|
290
|
-
if (shouldRecordSavedCount(wasCancelled, result.producedTokens)) {
|
|
291
|
-
// Turn ran to completion and produced content — record the new
|
|
292
|
-
// boundary so the next turn can slice its history.
|
|
293
|
-
await recordCacheSaveCount(cachePathToUse, history.length + 1);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
// The addon writes the cache file unconditionally on
|
|
297
|
-
// `saveCacheToDisk` turns, including cancellations and zero-token
|
|
298
|
-
// exits, so what's left on disk holds partial decode state that
|
|
299
|
-
// does not correspond to a clean turn boundary. Mirror the
|
|
300
|
-
// auto-key handling: drop the file, clear the in-memory init
|
|
301
|
-
// flag (otherwise `customCacheExists` would still report true),
|
|
302
|
-
// and forget the saved count. Next turn re-primes the system
|
|
303
|
-
// prompt cleanly — a one-turn perf hit, but no risk of the
|
|
304
|
-
// addon loading the stale KV state.
|
|
305
|
-
try {
|
|
306
|
-
await fsPromises.unlink(cachePathToUse);
|
|
307
|
-
}
|
|
308
|
-
catch (unlinkError) {
|
|
309
|
-
logger.warn(`[kv-cache] Failed to remove cache file after cancelled or empty custom-key turn; next turn may load stale KV state. path=${cachePathToUse} error=${unlinkError instanceof Error ? unlinkError.message : String(unlinkError)}`);
|
|
310
|
-
}
|
|
311
|
-
clearCacheRegistry({ cacheKey: kvCache, modelId });
|
|
312
|
-
cachedMessageCounts.delete(cachePathToUse);
|
|
313
|
-
}
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
// Auto-generate cache key based on conversation history
|
|
318
|
-
const cacheMessages = history.map((msg) => ({
|
|
319
|
-
role: msg.role,
|
|
320
|
-
content: msg.content,
|
|
321
|
-
attachments: msg.attachments ?? undefined,
|
|
322
|
-
}));
|
|
323
|
-
const existingCache = await findMatchingCache(modelId, configHash, cacheMessages);
|
|
324
|
-
const preResponseCacheInfo = await getCurrentCacheInfo(modelId, configHash, cacheMessages);
|
|
325
|
-
cachePathToUse =
|
|
326
|
-
existingCache !== null
|
|
327
|
-
? existingCache.cachePath
|
|
328
|
-
: preResponseCacheInfo.cachePath;
|
|
329
|
-
let cacheExists = existingCache !== null;
|
|
330
|
-
logCacheStatus("auto", cacheExists);
|
|
331
|
-
if (!cacheExists) {
|
|
332
|
-
await initSystemPromptCache(model, cachePathToUse, systemPromptToUse, "auto", staticTools ? tools : undefined);
|
|
333
|
-
markCacheInitialized(modelId, configHash, preResponseCacheInfo.cacheKey);
|
|
334
|
-
cacheExists = true;
|
|
335
|
-
}
|
|
336
|
-
const messagesToSend = prepareMessagesForCache(cachePathToUse, cacheExists, history, dynamicTools ? tools : undefined);
|
|
337
|
-
logMessagesToAddon(messagesToSend, "PROMPT_SEND");
|
|
338
|
-
const cancelCountBefore = snapshotCancelCount(modelId);
|
|
339
|
-
const result = yield* processModelResponse(model, messagesToSend, tools, mergedGenerationParams, { cacheKey: cachePathToUse, saveCacheToDisk: true }, dialect);
|
|
340
|
-
const wasCancelled = snapshotCancelCount(modelId) > cancelCountBefore;
|
|
341
|
-
// TODO: support auto-cache for tool-call turns by keying off the
|
|
342
|
-
// structured assistant/tool messages callers push into history,
|
|
343
|
-
// not result.responseText (which is raw tool-call markup here).
|
|
344
|
-
// Until then, remove any cache file the addon wrote so it doesn't
|
|
345
|
-
// leak on disk (the next turn would compute a different key and
|
|
346
|
-
// never reach it).
|
|
347
|
-
if (result.toolCalls.length > 0) {
|
|
348
|
-
logger.warn(`[kv-cache] Auto cache tool-call turn; removing orphaned cache to avoid disk leak. path=${cachePathToUse}`);
|
|
349
|
-
try {
|
|
350
|
-
await fsPromises.unlink(cachePathToUse);
|
|
351
|
-
}
|
|
352
|
-
catch (unlinkError) {
|
|
353
|
-
logger.warn(`[kv-cache] Failed to remove orphaned tool-turn cache file; disk leak likely. path=${cachePathToUse} error=${unlinkError instanceof Error ? unlinkError.message : String(unlinkError)}`);
|
|
354
|
-
}
|
|
355
|
-
cachedMessageCounts.delete(cachePathToUse);
|
|
356
|
-
return result;
|
|
357
|
-
}
|
|
358
|
-
// A cancelled or zero-token turn cannot be promoted to a post-response
|
|
359
|
-
// cache: the post-response key is derived from `result.responseText`,
|
|
360
|
-
// which is empty/partial in those cases, and the on-disk cache the
|
|
361
|
-
// addon wrote is not aligned with the current-history hash. Treat it
|
|
362
|
-
// like the tool-call branch — drop the cache file and clear the count.
|
|
363
|
-
if (!shouldRecordSavedCount(wasCancelled, result.producedTokens)) {
|
|
364
|
-
try {
|
|
365
|
-
await fsPromises.unlink(cachePathToUse);
|
|
366
|
-
}
|
|
367
|
-
catch (unlinkError) {
|
|
368
|
-
logger.warn(`[kv-cache] Failed to remove cache file after cancelled or empty turn; disk leak possible. path=${cachePathToUse} error=${unlinkError instanceof Error ? unlinkError.message : String(unlinkError)}`);
|
|
369
|
-
}
|
|
370
|
-
cachedMessageCounts.delete(cachePathToUse);
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
const savedHistory = buildAutoCacheSaveHistory(cacheMessages, result.responseText);
|
|
374
|
-
const postResponseCacheInfo = await getCurrentCacheInfo(modelId, configHash, savedHistory);
|
|
375
|
-
if (!(await renameCacheFile(cachePathToUse, postResponseCacheInfo.cachePath))) {
|
|
376
|
-
logger.warn(`[kv-cache] Auto cache rename failed; removing stale cache to avoid disk leak. from=${cachePathToUse} to=${postResponseCacheInfo.cachePath}`);
|
|
377
|
-
try {
|
|
378
|
-
await fsPromises.unlink(cachePathToUse);
|
|
379
|
-
}
|
|
380
|
-
catch (unlinkError) {
|
|
381
|
-
logger.warn(`[kv-cache] Failed to remove stale cache file; disk leak likely. path=${cachePathToUse} error=${unlinkError instanceof Error ? unlinkError.message : String(unlinkError)}`);
|
|
382
|
-
}
|
|
383
|
-
cachedMessageCounts.delete(cachePathToUse);
|
|
384
|
-
return result;
|
|
385
|
-
}
|
|
386
|
-
cachedMessageCounts.delete(cachePathToUse);
|
|
387
|
-
await recordCacheSaveCount(postResponseCacheInfo.cachePath, savedHistory.length);
|
|
388
|
-
return result;
|
|
252
|
+
// Hard-cancel wiring: when the registry aborts the request's signal,
|
|
253
|
+
// forward to the addon so the C++ work stops as soon as it can. The
|
|
254
|
+
// SDK still treats `signal.aborted` as the truth for cancel detection
|
|
255
|
+
// (post-completion bookkeeping below) — this listener only shortens
|
|
256
|
+
// the latency between "user clicked stop" and "addon stops decoding".
|
|
257
|
+
//
|
|
258
|
+
// Fire-and-forget by construction (event listeners can't `await`), but
|
|
259
|
+
// `addon.cancel()` returns a Promise — if it ever rejects the bare
|
|
260
|
+
// `void` would leak it as an unhandledRejection. Attach `.catch(...)`
|
|
261
|
+
// so a rejection is logged and the process stays clean; the iterator
|
|
262
|
+
// below still sees EOF/empty tokens via the addon's normal cancel path
|
|
263
|
+
// so callers aren't affected.
|
|
264
|
+
const onAbort = () => {
|
|
265
|
+
const addon = model.addon;
|
|
266
|
+
if (addon?.cancel) {
|
|
267
|
+
addon.cancel.call(addon).catch((err) => {
|
|
268
|
+
requestLogger.warn(`[cancel] addon.cancel() rejected during abort for modelId=${modelId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
269
|
+
});
|
|
389
270
|
}
|
|
390
|
-
}
|
|
391
|
-
|
|
271
|
+
};
|
|
272
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
273
|
+
// `addEventListener("abort", ..., { once: true })` does *not* fire if
|
|
274
|
+
// the signal is already aborted at register time — but the registry
|
|
275
|
+
// synchronously aborts a fresh controller when `parentSignal` was
|
|
276
|
+
// already aborted at `begin(...)`. Without this fall-through, the
|
|
277
|
+
// addon would keep decoding until the post-loop check notices.
|
|
278
|
+
// Re-using `onAbort` here keeps the listener body as the single
|
|
279
|
+
// source of truth for "what cancel does."
|
|
280
|
+
if (signal.aborted)
|
|
281
|
+
onAbort();
|
|
282
|
+
// Detach the abort listener on every exit path (happy, throw, generator
|
|
283
|
+
// `return()` from upstream). `{ once: true }` already removes the
|
|
284
|
+
// listener if the signal fires, so the `removeEventListener` here is
|
|
285
|
+
// the cleanup hook for the signal-never-fired path.
|
|
286
|
+
scope.defer(() => {
|
|
287
|
+
signal.removeEventListener("abort", onAbort);
|
|
288
|
+
});
|
|
289
|
+
if (!kvCache) {
|
|
290
|
+
// KV-cache disabled — straight passthrough, no session involvement.
|
|
392
291
|
let historyWithTools = history;
|
|
393
292
|
if (staticTools && tools) {
|
|
394
293
|
historyWithTools = prependToolsToHistory(history, tools);
|
|
@@ -401,4 +300,103 @@ export async function* completion(params) {
|
|
|
401
300
|
logMessagesToAddon(transformedHistory, "NO_CACHE");
|
|
402
301
|
return yield* processModelResponse(model, transformedHistory, tools, mergedGenerationParams, undefined, dialect);
|
|
403
302
|
}
|
|
303
|
+
// ---- KV-cache path. The session owns all three bookkeeping layers
|
|
304
|
+
// (on-disk `.bin`, `initializedCaches`, `cachedMessageCounts`). The
|
|
305
|
+
// handler asks for a turn, registers rollback on the scope, and on
|
|
306
|
+
// the happy path calls `commitTurn` which short-circuits the deferred
|
|
307
|
+
// rollback. Cancellations / zero-token replies / rename failures all
|
|
308
|
+
// unwind through the same `scope.defer` hook. ----
|
|
309
|
+
const session = createKvCacheSession(modelId, { logger: requestLogger });
|
|
310
|
+
const systemPromptFromHistory = extractSystemPrompt(history);
|
|
311
|
+
// Dynamic mode lets each turn carry its own tool set, so the cache
|
|
312
|
+
// hash must not depend on the tool list — otherwise a tool change
|
|
313
|
+
// would force a fresh cache file and defeat the whole optimisation.
|
|
314
|
+
const configHash = generateConfigHash(systemPromptFromHistory, dynamicTools ? undefined : tools);
|
|
315
|
+
const systemPromptToUse = systemPromptFromHistory ||
|
|
316
|
+
modelConfig.system_prompt ||
|
|
317
|
+
"You are a helpful assistant.";
|
|
318
|
+
const primeIfMissing = async (cachePath) => {
|
|
319
|
+
await initSystemPromptCache(model, cachePath, systemPromptToUse, typeof kvCache === "string" ? kvCache : "auto",
|
|
320
|
+
// Static-mode tools are baked into the system-prompt cache so
|
|
321
|
+
// they're shared across the session. Dynamic-mode tools belong
|
|
322
|
+
// to a per-turn anchor and must not enter the system cache.
|
|
323
|
+
staticTools ? tools : undefined);
|
|
324
|
+
};
|
|
325
|
+
let turn;
|
|
326
|
+
if (typeof kvCache === "string") {
|
|
327
|
+
turn = await session.beginTurn({
|
|
328
|
+
kind: "custom",
|
|
329
|
+
customKey: kvCache,
|
|
330
|
+
configHash,
|
|
331
|
+
primeIfMissing,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
const cacheMessages = history.map((msg) => ({
|
|
336
|
+
role: msg.role,
|
|
337
|
+
content: msg.content,
|
|
338
|
+
attachments: msg.attachments ?? undefined,
|
|
339
|
+
}));
|
|
340
|
+
turn = await session.beginTurn({
|
|
341
|
+
kind: "auto",
|
|
342
|
+
configHash,
|
|
343
|
+
history: cacheMessages,
|
|
344
|
+
primeIfMissing,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// Single cleanup hook for every non-success exit path. `commitTurn`
|
|
348
|
+
// flips the turn's internal `committed` flag so this becomes a no-op
|
|
349
|
+
// on the happy path. Scope unwinding is LIFO — registered after the
|
|
350
|
+
// `removeEventListener` defer above so rollback runs before the
|
|
351
|
+
// listener detach.
|
|
352
|
+
scope.defer(() => session.rollback(turn));
|
|
353
|
+
// `cacheExists` is implied by `beginTurn` — the session either found
|
|
354
|
+
// an existing cache or just primed one. Pass `true` to the message
|
|
355
|
+
// selector so the slicing branches engage.
|
|
356
|
+
const messagesToSend = prepareMessagesForCache(session, turn,
|
|
357
|
+
/* cacheExists */ true, history, dynamicTools ? tools : undefined);
|
|
358
|
+
logMessagesToAddon(messagesToSend, "PROMPT_SEND");
|
|
359
|
+
const result = yield* processModelResponse(model, messagesToSend, tools, mergedGenerationParams, { cacheKey: turn.cachePath, saveCacheToDisk: true }, dialect);
|
|
360
|
+
if (typeof kvCache === "string") {
|
|
361
|
+
// Custom-key path: the addon wrote the new cache state inline at
|
|
362
|
+
// the same path. Either commit (records the boundary, suppresses
|
|
363
|
+
// rollback) or fall through to the deferred rollback.
|
|
364
|
+
if (shouldCommitTurn(signal, result.producedTokens)) {
|
|
365
|
+
await session.commitTurn(turn, {
|
|
366
|
+
kind: "static",
|
|
367
|
+
messageCount: history.length + 1,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
// Auto-cache path.
|
|
373
|
+
//
|
|
374
|
+
// Tool-call turns: the auto-cache key is derived from
|
|
375
|
+
// `result.responseText`, which here is raw tool-call markup rather
|
|
376
|
+
// than a clean assistant message. There's no safe post-response key
|
|
377
|
+
// to rename to, so we let the deferred rollback drop the file. Once
|
|
378
|
+
// the SDK supports auto-cache for structured assistant/tool turns,
|
|
379
|
+
// this becomes a normal commit path.
|
|
380
|
+
if (result.toolCalls.length > 0) {
|
|
381
|
+
logger.warn(`[kv-cache] Auto cache tool-call turn; rolling back to avoid disk leak. path=${turn.cachePath}`);
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
if (!shouldCommitTurn(signal, result.producedTokens)) {
|
|
385
|
+
// Cancelled or zero-token turn — the addon wrote the file but its
|
|
386
|
+
// contents don't correspond to a clean turn boundary. Let the
|
|
387
|
+
// deferred rollback unlink it.
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
const savedHistory = buildAutoCacheSaveHistory(history.map((msg) => ({
|
|
391
|
+
role: msg.role,
|
|
392
|
+
content: msg.content,
|
|
393
|
+
attachments: msg.attachments ?? undefined,
|
|
394
|
+
})), result.responseText);
|
|
395
|
+
const postResponseCacheInfo = await getCurrentCacheInfo(modelId, configHash, savedHistory);
|
|
396
|
+
await session.commitTurn(turn, {
|
|
397
|
+
kind: "autoRename",
|
|
398
|
+
targetCachePath: postResponseCacheInfo.cachePath,
|
|
399
|
+
messageCount: savedHistory.length,
|
|
400
|
+
});
|
|
401
|
+
return result;
|
|
404
402
|
}
|
|
@@ -1,6 +1,61 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import fs from "bare-fs";
|
|
2
54
|
import { getModel, } from "../../../registry/model-registry.js";
|
|
3
55
|
import { CompletionFailedError, } from "../../../../../utils/errors-server.js";
|
|
56
|
+
import { getRequestRegistry, withRequestContext, } from "../../../runtime/index.js";
|
|
57
|
+
import { generateServerRequestId } from "../../../runtime/request-id.js";
|
|
58
|
+
import { getServerLogger } from "../../../../../logging/index.js";
|
|
4
59
|
const PAUSE_CHECKPOINT_PREFIX = "pause_checkpoint_step_";
|
|
5
60
|
const finetuneRuntimeState = new Set();
|
|
6
61
|
function getRunningFinetuneState(modelId) {
|
|
@@ -46,30 +101,67 @@ function validateExplicitFinetuneOperation(request) {
|
|
|
46
101
|
}
|
|
47
102
|
}
|
|
48
103
|
export async function startFinetune(request, onProgress) {
|
|
49
|
-
const
|
|
50
|
-
validateExplicitFinetuneOperation(request);
|
|
51
|
-
registerRunningFinetune(request.modelId);
|
|
104
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
52
105
|
try {
|
|
106
|
+
const model = getModel(request.modelId);
|
|
107
|
+
validateExplicitFinetuneOperation(request);
|
|
108
|
+
// Open a request-scoped lifecycle so finetune slots into the same
|
|
109
|
+
// registry-driven cancel surface as the streaming inference kinds.
|
|
110
|
+
// `cancel({ requestId })` and broad `cancel({ modelId, kind: "finetune" })`
|
|
111
|
+
// both route through this context's signal — the `onAbort` listener
|
|
112
|
+
// forwards to `model.cancel()` to match the plugin's
|
|
113
|
+
// `cancel: { scope: "model", hard: true }` declaration. The legacy
|
|
114
|
+
// `cancelFinetune(modelId)` wrapper below now goes through the
|
|
115
|
+
// registry instead of touching the addon directly.
|
|
116
|
+
const ctx = __addDisposableResource(env_1, getRequestRegistry().begin({
|
|
117
|
+
requestId: request.requestId ?? generateServerRequestId(),
|
|
118
|
+
kind: "finetune",
|
|
119
|
+
modelId: request.modelId,
|
|
120
|
+
}), true);
|
|
121
|
+
const requestLogger = withRequestContext(getServerLogger(), ctx);
|
|
122
|
+
registerRunningFinetune(request.modelId);
|
|
123
|
+
// Two-level try/finally collapses into a pair of `scope.defer`
|
|
124
|
+
// registrations. LIFO order — the listener-detach defer is
|
|
125
|
+
// registered after `clearFinetuneRuntimeState`, so on scope unwind
|
|
126
|
+
// the listener is removed first, then the runtime-state flag is
|
|
127
|
+
// cleared. This mirrors the legacy `finally` nesting where the inner
|
|
128
|
+
// `removeListener` ran before the outer `clearFinetuneRuntimeState`.
|
|
129
|
+
ctx.scope.defer(() => {
|
|
130
|
+
clearFinetuneRuntimeState(request.modelId);
|
|
131
|
+
});
|
|
132
|
+
const onAbort = () => {
|
|
133
|
+
model.cancel().catch((err) => {
|
|
134
|
+
requestLogger.warn(`[cancel] model.cancel() rejected during abort for modelId=${request.modelId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
138
|
+
if (ctx.signal.aborted)
|
|
139
|
+
onAbort();
|
|
140
|
+
ctx.scope.defer(() => {
|
|
141
|
+
ctx.signal.removeEventListener("abort", onAbort);
|
|
142
|
+
});
|
|
53
143
|
const handle = await model.finetune(request.options);
|
|
54
144
|
if (onProgress) {
|
|
55
145
|
handle.on("stats", onProgress);
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const result = await handle.await();
|
|
59
|
-
return {
|
|
60
|
-
type: "finetune",
|
|
61
|
-
status: result.status,
|
|
62
|
-
stats: result.stats,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
finally {
|
|
66
|
-
if (onProgress) {
|
|
146
|
+
ctx.scope.defer(() => {
|
|
67
147
|
handle.removeListener("stats", onProgress);
|
|
68
|
-
}
|
|
148
|
+
});
|
|
69
149
|
}
|
|
150
|
+
const result = await handle.await();
|
|
151
|
+
return {
|
|
152
|
+
type: "finetune",
|
|
153
|
+
status: result.status,
|
|
154
|
+
stats: result.stats,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (e_1) {
|
|
158
|
+
env_1.error = e_1;
|
|
159
|
+
env_1.hasError = true;
|
|
70
160
|
}
|
|
71
161
|
finally {
|
|
72
|
-
|
|
162
|
+
const result_1 = __disposeResources(env_1);
|
|
163
|
+
if (result_1)
|
|
164
|
+
await result_1;
|
|
73
165
|
}
|
|
74
166
|
}
|
|
75
167
|
export async function pauseFinetune(modelId) {
|
|
@@ -80,13 +172,25 @@ export async function pauseFinetune(modelId) {
|
|
|
80
172
|
status: "PAUSED",
|
|
81
173
|
};
|
|
82
174
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
175
|
+
// Thin compat wrapper over the request registry. The in-flight
|
|
176
|
+
// finetune is tracked by a `RequestContext` (kind `"finetune"`), and
|
|
177
|
+
// the registry owns the broadcast to its `AbortSignal`. `startFinetune`
|
|
178
|
+
// installs the addon-level `model.cancel()` listener tied to that
|
|
179
|
+
// signal, so callers see the same observable effect as a direct
|
|
180
|
+
// `model.cancel()` call. The addon-call wiring is centralised there —
|
|
181
|
+
// never invoke `model.cancel()` here.
|
|
182
|
+
export function cancelFinetune(modelId) {
|
|
183
|
+
// `registry.cancel(...)` is synchronous — it triggers the matching
|
|
184
|
+
// requests' abort signals and returns the cancelled count. Scope
|
|
185
|
+
// unwinding (and the `model.cancel()` forward installed by
|
|
186
|
+
// `startFinetune`) happens on the handler's own dispose path. The
|
|
187
|
+
// outer `Promise.resolve(...)` keeps the legacy `cancelFinetune`
|
|
188
|
+
// return shape (`Promise<FinetuneResult>`) intact for callers.
|
|
189
|
+
getRequestRegistry().cancel({ modelId, kind: "finetune" });
|
|
190
|
+
return Promise.resolve({
|
|
87
191
|
type: "finetune",
|
|
88
192
|
status: "CANCELLED",
|
|
89
|
-
};
|
|
193
|
+
});
|
|
90
194
|
}
|
|
91
195
|
export function getFinetuneState(params) {
|
|
92
196
|
const runtimeState = getRunningFinetuneState(params.modelId);
|