@qvac/sdk 0.6.1 → 0.7.1
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/LICENSE +1 -24
- package/NOTICE +632 -0
- package/README.md +2 -1
- package/dist/client/api/embed.js +1 -4
- package/dist/client/api/get-model-info.d.ts +10 -3
- package/dist/client/api/index.d.ts +2 -0
- package/dist/client/api/index.js +2 -0
- package/dist/client/api/invoke-plugin.d.ts +14 -0
- package/dist/client/api/invoke-plugin.js +38 -0
- package/dist/client/api/registry.d.ts +14 -0
- package/dist/client/api/registry.js +37 -0
- package/dist/client/api/transcribe.js +0 -4
- package/dist/client/api/unload-model.js +1 -1
- package/dist/client/config-loader/resolve-config.node.d.ts +3 -2
- package/dist/client/config-loader/resolve-config.node.js +31 -4
- package/dist/client/init-hooks.js +2 -1
- package/dist/client/rpc/bare-client.d.ts +2 -0
- package/dist/client/rpc/bare-client.js +48 -7
- package/dist/client/rpc/expo-device-info.d.ts +6 -0
- package/dist/client/rpc/expo-device-info.js +32 -0
- package/dist/client/rpc/expo-rpc-client.js +9 -32
- package/dist/client/rpc/node-rpc-client.d.ts +1 -1
- package/dist/client/rpc/node-rpc-client.js +201 -28
- package/dist/client/rpc/rpc-client.d.ts +1 -1
- package/dist/client/rpc/rpc-client.js +2 -2
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/registry.d.ts +2 -0
- package/dist/constants/registry.js +1 -0
- package/dist/examples/cache-management.js +2 -2
- package/dist/examples/config/plugins/plugins.config.d.ts +5 -0
- package/dist/examples/config/plugins/plugins.config.js +4 -0
- package/dist/examples/download-with-blind-relays.js +1 -1
- package/dist/examples/llamacpp-multimodal.js +3 -3
- package/dist/examples/llamacpp-native-tools.js +2 -2
- package/dist/examples/mcp-websearch.js +2 -2
- package/dist/examples/multi-model-demo.js +2 -2
- package/dist/examples/ocr-fasttext.js +4 -4
- package/dist/examples/parallel-download.js +2 -2
- package/dist/examples/plugins.d.ts +14 -0
- package/dist/examples/plugins.js +129 -0
- package/dist/examples/registry-query.d.ts +2 -0
- package/dist/examples/registry-query.js +87 -0
- package/dist/examples/translation/translation-bergamot-batch.js +2 -2
- package/dist/examples/translation/translation-bergamot.js +4 -4
- package/dist/examples/translation/translation-opus.js +2 -2
- package/dist/examples/translation/translation-stream.js +2 -2
- package/dist/examples/tts/chatterbox.d.ts +2 -0
- package/dist/examples/tts/chatterbox.js +59 -0
- package/dist/examples/tts/supertonic.d.ts +2 -0
- package/dist/examples/tts/supertonic.js +51 -0
- package/dist/examples/tts/utils.d.ts +17 -0
- package/dist/examples/tts/utils.js +93 -0
- package/dist/examples/whispercpp-filesystem.js +2 -3
- package/dist/examples/whispercpp-microphone-record.js +1 -2
- package/dist/examples/whispercpp-prompt.js +2 -3
- package/dist/expo/plugins/index.d.ts +3 -0
- package/dist/expo/plugins/index.js +3 -0
- package/dist/expo/plugins/resolve-sdk-package-dir.d.ts +15 -0
- package/dist/expo/plugins/resolve-sdk-package-dir.js +31 -0
- package/dist/expo/plugins/withAndroidArchitecture.d.ts +8 -0
- package/dist/expo/plugins/withAndroidArchitecture.js +52 -0
- package/dist/expo/plugins/withAndroidNdkVersion.d.ts +8 -0
- package/dist/expo/plugins/withAndroidNdkVersion.js +31 -0
- package/dist/expo/plugins/withDeviceInfo.d.ts +10 -0
- package/dist/expo/plugins/withDeviceInfo.js +48 -0
- package/dist/expo/plugins/withMobileBundle.d.ts +5 -2
- package/dist/expo/plugins/withMobileBundle.js +121 -65
- package/dist/expo/plugins/withQvacSDK.d.ts +4 -0
- package/dist/expo/plugins/withQvacSDK.js +26 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/models/{hyperdrive → registry}/index.js +1 -1
- package/dist/models/registry/models.d.ts +9056 -0
- package/dist/models/registry/models.js +9351 -0
- package/dist/models/update-models/codegen.d.ts +3 -0
- package/dist/models/update-models/codegen.js +135 -0
- package/dist/models/update-models/history.d.ts +13 -0
- package/dist/models/update-models/history.js +88 -0
- package/dist/models/update-models/index.d.ts +2 -0
- package/dist/models/update-models/index.js +124 -0
- package/dist/models/update-models/naming.d.ts +4 -0
- package/dist/models/update-models/naming.js +314 -0
- package/dist/models/update-models/processing.d.ts +8 -0
- package/dist/models/update-models/processing.js +67 -0
- package/dist/models/update-models/registry.d.ts +4 -0
- package/dist/models/update-models/registry.js +67 -0
- package/dist/models/update-models/shards.d.ts +4 -0
- package/dist/models/update-models/shards.js +57 -0
- package/dist/models/update-models/types.d.ts +60 -0
- package/dist/models/update-models/types.js +1 -0
- package/dist/models/update-models/utils.d.ts +3 -0
- package/dist/models/update-models/utils.js +21 -0
- package/dist/pear/pre.d.ts +18 -0
- package/dist/pear/pre.js +433 -0
- package/dist/schemas/common.d.ts +286 -32
- package/dist/schemas/common.js +12 -0
- package/dist/schemas/download-asset.d.ts +35 -11
- package/dist/schemas/download-asset.js +5 -1
- package/dist/schemas/engine-addon-map.d.ts +5 -0
- package/dist/schemas/engine-addon-map.js +49 -0
- package/dist/schemas/get-model-info.d.ts +32 -16
- package/dist/schemas/get-model-info.js +17 -8
- package/dist/schemas/index.d.ts +4 -1
- package/dist/schemas/index.js +4 -4
- package/dist/schemas/load-model.d.ts +2224 -900
- package/dist/schemas/load-model.js +130 -25
- package/dist/schemas/model-src-utils.d.ts +30 -12
- package/dist/schemas/model-src-utils.js +5 -2
- package/dist/schemas/model-types.d.ts +15 -4
- package/dist/schemas/model-types.js +21 -4
- package/dist/schemas/path-validation.d.ts +10 -0
- package/dist/schemas/path-validation.js +22 -0
- package/dist/schemas/plugin.d.ts +180 -0
- package/dist/schemas/plugin.js +132 -0
- package/dist/schemas/rag.d.ts +2 -2
- package/dist/schemas/rag.js +7 -3
- package/dist/schemas/registry.d.ts +338 -0
- package/dist/schemas/registry.js +91 -0
- package/dist/schemas/sdk-errors-client.d.ts +6 -6
- package/dist/schemas/sdk-errors-client.js +15 -15
- package/dist/schemas/sdk-errors-registry.d.ts +6 -0
- package/dist/schemas/sdk-errors-registry.js +8 -0
- package/dist/schemas/sdk-errors-server.d.ts +16 -2
- package/dist/schemas/sdk-errors-server.js +90 -10
- package/dist/schemas/text-to-speech.d.ts +670 -1
- package/dist/schemas/text-to-speech.js +51 -2
- package/dist/schemas/translate.d.ts +2 -2
- package/dist/scripts/run-security-tests-bare.d.ts +2 -0
- package/dist/scripts/run-security-tests-bare.js +78 -0
- package/dist/scripts/run-security-tests.d.ts +2 -0
- package/dist/scripts/run-security-tests.js +22 -0
- package/dist/server/bare/delegate-rpc-client.d.ts +6 -0
- package/dist/server/bare/delegate-rpc-client.js +136 -56
- package/dist/server/bare/{addons/llamacpp-embedding/handlers → ops}/embed.d.ts +1 -1
- package/dist/server/bare/{addons/llamacpp-embedding/handlers → ops}/embed.js +3 -3
- package/dist/server/bare/{addons/llamacpp-completion → ops}/kv-cache-utils.js +6 -6
- package/dist/server/bare/{addons → ops}/load-model.js +32 -42
- package/dist/server/bare/{addons/nmtcpp-translation/handlers → ops}/translate.d.ts +1 -1
- package/dist/server/bare/{addons/nmtcpp-translation/handlers → ops}/translate.js +5 -4
- package/dist/server/bare/plugins/index.d.ts +7 -0
- package/dist/server/bare/plugins/index.js +6 -0
- package/dist/server/bare/{addons/llamacpp-completion → plugins/llamacpp-completion/ops}/cache-logger.js +1 -1
- package/dist/server/bare/{addons/llamacpp-completion/handlers → plugins/llamacpp-completion/ops}/completion-stream.d.ts +1 -1
- package/dist/server/bare/{addons/llamacpp-completion/handlers → plugins/llamacpp-completion/ops}/completion-stream.js +2 -3
- package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +115 -0
- package/dist/server/bare/plugins/llamacpp-completion/plugin.js +142 -0
- package/dist/server/bare/plugins/llamacpp-embedding/plugin.d.ts +24 -0
- package/dist/server/bare/plugins/llamacpp-embedding/plugin.js +73 -0
- package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +45 -0
- package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +97 -0
- package/dist/server/bare/{addons/onnx-ocr/handlers → plugins/onnx-ocr/ops}/ocr-stream.js +2 -1
- package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +38 -0
- package/dist/server/bare/{addons/onnx-ocr/create-model.js → plugins/onnx-ocr/plugin.js} +39 -2
- package/dist/server/bare/{addons/onnx-tts/handlers → plugins/onnx-tts/ops}/text-to-speech.js +23 -1
- package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +26 -0
- package/dist/server/bare/plugins/onnx-tts/plugin.js +162 -0
- package/dist/server/bare/plugins/onnx-tts/wav-helper.d.ts +14 -0
- package/dist/server/bare/plugins/onnx-tts/wav-helper.js +107 -0
- package/dist/server/bare/plugins/whispercpp-transcription/ops/transcribe-stream.d.ts +3 -0
- package/dist/server/bare/{addons/whispercpp-transcription/handlers → plugins/whispercpp-transcription/ops}/transcribe-stream.js +1 -2
- package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +31 -0
- package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +74 -0
- package/dist/server/bare/rag-hyperdb/handlers/ingest.js +1 -1
- package/dist/server/bare/rag-hyperdb/handlers/search.js +1 -1
- package/dist/server/bare/rag-hyperdb/rag-workspace-manager.js +2 -1
- package/dist/server/bare/registry/config-registry.js +1 -1
- package/dist/server/bare/registry/registry-client.d.ts +5 -0
- package/dist/server/bare/registry/registry-client.js +37 -0
- package/dist/server/env.d.ts +22 -0
- package/dist/server/env.js +57 -0
- package/dist/server/plugins/index.d.ts +2 -0
- package/dist/server/plugins/index.js +1 -0
- package/dist/server/plugins/registry.d.ts +9 -0
- package/dist/server/plugins/registry.js +75 -0
- package/dist/server/rpc/delegate-transport.d.ts +11 -0
- package/dist/server/rpc/delegate-transport.js +60 -0
- package/dist/server/rpc/handle-request.d.ts +0 -9
- package/dist/server/rpc/handle-request.js +13 -321
- package/dist/server/rpc/handler-registry.d.ts +3 -0
- package/dist/server/rpc/handler-registry.js +84 -0
- package/dist/server/rpc/handler-utils.d.ts +22 -0
- package/dist/server/rpc/handler-utils.js +87 -0
- package/dist/server/rpc/handlers/cancelHandler.js +1 -1
- package/dist/server/rpc/handlers/completion-stream-delegated.js +1 -1
- package/dist/server/rpc/handlers/completion-stream.js +2 -43
- package/dist/server/rpc/handlers/delete-cache.js +1 -1
- package/dist/server/rpc/handlers/embed.js +2 -21
- package/dist/server/rpc/handlers/get-model-info.js +19 -15
- package/dist/server/rpc/handlers/index.d.ts +9 -0
- package/dist/server/rpc/handlers/index.js +9 -0
- package/dist/server/rpc/handlers/load-model/download-manager.d.ts +1 -0
- package/dist/server/rpc/handlers/load-model/download-manager.js +3 -0
- package/dist/server/rpc/handlers/load-model/handler.js +47 -24
- package/dist/server/rpc/handlers/load-model/http.js +3 -2
- package/dist/server/rpc/handlers/load-model/hyperdrive.js +14 -10
- package/dist/server/rpc/handlers/load-model/registry.d.ts +10 -0
- package/dist/server/rpc/handlers/load-model/registry.js +571 -0
- package/dist/server/rpc/handlers/load-model/resolve.js +18 -5
- package/dist/server/rpc/handlers/load-model-delegated.js +4 -2
- package/dist/server/rpc/handlers/ocr-stream.js +2 -37
- package/dist/server/rpc/handlers/ping.d.ts +3 -0
- package/dist/server/rpc/handlers/ping.js +3 -0
- package/dist/server/rpc/handlers/plugin-dispatch.d.ts +15 -0
- package/dist/server/rpc/handlers/plugin-dispatch.js +54 -0
- package/dist/server/rpc/handlers/plugin-invoke.d.ts +4 -0
- package/dist/server/rpc/handlers/plugin-invoke.js +87 -0
- package/dist/server/rpc/handlers/registry.d.ts +5 -0
- package/dist/server/rpc/handlers/registry.js +157 -0
- package/dist/server/rpc/handlers/text-to-speech.js +2 -5
- package/dist/server/rpc/handlers/transcribe-stream.js +2 -30
- package/dist/server/rpc/handlers/translate.js +2 -42
- package/dist/server/rpc/handlers/unload-model.js +1 -1
- package/dist/server/rpc/index.d.ts +1 -1
- package/dist/server/utils/archive.d.ts +1 -0
- package/dist/server/utils/archive.js +10 -2
- package/dist/server/utils/cache.d.ts +5 -0
- package/dist/server/utils/cache.js +26 -2
- package/dist/server/utils/gguf-tensor-extractor.js +3 -3
- package/dist/server/utils/index.d.ts +1 -1
- package/dist/server/utils/index.js +1 -1
- package/dist/server/utils/path-security.d.ts +12 -0
- package/dist/server/utils/path-security.js +25 -0
- package/dist/server/utils/shard-utils.js +3 -3
- package/dist/server/worker-core.d.ts +6 -0
- package/dist/server/worker-core.js +88 -0
- package/dist/server/worker.d.ts +4 -5
- package/dist/server/worker.js +18 -116
- package/dist/utils/errors-client.d.ts +9 -6
- package/dist/utils/errors-client.js +19 -11
- package/dist/utils/errors-server.d.ts +44 -5
- package/dist/utils/errors-server.js +82 -9
- package/dist/utils/path-sanitize.d.ts +17 -0
- package/dist/utils/path-sanitize.js +54 -0
- package/expo/plugins/patches/android-link.mjs +63 -0
- package/expo/plugins/patches/ios-link.mjs +63 -0
- package/package.json +72 -10
- package/dist/examples/text-to-speech.d.ts +0 -3
- package/dist/examples/text-to-speech.js +0 -137
- package/dist/models/hyperdrive/models.d.ts +0 -4003
- package/dist/models/hyperdrive/models.js +0 -4282
- package/dist/server/bare/addons/index.d.ts +0 -4
- package/dist/server/bare/addons/index.js +0 -3
- package/dist/server/bare/addons/llamacpp-completion/create-model.d.ts +0 -8
- package/dist/server/bare/addons/llamacpp-completion/create-model.js +0 -43
- package/dist/server/bare/addons/llamacpp-completion/index.d.ts +0 -4
- package/dist/server/bare/addons/llamacpp-completion/index.js +0 -3
- package/dist/server/bare/addons/llamacpp-embedding/create-model.d.ts +0 -8
- package/dist/server/bare/addons/llamacpp-embedding/create-model.js +0 -38
- package/dist/server/bare/addons/llamacpp-embedding/index.d.ts +0 -3
- package/dist/server/bare/addons/llamacpp-embedding/index.js +0 -2
- package/dist/server/bare/addons/nmtcpp-translation/create-model.d.ts +0 -8
- package/dist/server/bare/addons/nmtcpp-translation/create-model.js +0 -52
- package/dist/server/bare/addons/nmtcpp-translation/index.d.ts +0 -3
- package/dist/server/bare/addons/nmtcpp-translation/index.js +0 -2
- package/dist/server/bare/addons/onnx-ocr/create-model.d.ts +0 -10
- package/dist/server/bare/addons/onnx-ocr/index.d.ts +0 -3
- package/dist/server/bare/addons/onnx-ocr/index.js +0 -2
- package/dist/server/bare/addons/onnx-tts/create-model.d.ts +0 -10
- package/dist/server/bare/addons/onnx-tts/create-model.js +0 -25
- package/dist/server/bare/addons/onnx-tts/index.d.ts +0 -3
- package/dist/server/bare/addons/onnx-tts/index.js +0 -2
- package/dist/server/bare/addons/whispercpp-transcription/create-model.d.ts +0 -10
- package/dist/server/bare/addons/whispercpp-transcription/create-model.js +0 -37
- package/dist/server/bare/addons/whispercpp-transcription/handlers/transcribe-stream.d.ts +0 -3
- package/dist/server/bare/addons/whispercpp-transcription/index.d.ts +0 -3
- package/dist/server/bare/addons/whispercpp-transcription/index.js +0 -2
- /package/dist/models/{hyperdrive → registry}/index.d.ts +0 -0
- /package/dist/server/bare/{addons → ops}/cancel.d.ts +0 -0
- /package/dist/server/bare/{addons → ops}/cancel.js +0 -0
- /package/dist/server/bare/{addons/llamacpp-completion → ops}/kv-cache-utils.d.ts +0 -0
- /package/dist/server/bare/{addons → ops}/load-model.d.ts +0 -0
- /package/dist/server/bare/{addons → ops}/unload-model.d.ts +0 -0
- /package/dist/server/bare/{addons → ops}/unload-model.js +0 -0
- /package/dist/server/bare/{addons/llamacpp-completion → plugins/llamacpp-completion/ops}/cache-logger.d.ts +0 -0
- /package/dist/server/bare/{addons/onnx-ocr/handlers → plugins/onnx-ocr/ops}/ocr-stream.d.ts +0 -0
- /package/dist/server/bare/{addons/onnx-tts/handlers → plugins/onnx-tts/ops}/text-to-speech.d.ts +0 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import fs, { promises as fsPromises } from "bare-fs";
|
|
2
|
+
import path from "bare-path";
|
|
3
|
+
import {} from "bare-stream";
|
|
4
|
+
import { AbortController } from "bare-abort-controller";
|
|
5
|
+
import { getModelsCacheDir, generateShortHash, detectShardedModel, getShardedModelCacheDir, getShardPath, getOnnxModelPath, calculateFileChecksum, extractTensorsFromShards, calculatePercentage, } from "../../../utils/index.js";
|
|
6
|
+
import { getModelByPath } from "../../../../models/registry/index.js";
|
|
7
|
+
import { getRegistryClient, closeRegistryClient, } from "../../../bare/registry/registry-client.js";
|
|
8
|
+
import { getActiveDownload, registerDownload, unregisterDownload, createRegistryDownloadKey, clearClearCacheFlag, } from "./download-manager.js";
|
|
9
|
+
import { ChecksumValidationFailedError, DownloadCancelledError, ModelNotFoundError, RegistryDownloadFailedError, } from "../../../../utils/errors-server.js";
|
|
10
|
+
import { getServerLogger } from "../../../../logging/index.js";
|
|
11
|
+
const logger = getServerLogger();
|
|
12
|
+
const REGISTRY_STREAM_TIMEOUT_MS = 60_000;
|
|
13
|
+
function buildBlobBinding(meta) {
|
|
14
|
+
return {
|
|
15
|
+
coreKey: meta.blobCoreKey,
|
|
16
|
+
blockOffset: meta.blobBlockOffset,
|
|
17
|
+
blockLength: meta.blobBlockLength,
|
|
18
|
+
byteOffset: meta.blobByteOffset,
|
|
19
|
+
byteLength: meta.expectedSize,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate a cached file against expected size and checksum.
|
|
24
|
+
*/
|
|
25
|
+
async function validateCachedFile(modelPath, modelFileName, expectedSize, expectedChecksum) {
|
|
26
|
+
try {
|
|
27
|
+
await fsPromises.access(modelPath);
|
|
28
|
+
const localStats = await fsPromises.stat(modelPath);
|
|
29
|
+
const localSize = localStats.size;
|
|
30
|
+
if (localSize === expectedSize) {
|
|
31
|
+
logger.info(`✅ Model cached with correct size: ${modelPath}`);
|
|
32
|
+
// Validate checksum if provided
|
|
33
|
+
if (expectedChecksum && expectedChecksum.length === 64) {
|
|
34
|
+
const checksum = await calculateFileChecksum(modelPath);
|
|
35
|
+
if (checksum !== expectedChecksum) {
|
|
36
|
+
throw new ChecksumValidationFailedError(`${modelFileName}. Expected: ${expectedChecksum}. Actual: ${checksum}. File may be corrupted`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
logger.info(`✅ Model already cached and validated: ${modelPath}`);
|
|
40
|
+
return modelPath;
|
|
41
|
+
}
|
|
42
|
+
// File exists but wrong size (incomplete/corrupted) - remove it
|
|
43
|
+
logger.warn(`🗑️ Removing incomplete cached file (expected ${expectedSize}, got ${localSize}): ${modelPath}`);
|
|
44
|
+
await fsPromises.unlink(modelPath);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof ChecksumValidationFailedError) {
|
|
49
|
+
// Corrupted file - remove it so next download starts fresh
|
|
50
|
+
logger.warn(`🗑️ Removing corrupted cached file: ${modelPath}`);
|
|
51
|
+
await fsPromises.unlink(modelPath).catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
// File doesn't exist or was cleaned up - need to download
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Download a single file from the registry to filesystem.
|
|
59
|
+
* When blobBinding is provided, uses direct blob download (skips metadata sync).
|
|
60
|
+
* Otherwise falls back to metadata-based downloadModel.
|
|
61
|
+
*/
|
|
62
|
+
async function downloadSingleFileFromRegistry(registryPath, registrySource, modelPath, modelFileName, downloadKey, expectedSize, expectedChecksum, progressCallback, signal, blobBinding) {
|
|
63
|
+
if (signal?.aborted) {
|
|
64
|
+
throw new DownloadCancelledError();
|
|
65
|
+
}
|
|
66
|
+
const client = await getRegistryClient();
|
|
67
|
+
try {
|
|
68
|
+
let readStream;
|
|
69
|
+
if (blobBinding) {
|
|
70
|
+
logger.info(`📥 Downloading blob directly: ${modelFileName}`);
|
|
71
|
+
const result = await client.downloadBlob(blobBinding, { timeout: REGISTRY_STREAM_TIMEOUT_MS });
|
|
72
|
+
if (!("stream" in result.artifact)) {
|
|
73
|
+
throw new RegistryDownloadFailedError(`No stream returned for blob ${modelFileName}`);
|
|
74
|
+
}
|
|
75
|
+
readStream = result.artifact.stream;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
logger.info(`📥 Downloading from registry: ${registryPath}`);
|
|
79
|
+
const result = await client.downloadModel(registryPath, registrySource, {
|
|
80
|
+
timeout: REGISTRY_STREAM_TIMEOUT_MS,
|
|
81
|
+
});
|
|
82
|
+
if (!("stream" in result.artifact)) {
|
|
83
|
+
throw new RegistryDownloadFailedError(`No stream returned for ${registryPath}`);
|
|
84
|
+
}
|
|
85
|
+
readStream = result.artifact.stream;
|
|
86
|
+
}
|
|
87
|
+
const dir = path.dirname(modelPath);
|
|
88
|
+
await fsPromises.mkdir(dir, { recursive: true });
|
|
89
|
+
const writeStream = fs.createWriteStream(modelPath);
|
|
90
|
+
// Track progress
|
|
91
|
+
let downloadedBytes = 0;
|
|
92
|
+
readStream.on("data", (chunk) => {
|
|
93
|
+
const buffer = chunk;
|
|
94
|
+
downloadedBytes += buffer.length;
|
|
95
|
+
if (progressCallback) {
|
|
96
|
+
progressCallback({
|
|
97
|
+
type: "modelProgress",
|
|
98
|
+
downloaded: downloadedBytes,
|
|
99
|
+
total: expectedSize || downloadedBytes,
|
|
100
|
+
percentage: expectedSize
|
|
101
|
+
? calculatePercentage(downloadedBytes, expectedSize)
|
|
102
|
+
: 0,
|
|
103
|
+
downloadKey,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Pipe stream to file
|
|
108
|
+
readStream.pipe(writeStream);
|
|
109
|
+
// Wait for download to complete
|
|
110
|
+
await new Promise((resolve, reject) => {
|
|
111
|
+
writeStream.on("finish", resolve);
|
|
112
|
+
writeStream.on("error", reject);
|
|
113
|
+
readStream.on("error", reject);
|
|
114
|
+
signal?.addEventListener("abort", () => reject(new Error("Download cancelled")), { once: true });
|
|
115
|
+
});
|
|
116
|
+
logger.info(`✅ Downloaded to ${modelPath}`);
|
|
117
|
+
// Validate file size
|
|
118
|
+
const stats = await fsPromises.stat(modelPath);
|
|
119
|
+
if (expectedSize && stats.size !== expectedSize) {
|
|
120
|
+
await fsPromises.unlink(modelPath).catch(() => { });
|
|
121
|
+
throw new ChecksumValidationFailedError(`${modelFileName}. File size mismatch: expected ${expectedSize}, got ${stats.size}`);
|
|
122
|
+
}
|
|
123
|
+
// Validate checksum
|
|
124
|
+
if (expectedChecksum && expectedChecksum.length === 64) {
|
|
125
|
+
const checksum = await calculateFileChecksum(modelPath);
|
|
126
|
+
if (checksum !== expectedChecksum) {
|
|
127
|
+
await fsPromises.unlink(modelPath);
|
|
128
|
+
throw new ChecksumValidationFailedError(`${modelFileName}. Expected: ${expectedChecksum}. Actual: ${checksum}`);
|
|
129
|
+
}
|
|
130
|
+
logger.info(`✅ Checksum validated for ${modelFileName}`);
|
|
131
|
+
}
|
|
132
|
+
// Send final 100% progress
|
|
133
|
+
if (progressCallback) {
|
|
134
|
+
progressCallback({
|
|
135
|
+
type: "modelProgress",
|
|
136
|
+
downloaded: stats.size,
|
|
137
|
+
total: stats.size,
|
|
138
|
+
percentage: 100,
|
|
139
|
+
downloadKey,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await closeRegistryClient();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Find all shards for a model using path prefix query.
|
|
149
|
+
*/
|
|
150
|
+
async function findModelShards(registryPath) {
|
|
151
|
+
const client = await getRegistryClient();
|
|
152
|
+
try {
|
|
153
|
+
// Extract the base path without the shard suffix
|
|
154
|
+
const shardInfo = detectShardedModel(registryPath.split("/").pop() || "");
|
|
155
|
+
if (!shardInfo.isSharded) {
|
|
156
|
+
throw new Error(`Not a sharded model path: ${registryPath}`);
|
|
157
|
+
}
|
|
158
|
+
// Get path prefix by removing the shard suffix
|
|
159
|
+
const pathPrefix = registryPath.replace(/-\d{5}-of-\d{5}\./, ".");
|
|
160
|
+
const basePath = pathPrefix.substring(0, pathPrefix.lastIndexOf("."));
|
|
161
|
+
logger.info(`🔍 Finding shards with prefix: ${basePath}`);
|
|
162
|
+
// Use indexed path range query instead of fetching all models
|
|
163
|
+
const shards = await client.findModels({
|
|
164
|
+
gte: { path: basePath },
|
|
165
|
+
lte: { path: basePath + "\uffff" },
|
|
166
|
+
});
|
|
167
|
+
// Sort shards by shard number
|
|
168
|
+
const sortedShards = shards
|
|
169
|
+
.filter((s) => {
|
|
170
|
+
const info = detectShardedModel(s.path.split("/").pop() || "");
|
|
171
|
+
return info.isSharded;
|
|
172
|
+
})
|
|
173
|
+
.sort((a, b) => {
|
|
174
|
+
const aInfo = detectShardedModel(a.path.split("/").pop() || "");
|
|
175
|
+
const bInfo = detectShardedModel(b.path.split("/").pop() || "");
|
|
176
|
+
return (aInfo.currentShard || 0) - (bInfo.currentShard || 0);
|
|
177
|
+
})
|
|
178
|
+
.map((s) => ({
|
|
179
|
+
path: s.path,
|
|
180
|
+
source: s.source,
|
|
181
|
+
size: s.blobBinding?.byteLength || 0,
|
|
182
|
+
checksum: s.blobBinding?.["sha256"] ||
|
|
183
|
+
s.sha256 ||
|
|
184
|
+
"",
|
|
185
|
+
}));
|
|
186
|
+
logger.info(`📦 Found ${sortedShards.length} shards`);
|
|
187
|
+
return sortedShards;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
await closeRegistryClient();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Download sharded model files from registry.
|
|
195
|
+
* When localShardMetadata is provided, uses pre-computed metadata + blob direct download
|
|
196
|
+
* instead of querying the registry for shard info.
|
|
197
|
+
*/
|
|
198
|
+
async function downloadShardedFilesFromRegistry(registryPath, registrySource, cacheKey, progressCallback, signal, localShardMetadata) {
|
|
199
|
+
if (signal?.aborted) {
|
|
200
|
+
throw new DownloadCancelledError();
|
|
201
|
+
}
|
|
202
|
+
let shards;
|
|
203
|
+
if (localShardMetadata?.length) {
|
|
204
|
+
shards = localShardMetadata.map((shard) => ({
|
|
205
|
+
filename: shard.filename,
|
|
206
|
+
size: shard.expectedSize,
|
|
207
|
+
checksum: shard.sha256Checksum,
|
|
208
|
+
path: registryPath,
|
|
209
|
+
source: registrySource,
|
|
210
|
+
blobBinding: buildBlobBinding(shard),
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const filename = registryPath.split("/").pop() || registryPath;
|
|
215
|
+
const shardInfo = detectShardedModel(filename);
|
|
216
|
+
if (!shardInfo.isSharded || !shardInfo.totalShards) {
|
|
217
|
+
throw new RegistryDownloadFailedError(`Not a sharded model: ${filename}`);
|
|
218
|
+
}
|
|
219
|
+
const remoteShards = await findModelShards(registryPath);
|
|
220
|
+
if (remoteShards.length === 0) {
|
|
221
|
+
throw new ModelNotFoundError(`No shards found for ${registryPath}`);
|
|
222
|
+
}
|
|
223
|
+
if (remoteShards.length !== shardInfo.totalShards) {
|
|
224
|
+
logger.warn(`⚠️ Expected ${shardInfo.totalShards} shards but found ${remoteShards.length}`);
|
|
225
|
+
}
|
|
226
|
+
shards = remoteShards.map((s) => ({
|
|
227
|
+
filename: s.path.split("/").pop() || "",
|
|
228
|
+
size: s.size,
|
|
229
|
+
checksum: s.checksum,
|
|
230
|
+
path: s.path,
|
|
231
|
+
source: s.source,
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
const shardDir = getShardedModelCacheDir(cacheKey);
|
|
235
|
+
const downloadKey = createRegistryDownloadKey(registryPath);
|
|
236
|
+
logger.info(`📥 Downloading sharded model: ${shards.length} shards to ${shardDir}`);
|
|
237
|
+
const overallTotal = shards.reduce((sum, s) => sum + s.size, 0);
|
|
238
|
+
let overallDownloaded = 0;
|
|
239
|
+
for (let i = 0; i < shards.length; i++) {
|
|
240
|
+
if (signal?.aborted) {
|
|
241
|
+
throw new DownloadCancelledError();
|
|
242
|
+
}
|
|
243
|
+
const shard = shards[i];
|
|
244
|
+
const shardPath = getShardPath(cacheKey, shard.filename);
|
|
245
|
+
const cachedPath = await validateCachedFile(shardPath, shard.filename, shard.size, shard.checksum);
|
|
246
|
+
if (cachedPath) {
|
|
247
|
+
logger.debug(`✅ Shard ${i + 1}/${shards.length} already cached`);
|
|
248
|
+
overallDownloaded += shard.size;
|
|
249
|
+
if (progressCallback) {
|
|
250
|
+
progressCallback({
|
|
251
|
+
type: "modelProgress",
|
|
252
|
+
downloaded: shard.size,
|
|
253
|
+
total: shard.size,
|
|
254
|
+
percentage: 100,
|
|
255
|
+
downloadKey,
|
|
256
|
+
shardInfo: {
|
|
257
|
+
currentShard: i + 1,
|
|
258
|
+
totalShards: shards.length,
|
|
259
|
+
shardName: shard.filename,
|
|
260
|
+
overallDownloaded,
|
|
261
|
+
overallTotal,
|
|
262
|
+
overallPercentage: calculatePercentage(overallDownloaded, overallTotal),
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
logger.info(`📥 Downloading shard ${i + 1}/${shards.length}: ${shard.filename}`);
|
|
269
|
+
const shardProgressCallback = progressCallback
|
|
270
|
+
? (progress) => {
|
|
271
|
+
const currentOverall = overallDownloaded + progress.downloaded;
|
|
272
|
+
progressCallback({
|
|
273
|
+
...progress,
|
|
274
|
+
downloadKey,
|
|
275
|
+
shardInfo: {
|
|
276
|
+
currentShard: i + 1,
|
|
277
|
+
totalShards: shards.length,
|
|
278
|
+
shardName: shard.filename,
|
|
279
|
+
overallDownloaded: currentOverall,
|
|
280
|
+
overallTotal,
|
|
281
|
+
overallPercentage: calculatePercentage(currentOverall, overallTotal),
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
: undefined;
|
|
286
|
+
await downloadSingleFileFromRegistry(shard.path, shard.source, shardPath, shard.filename, downloadKey, shard.size, shard.checksum, shardProgressCallback, signal, shard.blobBinding);
|
|
287
|
+
overallDownloaded += shard.size;
|
|
288
|
+
logger.info(`✅ Shard ${i + 1}/${shards.length} downloaded`);
|
|
289
|
+
}
|
|
290
|
+
const firstShardFilename = shards[0].filename;
|
|
291
|
+
await extractTensorsFromShards(shardDir, firstShardFilename);
|
|
292
|
+
if (progressCallback) {
|
|
293
|
+
const lastShard = shards[shards.length - 1];
|
|
294
|
+
progressCallback({
|
|
295
|
+
type: "modelProgress",
|
|
296
|
+
downloaded: overallTotal,
|
|
297
|
+
total: overallTotal,
|
|
298
|
+
percentage: 100,
|
|
299
|
+
downloadKey,
|
|
300
|
+
shardInfo: {
|
|
301
|
+
currentShard: shards.length,
|
|
302
|
+
totalShards: shards.length,
|
|
303
|
+
shardName: lastShard.filename,
|
|
304
|
+
overallDownloaded: overallTotal,
|
|
305
|
+
overallTotal,
|
|
306
|
+
overallPercentage: 100,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return getShardPath(cacheKey, firstShardFilename);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Find companion ONNX data file in registry.
|
|
314
|
+
* ONNX models with external data have a .onnx file and a .onnx_data file.
|
|
315
|
+
*/
|
|
316
|
+
function findOnnxCompanionDataFile(registryPath) {
|
|
317
|
+
if (!registryPath.endsWith(".onnx"))
|
|
318
|
+
return undefined;
|
|
319
|
+
const dataPath = registryPath + "_data";
|
|
320
|
+
return getModelByPath(dataPath);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Download ONNX model with companion data file from registry.
|
|
324
|
+
* Both files are placed in the same directory to satisfy ONNX Runtime requirements.
|
|
325
|
+
* When blob bindings are provided, uses direct blob download (skips metadata sync).
|
|
326
|
+
*/
|
|
327
|
+
async function downloadOnnxWithDataFromRegistry(registryPath, registrySource, companionDataFile, cacheKey, progressCallback, signal, mainBlobBinding, dataBlobBinding) {
|
|
328
|
+
if (signal?.aborted) {
|
|
329
|
+
throw new DownloadCancelledError();
|
|
330
|
+
}
|
|
331
|
+
const mainFilename = registryPath.split("/").pop() || registryPath;
|
|
332
|
+
const dataFilename = companionDataFile.registryPath.split("/").pop() || "";
|
|
333
|
+
const downloadKey = createRegistryDownloadKey(registryPath);
|
|
334
|
+
const mainModelMetadata = getModelByPath(registryPath);
|
|
335
|
+
const mainPath = getOnnxModelPath(cacheKey, mainFilename);
|
|
336
|
+
const dataPath = getOnnxModelPath(cacheKey, dataFilename);
|
|
337
|
+
logger.info(`📥 Downloading ONNX model with external data: ${mainFilename} + ${dataFilename}`);
|
|
338
|
+
const mainExpectedSize = mainModelMetadata?.expectedSize || 0;
|
|
339
|
+
const mainChecksum = mainModelMetadata?.sha256Checksum || "";
|
|
340
|
+
const dataExpectedSize = companionDataFile.expectedSize || 0;
|
|
341
|
+
const dataChecksum = companionDataFile.sha256Checksum || "";
|
|
342
|
+
const overallTotal = mainExpectedSize + dataExpectedSize;
|
|
343
|
+
let overallDownloaded = 0;
|
|
344
|
+
// Check if main file already cached
|
|
345
|
+
const cachedMainPath = await validateCachedFile(mainPath, mainFilename, mainExpectedSize, mainChecksum);
|
|
346
|
+
if (cachedMainPath) {
|
|
347
|
+
logger.info(`✅ Main ONNX file already cached: ${mainFilename}`);
|
|
348
|
+
overallDownloaded += mainExpectedSize;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
// Download main ONNX file
|
|
352
|
+
const mainProgressCallback = progressCallback
|
|
353
|
+
? (progress) => {
|
|
354
|
+
const currentOverall = overallDownloaded + progress.downloaded;
|
|
355
|
+
progressCallback({
|
|
356
|
+
...progress,
|
|
357
|
+
downloadKey,
|
|
358
|
+
onnxInfo: {
|
|
359
|
+
currentFile: mainFilename,
|
|
360
|
+
fileIndex: 1,
|
|
361
|
+
totalFiles: 2,
|
|
362
|
+
overallDownloaded: currentOverall,
|
|
363
|
+
overallTotal,
|
|
364
|
+
overallPercentage: calculatePercentage(currentOverall, overallTotal),
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
: undefined;
|
|
369
|
+
await downloadSingleFileFromRegistry(registryPath, registrySource, mainPath, mainFilename, downloadKey, mainExpectedSize, mainChecksum, mainProgressCallback, signal, mainBlobBinding);
|
|
370
|
+
overallDownloaded += mainExpectedSize;
|
|
371
|
+
logger.info(`✅ Main ONNX file downloaded: ${mainFilename}`);
|
|
372
|
+
}
|
|
373
|
+
// Check if data file already cached
|
|
374
|
+
const cachedDataPath = await validateCachedFile(dataPath, dataFilename, dataExpectedSize, dataChecksum);
|
|
375
|
+
if (cachedDataPath) {
|
|
376
|
+
logger.info(`✅ ONNX data file already cached: ${dataFilename}`);
|
|
377
|
+
overallDownloaded = overallTotal;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
// Download companion data file
|
|
381
|
+
const dataProgressCallback = progressCallback
|
|
382
|
+
? (progress) => {
|
|
383
|
+
const currentOverall = overallDownloaded + progress.downloaded;
|
|
384
|
+
progressCallback({
|
|
385
|
+
...progress,
|
|
386
|
+
downloadKey,
|
|
387
|
+
onnxInfo: {
|
|
388
|
+
currentFile: dataFilename,
|
|
389
|
+
fileIndex: 2,
|
|
390
|
+
totalFiles: 2,
|
|
391
|
+
overallDownloaded: currentOverall,
|
|
392
|
+
overallTotal,
|
|
393
|
+
overallPercentage: calculatePercentage(currentOverall, overallTotal),
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
: undefined;
|
|
398
|
+
await downloadSingleFileFromRegistry(companionDataFile.registryPath, companionDataFile.registrySource, dataPath, dataFilename, downloadKey, dataExpectedSize, dataChecksum, dataProgressCallback, signal, dataBlobBinding);
|
|
399
|
+
logger.info(`✅ ONNX data file downloaded: ${dataFilename}`);
|
|
400
|
+
}
|
|
401
|
+
// Send final 100% progress
|
|
402
|
+
if (progressCallback) {
|
|
403
|
+
progressCallback({
|
|
404
|
+
type: "modelProgress",
|
|
405
|
+
downloaded: overallTotal,
|
|
406
|
+
total: overallTotal,
|
|
407
|
+
percentage: 100,
|
|
408
|
+
downloadKey,
|
|
409
|
+
onnxInfo: {
|
|
410
|
+
currentFile: dataFilename,
|
|
411
|
+
fileIndex: 2,
|
|
412
|
+
totalFiles: 2,
|
|
413
|
+
overallDownloaded: overallTotal,
|
|
414
|
+
overallTotal,
|
|
415
|
+
overallPercentage: 100,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return mainPath;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Create a managed download with abort controller support.
|
|
423
|
+
*/
|
|
424
|
+
function createManagedDownload(downloadKey, registryPath, downloadFn, progressCallback) {
|
|
425
|
+
const abortController = new AbortController();
|
|
426
|
+
const downloadPromise = (async () => {
|
|
427
|
+
try {
|
|
428
|
+
return await downloadFn(abortController.signal);
|
|
429
|
+
}
|
|
430
|
+
finally {
|
|
431
|
+
unregisterDownload(downloadKey);
|
|
432
|
+
clearClearCacheFlag(downloadKey);
|
|
433
|
+
}
|
|
434
|
+
})();
|
|
435
|
+
const downloadEntry = {
|
|
436
|
+
key: downloadKey,
|
|
437
|
+
promise: downloadPromise,
|
|
438
|
+
abortController,
|
|
439
|
+
startTime: Date.now(),
|
|
440
|
+
type: "registry",
|
|
441
|
+
registryPath,
|
|
442
|
+
...(progressCallback && { onProgress: progressCallback }),
|
|
443
|
+
};
|
|
444
|
+
registerDownload(downloadKey, downloadEntry);
|
|
445
|
+
return downloadPromise;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Download a model from the QVAC Registry.
|
|
449
|
+
*
|
|
450
|
+
* For known models (present in models.ts), uses direct blob download which
|
|
451
|
+
* skips the registry metadata sync entirely. Falls back to metadata-based
|
|
452
|
+
* download for unknown models.
|
|
453
|
+
*/
|
|
454
|
+
export async function downloadModelFromRegistry(registryPath, registrySource, progressCallback, expectedChecksum) {
|
|
455
|
+
const downloadKey = createRegistryDownloadKey(registryPath);
|
|
456
|
+
// Check if already downloading
|
|
457
|
+
const existing = getActiveDownload(downloadKey);
|
|
458
|
+
if (existing) {
|
|
459
|
+
logger.info(`📥 Reusing existing download for: ${downloadKey}`);
|
|
460
|
+
return existing.promise;
|
|
461
|
+
}
|
|
462
|
+
const filename = registryPath.split("/").pop() || registryPath;
|
|
463
|
+
const shardInfo = detectShardedModel(filename);
|
|
464
|
+
// Look up model metadata from our generated models.ts
|
|
465
|
+
const modelMetadata = getModelByPath(registryPath);
|
|
466
|
+
if (shardInfo.isSharded) {
|
|
467
|
+
const cacheKey = generateShortHash(registryPath);
|
|
468
|
+
const localShardMeta = modelMetadata?.shardMetadata;
|
|
469
|
+
// FS pre-check for known models: if all shards cached, return immediately
|
|
470
|
+
if (localShardMeta?.length) {
|
|
471
|
+
let allCached = true;
|
|
472
|
+
for (const shard of localShardMeta) {
|
|
473
|
+
const shardPath = getShardPath(cacheKey, shard.filename);
|
|
474
|
+
const cached = await validateCachedFile(shardPath, shard.filename, shard.expectedSize, shard.sha256Checksum);
|
|
475
|
+
if (!cached) {
|
|
476
|
+
allCached = false;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (allCached) {
|
|
481
|
+
const firstShardFilename = localShardMeta[0].filename;
|
|
482
|
+
logger.info(`✅ All ${localShardMeta.length} shards cached`);
|
|
483
|
+
if (progressCallback) {
|
|
484
|
+
const overallTotal = localShardMeta.reduce((sum, s) => sum + s.expectedSize, 0);
|
|
485
|
+
progressCallback({
|
|
486
|
+
type: "modelProgress",
|
|
487
|
+
downloaded: overallTotal,
|
|
488
|
+
total: overallTotal,
|
|
489
|
+
percentage: 100,
|
|
490
|
+
downloadKey,
|
|
491
|
+
shardInfo: {
|
|
492
|
+
currentShard: localShardMeta.length,
|
|
493
|
+
totalShards: localShardMeta.length,
|
|
494
|
+
shardName: firstShardFilename,
|
|
495
|
+
overallDownloaded: overallTotal,
|
|
496
|
+
overallTotal,
|
|
497
|
+
overallPercentage: 100,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return getShardPath(cacheKey, firstShardFilename);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return createManagedDownload(downloadKey, registryPath, (signal) => downloadShardedFilesFromRegistry(registryPath, registrySource, cacheKey, progressCallback, signal, localShardMeta), progressCallback);
|
|
505
|
+
}
|
|
506
|
+
// Check for ONNX with companion data file
|
|
507
|
+
const companionDataFile = findOnnxCompanionDataFile(registryPath);
|
|
508
|
+
if (companionDataFile) {
|
|
509
|
+
const cacheKey = generateShortHash(registryPath);
|
|
510
|
+
// FS pre-check: if both files cached, return immediately
|
|
511
|
+
const mainFilename = registryPath.split("/").pop() || registryPath;
|
|
512
|
+
const dataFilename = companionDataFile.registryPath.split("/").pop() || "";
|
|
513
|
+
const mainPath = getOnnxModelPath(cacheKey, mainFilename);
|
|
514
|
+
const dataPath = getOnnxModelPath(cacheKey, dataFilename);
|
|
515
|
+
const mainCached = await validateCachedFile(mainPath, mainFilename, modelMetadata?.expectedSize || 0, modelMetadata?.sha256Checksum);
|
|
516
|
+
const dataCached = await validateCachedFile(dataPath, dataFilename, companionDataFile.expectedSize, companionDataFile.sha256Checksum);
|
|
517
|
+
if (mainCached && dataCached) {
|
|
518
|
+
logger.info(`✅ ONNX model and data file both cached`);
|
|
519
|
+
if (progressCallback) {
|
|
520
|
+
const total = (modelMetadata?.expectedSize || 0) + companionDataFile.expectedSize;
|
|
521
|
+
progressCallback({
|
|
522
|
+
type: "modelProgress",
|
|
523
|
+
downloaded: total,
|
|
524
|
+
total,
|
|
525
|
+
percentage: 100,
|
|
526
|
+
downloadKey,
|
|
527
|
+
onnxInfo: {
|
|
528
|
+
currentFile: dataFilename,
|
|
529
|
+
fileIndex: 2,
|
|
530
|
+
totalFiles: 2,
|
|
531
|
+
overallDownloaded: total,
|
|
532
|
+
overallTotal: total,
|
|
533
|
+
overallPercentage: 100,
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return mainCached;
|
|
538
|
+
}
|
|
539
|
+
const mainBlobBinding = modelMetadata ? buildBlobBinding(modelMetadata) : undefined;
|
|
540
|
+
const dataBlobBinding = companionDataFile.blobCoreKey
|
|
541
|
+
? buildBlobBinding(companionDataFile)
|
|
542
|
+
: undefined;
|
|
543
|
+
return createManagedDownload(downloadKey, registryPath, (signal) => downloadOnnxWithDataFromRegistry(registryPath, registrySource, companionDataFile, cacheKey, progressCallback, signal, mainBlobBinding, dataBlobBinding), progressCallback);
|
|
544
|
+
}
|
|
545
|
+
// Single file download
|
|
546
|
+
const cacheDir = getModelsCacheDir();
|
|
547
|
+
const sourceHash = generateShortHash(registryPath);
|
|
548
|
+
const modelPath = path.join(cacheDir, `${sourceHash}_${filename}`);
|
|
549
|
+
// Check if already cached
|
|
550
|
+
const expectedSize = modelMetadata?.expectedSize || 0;
|
|
551
|
+
const checksum = expectedChecksum || modelMetadata?.sha256Checksum || "";
|
|
552
|
+
const cachedPath = await validateCachedFile(modelPath, filename, expectedSize, checksum);
|
|
553
|
+
if (cachedPath) {
|
|
554
|
+
logger.info(`✅ Using cached model: ${cachedPath}`);
|
|
555
|
+
if (progressCallback) {
|
|
556
|
+
progressCallback({
|
|
557
|
+
type: "modelProgress",
|
|
558
|
+
downloaded: expectedSize,
|
|
559
|
+
total: expectedSize,
|
|
560
|
+
percentage: 100,
|
|
561
|
+
downloadKey,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
return cachedPath;
|
|
565
|
+
}
|
|
566
|
+
const blobBinding = modelMetadata ? buildBlobBinding(modelMetadata) : undefined;
|
|
567
|
+
return createManagedDownload(downloadKey, registryPath, async (signal) => {
|
|
568
|
+
await downloadSingleFileFromRegistry(registryPath, registrySource, modelPath, filename, downloadKey, expectedSize, checksum, progressCallback, signal, blobBinding);
|
|
569
|
+
return modelPath;
|
|
570
|
+
}, progressCallback);
|
|
571
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { models } from "../../../../models/
|
|
2
|
-
import { hyperdriveUrlSchema, SUPPORTED_ARCHIVE_EXTENSIONS, } from "../../../../schemas/index.js";
|
|
1
|
+
import { models, getModelByPath } from "../../../../models/registry/models.js";
|
|
2
|
+
import { hyperdriveUrlSchema, registryUrlSchema, SUPPORTED_ARCHIVE_EXTENSIONS, } from "../../../../schemas/index.js";
|
|
3
3
|
import { getModelsCacheDir, getShardedModelCacheDir, generateShortHash, extractAndValidateShardedArchive, } from "../../../utils/index.js";
|
|
4
4
|
import { promises as fsPromises } from "bare-fs";
|
|
5
5
|
import path from "bare-path";
|
|
6
6
|
import { downloadModelFromHttp } from "./http.js";
|
|
7
7
|
import { downloadModelFromHyperdrive } from "./hyperdrive.js";
|
|
8
|
+
import { downloadModelFromRegistry } from "./registry.js";
|
|
8
9
|
import { ModelLoadFailedError, ModelNotFoundError, SeedingNotSupportedError, } from "../../../../utils/errors-server.js";
|
|
10
|
+
import { validateAndJoinPath } from "../../../utils/path-security.js";
|
|
9
11
|
import { getServerLogger } from "../../../../logging/index.js";
|
|
10
12
|
const logger = getServerLogger();
|
|
11
13
|
function isArchivePath(filePath) {
|
|
@@ -23,7 +25,7 @@ async function resolveLocalOrCachedFile(modelIdOrPath) {
|
|
|
23
25
|
}
|
|
24
26
|
// Check if it's a cached file or local file
|
|
25
27
|
const cacheDir = getModelsCacheDir();
|
|
26
|
-
const cachedPath =
|
|
28
|
+
const cachedPath = validateAndJoinPath(cacheDir, modelIdOrPath);
|
|
27
29
|
try {
|
|
28
30
|
await fsPromises.access(cachedPath);
|
|
29
31
|
logger.info(`Loading cached model: ${cachedPath}`);
|
|
@@ -67,9 +69,20 @@ export async function resolveModelPath(modelSrc, progressCallback, seed) {
|
|
|
67
69
|
let hyperdriveKey;
|
|
68
70
|
let actualModelSrc = srcString;
|
|
69
71
|
if (srcString.startsWith("pear://")) {
|
|
70
|
-
const { key, path } = hyperdriveUrlSchema.parse(srcString);
|
|
72
|
+
const { key, path: pathValue } = hyperdriveUrlSchema.parse(srcString);
|
|
71
73
|
hyperdriveKey = key;
|
|
72
|
-
actualModelSrc =
|
|
74
|
+
actualModelSrc = pathValue;
|
|
75
|
+
}
|
|
76
|
+
// Handle registry:// URLs
|
|
77
|
+
if (srcString.startsWith("registry://")) {
|
|
78
|
+
const { registryPath, registrySource } = registryUrlSchema.parse(srcString);
|
|
79
|
+
logger.info(`Loading from registry: ${registryPath}`);
|
|
80
|
+
// Look up model metadata for checksum validation
|
|
81
|
+
const modelMetadata = getModelByPath(registryPath);
|
|
82
|
+
const expectedChecksum = modelMetadata?.sha256Checksum;
|
|
83
|
+
const actualPath = await downloadModelFromRegistry(registryPath, registrySource, progressCallback, expectedChecksum);
|
|
84
|
+
logger.info(`Loaded Model to ${actualPath}`);
|
|
85
|
+
return actualPath;
|
|
73
86
|
}
|
|
74
87
|
let actualPath;
|
|
75
88
|
// Validate seeding is only used with hyperdrive models
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { modelInputToSrcSchema } from "../../../schemas/index.js";
|
|
2
2
|
import { registerModel } from "../../bare/registry/model-registry.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { send, stream } from "../delegate-transport.js";
|
|
4
|
+
import { getRPC, cleanupStaleConnection, } from "../../bare/delegate-rpc-client.js";
|
|
5
5
|
import { handleLoadModel } from "./load-model/index.js";
|
|
6
6
|
import { ModelLoadFailedError, DelegateNoFinalResponseError, } from "../../../utils/errors-server.js";
|
|
7
7
|
import { getServerLogger } from "../../../logging/index.js";
|
|
@@ -76,6 +76,8 @@ export async function handleLoadModelDelegated(request, progressCallback) {
|
|
|
76
76
|
}
|
|
77
77
|
catch (error) {
|
|
78
78
|
logger.error("Error in delegated load model:", error);
|
|
79
|
+
// Clean up stale RPC so next attempt creates a fresh connection
|
|
80
|
+
cleanupStaleConnection(providerPublicKey);
|
|
79
81
|
if (fallbackToLocal) {
|
|
80
82
|
logger.info("🔄 Fallback to local model loading enabled, attempting local load...");
|
|
81
83
|
try {
|
|
@@ -1,39 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getServerLogger } from "../../../logging/index.js";
|
|
3
|
-
const logger = getServerLogger();
|
|
1
|
+
import { dispatchPluginStream } from "./plugin-dispatch.js";
|
|
4
2
|
export async function* handleOCRStream(request) {
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
// Stream OCR results in real-time
|
|
8
|
-
for await (const result of ocr({
|
|
9
|
-
image,
|
|
10
|
-
modelId,
|
|
11
|
-
options,
|
|
12
|
-
})) {
|
|
13
|
-
if (result.blocks && result.blocks.length > 0) {
|
|
14
|
-
yield {
|
|
15
|
-
type: "ocrStream",
|
|
16
|
-
blocks: result.blocks,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
if (result.stats) {
|
|
20
|
-
yield {
|
|
21
|
-
type: "ocrStream",
|
|
22
|
-
stats: result.stats,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Signal completion
|
|
27
|
-
yield {
|
|
28
|
-
type: "ocrStream",
|
|
29
|
-
done: true,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
logger.error("Error during OCR:", error);
|
|
34
|
-
yield {
|
|
35
|
-
type: "ocrStream",
|
|
36
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
37
|
-
};
|
|
38
|
-
}
|
|
3
|
+
yield* dispatchPluginStream(request.modelId, "ocrStream", request);
|
|
39
4
|
}
|