@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.
Files changed (207) hide show
  1. package/README.md +22 -0
  2. package/dist/client/api/cancel.d.ts +1 -1
  3. package/dist/client/api/cancel.js +4 -3
  4. package/dist/client/api/completion-stream.d.ts +24 -32
  5. package/dist/client/api/completion-stream.js +142 -50
  6. package/dist/client/api/delete-cache.js +3 -2
  7. package/dist/client/api/download-asset.d.ts +3 -3
  8. package/dist/client/api/download-asset.js +12 -17
  9. package/dist/client/api/embed.d.ts +1 -1
  10. package/dist/client/api/embed.js +4 -3
  11. package/dist/client/api/get-model-info.d.ts +29 -0
  12. package/dist/client/api/get-model-info.js +14 -0
  13. package/dist/client/api/index.d.ts +3 -0
  14. package/dist/client/api/index.js +3 -0
  15. package/dist/client/api/load-model.d.ts +3 -3
  16. package/dist/client/api/load-model.js +13 -22
  17. package/dist/client/api/logging-stream.d.ts +1 -1
  18. package/dist/client/api/logging-stream.js +3 -2
  19. package/dist/client/api/ping.d.ts +1 -1
  20. package/dist/client/api/ping.js +3 -2
  21. package/dist/client/api/provide.d.ts +1 -1
  22. package/dist/client/api/provide.js +4 -3
  23. package/dist/client/api/rag.d.ts +3 -3
  24. package/dist/client/api/rag.js +13 -12
  25. package/dist/client/api/set-config.d.ts +7 -0
  26. package/dist/client/api/set-config.js +20 -0
  27. package/dist/client/api/stop-provider.d.ts +1 -1
  28. package/dist/client/api/stop-provider.js +4 -3
  29. package/dist/client/api/text-to-speech.d.ts +7 -0
  30. package/dist/client/api/text-to-speech.js +58 -0
  31. package/dist/client/api/transcribe.d.ts +4 -2
  32. package/dist/client/api/transcribe.js +9 -5
  33. package/dist/client/api/translate.d.ts +1 -1
  34. package/dist/client/api/translate.js +3 -2
  35. package/dist/client/api/unload-model.d.ts +5 -1
  36. package/dist/client/api/unload-model.js +14 -4
  37. package/dist/client/config-manager.d.ts +4 -0
  38. package/dist/client/config-manager.js +8 -0
  39. package/dist/client/index.d.ts +1 -1
  40. package/dist/client/index.js +1 -1
  41. package/dist/client/init-hooks.d.ts +6 -0
  42. package/dist/client/init-hooks.js +37 -0
  43. package/dist/client/rpc/bare-client.d.ts +69 -0
  44. package/dist/client/rpc/bare-client.js +5 -4
  45. package/dist/client/rpc/bare-rpc-client.js +7 -3
  46. package/dist/client/rpc/expo-rpc-client.d.ts +1 -0
  47. package/dist/client/rpc/expo-rpc-client.js +9 -1
  48. package/dist/client/rpc/node-rpc-client.d.ts +1 -1
  49. package/dist/client/rpc/node-rpc-client.js +22 -9
  50. package/dist/client/rpc/rpc-client.d.ts +1 -0
  51. package/dist/client/rpc/rpc-client.js +20 -1
  52. package/dist/examples/cache-management.d.ts +2 -0
  53. package/dist/examples/cache-management.js +97 -0
  54. package/dist/examples/delegated-inference/consumer.js +2 -2
  55. package/dist/examples/download-with-blind-relays.d.ts +2 -0
  56. package/dist/examples/download-with-blind-relays.js +48 -0
  57. package/dist/examples/download-with-cancel.js +3 -4
  58. package/dist/examples/embed-hyperdrive.js +0 -1
  59. package/dist/examples/kv-cache-cleanup.js +0 -1
  60. package/dist/examples/kv-cache-custom-key.js +0 -1
  61. package/dist/examples/kv-cache-example.js +0 -1
  62. package/dist/examples/llamacpp-file-logging.js +0 -1
  63. package/dist/examples/llamacpp-filesystem.js +2 -2
  64. package/dist/examples/llamacpp-http.js +0 -1
  65. package/dist/examples/llamacpp-hyperdrive.js +0 -1
  66. package/dist/examples/llamacpp-multimodal.js +0 -1
  67. package/dist/examples/llamacpp-native-tools.js +118 -36
  68. package/dist/examples/llamacpp-sharded.d.ts +2 -0
  69. package/dist/examples/llamacpp-sharded.js +44 -0
  70. package/dist/examples/multi-model-demo.js +0 -1
  71. package/dist/examples/parallel-download.js +2 -2
  72. package/dist/examples/rag/rag-chromadb.js +0 -1
  73. package/dist/examples/rag/rag-hyperdb-workspaces.js +0 -1
  74. package/dist/examples/rag/rag-hyperdb.js +0 -1
  75. package/dist/examples/rag/rag-lancedb.js +0 -1
  76. package/dist/examples/rag/rag-sqlite.js +0 -1
  77. package/dist/examples/text-to-speech.d.ts +3 -0
  78. package/dist/examples/text-to-speech.js +137 -0
  79. package/dist/examples/translation/translation-indic.js +3 -1
  80. package/dist/examples/translation/translation-llm-context.js +0 -1
  81. package/dist/examples/translation/translation-llm.js +2 -2
  82. package/dist/examples/translation/translation-opus.js +3 -1
  83. package/dist/examples/translation/translation-stream.js +0 -1
  84. package/dist/examples/whispercpp-filesystem.js +32 -5
  85. package/dist/examples/whispercpp-microphone-record.js +9 -9
  86. package/dist/examples/whispercpp-prompt.d.ts +2 -0
  87. package/dist/examples/whispercpp-prompt.js +68 -0
  88. package/dist/expo/plugins/withMobileBundle.js +9 -3
  89. package/dist/index.d.ts +4 -2
  90. package/dist/index.js +4 -2
  91. package/dist/models/hyperdrive/models.d.ts +1916 -100
  92. package/dist/models/hyperdrive/models.js +2042 -102
  93. package/dist/schemas/common.d.ts +413 -58
  94. package/dist/schemas/common.js +9 -0
  95. package/dist/schemas/completion-stream.d.ts +26 -22
  96. package/dist/schemas/completion-stream.js +9 -23
  97. package/dist/schemas/download-asset.d.ts +77 -2
  98. package/dist/schemas/download-asset.js +15 -4
  99. package/dist/schemas/get-model-info.d.ts +100 -0
  100. package/dist/schemas/get-model-info.js +40 -0
  101. package/dist/schemas/index.d.ts +7 -0
  102. package/dist/schemas/index.js +7 -0
  103. package/dist/schemas/llamacpp-config.d.ts +2 -1
  104. package/dist/schemas/llamacpp-config.js +3 -4
  105. package/dist/schemas/load-model.d.ts +2316 -266
  106. package/dist/schemas/load-model.js +157 -10
  107. package/dist/schemas/model-src-utils.d.ts +98 -0
  108. package/dist/schemas/model-src-utils.js +31 -0
  109. package/dist/schemas/sdk-errors-client.d.ts +25 -0
  110. package/dist/schemas/sdk-errors-client.js +111 -0
  111. package/dist/schemas/sdk-errors-server.d.ts +55 -0
  112. package/dist/schemas/sdk-errors-server.js +271 -0
  113. package/dist/schemas/set-config.d.ts +21 -0
  114. package/dist/schemas/set-config.js +14 -0
  115. package/dist/schemas/text-to-speech.d.ts +34 -0
  116. package/dist/schemas/text-to-speech.js +25 -0
  117. package/dist/schemas/tools.d.ts +62 -0
  118. package/dist/schemas/tools.js +43 -0
  119. package/dist/schemas/transcription.d.ts +25 -2
  120. package/dist/schemas/transcription.js +12 -1
  121. package/dist/schemas/translation-config.d.ts +33 -5
  122. package/dist/schemas/translation-config.js +18 -1
  123. package/dist/schemas/unload-model.d.ts +2 -0
  124. package/dist/schemas/unload-model.js +2 -0
  125. package/dist/schemas/whispercpp-config.d.ts +55 -18
  126. package/dist/schemas/whispercpp-config.js +63 -15
  127. package/dist/server/bare/addons/cancel.js +2 -1
  128. package/dist/server/bare/addons/llamacpp/create-model.d.ts +9 -2
  129. package/dist/server/bare/addons/llamacpp/create-model.js +23 -23
  130. package/dist/server/bare/addons/llamacpp/handlers/completion-stream.d.ts +10 -2
  131. package/dist/server/bare/addons/llamacpp/handlers/completion-stream.js +62 -43
  132. package/dist/server/bare/addons/llamacpp/handlers/embed.js +3 -2
  133. package/dist/server/bare/addons/llamacpp/kv-cache-utils.d.ts +1 -1
  134. package/dist/server/bare/addons/llamacpp/kv-cache-utils.js +6 -2
  135. package/dist/server/bare/addons/load-model.js +59 -28
  136. package/dist/server/bare/addons/translation/create-model.d.ts +5 -1
  137. package/dist/server/bare/addons/translation/create-model.js +17 -25
  138. package/dist/server/bare/addons/translation/handlers/translate.js +10 -1
  139. package/dist/server/bare/addons/tts/create-model.d.ts +10 -0
  140. package/dist/server/bare/addons/tts/create-model.js +25 -0
  141. package/dist/server/bare/addons/tts/handlers/text-to-speech.d.ts +3 -0
  142. package/dist/server/bare/addons/tts/handlers/text-to-speech.js +87 -0
  143. package/dist/server/bare/addons/tts/index.d.ts +3 -0
  144. package/dist/server/bare/addons/tts/index.js +2 -0
  145. package/dist/server/bare/addons/unload-model.js +21 -3
  146. package/dist/server/bare/addons/whispercpp/create-model.d.ts +5 -1
  147. package/dist/server/bare/addons/whispercpp/create-model.js +13 -32
  148. package/dist/server/bare/addons/whispercpp/handlers/transcribe-stream.js +41 -20
  149. package/dist/server/bare/delegate-rpc-client.js +3 -2
  150. package/dist/server/bare/hyperswarm.d.ts +3 -0
  151. package/dist/server/bare/hyperswarm.js +22 -1
  152. package/dist/server/bare/rag-hyperdb/rag-manager.js +2 -3
  153. package/dist/server/bare/registry/config-registry.d.ts +5 -0
  154. package/dist/server/bare/registry/config-registry.js +39 -0
  155. package/dist/server/bare/registry/model-registry.d.ts +13 -0
  156. package/dist/server/bare/registry/model-registry.js +24 -7
  157. package/dist/server/rpc/handle-request.js +42 -3
  158. package/dist/server/rpc/handlers/completion-stream-delegated.js +2 -1
  159. package/dist/server/rpc/handlers/completion-stream.js +11 -4
  160. package/dist/server/rpc/handlers/download-asset.js +2 -3
  161. package/dist/server/rpc/handlers/get-model-info.d.ts +3 -0
  162. package/dist/server/rpc/handlers/get-model-info.js +148 -0
  163. package/dist/server/rpc/handlers/index.d.ts +4 -0
  164. package/dist/server/rpc/handlers/index.js +6 -0
  165. package/dist/server/rpc/handlers/load-model/http.js +51 -10
  166. package/dist/server/rpc/handlers/load-model/hyperdrive.js +336 -156
  167. package/dist/server/rpc/handlers/load-model/index.js +1 -1
  168. package/dist/server/rpc/handlers/load-model/resolve.d.ts +1 -1
  169. package/dist/server/rpc/handlers/load-model/resolve.js +10 -6
  170. package/dist/server/rpc/handlers/load-model-delegated.js +7 -4
  171. package/dist/server/rpc/handlers/load-model.js +19 -1
  172. package/dist/server/rpc/handlers/provideHandler/handler.js +2 -1
  173. package/dist/server/rpc/handlers/rag.js +0 -3
  174. package/dist/server/rpc/handlers/set-config.d.ts +3 -0
  175. package/dist/server/rpc/handlers/set-config.js +18 -0
  176. package/dist/server/rpc/handlers/stopProvideHandler.js +3 -1
  177. package/dist/server/rpc/handlers/text-to-speech.d.ts +3 -0
  178. package/dist/server/rpc/handlers/text-to-speech.js +7 -0
  179. package/dist/server/rpc/handlers/transcribe-stream.js +6 -2
  180. package/dist/server/rpc/handlers/unload-model.js +7 -0
  181. package/dist/server/utils/cache.d.ts +13 -8
  182. package/dist/server/utils/cache.js +69 -14
  183. package/dist/server/utils/checksum.d.ts +2 -0
  184. package/dist/server/utils/checksum.js +24 -0
  185. package/dist/server/utils/index.d.ts +2 -0
  186. package/dist/server/utils/index.js +2 -0
  187. package/dist/server/utils/shard-utils.d.ts +22 -0
  188. package/dist/server/utils/shard-utils.js +37 -0
  189. package/dist/server/utils/tool-integration.d.ts +13 -0
  190. package/dist/server/utils/tool-integration.js +43 -0
  191. package/dist/server/utils/tool-parser.d.ts +7 -0
  192. package/dist/server/utils/tool-parser.js +244 -0
  193. package/dist/utils/errors-base.d.ts +3 -0
  194. package/dist/utils/errors-base.js +13 -0
  195. package/dist/utils/errors-client.d.ts +107 -0
  196. package/dist/utils/errors-client.js +186 -0
  197. package/dist/utils/errors-server.d.ts +146 -0
  198. package/dist/utils/errors-server.js +253 -0
  199. package/dist/utils/tool-helpers.d.ts +17 -0
  200. package/dist/utils/tool-helpers.js +99 -0
  201. package/package.json +12 -8
  202. package/dist/examples/llamacpp-tools-routing.d.ts +0 -2
  203. package/dist/examples/llamacpp-tools-routing.js +0 -364
  204. package/dist/examples/llamacpp-tools.d.ts +0 -2
  205. package/dist/examples/llamacpp-tools.js +0 -81
  206. package/dist/server/http-server.d.ts +0 -2
  207. 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
