@qvac/sdk 0.3.0 → 0.4.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/README.md +22 -0
- package/dist/client/api/cancel.d.ts +1 -1
- package/dist/client/api/cancel.js +4 -3
- package/dist/client/api/completion-stream.d.ts +24 -32
- package/dist/client/api/completion-stream.js +142 -50
- package/dist/client/api/delete-cache.js +3 -2
- package/dist/client/api/download-asset.d.ts +3 -3
- package/dist/client/api/download-asset.js +12 -17
- package/dist/client/api/embed.d.ts +1 -1
- package/dist/client/api/embed.js +4 -3
- package/dist/client/api/get-model-info.d.ts +29 -0
- package/dist/client/api/get-model-info.js +14 -0
- package/dist/client/api/index.d.ts +3 -0
- package/dist/client/api/index.js +3 -0
- package/dist/client/api/load-model.d.ts +3 -3
- package/dist/client/api/load-model.js +13 -22
- package/dist/client/api/logging-stream.d.ts +1 -1
- package/dist/client/api/logging-stream.js +3 -2
- package/dist/client/api/ping.d.ts +1 -1
- package/dist/client/api/ping.js +3 -2
- package/dist/client/api/provide.d.ts +1 -1
- package/dist/client/api/provide.js +4 -3
- package/dist/client/api/rag.d.ts +3 -3
- package/dist/client/api/rag.js +13 -12
- package/dist/client/api/set-config.d.ts +7 -0
- package/dist/client/api/set-config.js +20 -0
- package/dist/client/api/stop-provider.d.ts +1 -1
- package/dist/client/api/stop-provider.js +4 -3
- package/dist/client/api/text-to-speech.d.ts +7 -0
- package/dist/client/api/text-to-speech.js +58 -0
- package/dist/client/api/transcribe.d.ts +4 -2
- package/dist/client/api/transcribe.js +9 -5
- package/dist/client/api/translate.d.ts +1 -1
- package/dist/client/api/translate.js +3 -2
- package/dist/client/api/unload-model.d.ts +5 -1
- package/dist/client/api/unload-model.js +14 -4
- package/dist/client/config-manager.d.ts +4 -0
- package/dist/client/config-manager.js +8 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/init-hooks.d.ts +6 -0
- package/dist/client/init-hooks.js +37 -0
- package/dist/client/rpc/bare-client.d.ts +69 -0
- package/dist/client/rpc/bare-client.js +5 -4
- package/dist/client/rpc/bare-rpc-client.js +7 -3
- package/dist/client/rpc/expo-rpc-client.d.ts +1 -0
- package/dist/client/rpc/expo-rpc-client.js +9 -1
- package/dist/client/rpc/node-rpc-client.d.ts +1 -1
- package/dist/client/rpc/node-rpc-client.js +22 -9
- package/dist/client/rpc/rpc-client.d.ts +1 -0
- package/dist/client/rpc/rpc-client.js +20 -1
- package/dist/examples/cache-management.d.ts +2 -0
- package/dist/examples/cache-management.js +97 -0
- package/dist/examples/delegated-inference/consumer.js +2 -2
- package/dist/examples/download-with-blind-relays.d.ts +2 -0
- package/dist/examples/download-with-blind-relays.js +48 -0
- package/dist/examples/download-with-cancel.js +3 -4
- package/dist/examples/embed-hyperdrive.js +0 -1
- package/dist/examples/kv-cache-cleanup.js +0 -1
- package/dist/examples/kv-cache-custom-key.js +0 -1
- package/dist/examples/kv-cache-example.js +0 -1
- package/dist/examples/llamacpp-file-logging.js +0 -1
- package/dist/examples/llamacpp-filesystem.js +2 -2
- package/dist/examples/llamacpp-http.js +0 -1
- package/dist/examples/llamacpp-hyperdrive.js +0 -1
- package/dist/examples/llamacpp-multimodal.js +0 -1
- package/dist/examples/llamacpp-native-tools.js +118 -36
- package/dist/examples/llamacpp-sharded.d.ts +2 -0
- package/dist/examples/llamacpp-sharded.js +44 -0
- package/dist/examples/multi-model-demo.js +0 -1
- package/dist/examples/parallel-download.js +2 -2
- package/dist/examples/rag/rag-chromadb.js +0 -1
- package/dist/examples/rag/rag-hyperdb-workspaces.js +0 -1
- package/dist/examples/rag/rag-hyperdb.js +0 -1
- package/dist/examples/rag/rag-lancedb.js +0 -1
- package/dist/examples/rag/rag-sqlite.js +0 -1
- package/dist/examples/text-to-speech.d.ts +3 -0
- package/dist/examples/text-to-speech.js +137 -0
- package/dist/examples/translation/translation-indic.js +3 -1
- package/dist/examples/translation/translation-llm-context.js +0 -1
- package/dist/examples/translation/translation-llm.js +2 -2
- package/dist/examples/translation/translation-opus.js +3 -1
- package/dist/examples/translation/translation-stream.js +0 -1
- package/dist/examples/whispercpp-filesystem.js +32 -5
- package/dist/examples/whispercpp-microphone-record.js +9 -9
- package/dist/examples/whispercpp-prompt.d.ts +2 -0
- package/dist/examples/whispercpp-prompt.js +68 -0
- package/dist/expo/plugins/withMobileBundle.js +9 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/models/hyperdrive/models.d.ts +1916 -100
- package/dist/models/hyperdrive/models.js +2042 -102
- package/dist/schemas/common.d.ts +413 -58
- package/dist/schemas/common.js +9 -0
- package/dist/schemas/completion-stream.d.ts +26 -22
- package/dist/schemas/completion-stream.js +9 -23
- package/dist/schemas/download-asset.d.ts +77 -2
- package/dist/schemas/download-asset.js +15 -4
- package/dist/schemas/get-model-info.d.ts +100 -0
- package/dist/schemas/get-model-info.js +40 -0
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.js +7 -0
- package/dist/schemas/llamacpp-config.d.ts +2 -1
- package/dist/schemas/llamacpp-config.js +3 -4
- package/dist/schemas/load-model.d.ts +2316 -266
- package/dist/schemas/load-model.js +157 -10
- package/dist/schemas/model-src-utils.d.ts +98 -0
- package/dist/schemas/model-src-utils.js +31 -0
- package/dist/schemas/sdk-errors-client.d.ts +25 -0
- package/dist/schemas/sdk-errors-client.js +111 -0
- package/dist/schemas/sdk-errors-server.d.ts +55 -0
- package/dist/schemas/sdk-errors-server.js +271 -0
- package/dist/schemas/set-config.d.ts +21 -0
- package/dist/schemas/set-config.js +14 -0
- package/dist/schemas/text-to-speech.d.ts +34 -0
- package/dist/schemas/text-to-speech.js +25 -0
- package/dist/schemas/tools.d.ts +62 -0
- package/dist/schemas/tools.js +43 -0
- package/dist/schemas/transcription.d.ts +25 -2
- package/dist/schemas/transcription.js +12 -1
- package/dist/schemas/translation-config.d.ts +33 -5
- package/dist/schemas/translation-config.js +18 -1
- package/dist/schemas/unload-model.d.ts +2 -0
- package/dist/schemas/unload-model.js +2 -0
- package/dist/schemas/whispercpp-config.d.ts +55 -18
- package/dist/schemas/whispercpp-config.js +63 -15
- package/dist/server/bare/addons/cancel.js +2 -1
- package/dist/server/bare/addons/llamacpp/create-model.d.ts +9 -2
- package/dist/server/bare/addons/llamacpp/create-model.js +23 -23
- package/dist/server/bare/addons/llamacpp/handlers/completion-stream.d.ts +10 -2
- package/dist/server/bare/addons/llamacpp/handlers/completion-stream.js +62 -43
- package/dist/server/bare/addons/llamacpp/handlers/embed.js +3 -2
- package/dist/server/bare/addons/llamacpp/kv-cache-utils.d.ts +1 -1
- package/dist/server/bare/addons/llamacpp/kv-cache-utils.js +6 -2
- package/dist/server/bare/addons/load-model.js +59 -28
- package/dist/server/bare/addons/translation/create-model.d.ts +5 -1
- package/dist/server/bare/addons/translation/create-model.js +17 -25
- package/dist/server/bare/addons/translation/handlers/translate.js +10 -1
- package/dist/server/bare/addons/tts/create-model.d.ts +10 -0
- package/dist/server/bare/addons/tts/create-model.js +25 -0
- package/dist/server/bare/addons/tts/handlers/text-to-speech.d.ts +3 -0
- package/dist/server/bare/addons/tts/handlers/text-to-speech.js +87 -0
- package/dist/server/bare/addons/tts/index.d.ts +3 -0
- package/dist/server/bare/addons/tts/index.js +2 -0
- package/dist/server/bare/addons/unload-model.js +21 -3
- package/dist/server/bare/addons/whispercpp/create-model.d.ts +5 -1
- package/dist/server/bare/addons/whispercpp/create-model.js +13 -32
- package/dist/server/bare/addons/whispercpp/handlers/transcribe-stream.js +41 -20
- package/dist/server/bare/delegate-rpc-client.js +3 -2
- package/dist/server/bare/hyperswarm.d.ts +3 -0
- package/dist/server/bare/hyperswarm.js +22 -1
- package/dist/server/bare/rag-hyperdb/rag-manager.js +2 -3
- package/dist/server/bare/registry/config-registry.d.ts +5 -0
- package/dist/server/bare/registry/config-registry.js +39 -0
- package/dist/server/bare/registry/model-registry.d.ts +13 -0
- package/dist/server/bare/registry/model-registry.js +24 -7
- package/dist/server/rpc/handle-request.js +42 -3
- package/dist/server/rpc/handlers/completion-stream-delegated.js +2 -1
- package/dist/server/rpc/handlers/completion-stream.js +11 -4
- package/dist/server/rpc/handlers/download-asset.js +2 -3
- package/dist/server/rpc/handlers/get-model-info.d.ts +3 -0
- package/dist/server/rpc/handlers/get-model-info.js +148 -0
- package/dist/server/rpc/handlers/index.d.ts +4 -0
- package/dist/server/rpc/handlers/index.js +6 -0
- package/dist/server/rpc/handlers/load-model/http.js +51 -10
- package/dist/server/rpc/handlers/load-model/hyperdrive.js +336 -156
- package/dist/server/rpc/handlers/load-model/index.js +1 -1
- package/dist/server/rpc/handlers/load-model/resolve.d.ts +1 -1
- package/dist/server/rpc/handlers/load-model/resolve.js +10 -6
- package/dist/server/rpc/handlers/load-model-delegated.js +7 -4
- package/dist/server/rpc/handlers/load-model.js +19 -1
- package/dist/server/rpc/handlers/provideHandler/handler.js +2 -1
- package/dist/server/rpc/handlers/rag.js +0 -3
- package/dist/server/rpc/handlers/set-config.d.ts +3 -0
- package/dist/server/rpc/handlers/set-config.js +18 -0
- package/dist/server/rpc/handlers/stopProvideHandler.js +3 -1
- package/dist/server/rpc/handlers/text-to-speech.d.ts +3 -0
- package/dist/server/rpc/handlers/text-to-speech.js +7 -0
- package/dist/server/rpc/handlers/transcribe-stream.js +6 -2
- package/dist/server/rpc/handlers/unload-model.js +7 -0
- package/dist/server/utils/cache.d.ts +13 -8
- package/dist/server/utils/cache.js +69 -14
- package/dist/server/utils/checksum.d.ts +2 -0
- package/dist/server/utils/checksum.js +24 -0
- package/dist/server/utils/index.d.ts +2 -0
- package/dist/server/utils/index.js +2 -0
- package/dist/server/utils/shard-utils.d.ts +22 -0
- package/dist/server/utils/shard-utils.js +37 -0
- package/dist/server/utils/tool-integration.d.ts +13 -0
- package/dist/server/utils/tool-integration.js +43 -0
- package/dist/server/utils/tool-parser.d.ts +7 -0
- package/dist/server/utils/tool-parser.js +244 -0
- package/dist/utils/errors-base.d.ts +3 -0
- package/dist/utils/errors-base.js +13 -0
- package/dist/utils/errors-client.d.ts +107 -0
- package/dist/utils/errors-client.js +186 -0
- package/dist/utils/errors-server.d.ts +146 -0
- package/dist/utils/errors-server.js +253 -0
- package/dist/utils/tool-helpers.d.ts +17 -0
- package/dist/utils/tool-helpers.js +99 -0
- package/package.json +12 -8
- package/dist/examples/llamacpp-tools-routing.d.ts +0 -2
- package/dist/examples/llamacpp-tools-routing.js +0 -364
- package/dist/examples/llamacpp-tools.d.ts +0 -2
- package/dist/examples/llamacpp-tools.js +0 -81
- package/dist/server/http-server.d.ts +0 -2
- package/dist/server/http-server.js +0 -189
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
import fs, { promises as fsPromises } from "bare-fs";
|
|
2
2
|
import path from "bare-path";
|
|
3
|
-
import crypto from "bare-crypto";
|
|
4
3
|
import Corestore from "corestore";
|
|
5
4
|
import Hyperswarm, {} from "hyperswarm";
|
|
6
5
|
import Hyperdrive from "hyperdrive";
|
|
7
6
|
import {} from "bare-stream";
|
|
8
7
|
import { AbortController } from "bare-abort-controller";
|
|
9
8
|
import { getEnv } from "../../../worker.js";
|
|
10
|
-
import { getModelsCacheDir, generateShortHash } from "../../../utils/index.js";
|
|
9
|
+
import { getModelsCacheDir, generateShortHash, detectShardedModel, getShardedModelCacheDir, getShardPath, checkShardCompleteness, calculateFileChecksum, } from "../../../utils/index.js";
|
|
11
10
|
import { getModelBySrc } from "../../../../models/hyperdrive/index.js";
|
|
12
11
|
import { getActiveDownload, registerDownload, unregisterDownload, createHyperdriveDownloadKey, shouldClearCache, clearClearCacheFlag, } from "./download-manager.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
index < blob.blockOffset + blob.blockLength) {
|
|
18
|
-
downloadedBytes += bytes;
|
|
19
|
-
if (progressCallback && downloadKey) {
|
|
20
|
-
progressCallback({
|
|
21
|
-
type: "modelProgress",
|
|
22
|
-
downloaded: downloadedBytes,
|
|
23
|
-
total: blob.byteLength,
|
|
24
|
-
percentage: blob.byteLength > 0 ? (downloadedBytes / blob.byteLength) * 100 : 0,
|
|
25
|
-
downloadKey,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
};
|
|
12
|
+
import { getSDKConfig } from "../../../bare/registry/config-registry.js";
|
|
13
|
+
import { FileNotFoundError, ChecksumValidationFailedError, DownloadCancelledError, HyperdriveDownloadFailedError, ModelLoadFailedError, NoBlobFoundError, } from "../../../../utils/errors-server.js";
|
|
14
|
+
function getCorestoreDir(hyperdriveKey) {
|
|
15
|
+
return path.join(getEnv().HOME_DIR, ".qvac", "corestore", hyperdriveKey);
|
|
30
16
|
}
|
|
31
17
|
async function setupHyperdrive(hyperdriveKey, corestoreDir) {
|
|
32
18
|
const corestore = new Corestore(corestoreDir);
|
|
33
19
|
await corestore.ready();
|
|
34
20
|
const drive = new Hyperdrive(corestore, Buffer.from(hyperdriveKey, "hex"));
|
|
35
21
|
await drive.ready();
|
|
36
|
-
const
|
|
22
|
+
const getRelays = () => {
|
|
23
|
+
const config = getSDKConfig();
|
|
24
|
+
const relayPublicKeys = config.swarmRelays;
|
|
25
|
+
if (!relayPublicKeys || relayPublicKeys.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return relayPublicKeys.map((key) => Buffer.from(key, "hex"));
|
|
29
|
+
};
|
|
30
|
+
const swarmOptions = {
|
|
31
|
+
relayThrough: getRelays,
|
|
32
|
+
};
|
|
33
|
+
const swarm = new Hyperswarm(swarmOptions);
|
|
37
34
|
const doneFindingPeers = drive.findingPeers();
|
|
38
35
|
swarm.on("connection", (connection) => {
|
|
39
36
|
console.log(`🤝 Connected to peer for hyperdrive download`);
|
|
@@ -103,7 +100,7 @@ async function findFileInHyperdrive(drive, modelFileName) {
|
|
|
103
100
|
const filesInfo = availableFiles.length > 0
|
|
104
101
|
? `Available files: ${availableFiles.join(", ")}`
|
|
105
102
|
: "Could not list available files";
|
|
106
|
-
throw new
|
|
103
|
+
throw new FileNotFoundError(`${modelFileName} in hyperdrive. ${filesInfo}`);
|
|
107
104
|
}
|
|
108
105
|
async function checkFileDownloaded(drive, entry) {
|
|
109
106
|
const blobs = await drive.getBlobs();
|
|
@@ -143,39 +140,6 @@ async function validateFileExistence(drive, file) {
|
|
|
143
140
|
totalBytes,
|
|
144
141
|
};
|
|
145
142
|
}
|
|
146
|
-
async function validateFileChecksum(filePath, expectedChecksum) {
|
|
147
|
-
console.log(`🔍 Validating SHA256 checksum...`);
|
|
148
|
-
return new Promise((resolve) => {
|
|
149
|
-
try {
|
|
150
|
-
const hash = crypto.createHash("sha256");
|
|
151
|
-
const stream = fs.createReadStream(filePath);
|
|
152
|
-
stream.on("data", (chunk) => {
|
|
153
|
-
hash.update(chunk);
|
|
154
|
-
});
|
|
155
|
-
stream.on("end", () => {
|
|
156
|
-
const actualChecksum = hash.digest("hex");
|
|
157
|
-
console.log(`Expected: ${String(expectedChecksum)}`);
|
|
158
|
-
console.log(`Actual: ${String(actualChecksum)}`);
|
|
159
|
-
const isValid = actualChecksum === expectedChecksum;
|
|
160
|
-
if (isValid) {
|
|
161
|
-
console.log(`✅ Checksum validation passed!`);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
console.log(`❌ Checksum validation FAILED! File may be corrupted.`);
|
|
165
|
-
}
|
|
166
|
-
resolve(isValid);
|
|
167
|
-
});
|
|
168
|
-
stream.on("error", (error) => {
|
|
169
|
-
console.error(`❌ Error validating checksum: ${error.message}`);
|
|
170
|
-
resolve(false);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
console.error(`❌ Error validating checksum: ${error instanceof Error ? error.message : String(error)}`);
|
|
175
|
-
resolve(false);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
143
|
async function validateCachedFile(modelPath, modelFileName, expectedSize, expectedChecksum) {
|
|
180
144
|
try {
|
|
181
145
|
await fsPromises.access(modelPath);
|
|
@@ -187,18 +151,13 @@ async function validateCachedFile(modelPath, modelFileName, expectedSize, expect
|
|
|
187
151
|
console.log(`✅ Model cached with correct size: ${modelPath}`);
|
|
188
152
|
// Always validate checksum if provided, even when size matches
|
|
189
153
|
if (expectedChecksum && expectedChecksum.length === 64) {
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
|
|
154
|
+
const checksum = await calculateFileChecksum(modelPath);
|
|
155
|
+
if (checksum !== expectedChecksum) {
|
|
156
|
+
throw new ChecksumValidationFailedError(`${modelFileName}. Expected: ${expectedChecksum}. Actual: ${checksum}. File may be corrupted`);
|
|
193
157
|
}
|
|
194
|
-
else {
|
|
195
|
-
throw new Error(`File ${modelFileName} is fully downloaded but checksum validation failed. Expected: ${expectedChecksum}. File may be corrupted.`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
console.log(`✅ Model already cached and size validated: ${modelPath}`);
|
|
200
|
-
return modelPath;
|
|
201
158
|
}
|
|
159
|
+
console.log(`✅ Model already cached and size validated: ${modelPath}`);
|
|
160
|
+
return modelPath;
|
|
202
161
|
}
|
|
203
162
|
// File exists but incomplete - will be overwritten during download
|
|
204
163
|
// Resume state will be determined by corestore blocks, not file size
|
|
@@ -209,65 +168,47 @@ async function validateCachedFile(modelPath, modelFileName, expectedSize, expect
|
|
|
209
168
|
return null;
|
|
210
169
|
}
|
|
211
170
|
}
|
|
212
|
-
async function
|
|
171
|
+
async function downloadSingleFileToFilesystem(modelPath, modelFileName, hyperdriveKey, corestoreDir, downloadKey, expectedChecksum, progressCallback, seed, signal) {
|
|
213
172
|
// Check if already aborted
|
|
214
173
|
if (signal?.aborted) {
|
|
215
|
-
throw new
|
|
174
|
+
throw new DownloadCancelledError();
|
|
216
175
|
}
|
|
217
176
|
console.log(`Downloading model from hyperdrive ${hyperdriveKey} to ${modelPath}`);
|
|
218
177
|
const setup = await setupHyperdrive(hyperdriveKey, corestoreDir);
|
|
219
|
-
// Set up abort handler
|
|
220
|
-
let readStream;
|
|
221
|
-
let writeStream;
|
|
222
178
|
const cleanup = () => {
|
|
223
179
|
console.log("🛑 Aborting hyperdrive download...");
|
|
224
180
|
setup.swarm.suspended = true;
|
|
225
181
|
setup.swarm.suspend().catch(() => {
|
|
226
182
|
// Ignore errors during emergency suspend
|
|
227
183
|
});
|
|
228
|
-
// Destroy streams if they exist
|
|
229
|
-
if (readStream) {
|
|
230
|
-
readStream.destroy();
|
|
231
|
-
}
|
|
232
|
-
if (writeStream) {
|
|
233
|
-
writeStream.destroy();
|
|
234
|
-
}
|
|
235
184
|
};
|
|
236
185
|
signal?.addEventListener("abort", cleanup);
|
|
237
|
-
let downloadHandler;
|
|
238
186
|
try {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// Track only downloads for blocks within our file's blob range
|
|
248
|
-
downloadHandler = createProgressHandler(blob, progressCallback, downloadKey, initialBytes);
|
|
249
|
-
blobs.core.on("download", downloadHandler);
|
|
250
|
-
// Check if aborted before starting streams
|
|
251
|
-
if (signal?.aborted) {
|
|
252
|
-
throw new Error("Download cancelled");
|
|
253
|
-
}
|
|
254
|
-
// Trigger download for this file (hyperdrive will only download missing blocks)
|
|
255
|
-
setup.drive.download(targetPath);
|
|
256
|
-
readStream = setup.drive.createReadStream(targetPath);
|
|
257
|
-
writeStream = fs.createWriteStream(modelPath);
|
|
258
|
-
// Pipe the streams
|
|
259
|
-
readStream.pipe(writeStream);
|
|
260
|
-
// Wait for download to complete
|
|
261
|
-
await new Promise((resolve, reject) => {
|
|
262
|
-
writeStream.on("finish", resolve);
|
|
263
|
-
writeStream.on("error", reject);
|
|
264
|
-
readStream.on("error", reject);
|
|
265
|
-
// Also reject on abort
|
|
266
|
-
signal?.addEventListener("abort", () => {
|
|
267
|
-
reject(new Error("Download cancelled"));
|
|
268
|
-
}, { once: true });
|
|
269
|
-
});
|
|
187
|
+
const progressContext = progressCallback
|
|
188
|
+
? {
|
|
189
|
+
downloadKey,
|
|
190
|
+
callback: progressCallback,
|
|
191
|
+
}
|
|
192
|
+
: undefined;
|
|
193
|
+
await downloadAndValidateFile(setup.drive, modelFileName, modelPath, 0, // Size will be determined from drive
|
|
194
|
+
expectedChecksum, progressContext, signal);
|
|
270
195
|
console.log(`✅ Model downloaded successfully to ${modelPath}`);
|
|
196
|
+
// Send final 100% progress update
|
|
197
|
+
if (progressCallback) {
|
|
198
|
+
try {
|
|
199
|
+
const stats = await fsPromises.stat(modelPath);
|
|
200
|
+
progressCallback({
|
|
201
|
+
type: "modelProgress",
|
|
202
|
+
downloaded: stats.size,
|
|
203
|
+
total: stats.size,
|
|
204
|
+
percentage: 100,
|
|
205
|
+
downloadKey,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Ignore stat errors
|
|
210
|
+
}
|
|
211
|
+
}
|
|
271
212
|
// Delete corestore directory after successful download (only when not seeding, to save space)
|
|
272
213
|
if (!seed) {
|
|
273
214
|
await deleteCorestoreDirectory(setup.corestoreDir);
|
|
@@ -279,19 +220,10 @@ async function performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey
|
|
|
279
220
|
if (error instanceof Error && error.message === "Download cancelled") {
|
|
280
221
|
throw error;
|
|
281
222
|
}
|
|
282
|
-
throw new
|
|
223
|
+
throw new HyperdriveDownloadFailedError(`Download failed for ${modelFileName} from hyperdrive: ${hyperdriveKey}`, error);
|
|
283
224
|
}
|
|
284
225
|
finally {
|
|
285
226
|
signal?.removeEventListener("abort", cleanup);
|
|
286
|
-
if (downloadHandler) {
|
|
287
|
-
try {
|
|
288
|
-
const { blobs } = await validateFileExistence(setup.drive, modelFileName);
|
|
289
|
-
blobs.core.off("download", downloadHandler);
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
// If we can't get blobs, setup is already closed
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
227
|
if (!seed) {
|
|
296
228
|
await cleanupHyperdrive(setup);
|
|
297
229
|
}
|
|
@@ -308,56 +240,110 @@ async function performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey
|
|
|
308
240
|
clearClearCacheFlag(downloadKey);
|
|
309
241
|
}
|
|
310
242
|
}
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return existing.promise;
|
|
243
|
+
async function downloadAndValidateFile(drive, sourceFileName, targetFilePath, expectedSize, expectedChecksum, progressContext, signal) {
|
|
244
|
+
const { entry, blobs, targetPath, blocksDownloaded, totalBlocks, totalBytes, } = await validateFileExistence(drive, sourceFileName);
|
|
245
|
+
const blob = entry?.value?.blob;
|
|
246
|
+
if (!blob) {
|
|
247
|
+
throw new NoBlobFoundError(sourceFileName);
|
|
317
248
|
}
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
249
|
+
// Use actual file size from drive if expectedSize is 0
|
|
250
|
+
const fileSizeToValidate = expectedSize || totalBytes;
|
|
251
|
+
// Setup progress tracking
|
|
252
|
+
let downloadedBytes = 0;
|
|
253
|
+
const bytesPerBlock = totalBlocks > 0 ? totalBytes / totalBlocks : 0;
|
|
254
|
+
const initialBytes = Math.floor(blocksDownloaded * bytesPerBlock);
|
|
255
|
+
downloadedBytes = initialBytes;
|
|
256
|
+
const progressHandler = (index, bytes) => {
|
|
257
|
+
if (index >= blob.blockOffset &&
|
|
258
|
+
index < blob.blockOffset + blob.blockLength) {
|
|
259
|
+
downloadedBytes += bytes;
|
|
260
|
+
const cappedDownloaded = Math.min(downloadedBytes, totalBytes);
|
|
261
|
+
if (progressContext) {
|
|
262
|
+
const baseProgress = {
|
|
263
|
+
type: "modelProgress",
|
|
264
|
+
downloaded: cappedDownloaded,
|
|
265
|
+
total: totalBytes,
|
|
266
|
+
percentage: totalBytes > 0 ? (cappedDownloaded / totalBytes) * 100 : 0,
|
|
267
|
+
downloadKey: progressContext.downloadKey,
|
|
268
|
+
};
|
|
269
|
+
if (progressContext.shardInfo) {
|
|
270
|
+
const currentOverall = progressContext.shardInfo.overallDownloaded + cappedDownloaded;
|
|
271
|
+
progressContext.callback({
|
|
272
|
+
...baseProgress,
|
|
273
|
+
shardInfo: {
|
|
274
|
+
currentShard: progressContext.shardInfo.currentShard,
|
|
275
|
+
totalShards: progressContext.shardInfo.totalShards,
|
|
276
|
+
shardName: progressContext.shardInfo.shardName,
|
|
277
|
+
overallDownloaded: currentOverall,
|
|
278
|
+
overallTotal: progressContext.shardInfo.overallTotal,
|
|
279
|
+
overallPercentage: progressContext.shardInfo.overallTotal > 0
|
|
280
|
+
? (currentOverall / progressContext.shardInfo.overallTotal) *
|
|
281
|
+
100
|
|
282
|
+
: 0,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
progressContext.callback(baseProgress);
|
|
288
|
+
}
|
|
331
289
|
}
|
|
332
|
-
return cachedPath;
|
|
333
290
|
}
|
|
291
|
+
};
|
|
292
|
+
blobs.core.on("download", progressHandler);
|
|
293
|
+
let readStream;
|
|
294
|
+
let writeStream;
|
|
295
|
+
try {
|
|
296
|
+
// Download blocks and stream to file
|
|
297
|
+
drive.download(targetPath);
|
|
298
|
+
readStream = drive.createReadStream(targetPath);
|
|
299
|
+
writeStream = fs.createWriteStream(targetFilePath);
|
|
300
|
+
readStream.pipe(writeStream);
|
|
301
|
+
await new Promise((resolve, reject) => {
|
|
302
|
+
writeStream.on("finish", resolve);
|
|
303
|
+
writeStream.on("error", reject);
|
|
304
|
+
readStream.on("error", reject);
|
|
305
|
+
signal?.addEventListener("abort", () => reject(new Error("Download cancelled")), { once: true });
|
|
306
|
+
});
|
|
307
|
+
blobs.core.off("download", progressHandler);
|
|
308
|
+
// Validate file size
|
|
309
|
+
const stats = await fsPromises.stat(targetFilePath);
|
|
310
|
+
if (stats.size !== fileSizeToValidate) {
|
|
311
|
+
throw new ChecksumValidationFailedError(`${path.basename(targetFilePath)}. File size mismatch: expected ${fileSizeToValidate}, got ${stats.size}`);
|
|
312
|
+
}
|
|
313
|
+
// Validate checksum
|
|
314
|
+
if (expectedChecksum && expectedChecksum.length === 64) {
|
|
315
|
+
const checksum = await calculateFileChecksum(targetFilePath);
|
|
316
|
+
if (checksum !== expectedChecksum) {
|
|
317
|
+
await fsPromises.unlink(targetFilePath);
|
|
318
|
+
throw new ChecksumValidationFailedError(`${path.basename(targetFilePath)}. Expected: ${expectedChecksum}. Actual: ${checksum}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if (!expectedChecksum) {
|
|
322
|
+
console.log(`⚠️ No checksum available for validation`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
if (readStream) {
|
|
327
|
+
readStream.destroy();
|
|
328
|
+
}
|
|
329
|
+
if (writeStream) {
|
|
330
|
+
writeStream.destroy();
|
|
331
|
+
}
|
|
332
|
+
blobs.core.off("download", progressHandler);
|
|
333
|
+
throw error;
|
|
334
334
|
}
|
|
335
|
-
|
|
335
|
+
}
|
|
336
|
+
function createManagedDownload(downloadKey, hyperdriveKey, modelFileName, downloadFn, progressCallback) {
|
|
336
337
|
const abortController = new AbortController();
|
|
337
338
|
const downloadPromise = (async () => {
|
|
338
339
|
try {
|
|
339
|
-
|
|
340
|
-
await performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey, corestoreDir, downloadKey, progressCallback, seed, abortController.signal);
|
|
341
|
-
// Validate checksum using provided checksum or model's checksum (if available)
|
|
342
|
-
const checksumToValidate = expectedChecksum || model?.sha256Checksum;
|
|
343
|
-
if (checksumToValidate && checksumToValidate.length === 64) {
|
|
344
|
-
const isValid = await validateFileChecksum(modelPath, checksumToValidate);
|
|
345
|
-
if (!isValid) {
|
|
346
|
-
await fsPromises.unlink(modelPath);
|
|
347
|
-
throw new Error(`Checksum validation failed for ${modelFileName}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
console.log(`⚠️ No valid checksum available for validation`);
|
|
352
|
-
}
|
|
353
|
-
return modelPath;
|
|
340
|
+
return await downloadFn(abortController.signal);
|
|
354
341
|
}
|
|
355
342
|
finally {
|
|
356
|
-
// Cleanup from download manager
|
|
357
343
|
unregisterDownload(downloadKey);
|
|
344
|
+
clearClearCacheFlag(downloadKey);
|
|
358
345
|
}
|
|
359
346
|
})();
|
|
360
|
-
// Register download
|
|
361
347
|
const downloadEntry = {
|
|
362
348
|
key: downloadKey,
|
|
363
349
|
promise: downloadPromise,
|
|
@@ -371,3 +357,197 @@ export async function downloadModelFromHyperdrive(hyperdriveKey, modelFileName,
|
|
|
371
357
|
registerDownload(downloadKey, downloadEntry);
|
|
372
358
|
return downloadPromise;
|
|
373
359
|
}
|
|
360
|
+
async function downloadShardedFilesToFilesystem(hyperdriveKey, firstShardFileName, shardMetadata, progressCallback, seed, signal) {
|
|
361
|
+
if (signal?.aborted) {
|
|
362
|
+
throw new DownloadCancelledError();
|
|
363
|
+
}
|
|
364
|
+
const shardInfo = detectShardedModel(firstShardFileName);
|
|
365
|
+
if (!shardInfo.isSharded || !shardInfo.totalShards) {
|
|
366
|
+
throw new ModelLoadFailedError(`Not a sharded model: ${firstShardFileName}`);
|
|
367
|
+
}
|
|
368
|
+
const shardDir = getShardedModelCacheDir(hyperdriveKey);
|
|
369
|
+
const allFiles = shardMetadata.map((s) => s.filename);
|
|
370
|
+
const numberedShardCount = shardInfo.totalShards;
|
|
371
|
+
console.log(`Downloading sharded model with ${numberedShardCount} numbered shards + ${allFiles.length - numberedShardCount} companion files to ${shardDir}`);
|
|
372
|
+
// Check which files need to be downloaded
|
|
373
|
+
const invalidIndices = await checkShardCompleteness(hyperdriveKey, allFiles, shardMetadata);
|
|
374
|
+
if (invalidIndices.length === 0) {
|
|
375
|
+
console.log(`✅ All ${allFiles.length} files already downloaded and validated`);
|
|
376
|
+
if (progressCallback) {
|
|
377
|
+
const overallTotal = shardMetadata.reduce((sum, shard) => sum + shard.expectedSize, 0);
|
|
378
|
+
const lastFile = allFiles[allFiles.length - 1];
|
|
379
|
+
progressCallback({
|
|
380
|
+
type: "modelProgress",
|
|
381
|
+
downloaded: overallTotal,
|
|
382
|
+
total: overallTotal,
|
|
383
|
+
percentage: 100,
|
|
384
|
+
downloadKey: createHyperdriveDownloadKey(hyperdriveKey, firstShardFileName),
|
|
385
|
+
shardInfo: {
|
|
386
|
+
currentShard: allFiles.length,
|
|
387
|
+
totalShards: allFiles.length,
|
|
388
|
+
shardName: lastFile,
|
|
389
|
+
overallDownloaded: overallTotal,
|
|
390
|
+
overallTotal,
|
|
391
|
+
overallPercentage: 100,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return getShardPath(hyperdriveKey, allFiles[0]);
|
|
396
|
+
}
|
|
397
|
+
console.log(`📥 Need to download ${invalidIndices.length} of ${allFiles.length} files`);
|
|
398
|
+
// Setup hyperdrive once for all shards
|
|
399
|
+
const corestoreDir = getCorestoreDir(hyperdriveKey);
|
|
400
|
+
const setup = await setupHyperdrive(hyperdriveKey, corestoreDir);
|
|
401
|
+
const cleanup = () => {
|
|
402
|
+
console.log("🛑 Aborting sharded hyperdrive download...");
|
|
403
|
+
setup.swarm.suspended = true;
|
|
404
|
+
setup.swarm.suspend().catch(() => {
|
|
405
|
+
// Ignore errors during emergency suspend
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
signal?.addEventListener("abort", cleanup);
|
|
409
|
+
try {
|
|
410
|
+
// Calculate overall progress
|
|
411
|
+
const overallTotal = shardMetadata.reduce((sum, shard) => sum + shard.expectedSize, 0);
|
|
412
|
+
let overallDownloaded = 0;
|
|
413
|
+
// Download each file sequentially
|
|
414
|
+
for (let i = 0; i < allFiles.length; i++) {
|
|
415
|
+
if (signal?.aborted) {
|
|
416
|
+
throw new DownloadCancelledError();
|
|
417
|
+
}
|
|
418
|
+
const file = allFiles[i];
|
|
419
|
+
const fileMeta = shardMetadata[i];
|
|
420
|
+
const filePath = getShardPath(hyperdriveKey, file);
|
|
421
|
+
// Skip if already valid
|
|
422
|
+
if (!invalidIndices.includes(i)) {
|
|
423
|
+
console.log(`✅ File ${i + 1}/${allFiles.length} already valid: ${file}`);
|
|
424
|
+
overallDownloaded += fileMeta.expectedSize;
|
|
425
|
+
// Report progress for skipped files
|
|
426
|
+
if (progressCallback) {
|
|
427
|
+
progressCallback({
|
|
428
|
+
type: "modelProgress",
|
|
429
|
+
downloaded: fileMeta.expectedSize,
|
|
430
|
+
total: fileMeta.expectedSize,
|
|
431
|
+
percentage: 100,
|
|
432
|
+
downloadKey: createHyperdriveDownloadKey(hyperdriveKey, firstShardFileName),
|
|
433
|
+
shardInfo: {
|
|
434
|
+
currentShard: i + 1,
|
|
435
|
+
totalShards: allFiles.length,
|
|
436
|
+
shardName: file,
|
|
437
|
+
overallDownloaded,
|
|
438
|
+
overallTotal,
|
|
439
|
+
overallPercentage: overallTotal > 0 ? (overallDownloaded / overallTotal) * 100 : 0,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
console.log(`📥 Downloading file ${i + 1}/${allFiles.length}: ${file}`);
|
|
446
|
+
const progressContext = progressCallback
|
|
447
|
+
? {
|
|
448
|
+
downloadKey: createHyperdriveDownloadKey(hyperdriveKey, firstShardFileName),
|
|
449
|
+
callback: progressCallback,
|
|
450
|
+
shardInfo: {
|
|
451
|
+
currentShard: i + 1,
|
|
452
|
+
totalShards: allFiles.length,
|
|
453
|
+
shardName: file,
|
|
454
|
+
overallDownloaded,
|
|
455
|
+
overallTotal,
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
: undefined;
|
|
459
|
+
await downloadAndValidateFile(setup.drive, file, filePath, fileMeta.expectedSize, fileMeta.sha256Checksum, progressContext, signal);
|
|
460
|
+
if (signal?.aborted) {
|
|
461
|
+
throw new DownloadCancelledError();
|
|
462
|
+
}
|
|
463
|
+
console.log(`✅ File ${i + 1}/${allFiles.length} downloaded and validated`);
|
|
464
|
+
overallDownloaded += fileMeta.expectedSize;
|
|
465
|
+
}
|
|
466
|
+
if (progressCallback && overallDownloaded === overallTotal) {
|
|
467
|
+
const lastFile = allFiles[allFiles.length - 1];
|
|
468
|
+
progressCallback({
|
|
469
|
+
type: "modelProgress",
|
|
470
|
+
downloaded: overallTotal,
|
|
471
|
+
total: overallTotal,
|
|
472
|
+
percentage: 100,
|
|
473
|
+
downloadKey: createHyperdriveDownloadKey(hyperdriveKey, firstShardFileName),
|
|
474
|
+
shardInfo: {
|
|
475
|
+
currentShard: allFiles.length,
|
|
476
|
+
totalShards: allFiles.length,
|
|
477
|
+
shardName: lastFile,
|
|
478
|
+
overallDownloaded,
|
|
479
|
+
overallTotal,
|
|
480
|
+
overallPercentage: 100,
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
// Delete corestore directory after successful download (only when not seeding)
|
|
485
|
+
if (!seed) {
|
|
486
|
+
await deleteCorestoreDirectory(corestoreDir);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
console.error("❌ Error during sharded hyperdrive download:", error instanceof Error ? error.message : String(error));
|
|
491
|
+
// Preserve cancellation errors
|
|
492
|
+
if (error instanceof Error && error.message === "Download cancelled") {
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
495
|
+
throw new HyperdriveDownloadFailedError(`Sharded download failed for ${firstShardFileName} from hyperdrive: ${hyperdriveKey}`, error);
|
|
496
|
+
}
|
|
497
|
+
finally {
|
|
498
|
+
signal?.removeEventListener("abort", cleanup);
|
|
499
|
+
if (!seed) {
|
|
500
|
+
await cleanupHyperdrive(setup);
|
|
501
|
+
}
|
|
502
|
+
// Only delete corestore and partial files if user explicitly requested clearCache
|
|
503
|
+
const downloadKey = createHyperdriveDownloadKey(hyperdriveKey, firstShardFileName);
|
|
504
|
+
if (signal?.aborted && shouldClearCache(downloadKey)) {
|
|
505
|
+
await deleteCorestoreDirectory(corestoreDir);
|
|
506
|
+
}
|
|
507
|
+
clearClearCacheFlag(downloadKey);
|
|
508
|
+
}
|
|
509
|
+
return getShardPath(hyperdriveKey, allFiles[0]);
|
|
510
|
+
}
|
|
511
|
+
export async function downloadModelFromHyperdrive(hyperdriveKey, modelFileName, seed, progressCallback, expectedChecksum) {
|
|
512
|
+
const downloadKey = createHyperdriveDownloadKey(hyperdriveKey, modelFileName);
|
|
513
|
+
// Check if already downloading
|
|
514
|
+
const existing = getActiveDownload(downloadKey);
|
|
515
|
+
if (existing) {
|
|
516
|
+
return existing.promise;
|
|
517
|
+
}
|
|
518
|
+
const shardInfo = detectShardedModel(modelFileName);
|
|
519
|
+
const model = getModelBySrc(modelFileName, hyperdriveKey);
|
|
520
|
+
if (shardInfo.isSharded && model?.shardMetadata) {
|
|
521
|
+
return createManagedDownload(downloadKey, hyperdriveKey, modelFileName, (signal) => downloadShardedFilesToFilesystem(hyperdriveKey, modelFileName, model.shardMetadata, progressCallback, seed, signal), progressCallback);
|
|
522
|
+
}
|
|
523
|
+
// Non-sharded model download
|
|
524
|
+
const corestoreDir = getCorestoreDir(hyperdriveKey);
|
|
525
|
+
const cacheDir = getModelsCacheDir();
|
|
526
|
+
const sourceHash = generateShortHash(`${hyperdriveKey}/${modelFileName}`);
|
|
527
|
+
const modelPath = path.join(cacheDir, `${sourceHash}_${modelFileName}`);
|
|
528
|
+
// First, check if we already have a valid cached file (only if we have model metadata)
|
|
529
|
+
if (model) {
|
|
530
|
+
const cachedPath = await validateCachedFile(modelPath, modelFileName, model.expectedSize, expectedChecksum || model.sha256Checksum);
|
|
531
|
+
if (cachedPath) {
|
|
532
|
+
if (progressCallback) {
|
|
533
|
+
progressCallback({
|
|
534
|
+
type: "modelProgress",
|
|
535
|
+
downloaded: model.expectedSize,
|
|
536
|
+
total: model.expectedSize,
|
|
537
|
+
percentage: 100,
|
|
538
|
+
downloadKey,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
// Delete any leftover corestore directory from previous runs (only when not seeding)
|
|
542
|
+
if (!seed) {
|
|
543
|
+
await deleteCorestoreDirectory(corestoreDir);
|
|
544
|
+
}
|
|
545
|
+
return cachedPath;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return createManagedDownload(downloadKey, hyperdriveKey, modelFileName, async (signal) => {
|
|
549
|
+
const checksumToValidate = expectedChecksum || model?.sha256Checksum || "";
|
|
550
|
+
await downloadSingleFileToFilesystem(modelPath, modelFileName, hyperdriveKey, corestoreDir, downloadKey, checksumToValidate, progressCallback, seed, signal);
|
|
551
|
+
return modelPath;
|
|
552
|
+
}, progressCallback);
|
|
553
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type ModelProgressUpdate } from "../../../../schemas/index";
|
|
2
|
-
export declare function resolveModelPath(modelSrc:
|
|
2
|
+
export declare function resolveModelPath(modelSrc: unknown, progressCallback?: (progress: ModelProgressUpdate) => void, seed?: boolean): Promise<string>;
|
|
3
3
|
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -5,6 +5,7 @@ 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 { ModelLoadFailedError, ModelNotFoundError, SeedingNotSupportedError, } from "../../../../utils/errors-server.js";
|
|
8
9
|
/**
|
|
9
10
|
* Resolves a local file path or cached file
|
|
10
11
|
*/
|
|
@@ -12,7 +13,7 @@ async function resolveLocalOrCachedFile(modelIdOrPath) {
|
|
|
12
13
|
// Check if it's a hex string (arbitrary hyperdrive key)
|
|
13
14
|
const isHexString = /^[0-9a-fA-F]{64}$/.test(modelIdOrPath);
|
|
14
15
|
if (isHexString) {
|
|
15
|
-
throw new
|
|
16
|
+
throw new ModelLoadFailedError(`Direct hyperdrive keys not supported. Use hyperdriveKey parameter instead. Example: loadModel("model.gguf", "${modelIdOrPath}")`);
|
|
16
17
|
}
|
|
17
18
|
// Check if it's a cached file or local file
|
|
18
19
|
const cacheDir = getModelsCacheDir();
|
|
@@ -32,23 +33,26 @@ async function resolveLocalOrCachedFile(modelIdOrPath) {
|
|
|
32
33
|
catch {
|
|
33
34
|
// Invalid model ID - provide helpful error
|
|
34
35
|
const availableModels = models.map((m) => m.modelId);
|
|
35
|
-
throw new
|
|
36
|
+
throw new ModelNotFoundError(`${modelIdOrPath}". Available models: ${availableModels.join(", ")}`);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
import { modelInputToSrcSchema } from "../../../../schemas/index.js";
|
|
39
41
|
export async function resolveModelPath(modelSrc, progressCallback, seed) {
|
|
42
|
+
// Extract src and validate using Zod schema transform
|
|
43
|
+
const srcString = modelInputToSrcSchema.parse(modelSrc);
|
|
40
44
|
// Parse hyperdrive URLs if present
|
|
41
45
|
let hyperdriveKey;
|
|
42
|
-
let actualModelSrc =
|
|
43
|
-
if (
|
|
44
|
-
const { key, path } = hyperdriveUrlSchema.parse(
|
|
46
|
+
let actualModelSrc = srcString;
|
|
47
|
+
if (srcString.startsWith("pear://")) {
|
|
48
|
+
const { key, path } = hyperdriveUrlSchema.parse(srcString);
|
|
45
49
|
hyperdriveKey = key;
|
|
46
50
|
actualModelSrc = path;
|
|
47
51
|
}
|
|
48
52
|
let actualPath;
|
|
49
53
|
// Validate seeding is only used with hyperdrive models
|
|
50
54
|
if (seed && !hyperdriveKey) {
|
|
51
|
-
throw new
|
|
55
|
+
throw new SeedingNotSupportedError();
|
|
52
56
|
}
|
|
53
57
|
// Check if it's an HTTP/HTTPS URL
|
|
54
58
|
if (actualModelSrc.startsWith("http://") ||
|