@qvac/sdk 0.6.1 → 0.7.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 (277) hide show
  1. package/LICENSE +1 -24
  2. package/NOTICE +632 -0
  3. package/README.md +2 -1
  4. package/dist/client/api/embed.js +1 -4
  5. package/dist/client/api/get-model-info.d.ts +10 -3
  6. package/dist/client/api/index.d.ts +2 -0
  7. package/dist/client/api/index.js +2 -0
  8. package/dist/client/api/invoke-plugin.d.ts +14 -0
  9. package/dist/client/api/invoke-plugin.js +38 -0
  10. package/dist/client/api/registry.d.ts +14 -0
  11. package/dist/client/api/registry.js +37 -0
  12. package/dist/client/api/transcribe.js +0 -4
  13. package/dist/client/api/unload-model.js +1 -1
  14. package/dist/client/config-loader/resolve-config.node.d.ts +3 -2
  15. package/dist/client/config-loader/resolve-config.node.js +31 -4
  16. package/dist/client/init-hooks.js +2 -1
  17. package/dist/client/rpc/bare-client.d.ts +2 -0
  18. package/dist/client/rpc/bare-client.js +48 -7
  19. package/dist/client/rpc/expo-device-info.d.ts +6 -0
  20. package/dist/client/rpc/expo-device-info.js +32 -0
  21. package/dist/client/rpc/expo-rpc-client.js +9 -32
  22. package/dist/client/rpc/node-rpc-client.d.ts +1 -1
  23. package/dist/client/rpc/node-rpc-client.js +201 -28
  24. package/dist/client/rpc/rpc-client.d.ts +1 -1
  25. package/dist/client/rpc/rpc-client.js +2 -2
  26. package/dist/constants/index.d.ts +1 -0
  27. package/dist/constants/index.js +1 -0
  28. package/dist/constants/registry.d.ts +2 -0
  29. package/dist/constants/registry.js +1 -0
  30. package/dist/examples/cache-management.js +2 -2
  31. package/dist/examples/config/plugins/plugins.config.d.ts +5 -0
  32. package/dist/examples/config/plugins/plugins.config.js +4 -0
  33. package/dist/examples/download-with-blind-relays.js +1 -1
  34. package/dist/examples/llamacpp-multimodal.js +3 -3
  35. package/dist/examples/llamacpp-native-tools.js +2 -2
  36. package/dist/examples/mcp-websearch.js +2 -2
  37. package/dist/examples/multi-model-demo.js +2 -2
  38. package/dist/examples/ocr-fasttext.js +4 -4
  39. package/dist/examples/parallel-download.js +2 -2
  40. package/dist/examples/plugins.d.ts +14 -0
  41. package/dist/examples/plugins.js +129 -0
  42. package/dist/examples/registry-query.d.ts +2 -0
  43. package/dist/examples/registry-query.js +87 -0
  44. package/dist/examples/translation/translation-bergamot-batch.js +2 -2
  45. package/dist/examples/translation/translation-bergamot.js +4 -4
  46. package/dist/examples/translation/translation-opus.js +2 -2
  47. package/dist/examples/translation/translation-stream.js +2 -2
  48. package/dist/examples/tts/chatterbox.d.ts +2 -0
  49. package/dist/examples/tts/chatterbox.js +59 -0
  50. package/dist/examples/tts/supertonic.d.ts +2 -0
  51. package/dist/examples/tts/supertonic.js +51 -0
  52. package/dist/examples/tts/utils.d.ts +17 -0
  53. package/dist/examples/tts/utils.js +93 -0
  54. package/dist/examples/whispercpp-filesystem.js +2 -3
  55. package/dist/examples/whispercpp-microphone-record.js +1 -2
  56. package/dist/examples/whispercpp-prompt.js +2 -3
  57. package/dist/expo/plugins/index.d.ts +3 -0
  58. package/dist/expo/plugins/index.js +3 -0
  59. package/dist/expo/plugins/resolve-sdk-package-dir.d.ts +15 -0
  60. package/dist/expo/plugins/resolve-sdk-package-dir.js +31 -0
  61. package/dist/expo/plugins/withAndroidArchitecture.d.ts +8 -0
  62. package/dist/expo/plugins/withAndroidArchitecture.js +52 -0
  63. package/dist/expo/plugins/withAndroidNdkVersion.d.ts +8 -0
  64. package/dist/expo/plugins/withAndroidNdkVersion.js +31 -0
  65. package/dist/expo/plugins/withDeviceInfo.d.ts +10 -0
  66. package/dist/expo/plugins/withDeviceInfo.js +48 -0
  67. package/dist/expo/plugins/withMobileBundle.d.ts +5 -2
  68. package/dist/expo/plugins/withMobileBundle.js +121 -65
  69. package/dist/expo/plugins/withQvacSDK.d.ts +4 -0
  70. package/dist/expo/plugins/withQvacSDK.js +26 -1
  71. package/dist/index.d.ts +4 -4
  72. package/dist/index.js +5 -5
  73. package/dist/models/{hyperdrive → registry}/index.js +1 -1
  74. package/dist/models/registry/models.d.ts +9056 -0
  75. package/dist/models/registry/models.js +9351 -0
  76. package/dist/models/update-models/codegen.d.ts +3 -0
  77. package/dist/models/update-models/codegen.js +135 -0
  78. package/dist/models/update-models/history.d.ts +13 -0
  79. package/dist/models/update-models/history.js +88 -0
  80. package/dist/models/update-models/index.d.ts +2 -0
  81. package/dist/models/update-models/index.js +124 -0
  82. package/dist/models/update-models/naming.d.ts +4 -0
  83. package/dist/models/update-models/naming.js +314 -0
  84. package/dist/models/update-models/processing.d.ts +8 -0
  85. package/dist/models/update-models/processing.js +67 -0
  86. package/dist/models/update-models/registry.d.ts +4 -0
  87. package/dist/models/update-models/registry.js +67 -0
  88. package/dist/models/update-models/shards.d.ts +4 -0
  89. package/dist/models/update-models/shards.js +57 -0
  90. package/dist/models/update-models/types.d.ts +60 -0
  91. package/dist/models/update-models/types.js +1 -0
  92. package/dist/models/update-models/utils.d.ts +3 -0
  93. package/dist/models/update-models/utils.js +21 -0
  94. package/dist/pear/pre.d.ts +18 -0
  95. package/dist/pear/pre.js +433 -0
  96. package/dist/schemas/common.d.ts +286 -32
  97. package/dist/schemas/common.js +12 -0
  98. package/dist/schemas/download-asset.d.ts +35 -11
  99. package/dist/schemas/download-asset.js +5 -1
  100. package/dist/schemas/engine-addon-map.d.ts +5 -0
  101. package/dist/schemas/engine-addon-map.js +49 -0
  102. package/dist/schemas/get-model-info.d.ts +32 -16
  103. package/dist/schemas/get-model-info.js +17 -8
  104. package/dist/schemas/index.d.ts +4 -1
  105. package/dist/schemas/index.js +4 -4
  106. package/dist/schemas/load-model.d.ts +2224 -900
  107. package/dist/schemas/load-model.js +130 -25
  108. package/dist/schemas/model-src-utils.d.ts +30 -12
  109. package/dist/schemas/model-src-utils.js +5 -2
  110. package/dist/schemas/model-types.d.ts +15 -4
  111. package/dist/schemas/model-types.js +21 -4
  112. package/dist/schemas/path-validation.d.ts +10 -0
  113. package/dist/schemas/path-validation.js +22 -0
  114. package/dist/schemas/plugin.d.ts +180 -0
  115. package/dist/schemas/plugin.js +132 -0
  116. package/dist/schemas/rag.d.ts +2 -2
  117. package/dist/schemas/rag.js +7 -3
  118. package/dist/schemas/registry.d.ts +338 -0
  119. package/dist/schemas/registry.js +91 -0
  120. package/dist/schemas/sdk-errors-client.d.ts +6 -6
  121. package/dist/schemas/sdk-errors-client.js +15 -15
  122. package/dist/schemas/sdk-errors-registry.d.ts +6 -0
  123. package/dist/schemas/sdk-errors-registry.js +8 -0
  124. package/dist/schemas/sdk-errors-server.d.ts +16 -2
  125. package/dist/schemas/sdk-errors-server.js +90 -10
  126. package/dist/schemas/text-to-speech.d.ts +670 -1
  127. package/dist/schemas/text-to-speech.js +51 -2
  128. package/dist/schemas/translate.d.ts +2 -2
  129. package/dist/scripts/run-security-tests-bare.d.ts +2 -0
  130. package/dist/scripts/run-security-tests-bare.js +78 -0
  131. package/dist/scripts/run-security-tests.d.ts +2 -0
  132. package/dist/scripts/run-security-tests.js +22 -0
  133. package/dist/server/bare/delegate-rpc-client.d.ts +6 -0
  134. package/dist/server/bare/delegate-rpc-client.js +136 -56
  135. package/dist/server/bare/{addons/llamacpp-embedding/handlers → ops}/embed.d.ts +1 -1
  136. package/dist/server/bare/{addons/llamacpp-embedding/handlers → ops}/embed.js +3 -3
  137. package/dist/server/bare/{addons/llamacpp-completion → ops}/kv-cache-utils.js +6 -6
  138. package/dist/server/bare/{addons → ops}/load-model.js +32 -42
  139. package/dist/server/bare/{addons/nmtcpp-translation/handlers → ops}/translate.d.ts +1 -1
  140. package/dist/server/bare/{addons/nmtcpp-translation/handlers → ops}/translate.js +5 -4
  141. package/dist/server/bare/plugins/index.d.ts +7 -0
  142. package/dist/server/bare/plugins/index.js +6 -0
  143. package/dist/server/bare/{addons/llamacpp-completion → plugins/llamacpp-completion/ops}/cache-logger.js +1 -1
  144. package/dist/server/bare/{addons/llamacpp-completion/handlers → plugins/llamacpp-completion/ops}/completion-stream.d.ts +1 -1
  145. package/dist/server/bare/{addons/llamacpp-completion/handlers → plugins/llamacpp-completion/ops}/completion-stream.js +2 -3
  146. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +115 -0
  147. package/dist/server/bare/plugins/llamacpp-completion/plugin.js +142 -0
  148. package/dist/server/bare/plugins/llamacpp-embedding/plugin.d.ts +24 -0
  149. package/dist/server/bare/plugins/llamacpp-embedding/plugin.js +73 -0
  150. package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +45 -0
  151. package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +97 -0
  152. package/dist/server/bare/{addons/onnx-ocr/handlers → plugins/onnx-ocr/ops}/ocr-stream.js +2 -1
  153. package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +38 -0
  154. package/dist/server/bare/{addons/onnx-ocr/create-model.js → plugins/onnx-ocr/plugin.js} +39 -2
  155. package/dist/server/bare/{addons/onnx-tts/handlers → plugins/onnx-tts/ops}/text-to-speech.js +23 -1
  156. package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +26 -0
  157. package/dist/server/bare/plugins/onnx-tts/plugin.js +162 -0
  158. package/dist/server/bare/plugins/onnx-tts/wav-helper.d.ts +14 -0
  159. package/dist/server/bare/plugins/onnx-tts/wav-helper.js +107 -0
  160. package/dist/server/bare/plugins/whispercpp-transcription/ops/transcribe-stream.d.ts +3 -0
  161. package/dist/server/bare/{addons/whispercpp-transcription/handlers → plugins/whispercpp-transcription/ops}/transcribe-stream.js +1 -2
  162. package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +31 -0
  163. package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +74 -0
  164. package/dist/server/bare/rag-hyperdb/handlers/ingest.js +1 -1
  165. package/dist/server/bare/rag-hyperdb/handlers/search.js +1 -1
  166. package/dist/server/bare/rag-hyperdb/rag-workspace-manager.js +2 -1
  167. package/dist/server/bare/registry/config-registry.js +1 -1
  168. package/dist/server/bare/registry/registry-client.d.ts +5 -0
  169. package/dist/server/bare/registry/registry-client.js +37 -0
  170. package/dist/server/env.d.ts +22 -0
  171. package/dist/server/env.js +57 -0
  172. package/dist/server/plugins/index.d.ts +2 -0
  173. package/dist/server/plugins/index.js +1 -0
  174. package/dist/server/plugins/registry.d.ts +9 -0
  175. package/dist/server/plugins/registry.js +75 -0
  176. package/dist/server/rpc/delegate-transport.d.ts +11 -0
  177. package/dist/server/rpc/delegate-transport.js +60 -0
  178. package/dist/server/rpc/handle-request.d.ts +0 -9
  179. package/dist/server/rpc/handle-request.js +13 -321
  180. package/dist/server/rpc/handler-registry.d.ts +3 -0
  181. package/dist/server/rpc/handler-registry.js +84 -0
  182. package/dist/server/rpc/handler-utils.d.ts +22 -0
  183. package/dist/server/rpc/handler-utils.js +87 -0
  184. package/dist/server/rpc/handlers/cancelHandler.js +1 -1
  185. package/dist/server/rpc/handlers/completion-stream-delegated.js +1 -1
  186. package/dist/server/rpc/handlers/completion-stream.js +2 -43
  187. package/dist/server/rpc/handlers/delete-cache.js +1 -1
  188. package/dist/server/rpc/handlers/embed.js +2 -21
  189. package/dist/server/rpc/handlers/get-model-info.js +19 -15
  190. package/dist/server/rpc/handlers/index.d.ts +9 -0
  191. package/dist/server/rpc/handlers/index.js +9 -0
  192. package/dist/server/rpc/handlers/load-model/download-manager.d.ts +1 -0
  193. package/dist/server/rpc/handlers/load-model/download-manager.js +3 -0
  194. package/dist/server/rpc/handlers/load-model/handler.js +47 -24
  195. package/dist/server/rpc/handlers/load-model/http.js +3 -2
  196. package/dist/server/rpc/handlers/load-model/hyperdrive.js +14 -10
  197. package/dist/server/rpc/handlers/load-model/registry.d.ts +10 -0
  198. package/dist/server/rpc/handlers/load-model/registry.js +571 -0
  199. package/dist/server/rpc/handlers/load-model/resolve.js +18 -5
  200. package/dist/server/rpc/handlers/load-model-delegated.js +4 -2
  201. package/dist/server/rpc/handlers/ocr-stream.js +2 -37
  202. package/dist/server/rpc/handlers/ping.d.ts +3 -0
  203. package/dist/server/rpc/handlers/ping.js +3 -0
  204. package/dist/server/rpc/handlers/plugin-dispatch.d.ts +15 -0
  205. package/dist/server/rpc/handlers/plugin-dispatch.js +54 -0
  206. package/dist/server/rpc/handlers/plugin-invoke.d.ts +4 -0
  207. package/dist/server/rpc/handlers/plugin-invoke.js +87 -0
  208. package/dist/server/rpc/handlers/registry.d.ts +5 -0
  209. package/dist/server/rpc/handlers/registry.js +157 -0
  210. package/dist/server/rpc/handlers/text-to-speech.js +2 -5
  211. package/dist/server/rpc/handlers/transcribe-stream.js +2 -30
  212. package/dist/server/rpc/handlers/translate.js +2 -42
  213. package/dist/server/rpc/handlers/unload-model.js +1 -1
  214. package/dist/server/rpc/index.d.ts +1 -1
  215. package/dist/server/utils/archive.d.ts +1 -0
  216. package/dist/server/utils/archive.js +10 -2
  217. package/dist/server/utils/cache.d.ts +5 -0
  218. package/dist/server/utils/cache.js +26 -2
  219. package/dist/server/utils/gguf-tensor-extractor.js +3 -3
  220. package/dist/server/utils/index.d.ts +1 -1
  221. package/dist/server/utils/index.js +1 -1
  222. package/dist/server/utils/path-security.d.ts +12 -0
  223. package/dist/server/utils/path-security.js +25 -0
  224. package/dist/server/utils/shard-utils.js +3 -3
  225. package/dist/server/worker-core.d.ts +6 -0
  226. package/dist/server/worker-core.js +88 -0
  227. package/dist/server/worker.d.ts +4 -5
  228. package/dist/server/worker.js +18 -116
  229. package/dist/utils/errors-client.d.ts +9 -6
  230. package/dist/utils/errors-client.js +19 -11
  231. package/dist/utils/errors-server.d.ts +44 -5
  232. package/dist/utils/errors-server.js +82 -9
  233. package/dist/utils/path-sanitize.d.ts +17 -0
  234. package/dist/utils/path-sanitize.js +54 -0
  235. package/expo/plugins/patches/android-link.mjs +63 -0
  236. package/expo/plugins/patches/ios-link.mjs +63 -0
  237. package/package.json +72 -10
  238. package/dist/examples/text-to-speech.d.ts +0 -3
  239. package/dist/examples/text-to-speech.js +0 -137
  240. package/dist/models/hyperdrive/models.d.ts +0 -4003
  241. package/dist/models/hyperdrive/models.js +0 -4282
  242. package/dist/server/bare/addons/index.d.ts +0 -4
  243. package/dist/server/bare/addons/index.js +0 -3
  244. package/dist/server/bare/addons/llamacpp-completion/create-model.d.ts +0 -8
  245. package/dist/server/bare/addons/llamacpp-completion/create-model.js +0 -43
  246. package/dist/server/bare/addons/llamacpp-completion/index.d.ts +0 -4
  247. package/dist/server/bare/addons/llamacpp-completion/index.js +0 -3
  248. package/dist/server/bare/addons/llamacpp-embedding/create-model.d.ts +0 -8
  249. package/dist/server/bare/addons/llamacpp-embedding/create-model.js +0 -38
  250. package/dist/server/bare/addons/llamacpp-embedding/index.d.ts +0 -3
  251. package/dist/server/bare/addons/llamacpp-embedding/index.js +0 -2
  252. package/dist/server/bare/addons/nmtcpp-translation/create-model.d.ts +0 -8
  253. package/dist/server/bare/addons/nmtcpp-translation/create-model.js +0 -52
  254. package/dist/server/bare/addons/nmtcpp-translation/index.d.ts +0 -3
  255. package/dist/server/bare/addons/nmtcpp-translation/index.js +0 -2
  256. package/dist/server/bare/addons/onnx-ocr/create-model.d.ts +0 -10
  257. package/dist/server/bare/addons/onnx-ocr/index.d.ts +0 -3
  258. package/dist/server/bare/addons/onnx-ocr/index.js +0 -2
  259. package/dist/server/bare/addons/onnx-tts/create-model.d.ts +0 -10
  260. package/dist/server/bare/addons/onnx-tts/create-model.js +0 -25
  261. package/dist/server/bare/addons/onnx-tts/index.d.ts +0 -3
  262. package/dist/server/bare/addons/onnx-tts/index.js +0 -2
  263. package/dist/server/bare/addons/whispercpp-transcription/create-model.d.ts +0 -10
  264. package/dist/server/bare/addons/whispercpp-transcription/create-model.js +0 -37
  265. package/dist/server/bare/addons/whispercpp-transcription/handlers/transcribe-stream.d.ts +0 -3
  266. package/dist/server/bare/addons/whispercpp-transcription/index.d.ts +0 -3
  267. package/dist/server/bare/addons/whispercpp-transcription/index.js +0 -2
  268. /package/dist/models/{hyperdrive → registry}/index.d.ts +0 -0
  269. /package/dist/server/bare/{addons → ops}/cancel.d.ts +0 -0
  270. /package/dist/server/bare/{addons → ops}/cancel.js +0 -0
  271. /package/dist/server/bare/{addons/llamacpp-completion → ops}/kv-cache-utils.d.ts +0 -0
  272. /package/dist/server/bare/{addons → ops}/load-model.d.ts +0 -0
  273. /package/dist/server/bare/{addons → ops}/unload-model.d.ts +0 -0
  274. /package/dist/server/bare/{addons → ops}/unload-model.js +0 -0
  275. /package/dist/server/bare/{addons/llamacpp-completion → plugins/llamacpp-completion/ops}/cache-logger.d.ts +0 -0
  276. /package/dist/server/bare/{addons/onnx-ocr/handlers → plugins/onnx-ocr/ops}/ocr-stream.d.ts +0 -0
  277. /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/hyperdrive/models.js";
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 = path.join(cacheDir, modelIdOrPath);
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 = path;
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 { getRPC } from "../../bare/delegate-rpc-client.js";
4
- import { send, stream } from "../../../client/rpc/rpc-client.js";
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 { ocr } from "../../bare/addons/onnx-ocr/index.js";
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
- const { modelId, image, options } = request;
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
  }
@@ -0,0 +1,3 @@
1
+ import type { PingResponse } from "../../../schemas/index";
2
+ export declare function handlePing(): PingResponse;
3
+ //# sourceMappingURL=ping.d.ts.map