- function createProgressHandler(blob, progressCallback, downloadKey, initialBytes = 0) {
14
- let downloadedBytes = initialBytes;
15
- return (index, bytes) => {
16
- if (index >= blob.blockOffset &&
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 swarm = new Hyperswarm();
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 Error(`File ${modelFileName} not found in hyperdrive. ${filesInfo}`);
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 isValid = await validateFileChecksum(modelPath, expectedChecksum);
191
- if (isValid) {
192
- return modelPath;
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 performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey, corestoreDir, downloadKey, progressCallback, seed, signal) {
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 Error("Download cancelled");
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 { entry, blobs, targetPath, blocksDownloaded, totalBlocks, totalBytes, } = await validateFileExistence(setup.drive, modelFileName);
240
- const blob = entry?.value?.blob;
241
- if (!blob) {
242
- throw new Error(`No blob found for ${modelFileName}`);
243
- }
244
- // Calculate already downloaded bytes for resuming
245
- const bytesPerBlock = totalBlocks > 0 ? totalBytes / totalBlocks : 0;
246
- const initialBytes = Math.floor(blocksDownloaded * bytesPerBlock);
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 Error(`Download failed for ${modelFileName} from hyperdrive: ${hyperdriveKey}`);
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
- export async function downloadModelFromHyperdrive(hyperdriveKey, modelFileName, seed, progressCallback, expectedChecksum) {
312
- const downloadKey = createHyperdriveDownloadKey(hyperdriveKey, modelFileName);
313
- // Check if already downloading
314
- const existing = getActiveDownload(downloadKey);
315
- if (existing) {
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
- const corestoreDir = path.join(getEnv().HOME_DIR, ".qvac", "corestore", hyperdriveKey);
319
- const cacheDir = getModelsCacheDir();
320
- const sourceHash = generateShortHash(`${hyperdriveKey}/${modelFileName}`);
321
- const modelPath = path.join(cacheDir, `${sourceHash}_${modelFileName}`);
322
- // Get model definition to get expected size (if it's a known model)
323
- const model = getModelBySrc(modelFileName, hyperdriveKey);
324
- // First, check if we already have a valid cached file (only if we have model metadata)
325
- if (model) {
326
- const cachedPath = await validateCachedFile(modelPath, modelFileName, model.expectedSize, expectedChecksum || model.sha256Checksum);
327
- if (cachedPath) {
328
- // Delete any leftover corestore directory from previous runs (only when not seeding)
329
- if (!seed) {
330
- await deleteCorestoreDirectory(corestoreDir);
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
- // Create managed download with AbortController
335
+ }
336
+ function createManagedDownload(downloadKey, hyperdriveKey, modelFileName, downloadFn, progressCallback) {
336
337
  const abortController = new AbortController();
337
338
  const downloadPromise = (async () => {
338
339
  try {
339
- // Download the file from hyperdrive
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
- export { handleLoadModel } from "../load-model";
1
+ export { handleLoadModel } from "../load-model.js";
2
2
  export { downloadModelFromHyperdrive } from "./hyperdrive.js";
3
3
  export { resolveModelPath } from "./resolve.js";
@@ -1,3 +1,3 @@
1
1
  import { type ModelProgressUpdate } from "../../../../schemas/index";
2
- export declare function resolveModelPath(modelSrc: string, progressCallback?: (progress: ModelProgressUpdate) => void, seed?: boolean): Promise<string>;
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 Error(`Direct hyperdrive keys not supported. Use hyperdriveKey parameter instead. Example: loadModel("model.gguf", "${modelIdOrPath}")`);
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 Error(`Invalid model ID: "${modelIdOrPath}". Available models: ${availableModels.join(", ")}`);
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 = modelSrc;
43
- if (modelSrc.startsWith("pear://")) {
44
- const { key, path } = hyperdriveUrlSchema.parse(modelSrc);
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 Error("Seeding is only supported for hyperdrive models");
55
+ throw new SeedingNotSupportedError();
52
56
  }
53
57
  // Check if it's an HTTP/HTTPS URL
54
58
  if (actualModelSrc.startsWith("http://") ||