@qvac/sdk 0.13.4 → 0.14.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 +95 -1151
- package/README.md +1 -2
- package/bare-imports.json +0 -5
- package/dist/client/api/bci-transcribe.js +2 -1
- package/dist/client/api/download-asset.js +2 -1
- package/dist/client/api/finetune.js +6 -5
- 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 +11 -8
- package/dist/client/api/load-model.js +6 -6
- package/dist/client/api/subscribe-logs.d.ts +28 -0
- package/dist/client/api/subscribe-logs.js +43 -0
- package/dist/client/api/text-to-speech.js +2 -1
- package/dist/client/config-loader/config-utils.js +2 -4
- package/dist/client/config-loader/resolve-config.bare.js +4 -3
- package/dist/client/parse-input.d.ts +3 -0
- package/dist/client/parse-input.js +14 -0
- package/dist/client/rpc/node-rpc-client.js +8 -2
- package/dist/client/rpc/rpc-client.js +39 -7
- package/dist/commands/bundle/constants.js +2 -1
- package/dist/examples/bci/bci-filesystem-streaming.js +14 -14
- package/dist/examples/bci/bci-filesystem.js +17 -13
- package/dist/examples/cache-management.js +38 -38
- package/dist/examples/cancel-by-request-id.js +9 -9
- package/dist/examples/completion-events.js +20 -14
- package/dist/examples/completion-stop-reason.js +13 -7
- package/dist/examples/concurrent-completion-collision.js +7 -7
- package/dist/examples/config-reload.js +7 -8
- package/dist/examples/default-config-usage.js +4 -4
- package/dist/examples/delegated-inference/composite.js +14 -10
- package/dist/examples/delegated-inference/consumer-profiled.js +13 -13
- package/dist/examples/delegated-inference/consumer.js +16 -12
- package/dist/examples/delegated-inference/provider.js +12 -12
- package/dist/examples/diffusion-esrgan-upscale.js +19 -15
- package/dist/examples/diffusion-flux2-klein-img2img.js +16 -11
- package/dist/examples/diffusion-flux2-klein.js +14 -9
- package/dist/examples/diffusion-img2img.js +9 -10
- package/dist/examples/diffusion-img2vid.js +17 -12
- package/dist/examples/diffusion-simple.js +1 -1
- package/dist/examples/diffusion-txt2img.js +14 -9
- package/dist/examples/diffusion-txt2vid.js +15 -10
- package/dist/examples/download-with-blind-relays.js +16 -21
- package/dist/examples/download-with-cancel.js +15 -16
- package/dist/examples/embed-multi-gpu.js +3 -4
- package/dist/examples/embed-p2p.js +11 -7
- package/dist/examples/finetune/llamacpp-finetune.js +7 -7
- package/dist/examples/kv-cache-cleanup.js +35 -35
- package/dist/examples/kv-cache-custom-key.js +24 -24
- package/dist/examples/kv-cache-example.js +9 -9
- package/dist/examples/llamacpp-cache.js +18 -14
- package/dist/examples/llamacpp-dynamic-tools.js +15 -9
- package/dist/examples/llamacpp-filesystem.js +15 -9
- package/dist/examples/llamacpp-http.js +12 -10
- package/dist/examples/llamacpp-multi-gpu.js +4 -4
- package/dist/examples/llamacpp-multimodal.js +13 -9
- package/dist/examples/llamacpp-p2p.js +8 -4
- package/dist/examples/llamacpp-sharded.js +5 -5
- package/dist/examples/llamacpp-structured-output.js +15 -9
- package/dist/examples/logging-file-transport.js +3 -3
- package/dist/examples/logging-global.d.ts +2 -0
- package/dist/examples/logging-global.js +34 -0
- package/dist/examples/logging-streaming.js +7 -10
- package/dist/examples/mcp-websearch.js +24 -18
- package/dist/examples/multi-model-demo.js +17 -11
- package/dist/examples/ocr-fasttext.js +14 -16
- package/dist/examples/parallel-download.js +7 -7
- package/dist/examples/plugins.js +40 -28
- package/dist/examples/profiling/basic.js +16 -10
- package/dist/examples/profiling/per-call.js +17 -11
- package/dist/examples/quickstart.bare.d.ts +2 -0
- package/dist/examples/quickstart.bare.js +18 -0
- package/dist/examples/quickstart.js +10 -3
- package/dist/examples/rag/rag-chromadb.js +18 -14
- package/dist/examples/rag/rag-hyperdb/cancellation.js +17 -14
- package/dist/examples/rag/rag-hyperdb/ingest.js +14 -12
- package/dist/examples/rag/rag-hyperdb/pipeline.js +19 -17
- package/dist/examples/rag/rag-hyperdb/workspaces.js +31 -27
- package/dist/examples/rag/rag-lancedb.js +12 -8
- package/dist/examples/rag/rag-sqlite.js +11 -7
- package/dist/examples/registry-query.js +17 -17
- package/dist/examples/seed-p2p.js +10 -6
- package/dist/examples/suspend-resume.js +20 -16
- package/dist/examples/tools/llamacpp-native-tools.js +24 -18
- package/dist/examples/tools/llamacpp-tools-gemma4.js +14 -8
- package/dist/examples/tools/llamacpp-tools-harmony-multiturn.js +20 -14
- package/dist/examples/tools/llamacpp-tools-pythonic.js +14 -8
- package/dist/examples/tools/llamacpp-tools-qwen35.js +14 -8
- package/dist/examples/transcription/parakeet-ctc-filesystem.js +12 -9
- package/dist/examples/transcription/parakeet-microphone-record.js +15 -9
- package/dist/examples/transcription/parakeet-microphone-stream.js +15 -9
- package/dist/examples/transcription/parakeet-sortformer-streaming.js +12 -6
- package/dist/examples/transcription/parakeet-sortformer.js +3 -5
- package/dist/examples/transcription/parakeet-tdt-filesystem.js +13 -10
- package/dist/examples/transcription/whispercpp-filesystem-streaming.js +16 -17
- package/dist/examples/transcription/whispercpp-filesystem.js +14 -10
- package/dist/examples/transcription/whispercpp-microphone-conversation.js +10 -10
- package/dist/examples/transcription/whispercpp-microphone-record.js +6 -6
- package/dist/examples/transcription/whispercpp-prompt.js +14 -10
- package/dist/examples/translation/translation-bergamot-batch.js +11 -7
- package/dist/examples/translation/translation-bergamot-pivot.js +13 -9
- package/dist/examples/translation/translation-bergamot.js +8 -4
- package/dist/examples/translation/translation-indic.js +8 -4
- package/dist/examples/translation/translation-llm-afriquegemma.js +7 -3
- package/dist/examples/translation/translation-llm-context.js +8 -4
- package/dist/examples/translation/translation-llm.js +7 -3
- package/dist/examples/translation/translation-stream.js +4 -4
- package/dist/examples/tts/chatterbox.js +19 -11
- package/dist/examples/tts/llm-to-tts-streaming.js +27 -14
- package/dist/examples/tts/supertonic-multilingual.js +18 -14
- package/dist/examples/tts/supertonic.js +19 -15
- package/dist/examples/tts/utils.js +1 -1
- package/dist/examples/vla-pi05.js +18 -14
- package/dist/examples/vla-smolvla.js +18 -14
- package/dist/examples/voice-assistant/voice-assistant.js +12 -12
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -4
- package/dist/logging/client-logger.js +2 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +1 -1
- package/dist/logging/namespaces.d.ts +1 -0
- package/dist/logging/namespaces.js +2 -0
- package/dist/logging/server-logger.js +1 -4
- package/dist/logging/utils.js +6 -1
- package/dist/models/registry/index.d.ts +17 -0
- package/dist/models/registry/index.js +20 -1
- package/dist/models/registry/models.d.ts +1062 -38
- package/dist/models/registry/models.js +3546 -2490
- package/dist/pear/pre.js +2 -2
- package/dist/schemas/bci-config.d.ts +37 -0
- package/dist/schemas/bci-config.js +2 -0
- package/dist/schemas/bci.d.ts +2 -0
- package/dist/schemas/common.d.ts +171 -94
- package/dist/schemas/completion-stream.d.ts +6 -3
- package/dist/schemas/completion-stream.js +10 -2
- package/dist/schemas/download-asset.d.ts +5 -5
- package/dist/schemas/engine-addon-map.d.ts +1 -1
- package/dist/schemas/engine-addon-map.js +6 -3
- package/dist/schemas/error.js +6 -1
- package/dist/schemas/llamacpp-config.d.ts +27 -8
- package/dist/schemas/llamacpp-config.js +19 -2
- package/dist/schemas/load-model.d.ts +4170 -570
- package/dist/schemas/load-model.js +85 -26
- package/dist/schemas/logging-stream.js +7 -1
- package/dist/schemas/model-src-utils.d.ts +6 -6
- package/dist/schemas/model-types.d.ts +14 -15
- package/dist/schemas/model-types.js +10 -11
- package/dist/schemas/ocr.d.ts +10 -8
- package/dist/schemas/ocr.js +8 -6
- package/dist/schemas/plugin.d.ts +6 -5
- package/dist/schemas/plugin.js +4 -4
- package/dist/schemas/rag.d.ts +4 -4
- package/dist/schemas/registry.d.ts +8 -8
- package/dist/schemas/registry.js +1 -1
- package/dist/schemas/reload-config.d.ts +6 -6
- package/dist/schemas/sdcpp-config.d.ts +12 -12
- package/dist/schemas/sdk-config.d.ts +171 -117
- package/dist/schemas/sdk-config.js +5 -5
- package/dist/schemas/sdk-errors-client.d.ts +1 -0
- package/dist/schemas/sdk-errors-client.js +7 -2
- package/dist/schemas/text-to-speech.d.ts +157 -13
- package/dist/schemas/text-to-speech.js +70 -7
- package/dist/schemas/transcription-config.d.ts +5 -1
- package/dist/schemas/transcription-config.js +2 -0
- package/dist/schemas/transcription.d.ts +3 -0
- package/dist/schemas/transcription.js +2 -1
- package/dist/schemas/translate.d.ts +6 -6
- package/dist/schemas/translate.js +2 -3
- package/dist/schemas/translation-config.d.ts +20 -20
- package/dist/server/bare/hyperswarm.js +3 -3
- package/dist/server/bare/ops/transcribe.js +46 -15
- package/dist/server/bare/plugins/bci-whispercpp-transcription/args.d.ts +3 -0
- package/dist/server/bare/plugins/bci-whispercpp-transcription/args.js +13 -0
- package/dist/server/bare/plugins/bci-whispercpp-transcription/plugin.d.ts +41 -0
- package/dist/server/bare/plugins/bci-whispercpp-transcription/plugin.js +6 -11
- package/dist/server/bare/plugins/bci-whispercpp-transcription/resolve-config.d.ts +75 -0
- package/dist/server/bare/plugins/bci-whispercpp-transcription/resolve-config.js +11 -0
- package/dist/server/bare/plugins/{onnx-ocr → ggml-ocr}/plugin.d.ts +16 -10
- package/dist/server/bare/plugins/{onnx-ocr → ggml-ocr}/plugin.js +25 -18
- package/dist/server/bare/plugins/index.d.ts +1 -1
- package/dist/server/bare/plugins/index.js +1 -1
- package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stats.d.ts +10 -0
- package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stats.js +29 -0
- package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +4 -22
- package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +15 -7
- package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +7 -7
- package/dist/server/bare/plugins/parakeet-transcription/plugin.d.ts +2 -0
- package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +10 -10
- package/dist/server/bare/plugins/tts-ggml/plugin.d.ts +14 -8
- package/dist/server/bare/plugins/tts-ggml/plugin.js +12 -0
- package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +3 -1
- package/dist/server/bare/registry/logging-stream-registry.d.ts +4 -2
- package/dist/server/bare/registry/logging-stream-registry.js +12 -7
- package/dist/server/bare/registry/model-config-utils.js +2 -2
- package/dist/server/bare/registry/registry-client.js +1 -1
- package/dist/server/bare/runtime-lifecycle.d.ts +9 -0
- package/dist/server/bare/runtime-lifecycle.js +24 -0
- package/dist/server/bare/types/addon-responses.d.ts +1 -0
- package/dist/server/env.js +7 -10
- package/dist/server/plugins/registry.js +2 -4
- package/dist/server/rpc/handlers/load-model/handler.js +2 -4
- package/dist/server/rpc/handlers/load-model/http.js +253 -51
- package/dist/server/rpc/handlers/logging-stream.js +4 -2
- package/dist/server/rpc/handlers/plugin-invoke.js +5 -16
- package/dist/server/utils/completion-normalizer.js +13 -4
- package/dist/server/utils/worker-lock.js +5 -5
- package/dist/server/worker-core.d.ts +1 -1
- package/dist/server/worker-core.js +28 -18
- package/dist/utils/errors-client.d.ts +3 -0
- package/dist/utils/errors-client.js +5 -0
- package/dist/utils/errors-server.d.ts +2 -0
- package/dist/utils/errors-server.js +3 -0
- package/dist/utils/zod-error.d.ts +3 -0
- package/dist/utils/zod-error.js +4 -0
- package/package.json +21 -24
- package/dist/polyfills/bare-globals.d.ts +0 -2
- package/dist/polyfills/bare-globals.js +0 -4
- package/dist/polyfills/bare-globals.noop.d.ts +0 -2
- package/dist/polyfills/bare-globals.noop.js +0 -1
- /package/dist/server/bare/plugins/{onnx-ocr → ggml-ocr}/ops/ocr-stream.d.ts +0 -0
- /package/dist/server/bare/plugins/{onnx-ocr → ggml-ocr}/ops/ocr-stream.js +0 -0
|
@@ -11,7 +11,7 @@ const logger = getServerLogger();
|
|
|
11
11
|
// fd-lock issue a blocking flock(LOCK_EX) on a libuv worker thread. The
|
|
12
12
|
// blocking variant cannot be cancelled from JS, so if another SDK process
|
|
13
13
|
// holds the lock indefinitely it leaves a pending native handle that
|
|
14
|
-
// prevents
|
|
14
|
+
// prevents Bare.exit() from terminating the worker — see QVAC-18197.
|
|
15
15
|
//
|
|
16
16
|
// With tryLock + bounded retries we get the same "tolerate transient locks
|
|
17
17
|
// during another SDK's startup/shutdown" property #1480 wanted, but every
|
|
@@ -12,6 +12,15 @@ interface ResourceMeta {
|
|
|
12
12
|
label: string;
|
|
13
13
|
createdAt: number;
|
|
14
14
|
}
|
|
15
|
+
type ResumeListener = () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Register a callback fired after the runtime returns to "active". Lets in-flight
|
|
18
|
+
* work (e.g. an HTTP download whose socket died while backgrounded) recover the
|
|
19
|
+
* instant `resume()` runs instead of waiting for a timeout. Returns an
|
|
20
|
+
* unregister function — callers MUST call it when their work finishes so the
|
|
21
|
+
* listener doesn't leak into the next operation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function onResume(listener: ResumeListener): () => void;
|
|
15
24
|
export declare function registerSwarm(swarm: SuspendableSwarm, meta: ResourceMeta): void;
|
|
16
25
|
export declare function unregisterSwarm(swarm: SuspendableSwarm): void;
|
|
17
26
|
export declare function registerCorestore(store: SuspendableStore, meta: ResourceMeta): void;
|
|
@@ -8,8 +8,30 @@ const LIFECYCLE_ALLOWED_TYPES = new Set([
|
|
|
8
8
|
]);
|
|
9
9
|
const swarms = new Map();
|
|
10
10
|
const stores = new Map();
|
|
11
|
+
const resumeListeners = new Set();
|
|
11
12
|
let state = "active";
|
|
12
13
|
let transitionPromise = null;
|
|
14
|
+
/**
|
|
15
|
+
* Register a callback fired after the runtime returns to "active". Lets in-flight
|
|
16
|
+
* work (e.g. an HTTP download whose socket died while backgrounded) recover the
|
|
17
|
+
* instant `resume()` runs instead of waiting for a timeout. Returns an
|
|
18
|
+
* unregister function — callers MUST call it when their work finishes so the
|
|
19
|
+
* listener doesn't leak into the next operation.
|
|
20
|
+
*/
|
|
21
|
+
export function onResume(listener) {
|
|
22
|
+
resumeListeners.add(listener);
|
|
23
|
+
return () => resumeListeners.delete(listener);
|
|
24
|
+
}
|
|
25
|
+
function notifyResume() {
|
|
26
|
+
for (const listener of Array.from(resumeListeners)) {
|
|
27
|
+
try {
|
|
28
|
+
listener();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
logger.error("Lifecycle: onResume listener threw:", error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
13
35
|
export function registerSwarm(swarm, meta) {
|
|
14
36
|
swarms.set(swarm, meta);
|
|
15
37
|
logger.debug(`Lifecycle: registered swarm [${meta.label}]`);
|
|
@@ -46,6 +68,7 @@ export function getRegisteredResourceCounts() {
|
|
|
46
68
|
export function resetLifecycleState() {
|
|
47
69
|
swarms.clear();
|
|
48
70
|
stores.clear();
|
|
71
|
+
resumeListeners.clear();
|
|
49
72
|
state = "active";
|
|
50
73
|
transitionPromise = null;
|
|
51
74
|
}
|
|
@@ -130,6 +153,7 @@ export async function resumeRuntime() {
|
|
|
130
153
|
.then(() => {
|
|
131
154
|
state = "active";
|
|
132
155
|
logger.info("▶️ Runtime resumed");
|
|
156
|
+
notifyResume();
|
|
133
157
|
})
|
|
134
158
|
.catch((error) => {
|
|
135
159
|
state = "suspended";
|
package/dist/server/env.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import env from "bare-env";
|
|
2
2
|
import { isBareKit } from "which-runtime";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
const envSchema = z.object({
|
|
@@ -13,21 +13,18 @@ let validatedEnv = null;
|
|
|
13
13
|
export function initEnv() {
|
|
14
14
|
const defaultHomeDir =
|
|
15
15
|
// Snap's HOME can be revision-scoped; SNAP_USER_COMMON is stable.
|
|
16
|
-
|
|
17
|
-
process.env["HOME"] ??
|
|
18
|
-
process.env["USERPROFILE"] ??
|
|
19
|
-
"/tmp";
|
|
16
|
+
env["SNAP_USER_COMMON"] ?? env["HOME"] ?? env["USERPROFILE"] ?? "/tmp";
|
|
20
17
|
let envConfig = {
|
|
21
18
|
HOME_DIR: defaultHomeDir,
|
|
22
19
|
};
|
|
23
20
|
let hasRPCConfig = false;
|
|
24
|
-
if (isBareKit &&
|
|
25
|
-
envConfig["HOME_DIR"] =
|
|
21
|
+
if (isBareKit && Bare.argv[0]) {
|
|
22
|
+
envConfig["HOME_DIR"] = Bare.argv[0];
|
|
26
23
|
}
|
|
27
24
|
// Try to parse any argument as JSON config (fail gracefully)
|
|
28
|
-
if (
|
|
25
|
+
if (Bare.argv[2]) {
|
|
29
26
|
try {
|
|
30
|
-
const rpcArgs = JSON.parse(
|
|
27
|
+
const rpcArgs = JSON.parse(Bare.argv[2]);
|
|
31
28
|
envConfig = { ...envConfig, ...rpcArgs };
|
|
32
29
|
hasRPCConfig = true;
|
|
33
30
|
}
|
|
@@ -47,7 +44,7 @@ export function getEnv() {
|
|
|
47
44
|
initEnv();
|
|
48
45
|
}
|
|
49
46
|
return {
|
|
50
|
-
...
|
|
47
|
+
...env,
|
|
51
48
|
...validatedEnv,
|
|
52
49
|
};
|
|
53
50
|
}
|
|
@@ -2,6 +2,7 @@ import { pluginDefinitionRuntimeSchema, } from "../../schemas/plugin.js";
|
|
|
2
2
|
import { isModelTypeAlias } from "../../schemas/index.js";
|
|
3
3
|
import { PluginAlreadyRegisteredError, PluginDefinitionInvalidError, PluginLoggingInvalidError, PluginModelTypeReservedError, } from "../../utils/errors-server.js";
|
|
4
4
|
import { createAddonLoggerCallback } from "../../logging/addon.js";
|
|
5
|
+
import { formatZodError } from "../../utils/zod-error.js";
|
|
5
6
|
const plugins = new Map();
|
|
6
7
|
function getModelTypeForError(plugin) {
|
|
7
8
|
if (!plugin || typeof plugin !== "object")
|
|
@@ -17,10 +18,7 @@ function validatePluginDefinition(plugin) {
|
|
|
17
18
|
const result = pluginDefinitionRuntimeSchema.safeParse(plugin);
|
|
18
19
|
if (result.success)
|
|
19
20
|
return;
|
|
20
|
-
|
|
21
|
-
.map((i) => `${String(i.path.join("."))}: ${i.message}`)
|
|
22
|
-
.join(", ");
|
|
23
|
-
throw new PluginDefinitionInvalidError(getModelTypeForError(plugin), details);
|
|
21
|
+
throw new PluginDefinitionInvalidError(getModelTypeForError(plugin), formatZodError(result.error));
|
|
24
22
|
}
|
|
25
23
|
export function registerPlugin(plugin) {
|
|
26
24
|
validatePluginDefinition(plugin);
|
|
@@ -59,6 +59,7 @@ import { generateShortHash, canonicalConfigString, transformConfigForReload, } f
|
|
|
59
59
|
import { buildDownloadProfilingFields } from "./types.js";
|
|
60
60
|
import { ConfigReloadNotSupportedError, InferenceCancelledError, ModelTypeMismatchError, ModelIsDelegatedError, ModelNotFoundError, ModelLoadFailedError, PluginLoadConfigValidationFailedError, PluginNotFoundError, } from "../../../../utils/errors-server.js";
|
|
61
61
|
import { getServerLogger } from "../../../../logging/index.js";
|
|
62
|
+
import { formatZodError } from "../../../../utils/zod-error.js";
|
|
62
63
|
import { getPlugin } from "../../../plugins/index.js";
|
|
63
64
|
import { getRequestRegistry, withRequestContext, } from "../../../bare/runtime/index.js";
|
|
64
65
|
import { generateServerRequestId } from "../../../bare/runtime/request-id.js";
|
|
@@ -96,10 +97,7 @@ export async function handleLoadModel(request, progressCallback) {
|
|
|
96
97
|
let resolvedModelConfig = (request.modelConfig ?? {});
|
|
97
98
|
const parseResult = plugin.loadConfigSchema.safeParse(resolvedModelConfig);
|
|
98
99
|
if (!parseResult.success) {
|
|
99
|
-
|
|
100
|
-
.map((i) => `${String(i.path.join("."))}: ${i.message}`)
|
|
101
|
-
.join(", ");
|
|
102
|
-
throw new PluginLoadConfigValidationFailedError(canonicalModelType, details);
|
|
100
|
+
throw new PluginLoadConfigValidationFailedError(canonicalModelType, formatZodError(parseResult.error));
|
|
103
101
|
}
|
|
104
102
|
resolvedModelConfig = parseResult.data;
|
|
105
103
|
const totalLoadStart = profilingEnabled ? nowMs() : 0;
|
|
@@ -2,15 +2,21 @@ import fs, { promises as fsPromises } from "bare-fs";
|
|
|
2
2
|
import path from "bare-path";
|
|
3
3
|
import { Readable } from "bare-stream";
|
|
4
4
|
import fetch, { Headers } from "bare-fetch";
|
|
5
|
+
import { AbortController } from "bare-abort-controller";
|
|
5
6
|
import { withTimeout } from "../../../../utils/withTimeout.js";
|
|
6
7
|
import { getModelsCacheDir, getShardedModelCacheDir, generateShortHash, detectShardedModel, parsePatternBasedShardUrl, extractTensorsFromShards, calculatePercentage, isArchiveUrl, sanitizePathComponent, extractAndValidateShardedArchive, validateShardedModelCache, checkAllShardsExist, generateShardFilenames, hasValidGGUFHeader, } from "../../../utils/index.js";
|
|
7
8
|
import { getSDKConfig } from "../../../bare/registry/config-registry.js";
|
|
9
|
+
import { getLifecycleState, onResume } from "../../../bare/runtime-lifecycle.js";
|
|
8
10
|
import { createHttpDownloadKey, startOrJoinDownload, applyJoinedDownloadStats, } from "./download-manager.js";
|
|
9
11
|
import { DownloadCancelledError, HTTPError, NoResponseBodyError, PartialDownloadOfflineError, ResponseBodyNotReadableError, } from "../../../../utils/errors-server.js";
|
|
10
12
|
import { getServerLogger } from "../../../../logging/index.js";
|
|
11
13
|
const logger = getServerLogger();
|
|
12
14
|
const DEFAULT_CONCURRENCY = 3;
|
|
13
15
|
const DEFAULT_HTTP_CONNECTION_TIMEOUT_MS = 10_000;
|
|
16
|
+
// If no bytes arrive for this long mid-stream, treat the transfer as stalled
|
|
17
|
+
// (dead socket after a suspend or network drop that didn't surface an error)
|
|
18
|
+
// and abort so the caller can resume from the partial via a Range request.
|
|
19
|
+
const HTTP_STREAM_STALL_TIMEOUT_MS = 10_000;
|
|
14
20
|
function extractFilenameFromUrl(url) {
|
|
15
21
|
// Parse URL to get the filename from the path
|
|
16
22
|
const urlParts = url.split("/");
|
|
@@ -180,72 +186,140 @@ async function performHttpDownload(url, modelPath, downloadKey, progressCallback
|
|
|
180
186
|
(typeof body.pipe === "function" &&
|
|
181
187
|
typeof body.on === "function");
|
|
182
188
|
if (isReadable) {
|
|
183
|
-
// Track progress by intercepting data events if possible
|
|
184
|
-
body.on("data", (chunk) => {
|
|
185
|
-
downloadedBytes += chunk.length;
|
|
186
|
-
if (progressCallback) {
|
|
187
|
-
progressCallback({
|
|
188
|
-
type: "modelProgress",
|
|
189
|
-
downloaded: downloadedBytes,
|
|
190
|
-
total: totalBytes,
|
|
191
|
-
percentage: calculatePercentage(downloadedBytes, totalBytes),
|
|
192
|
-
downloadKey,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
// Pipe directly to file
|
|
197
|
-
body.pipe(writeStream);
|
|
198
189
|
// Wait for download to complete
|
|
199
190
|
await new Promise((resolve, reject) => {
|
|
200
|
-
|
|
191
|
+
let stallTimer;
|
|
192
|
+
const clearStall = () => {
|
|
193
|
+
if (stallTimer) {
|
|
194
|
+
clearTimeout(stallTimer);
|
|
195
|
+
stallTimer = undefined;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
// Reset on every chunk; if it ever fires, the socket went quiet
|
|
199
|
+
// mid-stream (suspend/drop) — abort so the caller resumes via Range.
|
|
200
|
+
const armStall = () => {
|
|
201
|
+
clearStall();
|
|
202
|
+
stallTimer = setTimeout(() => {
|
|
203
|
+
body.destroy();
|
|
204
|
+
writeStream.destroy();
|
|
205
|
+
reject(new Error(`HTTP stream stalled: no data for ${HTTP_STREAM_STALL_TIMEOUT_MS}ms`));
|
|
206
|
+
}, HTTP_STREAM_STALL_TIMEOUT_MS);
|
|
207
|
+
};
|
|
201
208
|
const abortHandler = () => {
|
|
202
|
-
|
|
209
|
+
clearStall();
|
|
203
210
|
body.destroy();
|
|
204
211
|
writeStream.destroy();
|
|
205
|
-
reject(
|
|
212
|
+
reject(new DownloadCancelledError());
|
|
206
213
|
};
|
|
207
214
|
if (signal) {
|
|
215
|
+
if (signal.aborted) {
|
|
216
|
+
abortHandler();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
208
219
|
signal.addEventListener("abort", abortHandler);
|
|
209
220
|
}
|
|
221
|
+
// Track progress + keep the stall watchdog fed
|
|
222
|
+
body.on("data", (chunk) => {
|
|
223
|
+
armStall();
|
|
224
|
+
downloadedBytes += chunk.length;
|
|
225
|
+
if (progressCallback) {
|
|
226
|
+
progressCallback({
|
|
227
|
+
type: "modelProgress",
|
|
228
|
+
downloaded: downloadedBytes,
|
|
229
|
+
total: totalBytes,
|
|
230
|
+
percentage: calculatePercentage(downloadedBytes, totalBytes),
|
|
231
|
+
downloadKey,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// Pipe directly to file
|
|
236
|
+
body.pipe(writeStream);
|
|
237
|
+
armStall();
|
|
210
238
|
writeStream.on("finish", () => {
|
|
239
|
+
clearStall();
|
|
211
240
|
logger.info(`✅ Model downloaded successfully to ${modelPath}`);
|
|
212
241
|
if (signal) {
|
|
213
242
|
signal.removeEventListener("abort", abortHandler);
|
|
214
243
|
}
|
|
215
244
|
resolve(undefined);
|
|
216
245
|
});
|
|
217
|
-
writeStream.on("error",
|
|
218
|
-
|
|
246
|
+
writeStream.on("error", (error) => {
|
|
247
|
+
clearStall();
|
|
248
|
+
reject(error);
|
|
249
|
+
});
|
|
250
|
+
body.on("error", (error) => {
|
|
251
|
+
clearStall();
|
|
252
|
+
reject(error);
|
|
253
|
+
});
|
|
219
254
|
});
|
|
220
255
|
}
|
|
221
256
|
else if (body[Symbol.asyncIterator]) {
|
|
222
|
-
// Body is an async iterable
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
// Body is an async iterable. Drive the iterator manually so each pull can
|
|
258
|
+
// race a stall timeout: a dead socket after suspend/drop produces neither
|
|
259
|
+
// a chunk nor an error, so the timeout converts it into a retriable
|
|
260
|
+
// failure and we abandon the iterator (which cancels the stream).
|
|
261
|
+
const iterator = body[Symbol.asyncIterator]();
|
|
262
|
+
let stalled = false;
|
|
263
|
+
try {
|
|
264
|
+
for (;;) {
|
|
265
|
+
let stallTimer;
|
|
266
|
+
const nextPromise = iterator.next();
|
|
267
|
+
nextPromise.catch(() => { });
|
|
268
|
+
let step;
|
|
269
|
+
try {
|
|
270
|
+
step = await Promise.race([
|
|
271
|
+
nextPromise,
|
|
272
|
+
new Promise((_, reject) => {
|
|
273
|
+
stallTimer = setTimeout(() => {
|
|
274
|
+
stalled = true;
|
|
275
|
+
reject(new Error(`HTTP stream stalled: no data for ${HTTP_STREAM_STALL_TIMEOUT_MS}ms`));
|
|
276
|
+
}, HTTP_STREAM_STALL_TIMEOUT_MS);
|
|
277
|
+
}),
|
|
278
|
+
]);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
if (stallTimer)
|
|
282
|
+
clearTimeout(stallTimer);
|
|
283
|
+
}
|
|
284
|
+
if (step.done)
|
|
285
|
+
break;
|
|
286
|
+
if (signal?.aborted) {
|
|
287
|
+
writeStream.destroy();
|
|
288
|
+
throw new DownloadCancelledError();
|
|
289
|
+
}
|
|
290
|
+
const buffer = Buffer.isBuffer(step.value)
|
|
291
|
+
? step.value
|
|
292
|
+
: Buffer.from(step.value);
|
|
293
|
+
downloadedBytes += buffer.length;
|
|
294
|
+
if (progressCallback) {
|
|
295
|
+
progressCallback({
|
|
296
|
+
type: "modelProgress",
|
|
297
|
+
downloaded: downloadedBytes,
|
|
298
|
+
total: totalBytes,
|
|
299
|
+
percentage: calculatePercentage(downloadedBytes, totalBytes),
|
|
300
|
+
downloadKey,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
// Write chunk to file
|
|
304
|
+
await new Promise((resolve, reject) => {
|
|
305
|
+
writeStream.write(buffer, (err) => {
|
|
306
|
+
if (err)
|
|
307
|
+
reject(new Error(err instanceof Error ? err.message : String(err)));
|
|
308
|
+
else
|
|
309
|
+
resolve();
|
|
310
|
+
});
|
|
238
311
|
});
|
|
239
312
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
if (stalled) {
|
|
316
|
+
try {
|
|
317
|
+
await iterator.return?.();
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
/* best effort: free the dead socket */
|
|
321
|
+
}
|
|
322
|
+
}
|
|
249
323
|
}
|
|
250
324
|
// Close the write stream
|
|
251
325
|
await new Promise((resolve, reject) => {
|
|
@@ -257,17 +331,45 @@ async function performHttpDownload(url, modelPath, downloadKey, progressCallback
|
|
|
257
331
|
});
|
|
258
332
|
}
|
|
259
333
|
else {
|
|
260
|
-
// Fallback:
|
|
334
|
+
// Fallback: getReader() for a WHATWG ReadableStream (bare-fetch body).
|
|
261
335
|
const readableStreamBody = body;
|
|
262
336
|
const reader = readableStreamBody.getReader
|
|
263
337
|
? readableStreamBody.getReader()
|
|
264
338
|
: null;
|
|
265
339
|
if (reader) {
|
|
340
|
+
let cancelled = false;
|
|
266
341
|
try {
|
|
267
|
-
|
|
268
|
-
|
|
342
|
+
for (;;) {
|
|
343
|
+
// Race each read against a stall timeout. A dead socket after a
|
|
344
|
+
// suspend/drop yields no data and no error; the timeout converts
|
|
345
|
+
// that into a retriable failure so the caller resumes via Range.
|
|
346
|
+
let stallTimer;
|
|
347
|
+
const readPromise = reader.read();
|
|
348
|
+
// Avoid an unhandled rejection if the read loses the race.
|
|
349
|
+
readPromise.catch(() => { });
|
|
350
|
+
let result;
|
|
351
|
+
try {
|
|
352
|
+
result = await Promise.race([
|
|
353
|
+
readPromise,
|
|
354
|
+
new Promise((_, reject) => {
|
|
355
|
+
stallTimer = setTimeout(() => {
|
|
356
|
+
cancelled = true;
|
|
357
|
+
reject(new Error(`HTTP stream stalled: no data for ${HTTP_STREAM_STALL_TIMEOUT_MS}ms`));
|
|
358
|
+
}, HTTP_STREAM_STALL_TIMEOUT_MS);
|
|
359
|
+
}),
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
if (stallTimer)
|
|
364
|
+
clearTimeout(stallTimer);
|
|
365
|
+
}
|
|
366
|
+
const { done, value } = result;
|
|
269
367
|
if (done)
|
|
270
368
|
break;
|
|
369
|
+
if (signal?.aborted) {
|
|
370
|
+
cancelled = true;
|
|
371
|
+
throw new DownloadCancelledError();
|
|
372
|
+
}
|
|
271
373
|
const buffer = Buffer.from(value);
|
|
272
374
|
downloadedBytes += buffer.length;
|
|
273
375
|
if (progressCallback) {
|
|
@@ -291,6 +393,14 @@ async function performHttpDownload(url, modelPath, downloadKey, progressCallback
|
|
|
291
393
|
}
|
|
292
394
|
}
|
|
293
395
|
finally {
|
|
396
|
+
if (cancelled) {
|
|
397
|
+
try {
|
|
398
|
+
await reader.cancel();
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
/* best effort: free the dead socket */
|
|
402
|
+
}
|
|
403
|
+
}
|
|
294
404
|
reader.releaseLock();
|
|
295
405
|
}
|
|
296
406
|
// Close the write stream
|
|
@@ -313,6 +423,98 @@ async function performHttpDownload(url, modelPath, downloadKey, progressCallback
|
|
|
313
423
|
throw error instanceof Error ? error : new Error(String(error));
|
|
314
424
|
}
|
|
315
425
|
}
|
|
426
|
+
const DEFAULT_HTTP_DOWNLOAD_MAX_RETRIES = 5;
|
|
427
|
+
const HTTP_RETRY_BASE_DELAY_MS = 500;
|
|
428
|
+
const LIFECYCLE_WAIT_POLL_MS = 200;
|
|
429
|
+
const LIFECYCLE_WAIT_MAX_MS = 5 * 60_000;
|
|
430
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
431
|
+
/**
|
|
432
|
+
* A mid-stream socket/body error, a connection failure (HTTP status 0, no
|
|
433
|
+
* response), or our own resume-triggered abort is recoverable by resuming from
|
|
434
|
+
* the partial. A real HTTP status (4xx/5xx) or a missing/unreadable body is not.
|
|
435
|
+
*
|
|
436
|
+
* `DownloadCancelledError` is intentionally NOT excluded here: this is only
|
|
437
|
+
* reached after the caller's consumer-cancel gate, so a cancellation at this
|
|
438
|
+
* point is our per-attempt abort (resume interrupt), which must be retried.
|
|
439
|
+
*/
|
|
440
|
+
function isResumableTransferError(error) {
|
|
441
|
+
if (error instanceof NoResponseBodyError ||
|
|
442
|
+
error instanceof ResponseBodyNotReadableError ||
|
|
443
|
+
error instanceof PartialDownloadOfflineError) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
if (error instanceof HTTPError)
|
|
447
|
+
return error.httpStatus === 0;
|
|
448
|
+
return error instanceof Error;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Block until the runtime is active again before retrying. After a suspend the
|
|
452
|
+
* socket is dead and the process can't service the transfer; resuming the fetch
|
|
453
|
+
* only makes sense once `resume()` has run. Bounded and abort-aware.
|
|
454
|
+
*/
|
|
455
|
+
async function waitForLifecycleActive(signal) {
|
|
456
|
+
if (getLifecycleState() === "active")
|
|
457
|
+
return;
|
|
458
|
+
const start = Date.now();
|
|
459
|
+
while (getLifecycleState() !== "active") {
|
|
460
|
+
if (signal?.aborted)
|
|
461
|
+
throw new DownloadCancelledError();
|
|
462
|
+
if (Date.now() - start > LIFECYCLE_WAIT_MAX_MS)
|
|
463
|
+
return;
|
|
464
|
+
await sleep(LIFECYCLE_WAIT_POLL_MS);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Run `performHttpDownload` with bounded retry. On a recoverable interruption
|
|
469
|
+
* (mid-stream socket drop, or a dead socket after suspend/network loss) it
|
|
470
|
+
* waits for the runtime to be active and re-issues the request, which resumes
|
|
471
|
+
* from the on-disk partial via a Range header. No consumer-side babysitting.
|
|
472
|
+
*
|
|
473
|
+
* Each attempt runs on its own AbortController that is aborted either by the
|
|
474
|
+
* consumer `signal` (a real cancel — terminal) or by `resume()` (a proactive
|
|
475
|
+
* recovery — the in-flight socket is dead after a background, so abort and
|
|
476
|
+
* range-resume immediately instead of waiting for the stall watchdog).
|
|
477
|
+
*/
|
|
478
|
+
async function performHttpDownloadWithResume(url, modelPath, downloadKey, progressCallback, signal) {
|
|
479
|
+
const maxRetries = DEFAULT_HTTP_DOWNLOAD_MAX_RETRIES;
|
|
480
|
+
let attempt = 0;
|
|
481
|
+
for (;;) {
|
|
482
|
+
const attemptController = new AbortController();
|
|
483
|
+
const forwardCancel = () => attemptController.abort();
|
|
484
|
+
if (signal) {
|
|
485
|
+
if (signal.aborted)
|
|
486
|
+
attemptController.abort();
|
|
487
|
+
else
|
|
488
|
+
signal.addEventListener("abort", forwardCancel, { once: true });
|
|
489
|
+
}
|
|
490
|
+
// resume() → abort this attempt so it range-resumes now, not after the stall.
|
|
491
|
+
const offResume = onResume(() => attemptController.abort());
|
|
492
|
+
try {
|
|
493
|
+
await performHttpDownload(url, modelPath, downloadKey, progressCallback, attemptController.signal);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
// A real consumer cancel is terminal; anything else (resume abort, stall,
|
|
498
|
+
// network error) is recoverable from the partial.
|
|
499
|
+
if (signal?.aborted) {
|
|
500
|
+
throw error instanceof DownloadCancelledError
|
|
501
|
+
? error
|
|
502
|
+
: new DownloadCancelledError();
|
|
503
|
+
}
|
|
504
|
+
attempt++;
|
|
505
|
+
if (attempt > maxRetries || !isResumableTransferError(error))
|
|
506
|
+
throw error;
|
|
507
|
+
logger.warn(`⚠️ HTTP download interrupted (attempt ${attempt}/${maxRetries}), resuming from partial: ${error instanceof Error ? error.message : String(error)}`);
|
|
508
|
+
await waitForLifecycleActive(signal);
|
|
509
|
+
await sleep(HTTP_RETRY_BASE_DELAY_MS * attempt);
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
if (signal)
|
|
513
|
+
signal.removeEventListener("abort", forwardCancel);
|
|
514
|
+
offResume();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
316
518
|
export async function downloadModelFromHttp(url, progressCallback, hooks) {
|
|
317
519
|
const filename = extractFilenameFromUrl(url);
|
|
318
520
|
if (isArchiveUrl(url)) {
|
|
@@ -355,7 +557,7 @@ export async function downloadModelFromHttp(url, progressCallback, hooks) {
|
|
|
355
557
|
// Download the file
|
|
356
558
|
hooks?.markCacheMiss?.();
|
|
357
559
|
ctx.setCacheHit(false);
|
|
358
|
-
await
|
|
560
|
+
await performHttpDownloadWithResume(url, modelPath, downloadKey, ctx.broadcastProgress, ctx.signal);
|
|
359
561
|
try {
|
|
360
562
|
const stats = await fsPromises.stat(modelPath);
|
|
361
563
|
ctx.broadcastProgress({
|
|
@@ -576,7 +778,7 @@ async function downloadShardedModelFromArchive(archiveUrl, progressCallback, hoo
|
|
|
576
778
|
throw error;
|
|
577
779
|
}
|
|
578
780
|
async function downloadAndExtractArchive() {
|
|
579
|
-
await
|
|
781
|
+
await performHttpDownloadWithResume(archiveUrl, archivePath, downloadKey, ctx.broadcastProgress, ctx.signal);
|
|
580
782
|
logger.info(`✅ Archive downloaded, extracting to: ${extractDir}`);
|
|
581
783
|
const firstShardPath = await extractAndValidateShardedArchive(archivePath, extractDir, ctx.signal);
|
|
582
784
|
try {
|
|
@@ -605,7 +807,7 @@ async function downloadShardsWithConcurrency(shardsToDownload, allShards, concur
|
|
|
605
807
|
const state = queue.shift();
|
|
606
808
|
const downloadPromise = (async () => {
|
|
607
809
|
logger.info(`📥 Downloading shard ${state.index + 1}: ${state.shard.filename}`);
|
|
608
|
-
await
|
|
810
|
+
await performHttpDownloadWithResume(state.shard.url, state.shardPath, downloadKey, (progress) => {
|
|
609
811
|
state.downloadedBytes = progress.downloaded;
|
|
610
812
|
if (progressCallback) {
|
|
611
813
|
const overallDownloaded = allShards.reduce((sum, s) => sum + s.downloadedBytes, 0);
|
|
@@ -3,10 +3,12 @@ export async function* handleLoggingStream(request) {
|
|
|
3
3
|
const { id } = request;
|
|
4
4
|
const logQueue = [];
|
|
5
5
|
let pendingResolve = null;
|
|
6
|
-
const streamHandler = (level, namespace, message) => {
|
|
6
|
+
const streamHandler = (level, namespace, message, sourceId) => {
|
|
7
7
|
const logResponse = {
|
|
8
8
|
type: "loggingStream",
|
|
9
|
-
|
|
9
|
+
// For the global SDK_ALL_LOG_ID stream `sourceId` is the real origin of the
|
|
10
|
+
// log; for a per-id stream it equals the subscription `id`.
|
|
11
|
+
id: sourceId,
|
|
10
12
|
level: level,
|
|
11
13
|
namespace,
|
|
12
14
|
message,
|
|
@@ -3,6 +3,7 @@ import { getPlugin, getPluginHandler } from "../../plugins/index.js";
|
|
|
3
3
|
import { profileReplyHandler, profileStreamHandler, } from "../profiling/index.js";
|
|
4
4
|
import { PluginNotFoundError, PluginHandlerNotFoundError, PluginHandlerTypeMismatchError, PluginRequestValidationFailedError, PluginResponseValidationFailedError, ModelIsDelegatedError, ModelNotFoundError, } from "../../../utils/errors-server.js";
|
|
5
5
|
import { getServerLogger } from "../../../logging/index.js";
|
|
6
|
+
import { formatZodError } from "../../../utils/zod-error.js";
|
|
6
7
|
const logger = getServerLogger();
|
|
7
8
|
function resolvePluginHandler(modelId, handlerName) {
|
|
8
9
|
const modelEntry = getModelEntry(modelId);
|
|
@@ -34,18 +35,12 @@ export async function handlePluginInvoke(request) {
|
|
|
34
35
|
}
|
|
35
36
|
const parseResult = handlerDef.requestSchema.safeParse(params);
|
|
36
37
|
if (!parseResult.success) {
|
|
37
|
-
|
|
38
|
-
.map((i) => `${String(i.path.join("."))}: ${i.message}`)
|
|
39
|
-
.join(", ");
|
|
40
|
-
throw new PluginRequestValidationFailedError(handlerName, details);
|
|
38
|
+
throw new PluginRequestValidationFailedError(handlerName, formatZodError(parseResult.error));
|
|
41
39
|
}
|
|
42
40
|
const result = await handlerDef.handler(parseResult.data);
|
|
43
41
|
const responseParseResult = handlerDef.responseSchema.safeParse(result);
|
|
44
42
|
if (!responseParseResult.success) {
|
|
45
|
-
|
|
46
|
-
.map((i) => `${String(i.path.join("."))}: ${i.message}`)
|
|
47
|
-
.join(", ");
|
|
48
|
-
throw new PluginResponseValidationFailedError(handlerName, details);
|
|
43
|
+
throw new PluginResponseValidationFailedError(handlerName, formatZodError(responseParseResult.error));
|
|
49
44
|
}
|
|
50
45
|
return {
|
|
51
46
|
type: "pluginInvoke",
|
|
@@ -63,19 +58,13 @@ export async function* handlePluginInvokeStream(request) {
|
|
|
63
58
|
}
|
|
64
59
|
const parseResult = handlerDef.requestSchema.safeParse(params);
|
|
65
60
|
if (!parseResult.success) {
|
|
66
|
-
|
|
67
|
-
.map((i) => `${String(i.path.join("."))}: ${i.message}`)
|
|
68
|
-
.join(", ");
|
|
69
|
-
throw new PluginRequestValidationFailedError(handlerName, details);
|
|
61
|
+
throw new PluginRequestValidationFailedError(handlerName, formatZodError(parseResult.error));
|
|
70
62
|
}
|
|
71
63
|
const generator = handlerDef.handler(parseResult.data);
|
|
72
64
|
for await (const chunk of generator) {
|
|
73
65
|
const responseParseResult = handlerDef.responseSchema.safeParse(chunk);
|
|
74
66
|
if (!responseParseResult.success) {
|
|
75
|
-
|
|
76
|
-
.map((i) => `${String(i.path.join("."))}: ${i.message}`)
|
|
77
|
-
.join(", ");
|
|
78
|
-
throw new PluginResponseValidationFailedError(handlerName, details);
|
|
67
|
+
throw new PluginResponseValidationFailedError(handlerName, formatZodError(responseParseResult.error));
|
|
79
68
|
}
|
|
80
69
|
yield {
|
|
81
70
|
type: "pluginInvokeStream",
|