@elizaos/plugin-local-inference 2.0.0-beta.1 → 2.0.11-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/package.json +81 -15
- package/src/actions/generate-media.d.ts +59 -0
- package/src/actions/generate-media.d.ts.map +1 -0
- package/src/actions/generate-media.ts +647 -0
- package/src/actions/identify-speaker.d.ts +23 -0
- package/src/actions/identify-speaker.d.ts.map +1 -0
- package/src/actions/identify-speaker.ts +171 -0
- package/src/adapters/capacitor-llama/__tests__/compat-behavior.test.ts +218 -0
- package/src/adapters/capacitor-llama/__tests__/index.test.ts +68 -0
- package/src/adapters/capacitor-llama/__tests__/structured-output.test.ts +215 -0
- package/src/adapters/capacitor-llama/__tests__/text-streaming.test.ts +174 -0
- package/src/adapters/capacitor-llama/environment.ts +71 -0
- package/src/adapters/capacitor-llama/index.browser.ts +83 -0
- package/src/adapters/capacitor-llama/index.ts +807 -0
- package/src/adapters/capacitor-llama/loader.ts +109 -0
- package/src/adapters/capacitor-llama/structured-output.ts +165 -0
- package/src/adapters/capacitor-llama/text-streaming.ts +227 -0
- package/src/adapters/capacitor-llama/types.ts +374 -0
- package/src/backends/apple-foundation.ts +127 -0
- package/src/index.d.ts +7 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +54 -0
- package/src/local-inference-routes.d.ts +38 -0
- package/src/local-inference-routes.d.ts.map +1 -0
- package/src/local-inference-routes.test.ts +344 -0
- package/src/local-inference-routes.ts +1543 -0
- package/src/provider.d.ts +21 -0
- package/src/provider.d.ts.map +1 -0
- package/src/provider.ts +1171 -0
- package/src/routes/compat-helpers.d.ts +18 -0
- package/src/routes/compat-helpers.d.ts.map +1 -0
- package/src/routes/compat-helpers.ts +274 -0
- package/src/routes/family-member-route.d.ts +62 -0
- package/src/routes/family-member-route.d.ts.map +1 -0
- package/src/routes/family-member-route.ts +353 -0
- package/src/routes/index.d.ts +19 -0
- package/src/routes/index.d.ts.map +1 -0
- package/src/routes/index.ts +60 -0
- package/src/routes/live-diarization-route.d.ts +26 -0
- package/src/routes/live-diarization-route.d.ts.map +1 -0
- package/src/routes/live-diarization-route.test.ts +213 -0
- package/src/routes/live-diarization-route.ts +122 -0
- package/src/routes/local-inference-asr-route.d.ts +4 -0
- package/src/routes/local-inference-asr-route.d.ts.map +1 -0
- package/src/routes/local-inference-asr-route.test.ts +190 -0
- package/src/routes/local-inference-asr-route.ts +213 -0
- package/src/routes/local-inference-compat-routes.d.ts +16 -0
- package/src/routes/local-inference-compat-routes.d.ts.map +1 -0
- package/src/routes/local-inference-compat-routes.test.ts +423 -0
- package/src/routes/local-inference-compat-routes.ts +782 -0
- package/src/routes/local-inference-tts-route.d.ts +7 -0
- package/src/routes/local-inference-tts-route.d.ts.map +1 -0
- package/src/routes/local-inference-tts-route.test.ts +179 -0
- package/src/routes/local-inference-tts-route.ts +230 -0
- package/src/routes/voice-first-run-routes.d.ts +62 -0
- package/src/routes/voice-first-run-routes.d.ts.map +1 -0
- package/src/routes/voice-first-run-routes.ts +524 -0
- package/src/routes/voice-models-routes.d.ts +62 -0
- package/src/routes/voice-models-routes.d.ts.map +1 -0
- package/src/routes/voice-models-routes.ts +554 -0
- package/src/routes/voice-profile-plugin-routes.d.ts +19 -0
- package/src/routes/voice-profile-plugin-routes.d.ts.map +1 -0
- package/src/routes/voice-profile-plugin-routes.ts +138 -0
- package/src/routes/voice-profiles-management-routes.d.ts +52 -0
- package/src/routes/voice-profiles-management-routes.d.ts.map +1 -0
- package/src/routes/voice-profiles-management-routes.ts +476 -0
- package/src/routes/voice-speaker-profile-routes.d.ts +57 -0
- package/src/routes/voice-speaker-profile-routes.d.ts.map +1 -0
- package/src/routes/voice-speaker-profile-routes.ts +199 -0
- package/src/runtime/aosp-llama-loader-selection.test.ts +80 -0
- package/src/runtime/capacitor-llama.d.ts +25 -0
- package/src/runtime/embedding-manager-support.d.ts +77 -0
- package/src/runtime/embedding-manager-support.d.ts.map +1 -0
- package/src/runtime/embedding-manager-support.ts +497 -0
- package/src/runtime/embedding-presets.d.ts +16 -0
- package/src/runtime/embedding-presets.d.ts.map +1 -0
- package/src/runtime/embedding-presets.ts +81 -0
- package/src/runtime/embedding-warmup-policy.d.ts +14 -0
- package/src/runtime/embedding-warmup-policy.d.ts.map +1 -0
- package/src/runtime/embedding-warmup-policy.test.ts +53 -0
- package/src/runtime/embedding-warmup-policy.ts +48 -0
- package/src/runtime/ensure-local-inference-handler.d.ts +53 -0
- package/src/runtime/ensure-local-inference-handler.d.ts.map +1 -0
- package/src/runtime/ensure-local-inference-handler.test.ts +528 -0
- package/src/runtime/ensure-local-inference-handler.ts +1398 -0
- package/src/runtime/index.d.ts +14 -0
- package/src/runtime/index.d.ts.map +1 -0
- package/src/runtime/index.ts +27 -0
- package/src/runtime/mobile-local-inference-gate.d.ts +31 -0
- package/src/runtime/mobile-local-inference-gate.d.ts.map +1 -0
- package/src/runtime/mobile-local-inference-gate.test.ts +69 -0
- package/src/runtime/mobile-local-inference-gate.ts +44 -0
- package/src/runtime/voice-entity-binding.d.ts +103 -0
- package/src/runtime/voice-entity-binding.d.ts.map +1 -0
- package/src/runtime/voice-entity-binding.transcript.test.ts +69 -0
- package/src/runtime/voice-entity-binding.ts +328 -0
- package/src/services/README.md +71 -0
- package/src/services/__tests__/backend-selector.test.ts +101 -0
- package/src/services/__tests__/checkpoint-manager.test.ts +376 -0
- package/src/services/__tests__/gpu-autotune.test.ts +400 -0
- package/src/services/__tests__/llm-streaming-binding.test.ts +85 -0
- package/src/services/__tests__/planner-grammar.test.ts +372 -0
- package/src/services/__tests__/runtime-target.test.ts +176 -0
- package/src/services/active-model-switch-rollback.test.ts +183 -0
- package/src/services/active-model.d.ts +282 -0
- package/src/services/active-model.d.ts.map +1 -0
- package/src/services/active-model.ts +1213 -0
- package/src/services/asr/errors.d.ts +21 -0
- package/src/services/asr/errors.d.ts.map +1 -0
- package/src/services/asr/errors.ts +50 -0
- package/src/services/asr/hash.d.ts +28 -0
- package/src/services/asr/hash.d.ts.map +1 -0
- package/src/services/asr/hash.ts +49 -0
- package/src/services/asr/index.d.ts +76 -0
- package/src/services/asr/index.d.ts.map +1 -0
- package/src/services/asr/index.ts +178 -0
- package/src/services/asr/types.d.ts +91 -0
- package/src/services/asr/types.d.ts.map +1 -0
- package/src/services/asr/types.ts +95 -0
- package/src/services/assignments.d.ts +71 -0
- package/src/services/assignments.d.ts.map +1 -0
- package/src/services/assignments.test.ts +80 -0
- package/src/services/assignments.ts +230 -0
- package/src/services/backend-selector.ts +95 -0
- package/src/services/backend.d.ts +346 -0
- package/src/services/backend.d.ts.map +1 -0
- package/src/services/backend.ts +612 -0
- package/src/services/bundled-models.d.ts +34 -0
- package/src/services/bundled-models.d.ts.map +1 -0
- package/src/services/bundled-models.ts +129 -0
- package/src/services/cache-bridge.d.ts +206 -0
- package/src/services/cache-bridge.d.ts.map +1 -0
- package/src/services/cache-bridge.test.ts +516 -0
- package/src/services/cache-bridge.ts +423 -0
- package/src/services/catalog.d.ts +10 -0
- package/src/services/catalog.d.ts.map +1 -0
- package/src/services/catalog.test.ts +240 -0
- package/src/services/catalog.ts +27 -0
- package/src/services/checkpoint-client.d.ts +109 -0
- package/src/services/checkpoint-client.d.ts.map +1 -0
- package/src/services/checkpoint-client.ts +258 -0
- package/src/services/checkpoint-manager.ts +474 -0
- package/src/services/cloud-fallback.d.ts +102 -0
- package/src/services/cloud-fallback.d.ts.map +1 -0
- package/src/services/cloud-fallback.ts +230 -0
- package/src/services/conversation-registry.d.ts +142 -0
- package/src/services/conversation-registry.d.ts.map +1 -0
- package/src/services/conversation-registry.test.ts +235 -0
- package/src/services/conversation-registry.ts +264 -0
- package/src/services/desktop-fused-ffi-backend-runtime.d.ts +92 -0
- package/src/services/desktop-fused-ffi-backend-runtime.d.ts.map +1 -0
- package/src/services/desktop-fused-ffi-backend-runtime.ts +333 -0
- package/src/services/device-bridge.d.ts +188 -0
- package/src/services/device-bridge.d.ts.map +1 -0
- package/src/services/device-bridge.ts +1237 -0
- package/src/services/device-resource-metrics.d.ts +149 -0
- package/src/services/device-resource-metrics.d.ts.map +1 -0
- package/src/services/device-resource-metrics.test.ts +98 -0
- package/src/services/device-resource-metrics.ts +346 -0
- package/src/services/device-tier.d.ts +115 -0
- package/src/services/device-tier.d.ts.map +1 -0
- package/src/services/device-tier.test.ts +371 -0
- package/src/services/device-tier.ts +410 -0
- package/src/services/downloader.d.ts +82 -0
- package/src/services/downloader.d.ts.map +1 -0
- package/src/services/downloader.test.ts +724 -0
- package/src/services/downloader.ts +899 -0
- package/src/services/engine-direct-bundle.test.ts +58 -0
- package/src/services/engine-streaming.test.ts +80 -0
- package/src/services/engine.d.ts +534 -0
- package/src/services/engine.d.ts.map +1 -0
- package/src/services/engine.ts +1891 -0
- package/src/services/ensure-local-artifacts.integration.test.ts +273 -0
- package/src/services/ensure-local-artifacts.test.ts +368 -0
- package/src/services/ensure-local-artifacts.ts +351 -0
- package/src/services/external-scanner.d.ts +17 -0
- package/src/services/external-scanner.d.ts.map +1 -0
- package/src/services/external-scanner.ts +312 -0
- package/src/services/ffi-llm-mock.ts +354 -0
- package/src/services/ffi-llm-streaming-abi.ts +442 -0
- package/src/services/ffi-streaming-backend.d.ts +180 -0
- package/src/services/ffi-streaming-backend.d.ts.map +1 -0
- package/src/services/ffi-streaming-backend.ts +382 -0
- package/src/services/ffi-streaming-runner.d.ts +122 -0
- package/src/services/ffi-streaming-runner.d.ts.map +1 -0
- package/src/services/ffi-streaming-runner.test.ts +60 -0
- package/src/services/ffi-streaming-runner.ts +354 -0
- package/src/services/ffi-unload-ordering.test.ts +162 -0
- package/src/services/gpu-autotune.ts +534 -0
- package/src/services/gpu-detect.ts +139 -0
- package/src/services/handler-registry.d.ts +72 -0
- package/src/services/handler-registry.d.ts.map +1 -0
- package/src/services/handler-registry.ts +240 -0
- package/src/services/hardware.d.ts +63 -0
- package/src/services/hardware.d.ts.map +1 -0
- package/src/services/hardware.test.ts +183 -0
- package/src/services/hardware.ts +404 -0
- package/src/services/hf-search.d.ts +26 -0
- package/src/services/hf-search.d.ts.map +1 -0
- package/src/services/hf-search.test.ts +69 -0
- package/src/services/hf-search.ts +420 -0
- package/src/services/image-description-runtime.d.ts +14 -0
- package/src/services/image-description-runtime.d.ts.map +1 -0
- package/src/services/image-description-runtime.test.ts +61 -0
- package/src/services/image-description-runtime.ts +118 -0
- package/src/services/imagegen/aosp-unavailable.d.ts +134 -0
- package/src/services/imagegen/aosp-unavailable.d.ts.map +1 -0
- package/src/services/imagegen/aosp-unavailable.ts +229 -0
- package/src/services/imagegen/backend-selector.d.ts +118 -0
- package/src/services/imagegen/backend-selector.d.ts.map +1 -0
- package/src/services/imagegen/backend-selector.ts +281 -0
- package/src/services/imagegen/coreml-unavailable.d.ts +105 -0
- package/src/services/imagegen/coreml-unavailable.d.ts.map +1 -0
- package/src/services/imagegen/coreml-unavailable.ts +237 -0
- package/src/services/imagegen/errors.d.ts +16 -0
- package/src/services/imagegen/errors.d.ts.map +1 -0
- package/src/services/imagegen/errors.ts +40 -0
- package/src/services/imagegen/index.d.ts +58 -0
- package/src/services/imagegen/index.d.ts.map +1 -0
- package/src/services/imagegen/index.ts +144 -0
- package/src/services/imagegen/mflux.d.ts +74 -0
- package/src/services/imagegen/mflux.d.ts.map +1 -0
- package/src/services/imagegen/mflux.ts +313 -0
- package/src/services/imagegen/sd-cpp.d.ts +180 -0
- package/src/services/imagegen/sd-cpp.d.ts.map +1 -0
- package/src/services/imagegen/sd-cpp.ts +718 -0
- package/src/services/imagegen/tensorrt-unavailable.d.ts +83 -0
- package/src/services/imagegen/tensorrt-unavailable.d.ts.map +1 -0
- package/src/services/imagegen/tensorrt-unavailable.ts +295 -0
- package/src/services/imagegen/types.d.ts +181 -0
- package/src/services/imagegen/types.d.ts.map +1 -0
- package/src/services/imagegen/types.ts +193 -0
- package/src/services/index.d.ts +30 -0
- package/src/services/index.d.ts.map +1 -0
- package/src/services/index.ts +225 -0
- package/src/services/inference-capabilities.d.ts +132 -0
- package/src/services/inference-capabilities.d.ts.map +1 -0
- package/src/services/inference-capabilities.test.ts +75 -0
- package/src/services/inference-capabilities.ts +204 -0
- package/src/services/inference-telemetry.d.ts +59 -0
- package/src/services/inference-telemetry.d.ts.map +1 -0
- package/src/services/inference-telemetry.ts +143 -0
- package/src/services/ios-llama-streaming.ts +248 -0
- package/src/services/kv-spill.d.ts +189 -0
- package/src/services/kv-spill.d.ts.map +1 -0
- package/src/services/kv-spill.test.ts +222 -0
- package/src/services/kv-spill.ts +356 -0
- package/src/services/latency-trace.d.ts +346 -0
- package/src/services/latency-trace.d.ts.map +1 -0
- package/src/services/latency-trace.test.ts +266 -0
- package/src/services/latency-trace.ts +844 -0
- package/src/services/llama-server-metrics.ts +304 -0
- package/src/services/llm-streaming-binding.d.ts +96 -0
- package/src/services/llm-streaming-binding.d.ts.map +1 -0
- package/src/services/llm-streaming-binding.ts +136 -0
- package/src/services/load-args.d.ts +82 -0
- package/src/services/load-args.d.ts.map +1 -0
- package/src/services/load-args.ts +81 -0
- package/src/services/manifest/eliza-1.manifest.v1.json +708 -0
- package/src/services/manifest/index.d.ts +4 -0
- package/src/services/manifest/index.d.ts.map +1 -0
- package/src/services/manifest/index.ts +66 -0
- package/src/services/manifest/manifest.test.ts +693 -0
- package/src/services/manifest/schema.d.ts +715 -0
- package/src/services/manifest/schema.d.ts.map +1 -0
- package/src/services/manifest/schema.ts +655 -0
- package/src/services/manifest/types.d.ts +30 -0
- package/src/services/manifest/types.d.ts.map +1 -0
- package/src/services/manifest/types.ts +55 -0
- package/src/services/manifest/validator.d.ts +66 -0
- package/src/services/manifest/validator.d.ts.map +1 -0
- package/src/services/manifest/validator.ts +569 -0
- package/src/services/memory-arbiter.d.ts +343 -0
- package/src/services/memory-arbiter.d.ts.map +1 -0
- package/src/services/memory-arbiter.test.ts +419 -0
- package/src/services/memory-arbiter.ts +1000 -0
- package/src/services/memory-monitor.d.ts +119 -0
- package/src/services/memory-monitor.d.ts.map +1 -0
- package/src/services/memory-monitor.test.ts +208 -0
- package/src/services/memory-monitor.ts +296 -0
- package/src/services/memory-pressure.d.ts +127 -0
- package/src/services/memory-pressure.d.ts.map +1 -0
- package/src/services/memory-pressure.ts +413 -0
- package/src/services/mtp-doctor.d.ts +13 -0
- package/src/services/mtp-doctor.d.ts.map +1 -0
- package/src/services/mtp-doctor.ts +78 -0
- package/src/services/network-policy.d.ts +127 -0
- package/src/services/network-policy.d.ts.map +1 -0
- package/src/services/network-policy.ts +346 -0
- package/src/services/paths.d.ts +6 -0
- package/src/services/paths.d.ts.map +1 -0
- package/src/services/paths.ts +25 -0
- package/src/services/planner-skeleton.d.ts +124 -0
- package/src/services/planner-skeleton.d.ts.map +1 -0
- package/src/services/planner-skeleton.ts +175 -0
- package/src/services/providers.d.ts +38 -0
- package/src/services/providers.d.ts.map +1 -0
- package/src/services/providers.ts +507 -0
- package/src/services/ram-budget-cache.test.ts +163 -0
- package/src/services/ram-budget.d.ts +110 -0
- package/src/services/ram-budget.d.ts.map +1 -0
- package/src/services/ram-budget.ts +0 -0
- package/src/services/readiness.d.ts +9 -0
- package/src/services/readiness.d.ts.map +1 -0
- package/src/services/readiness.test.ts +87 -0
- package/src/services/readiness.ts +238 -0
- package/src/services/recommendation.d.ts +111 -0
- package/src/services/recommendation.d.ts.map +1 -0
- package/src/services/recommendation.ts +672 -0
- package/src/services/registry.d.ts +35 -0
- package/src/services/registry.d.ts.map +1 -0
- package/src/services/registry.ts +151 -0
- package/src/services/router-handler.d.ts +92 -0
- package/src/services/router-handler.d.ts.map +1 -0
- package/src/services/router-handler.test.ts +45 -0
- package/src/services/router-handler.ts +376 -0
- package/src/services/routing-policy.d.ts +55 -0
- package/src/services/routing-policy.d.ts.map +1 -0
- package/src/services/routing-policy.ts +228 -0
- package/src/services/routing-preferences.d.ts +8 -0
- package/src/services/routing-preferences.d.ts.map +1 -0
- package/src/services/routing-preferences.ts +15 -0
- package/src/services/runtime-target.d.ts +98 -0
- package/src/services/runtime-target.d.ts.map +1 -0
- package/src/services/runtime-target.ts +154 -0
- package/src/services/service.d.ts +128 -0
- package/src/services/service.d.ts.map +1 -0
- package/src/services/service.test.ts +223 -0
- package/src/services/service.ts +735 -0
- package/src/services/session-pool.d.ts +72 -0
- package/src/services/session-pool.d.ts.map +1 -0
- package/src/services/session-pool.ts +153 -0
- package/src/services/structured-output/deterministic-repair.d.ts +23 -0
- package/src/services/structured-output/deterministic-repair.d.ts.map +1 -0
- package/src/services/structured-output/deterministic-repair.test.ts +169 -0
- package/src/services/structured-output/deterministic-repair.ts +443 -0
- package/src/services/structured-output/index.ts +4 -0
- package/src/services/structured-output.d.ts +311 -0
- package/src/services/structured-output.d.ts.map +1 -0
- package/src/services/structured-output.test.ts +483 -0
- package/src/services/structured-output.ts +712 -0
- package/src/services/transcription-priority.test.ts +211 -0
- package/src/services/tts/errors.ts +46 -0
- package/src/services/tts/index.ts +214 -0
- package/src/services/tts/tts-audio-cache.ts +235 -0
- package/src/services/tts/types.ts +157 -0
- package/src/services/types.d.ts +19 -0
- package/src/services/types.d.ts.map +1 -0
- package/src/services/types.ts +55 -0
- package/src/services/verify-on-device.d.ts +34 -0
- package/src/services/verify-on-device.d.ts.map +1 -0
- package/src/services/verify-on-device.test.ts +87 -0
- package/src/services/verify-on-device.ts +127 -0
- package/src/services/verify.d.ts +8 -0
- package/src/services/verify.d.ts.map +1 -0
- package/src/services/verify.ts +13 -0
- package/src/services/vision/aosp-unavailable.d.ts +115 -0
- package/src/services/vision/aosp-unavailable.d.ts.map +1 -0
- package/src/services/vision/aosp-unavailable.ts +163 -0
- package/src/services/vision/capacitor-llama.d.ts +99 -0
- package/src/services/vision/capacitor-llama.d.ts.map +1 -0
- package/src/services/vision/capacitor-llama.ts +255 -0
- package/src/services/vision/cloud-fallback.d.ts +47 -0
- package/src/services/vision/cloud-fallback.d.ts.map +1 -0
- package/src/services/vision/cloud-fallback.test.ts +243 -0
- package/src/services/vision/cloud-fallback.ts +268 -0
- package/src/services/vision/fallback-chain.test.ts +86 -0
- package/src/services/vision/hash.d.ts +71 -0
- package/src/services/vision/hash.d.ts.map +1 -0
- package/src/services/vision/hash.ts +157 -0
- package/src/services/vision/index.d.ts +95 -0
- package/src/services/vision/index.d.ts.map +1 -0
- package/src/services/vision/index.ts +251 -0
- package/src/services/vision/llama-server.d.ts +73 -0
- package/src/services/vision/llama-server.d.ts.map +1 -0
- package/src/services/vision/llama-server.ts +177 -0
- package/src/services/vision/types.d.ts +153 -0
- package/src/services/vision/types.d.ts.map +1 -0
- package/src/services/vision/types.ts +154 -0
- package/src/services/vision/vast-fallback.d.ts +18 -0
- package/src/services/vision/vast-fallback.d.ts.map +1 -0
- package/src/services/vision/vast-fallback.ts +127 -0
- package/src/services/vision-embedding-cache.d.ts +98 -0
- package/src/services/vision-embedding-cache.d.ts.map +1 -0
- package/src/services/vision-embedding-cache.ts +189 -0
- package/src/services/voice/VOICE_WORKBENCH.md +88 -0
- package/src/services/voice/__test-helpers__/fake-ffi.ts +92 -0
- package/src/services/voice/__test-helpers__/synthetic-speech.ts +124 -0
- package/src/services/voice/__tests__/checkpoint-manager.test.ts +241 -0
- package/src/services/voice/__tests__/checkpoint-policy.test.ts +270 -0
- package/src/services/voice/__tests__/eager-context-builder.test.ts +257 -0
- package/src/services/voice/__tests__/eliza1-eot-scorer.test.ts +288 -0
- package/src/services/voice/__tests__/eot-classifier.test.ts +431 -0
- package/src/services/voice/__tests__/optimistic-rollback.test.ts +312 -0
- package/src/services/voice/__tests__/prefill-client.test.ts +266 -0
- package/src/services/voice/__tests__/prefix-preserving-queue.test.ts +208 -0
- package/src/services/voice/__tests__/streaming-asr.test.ts +450 -0
- package/src/services/voice/__tests__/streaming-transcriber.test.ts +339 -0
- package/src/services/voice/__tests__/turn-detector-resolver.test.ts +197 -0
- package/src/services/voice/__tests__/voice-state-machine-prefill.test.ts +275 -0
- package/src/services/voice/__tests__/voice-state-machine.test.ts +354 -0
- package/src/services/voice/audio-frame-consumer.d.ts +212 -0
- package/src/services/voice/audio-frame-consumer.d.ts.map +1 -0
- package/src/services/voice/audio-frame-consumer.test.ts +343 -0
- package/src/services/voice/audio-frame-consumer.ts +491 -0
- package/src/services/voice/barge-in.d.ts +112 -0
- package/src/services/voice/barge-in.d.ts.map +1 -0
- package/src/services/voice/barge-in.test.ts +244 -0
- package/src/services/voice/barge-in.ts +336 -0
- package/src/services/voice/cancellation-coordinator.d.ts +127 -0
- package/src/services/voice/cancellation-coordinator.d.ts.map +1 -0
- package/src/services/voice/cancellation-coordinator.test.ts +196 -0
- package/src/services/voice/cancellation-coordinator.ts +269 -0
- package/src/services/voice/checkpoint-manager.d.ts +199 -0
- package/src/services/voice/checkpoint-manager.d.ts.map +1 -0
- package/src/services/voice/checkpoint-manager.ts +401 -0
- package/src/services/voice/checkpoint-policy.ts +336 -0
- package/src/services/voice/composite-eot-classifier.test.ts +59 -0
- package/src/services/voice/e2e-harness.test.ts +182 -0
- package/src/services/voice/e2e-harness.ts +743 -0
- package/src/services/voice/eager-context-builder.d.ts +170 -0
- package/src/services/voice/eager-context-builder.d.ts.map +1 -0
- package/src/services/voice/eager-context-builder.ts +262 -0
- package/src/services/voice/eliza1-eot-scorer.d.ts +124 -0
- package/src/services/voice/eliza1-eot-scorer.d.ts.map +1 -0
- package/src/services/voice/eliza1-eot-scorer.ts +242 -0
- package/src/services/voice/embedding-server.ts +200 -0
- package/src/services/voice/embedding.d.ts +133 -0
- package/src/services/voice/embedding.d.ts.map +1 -0
- package/src/services/voice/embedding.test.ts +148 -0
- package/src/services/voice/embedding.ts +244 -0
- package/src/services/voice/emotion-attribution.d.ts +68 -0
- package/src/services/voice/emotion-attribution.d.ts.map +1 -0
- package/src/services/voice/emotion-attribution.test.ts +129 -0
- package/src/services/voice/emotion-attribution.ts +361 -0
- package/src/services/voice/engine-bridge-cancellation.test.ts +422 -0
- package/src/services/voice/engine-bridge.d.ts +746 -0
- package/src/services/voice/engine-bridge.d.ts.map +1 -0
- package/src/services/voice/engine-bridge.test.ts +384 -0
- package/src/services/voice/engine-bridge.ts +2226 -0
- package/src/services/voice/eot-classifier-ggml.d.ts +179 -0
- package/src/services/voice/eot-classifier-ggml.d.ts.map +1 -0
- package/src/services/voice/eot-classifier-ggml.ts +566 -0
- package/src/services/voice/eot-classifier.d.ts +214 -0
- package/src/services/voice/eot-classifier.d.ts.map +1 -0
- package/src/services/voice/eot-classifier.ts +533 -0
- package/src/services/voice/errors.d.ts +20 -0
- package/src/services/voice/errors.d.ts.map +1 -0
- package/src/services/voice/errors.ts +32 -0
- package/src/services/voice/expressive-tags.d.ts +158 -0
- package/src/services/voice/expressive-tags.d.ts.map +1 -0
- package/src/services/voice/expressive-tags.ts +405 -0
- package/src/services/voice/ffi-bindings.d.ts +636 -0
- package/src/services/voice/ffi-bindings.d.ts.map +1 -0
- package/src/services/voice/ffi-bindings.test.ts +671 -0
- package/src/services/voice/ffi-bindings.ts +3050 -0
- package/src/services/voice/first-line-cache.d.ts +181 -0
- package/src/services/voice/first-line-cache.d.ts.map +1 -0
- package/src/services/voice/first-line-cache.ts +725 -0
- package/src/services/voice/fused-eot-scorer.d.ts +51 -0
- package/src/services/voice/fused-eot-scorer.d.ts.map +1 -0
- package/src/services/voice/fused-eot-scorer.ts +135 -0
- package/src/services/voice/index.d.ts +91 -0
- package/src/services/voice/index.d.ts.map +1 -0
- package/src/services/voice/index.ts +481 -0
- package/src/services/voice/kokoro/__tests__/kokoro-backend.test.ts +151 -0
- package/src/services/voice/kokoro/__tests__/kokoro-engine-bridge.real.test.ts +151 -0
- package/src/services/voice/kokoro/__tests__/kokoro-engine-bridge.test.ts +60 -0
- package/src/services/voice/kokoro/__tests__/kokoro-engine-discovery.test.ts +277 -0
- package/src/services/voice/kokoro/__tests__/kokoro-ffi-runtime.test.ts +235 -0
- package/src/services/voice/kokoro/__tests__/kokoro-runtime.test.ts +95 -0
- package/src/services/voice/kokoro/__tests__/phonemizer.test.ts +53 -0
- package/src/services/voice/kokoro/__tests__/runtime-selection.test.ts +231 -0
- package/src/services/voice/kokoro/__tests__/voices.test.ts +57 -0
- package/src/services/voice/kokoro/index.ts +79 -0
- package/src/services/voice/kokoro/kokoro-backend.d.ts +72 -0
- package/src/services/voice/kokoro/kokoro-backend.d.ts.map +1 -0
- package/src/services/voice/kokoro/kokoro-backend.ts +207 -0
- package/src/services/voice/kokoro/kokoro-engine-discovery.d.ts +58 -0
- package/src/services/voice/kokoro/kokoro-engine-discovery.d.ts.map +1 -0
- package/src/services/voice/kokoro/kokoro-engine-discovery.ts +177 -0
- package/src/services/voice/kokoro/kokoro-ffi-runtime.d.ts +75 -0
- package/src/services/voice/kokoro/kokoro-ffi-runtime.d.ts.map +1 -0
- package/src/services/voice/kokoro/kokoro-ffi-runtime.ts +233 -0
- package/src/services/voice/kokoro/kokoro-runtime.d.ts +100 -0
- package/src/services/voice/kokoro/kokoro-runtime.d.ts.map +1 -0
- package/src/services/voice/kokoro/kokoro-runtime.ts +170 -0
- package/src/services/voice/kokoro/phoneme-stream.ts +123 -0
- package/src/services/voice/kokoro/phonemizer.d.ts +50 -0
- package/src/services/voice/kokoro/phonemizer.d.ts.map +1 -0
- package/src/services/voice/kokoro/phonemizer.ts +344 -0
- package/src/services/voice/kokoro/pick-runtime.d.ts +61 -0
- package/src/services/voice/kokoro/pick-runtime.d.ts.map +1 -0
- package/src/services/voice/kokoro/pick-runtime.test.ts +91 -0
- package/src/services/voice/kokoro/pick-runtime.ts +130 -0
- package/src/services/voice/kokoro/runtime-selection.d.ts +92 -0
- package/src/services/voice/kokoro/runtime-selection.d.ts.map +1 -0
- package/src/services/voice/kokoro/runtime-selection.ts +237 -0
- package/src/services/voice/kokoro/types.d.ts +82 -0
- package/src/services/voice/kokoro/types.d.ts.map +1 -0
- package/src/services/voice/kokoro/types.ts +95 -0
- package/src/services/voice/kokoro/voice-presets.d.ts +23 -0
- package/src/services/voice/kokoro/voice-presets.d.ts.map +1 -0
- package/src/services/voice/kokoro/voice-presets.ts +129 -0
- package/src/services/voice/kokoro/voices.d.ts +30 -0
- package/src/services/voice/kokoro/voices.d.ts.map +1 -0
- package/src/services/voice/kokoro/voices.ts +64 -0
- package/src/services/voice/lifecycle.d.ts +135 -0
- package/src/services/voice/lifecycle.d.ts.map +1 -0
- package/src/services/voice/lifecycle.test.ts +315 -0
- package/src/services/voice/lifecycle.ts +301 -0
- package/src/services/voice/live-diarization-session.d.ts +96 -0
- package/src/services/voice/live-diarization-session.d.ts.map +1 -0
- package/src/services/voice/live-diarization-session.ts +289 -0
- package/src/services/voice/mic-source.d.ts +136 -0
- package/src/services/voice/mic-source.d.ts.map +1 -0
- package/src/services/voice/mic-source.test.ts +210 -0
- package/src/services/voice/mic-source.ts +503 -0
- package/src/services/voice/optimistic-policy.d.ts +109 -0
- package/src/services/voice/optimistic-policy.d.ts.map +1 -0
- package/src/services/voice/optimistic-policy.test.ts +101 -0
- package/src/services/voice/optimistic-policy.ts +192 -0
- package/src/services/voice/optimistic-rollback.ts +343 -0
- package/src/services/voice/partial-stabilizer.d.ts +73 -0
- package/src/services/voice/partial-stabilizer.d.ts.map +1 -0
- package/src/services/voice/partial-stabilizer.test.ts +68 -0
- package/src/services/voice/partial-stabilizer.ts +140 -0
- package/src/services/voice/phoneme-tokenizer.d.ts +49 -0
- package/src/services/voice/phoneme-tokenizer.d.ts.map +1 -0
- package/src/services/voice/phoneme-tokenizer.ts +158 -0
- package/src/services/voice/phrase-cache.d.ts +76 -0
- package/src/services/voice/phrase-cache.d.ts.map +1 -0
- package/src/services/voice/phrase-cache.test.ts +242 -0
- package/src/services/voice/phrase-cache.ts +186 -0
- package/src/services/voice/phrase-chunker.d.ts +62 -0
- package/src/services/voice/phrase-chunker.d.ts.map +1 -0
- package/src/services/voice/phrase-chunker.test.ts +239 -0
- package/src/services/voice/phrase-chunker.ts +281 -0
- package/src/services/voice/pipeline-impls.d.ts +151 -0
- package/src/services/voice/pipeline-impls.d.ts.map +1 -0
- package/src/services/voice/pipeline-impls.l6.test.ts +110 -0
- package/src/services/voice/pipeline-impls.test.ts +292 -0
- package/src/services/voice/pipeline-impls.ts +315 -0
- package/src/services/voice/pipeline.d.ts +216 -0
- package/src/services/voice/pipeline.d.ts.map +1 -0
- package/src/services/voice/pipeline.ts +505 -0
- package/src/services/voice/prefill-client.d.ts +123 -0
- package/src/services/voice/prefill-client.d.ts.map +1 -0
- package/src/services/voice/prefill-client.ts +316 -0
- package/src/services/voice/prefix-preserving-queue.d.ts +113 -0
- package/src/services/voice/prefix-preserving-queue.d.ts.map +1 -0
- package/src/services/voice/prefix-preserving-queue.ts +162 -0
- package/src/services/voice/profile-store.d.ts +248 -0
- package/src/services/voice/profile-store.d.ts.map +1 -0
- package/src/services/voice/profile-store.ts +887 -0
- package/src/services/voice/ring-buffer.d.ts +40 -0
- package/src/services/voice/ring-buffer.d.ts.map +1 -0
- package/src/services/voice/ring-buffer.ts +105 -0
- package/src/services/voice/rollback-queue.d.ts +24 -0
- package/src/services/voice/rollback-queue.d.ts.map +1 -0
- package/src/services/voice/rollback-queue.ts +74 -0
- package/src/services/voice/samantha-preset-placeholder.d.ts +67 -0
- package/src/services/voice/samantha-preset-placeholder.d.ts.map +1 -0
- package/src/services/voice/samantha-preset-placeholder.test.ts +97 -0
- package/src/services/voice/samantha-preset-placeholder.ts +148 -0
- package/src/services/voice/samantha-preset-regenerator.d.ts +87 -0
- package/src/services/voice/samantha-preset-regenerator.d.ts.map +1 -0
- package/src/services/voice/samantha-preset-regenerator.ts +393 -0
- package/src/services/voice/scheduler.d.ts +146 -0
- package/src/services/voice/scheduler.d.ts.map +1 -0
- package/src/services/voice/scheduler.t2.test.ts +141 -0
- package/src/services/voice/scheduler.ts +927 -0
- package/src/services/voice/shared-resources.d.ts +190 -0
- package/src/services/voice/shared-resources.d.ts.map +1 -0
- package/src/services/voice/shared-resources.ts +320 -0
- package/src/services/voice/speaker/attribution-pipeline.d.ts +74 -0
- package/src/services/voice/speaker/attribution-pipeline.d.ts.map +1 -0
- package/src/services/voice/speaker/attribution-pipeline.ts +386 -0
- package/src/services/voice/speaker/diarizer-fused.d.ts +59 -0
- package/src/services/voice/speaker/diarizer-fused.d.ts.map +1 -0
- package/src/services/voice/speaker/diarizer-fused.real.test.ts +100 -0
- package/src/services/voice/speaker/diarizer-fused.ts +154 -0
- package/src/services/voice/speaker/diarizer.d.ts +75 -0
- package/src/services/voice/speaker/diarizer.d.ts.map +1 -0
- package/src/services/voice/speaker/diarizer.ts +218 -0
- package/src/services/voice/speaker/encoder-fused.d.ts +60 -0
- package/src/services/voice/speaker/encoder-fused.d.ts.map +1 -0
- package/src/services/voice/speaker/encoder-fused.real.test.ts +113 -0
- package/src/services/voice/speaker/encoder-fused.ts +138 -0
- package/src/services/voice/speaker/encoder-ggml.d.ts +33 -0
- package/src/services/voice/speaker/encoder-ggml.d.ts.map +1 -0
- package/src/services/voice/speaker/encoder-ggml.ts +79 -0
- package/src/services/voice/speaker/encoder.d.ts +37 -0
- package/src/services/voice/speaker/encoder.d.ts.map +1 -0
- package/src/services/voice/speaker/encoder.ts +105 -0
- package/src/services/voice/speaker-imprint.d.ts +83 -0
- package/src/services/voice/speaker-imprint.d.ts.map +1 -0
- package/src/services/voice/speaker-imprint.test.ts +185 -0
- package/src/services/voice/speaker-imprint.ts +312 -0
- package/src/services/voice/speaker-preset-cache.d.ts +77 -0
- package/src/services/voice/speaker-preset-cache.d.ts.map +1 -0
- package/src/services/voice/speaker-preset-cache.test.ts +154 -0
- package/src/services/voice/speaker-preset-cache.ts +195 -0
- package/src/services/voice/streaming-asr/streaming-pipeline-adapter.ts +292 -0
- package/src/services/voice/system-audio-sink.d.ts +73 -0
- package/src/services/voice/system-audio-sink.d.ts.map +1 -0
- package/src/services/voice/system-audio-sink.test.ts +29 -0
- package/src/services/voice/system-audio-sink.ts +366 -0
- package/src/services/voice/transcriber.d.ts +244 -0
- package/src/services/voice/transcriber.d.ts.map +1 -0
- package/src/services/voice/transcriber.test.ts +392 -0
- package/src/services/voice/transcriber.ts +704 -0
- package/src/services/voice/turn-controller.d.ts +183 -0
- package/src/services/voice/turn-controller.d.ts.map +1 -0
- package/src/services/voice/turn-controller.test.ts +575 -0
- package/src/services/voice/turn-controller.ts +596 -0
- package/src/services/voice/types.d.ts +643 -0
- package/src/services/voice/types.d.ts.map +1 -0
- package/src/services/voice/types.ts +699 -0
- package/src/services/voice/vad.d.ts +282 -0
- package/src/services/voice/vad.d.ts.map +1 -0
- package/src/services/voice/vad.test.ts +480 -0
- package/src/services/voice/vad.ts +827 -0
- package/src/services/voice/vad.v1-v4.test.ts +222 -0
- package/src/services/voice/voice-budget.d.ts +241 -0
- package/src/services/voice/voice-budget.d.ts.map +1 -0
- package/src/services/voice/voice-budget.test.ts +420 -0
- package/src/services/voice/voice-budget.ts +656 -0
- package/src/services/voice/voice-duet.test.ts +375 -0
- package/src/services/voice/voice-emotion-classifier.d.ts +95 -0
- package/src/services/voice/voice-emotion-classifier.d.ts.map +1 -0
- package/src/services/voice/voice-emotion-classifier.test.ts +210 -0
- package/src/services/voice/voice-emotion-classifier.ts +273 -0
- package/src/services/voice/voice-preset-format.d.ts +158 -0
- package/src/services/voice/voice-preset-format.d.ts.map +1 -0
- package/src/services/voice/voice-preset-format.ts +700 -0
- package/src/services/voice/voice-preset-generator.test.ts +89 -0
- package/src/services/voice/voice-profile-artifact.d.ts +116 -0
- package/src/services/voice/voice-profile-artifact.d.ts.map +1 -0
- package/src/services/voice/voice-profile-artifact.test.ts +138 -0
- package/src/services/voice/voice-profile-artifact.ts +518 -0
- package/src/services/voice/voice-profile-routes.d.ts +83 -0
- package/src/services/voice/voice-profile-routes.d.ts.map +1 -0
- package/src/services/voice/voice-profile-routes.test.ts +429 -0
- package/src/services/voice/voice-profile-routes.ts +425 -0
- package/src/services/voice/voice-scenario.ts +154 -0
- package/src/services/voice/voice-settings.d.ts +82 -0
- package/src/services/voice/voice-settings.d.ts.map +1 -0
- package/src/services/voice/voice-settings.ts +172 -0
- package/src/services/voice/voice-state-machine.d.ts +364 -0
- package/src/services/voice/voice-state-machine.d.ts.map +1 -0
- package/src/services/voice/voice-state-machine.ts +727 -0
- package/src/services/voice/voice-workbench-report.test.ts +168 -0
- package/src/services/voice/voice-workbench-report.ts +326 -0
- package/src/services/voice/voice-workbench.test.ts +158 -0
- package/src/services/voice/voice.test.ts +1070 -0
- package/src/services/voice/wake-word-ggml.d.ts +101 -0
- package/src/services/voice/wake-word-ggml.d.ts.map +1 -0
- package/src/services/voice/wake-word-ggml.ts +320 -0
- package/src/services/voice/wake-word.d.ts +255 -0
- package/src/services/voice/wake-word.d.ts.map +1 -0
- package/src/services/voice/wake-word.test.ts +298 -0
- package/src/services/voice/wake-word.ts +554 -0
- package/src/services/voice/wrap-with-first-line-cache.d.ts +70 -0
- package/src/services/voice/wrap-with-first-line-cache.d.ts.map +1 -0
- package/src/services/voice/wrap-with-first-line-cache.ts +267 -0
- package/src/services/voice-model-updater.d.ts +240 -0
- package/src/services/voice-model-updater.d.ts.map +1 -0
- package/src/services/voice-model-updater.ts +724 -0
- package/src/services/voice-prewarm.d.ts +3 -0
- package/src/services/voice-prewarm.d.ts.map +1 -0
- package/src/services/voice-prewarm.ts +51 -0
- package/dist/index.d.ts +0 -37
- package/dist/index.js +0 -1098
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resumable GGUF downloader.
|
|
3
|
+
*
|
|
4
|
+
* Streams directly from HuggingFace to a staging file under
|
|
5
|
+
* `$STATE_DIR/local-inference/downloads/<id>.part`, then atomically moves
|
|
6
|
+
* it into `models/<id>.gguf` on success. On restart the staging file is
|
|
7
|
+
* still there; `resumeIfPossible` sends a Range request starting at the
|
|
8
|
+
* current partial size.
|
|
9
|
+
*
|
|
10
|
+
* Concurrency model: at most one download per model id. Callers use
|
|
11
|
+
* `subscribe()` to receive progress events; the service facade wires that
|
|
12
|
+
* to SSE.
|
|
13
|
+
*
|
|
14
|
+
* The runtime `fetch` follows HuggingFace redirects and still gives us a body
|
|
15
|
+
* stream that can be piped into a Node WriteStream.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import fsp from "node:fs/promises";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { Readable, type Writable } from "node:stream";
|
|
23
|
+
import { pipeline } from "node:stream/promises";
|
|
24
|
+
import { ensureDefaultAssignment } from "./assignments";
|
|
25
|
+
import {
|
|
26
|
+
buildHuggingFaceResolveUrl,
|
|
27
|
+
buildHuggingFaceResolveUrlForPath,
|
|
28
|
+
findCatalogModel,
|
|
29
|
+
isDefaultEligibleId,
|
|
30
|
+
} from "./catalog";
|
|
31
|
+
import { deviceCapsFromProbe, probeHardware } from "./hardware";
|
|
32
|
+
import {
|
|
33
|
+
type Eliza1DeviceCaps,
|
|
34
|
+
type Eliza1FileEntry,
|
|
35
|
+
type Eliza1Files,
|
|
36
|
+
type Eliza1Manifest,
|
|
37
|
+
parseManifestOrThrow,
|
|
38
|
+
SUPPORTED_BACKENDS_BY_TIER,
|
|
39
|
+
} from "./manifest";
|
|
40
|
+
import {
|
|
41
|
+
downloadsStagingDir,
|
|
42
|
+
elizaModelsDir,
|
|
43
|
+
localInferenceRoot,
|
|
44
|
+
} from "./paths";
|
|
45
|
+
import { upsertElizaModel } from "./registry";
|
|
46
|
+
import type {
|
|
47
|
+
CatalogModel,
|
|
48
|
+
DownloadEvent,
|
|
49
|
+
DownloadJob,
|
|
50
|
+
DownloadState,
|
|
51
|
+
InstalledModel,
|
|
52
|
+
} from "./types";
|
|
53
|
+
import { hashFile } from "./verify";
|
|
54
|
+
|
|
55
|
+
interface ActiveJob {
|
|
56
|
+
job: DownloadJob;
|
|
57
|
+
abortController: AbortController;
|
|
58
|
+
stagingPath: string;
|
|
59
|
+
finalPath: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type DownloadListener = (event: DownloadEvent) => void;
|
|
63
|
+
type BundleFileKind = keyof Eliza1Files;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Thrown before any weight byte is fetched when an Eliza-1 bundle's manifest
|
|
67
|
+
* is incompatible with this device — wrong schema version, no overlapping
|
|
68
|
+
* verified backend, or a RAM budget that exceeds the device's memory. Per
|
|
69
|
+
* `packages/inference/AGENTS.md` §7 there is no "download anyway" path.
|
|
70
|
+
*/
|
|
71
|
+
export class BundleIncompatibleError extends Error {
|
|
72
|
+
readonly code = "ELIZA1_BUNDLE_INCOMPATIBLE" as const;
|
|
73
|
+
constructor(message: string) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = "BundleIncompatibleError";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* One-time verify-on-device pass per `packages/inference/AGENTS.md` §7:
|
|
81
|
+
* load → 1-token text generation → 1-phrase voice generation → barge-in
|
|
82
|
+
* cancel. The downloader stays decoupled from the engine — the service
|
|
83
|
+
* layer injects this; when absent the bundle is materialized and registered
|
|
84
|
+
* but its `bundleVerifiedAt` stays unset and it does NOT auto-fill an empty
|
|
85
|
+
* default slot (an unverified bundle must not become the recommended
|
|
86
|
+
* default).
|
|
87
|
+
*/
|
|
88
|
+
export type VerifyBundleOnDevice = (args: {
|
|
89
|
+
modelId: string;
|
|
90
|
+
bundleRoot: string;
|
|
91
|
+
manifestPath: string;
|
|
92
|
+
textGgufPath: string;
|
|
93
|
+
}) => Promise<void>;
|
|
94
|
+
|
|
95
|
+
export interface DownloaderOptions {
|
|
96
|
+
/** Override the device-capability probe (tests / headless environments). */
|
|
97
|
+
probeDeviceCaps?: () => Promise<Eliza1DeviceCaps>;
|
|
98
|
+
/** Verify-on-device smoke run; see {@link VerifyBundleOnDevice}. */
|
|
99
|
+
verifyOnDevice?: VerifyBundleOnDevice;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function defaultProbeDeviceCaps(): Promise<Eliza1DeviceCaps> {
|
|
103
|
+
return deviceCapsFromProbe(await probeHardware());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reject bundles this device cannot run — runs against the manifest before
|
|
108
|
+
* any weight byte is fetched. Mirrors the publish-side `canSetAsDefault`
|
|
109
|
+
* device check, minus the `defaultEligible` flag (a user may explicitly
|
|
110
|
+
* install a non-default bundle, but only one the device can actually load).
|
|
111
|
+
*/
|
|
112
|
+
function assertBundleInstallable(
|
|
113
|
+
manifest: Eliza1Manifest,
|
|
114
|
+
device: Eliza1DeviceCaps,
|
|
115
|
+
): void {
|
|
116
|
+
// Schema version is enforced upstream by `parseManifestOrThrow` — the Zod
|
|
117
|
+
// schema only accepts the current `$schema` URL, so a manifest with a
|
|
118
|
+
// non-current schema version is rejected before we get here.
|
|
119
|
+
if (manifest.ramBudgetMb.min > device.ramMb) {
|
|
120
|
+
throw new BundleIncompatibleError(
|
|
121
|
+
`Eliza-1 bundle ${manifest.id} needs at least ${manifest.ramBudgetMb.min} MB RAM; this device has ${device.ramMb} MB`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const tierBackends = new Set(SUPPORTED_BACKENDS_BY_TIER[manifest.tier]);
|
|
125
|
+
const usable = device.availableBackends.filter(
|
|
126
|
+
(b) =>
|
|
127
|
+
tierBackends.has(b) &&
|
|
128
|
+
manifest.kernels.verifiedBackends[b].status === "pass",
|
|
129
|
+
);
|
|
130
|
+
if (usable.length === 0) {
|
|
131
|
+
const verified = Object.entries(manifest.kernels.verifiedBackends)
|
|
132
|
+
.filter(([, v]) => v.status === "pass")
|
|
133
|
+
.map(([b]) => b);
|
|
134
|
+
throw new BundleIncompatibleError(
|
|
135
|
+
`Eliza-1 bundle ${manifest.id}: no required-kernel backend is available on this device. ` +
|
|
136
|
+
`bundle verified [${verified.join(", ") || "none"}], device has [${device.availableBackends.join(", ")}], tier ${manifest.tier} supports [${[...tierBackends].join(", ")}]`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface DownloadedFile {
|
|
142
|
+
path: string;
|
|
143
|
+
sizeBytes: number;
|
|
144
|
+
sha256: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const PROGRESS_THROTTLE_MS = 250;
|
|
148
|
+
const TERMINAL_DOWNLOADS_FILENAME = "download-status.json";
|
|
149
|
+
const TERMINAL_DOWNLOAD_LIMIT = 32;
|
|
150
|
+
|
|
151
|
+
interface TerminalDownloadsFile {
|
|
152
|
+
version: 1;
|
|
153
|
+
jobs: DownloadJob[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function* readFetchBody(
|
|
157
|
+
body: ReadableStream<Uint8Array>,
|
|
158
|
+
): AsyncIterable<Buffer> {
|
|
159
|
+
const reader = body.getReader();
|
|
160
|
+
try {
|
|
161
|
+
while (true) {
|
|
162
|
+
const { done, value } = await reader.read();
|
|
163
|
+
if (done) return;
|
|
164
|
+
if (value) yield Buffer.from(value);
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
reader.releaseLock();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stagingFilename(modelId: string): string {
|
|
172
|
+
// Filename is derived deterministically so repeated download attempts
|
|
173
|
+
// reuse the same partial file and actually resume.
|
|
174
|
+
const safe = modelId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
175
|
+
return `${safe}.part`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function finalFilename(model: CatalogModel): string {
|
|
179
|
+
const safe = model.id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
180
|
+
return `${safe}.gguf`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function bundleDirname(modelId: string): string {
|
|
184
|
+
const safe = modelId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
185
|
+
return `${safe}.bundle`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function bundleStagingFilename(modelId: string, filePath: string): string {
|
|
189
|
+
const safePath = filePath.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
190
|
+
return stagingFilename(`${modelId}__${safePath}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function bundleTargetPath(root: string, filePath: string): string {
|
|
194
|
+
if (
|
|
195
|
+
!filePath ||
|
|
196
|
+
path.isAbsolute(filePath) ||
|
|
197
|
+
/^[a-zA-Z]:[\\/]/.test(filePath)
|
|
198
|
+
) {
|
|
199
|
+
throw new Error(`Invalid bundle file path: ${filePath}`);
|
|
200
|
+
}
|
|
201
|
+
const resolvedRoot = path.resolve(root);
|
|
202
|
+
const target = path.resolve(resolvedRoot, filePath);
|
|
203
|
+
if (
|
|
204
|
+
target !== resolvedRoot &&
|
|
205
|
+
!target.startsWith(`${resolvedRoot}${path.sep}`)
|
|
206
|
+
) {
|
|
207
|
+
throw new Error(`Bundle file escapes install root: ${filePath}`);
|
|
208
|
+
}
|
|
209
|
+
return target;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseBundleManifestOrThrow(
|
|
213
|
+
input: unknown,
|
|
214
|
+
catalogEntry: CatalogModel,
|
|
215
|
+
): Eliza1Manifest {
|
|
216
|
+
const manifest = parseManifestOrThrow(input);
|
|
217
|
+
if (manifest.id !== catalogEntry.id) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Invalid Eliza-1 manifest: id ${manifest.id} does not match ${catalogEntry.id}`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (
|
|
223
|
+
!manifest.files.text.some((entry) => entry.path === catalogEntry.ggufFile)
|
|
224
|
+
) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Invalid Eliza-1 manifest: primary text file ${catalogEntry.ggufFile} is missing`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return manifest;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function collectBundleFiles(
|
|
234
|
+
manifest: Eliza1Manifest,
|
|
235
|
+
): Array<{ kind: BundleFileKind; entry: Eliza1FileEntry }> {
|
|
236
|
+
const seen = new Map<
|
|
237
|
+
string,
|
|
238
|
+
{ kind: BundleFileKind; entry: Eliza1FileEntry }
|
|
239
|
+
>();
|
|
240
|
+
for (const kind of [
|
|
241
|
+
"text",
|
|
242
|
+
"voice",
|
|
243
|
+
"asr",
|
|
244
|
+
"vision",
|
|
245
|
+
"mtp",
|
|
246
|
+
"cache",
|
|
247
|
+
"embedding",
|
|
248
|
+
"vad",
|
|
249
|
+
"wakeword",
|
|
250
|
+
] as const) {
|
|
251
|
+
for (const entry of manifest.files[kind] ?? []) {
|
|
252
|
+
const current = seen.get(entry.path);
|
|
253
|
+
if (current && current.entry.sha256 !== entry.sha256) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Conflicting sha256 entries for bundle file ${entry.path}`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
seen.set(entry.path, { kind, entry });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return [...seen.values()];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function ensureDirs(): Promise<void> {
|
|
265
|
+
await fsp.mkdir(downloadsStagingDir(), { recursive: true });
|
|
266
|
+
await fsp.mkdir(elizaModelsDir(), { recursive: true });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function terminalDownloadsPath(): string {
|
|
270
|
+
return path.join(localInferenceRoot(), TERMINAL_DOWNLOADS_FILENAME);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function partialSize(stagingPath: string): Promise<number> {
|
|
274
|
+
try {
|
|
275
|
+
const stat = await fsp.stat(stagingPath);
|
|
276
|
+
return stat.isFile() ? stat.size : 0;
|
|
277
|
+
} catch {
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export class Downloader {
|
|
283
|
+
private readonly active = new Map<string, ActiveJob>();
|
|
284
|
+
private readonly terminal = new Map<string, DownloadJob>();
|
|
285
|
+
private readonly listeners = new Set<DownloadListener>();
|
|
286
|
+
private readonly lastEmit = new Map<string, number>();
|
|
287
|
+
private readonly probeDeviceCaps: () => Promise<Eliza1DeviceCaps>;
|
|
288
|
+
private readonly verifyOnDevice?: VerifyBundleOnDevice;
|
|
289
|
+
|
|
290
|
+
constructor(options: DownloaderOptions = {}) {
|
|
291
|
+
this.probeDeviceCaps = options.probeDeviceCaps ?? defaultProbeDeviceCaps;
|
|
292
|
+
this.verifyOnDevice = options.verifyOnDevice;
|
|
293
|
+
this.loadTerminalDownloads();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
subscribe(listener: DownloadListener): () => void {
|
|
297
|
+
this.listeners.add(listener);
|
|
298
|
+
return () => {
|
|
299
|
+
this.listeners.delete(listener);
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
snapshot(): DownloadJob[] {
|
|
304
|
+
const active = [...this.active.values()].map((a) => ({ ...a.job }));
|
|
305
|
+
const activeIds = new Set(active.map((job) => job.modelId));
|
|
306
|
+
const terminal = [...this.terminal.values()]
|
|
307
|
+
.filter((job) => !activeIds.has(job.modelId))
|
|
308
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
309
|
+
.map((job) => ({ ...job }));
|
|
310
|
+
return [...active, ...terminal];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
isActive(modelId: string): boolean {
|
|
314
|
+
const current = this.active.get(modelId);
|
|
315
|
+
return (
|
|
316
|
+
!!current &&
|
|
317
|
+
(current.job.state === "queued" || current.job.state === "downloading")
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Start a download for a model. Accepts either a curated catalog id, or
|
|
323
|
+
* a full `CatalogModel` spec for ad-hoc HF-search results. Idempotent —
|
|
324
|
+
* returns the existing job if one is already running for the same id.
|
|
325
|
+
*/
|
|
326
|
+
async start(modelIdOrSpec: string | CatalogModel): Promise<DownloadJob> {
|
|
327
|
+
const catalogEntry =
|
|
328
|
+
typeof modelIdOrSpec === "string"
|
|
329
|
+
? findCatalogModel(modelIdOrSpec)
|
|
330
|
+
: modelIdOrSpec;
|
|
331
|
+
if (!catalogEntry) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`Unknown model id: ${typeof modelIdOrSpec === "string" ? modelIdOrSpec : "(no id)"}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const modelId = catalogEntry.id;
|
|
337
|
+
this.clearTerminalDownload(modelId);
|
|
338
|
+
|
|
339
|
+
const existing = this.active.get(modelId);
|
|
340
|
+
if (
|
|
341
|
+
existing &&
|
|
342
|
+
(existing.job.state === "queued" || existing.job.state === "downloading")
|
|
343
|
+
) {
|
|
344
|
+
return { ...existing.job };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await ensureDirs();
|
|
348
|
+
const stagingPath = path.join(
|
|
349
|
+
downloadsStagingDir(),
|
|
350
|
+
stagingFilename(modelId),
|
|
351
|
+
);
|
|
352
|
+
const finalPath = path.join(elizaModelsDir(), finalFilename(catalogEntry));
|
|
353
|
+
|
|
354
|
+
const job: DownloadJob = {
|
|
355
|
+
jobId: randomUUID(),
|
|
356
|
+
modelId,
|
|
357
|
+
state: "queued",
|
|
358
|
+
received: await partialSize(stagingPath),
|
|
359
|
+
total: Math.round(catalogEntry.sizeGb * 1024 ** 3),
|
|
360
|
+
bytesPerSec: 0,
|
|
361
|
+
etaMs: null,
|
|
362
|
+
startedAt: new Date().toISOString(),
|
|
363
|
+
updatedAt: new Date().toISOString(),
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const abortController = new AbortController();
|
|
367
|
+
const record: ActiveJob = {
|
|
368
|
+
job,
|
|
369
|
+
abortController,
|
|
370
|
+
stagingPath,
|
|
371
|
+
finalPath,
|
|
372
|
+
};
|
|
373
|
+
this.active.set(modelId, record);
|
|
374
|
+
|
|
375
|
+
// Fire-and-forget; errors are captured and emitted as a "failed" event.
|
|
376
|
+
void this.runJob(catalogEntry, record).catch(() => {
|
|
377
|
+
// `runJob` handles its own failure telemetry; we only need to swallow
|
|
378
|
+
// the unhandled-rejection here.
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
this.emit({ type: "progress", job: { ...job } });
|
|
382
|
+
return { ...job };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
cancel(modelId: string): boolean {
|
|
386
|
+
const record = this.active.get(modelId);
|
|
387
|
+
if (!record) return false;
|
|
388
|
+
if (record.job.state !== "downloading" && record.job.state !== "queued") {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
record.abortController.abort();
|
|
392
|
+
this.updateState(record, "cancelled");
|
|
393
|
+
this.rememberTerminalDownload(record.job);
|
|
394
|
+
this.emit({ type: "cancelled", job: { ...record.job } });
|
|
395
|
+
this.active.delete(modelId);
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private emit(event: DownloadEvent): void {
|
|
400
|
+
for (const listener of this.listeners) {
|
|
401
|
+
try {
|
|
402
|
+
listener(event);
|
|
403
|
+
} catch {
|
|
404
|
+
// A bad listener must not kill the downloader; drop it silently.
|
|
405
|
+
this.listeners.delete(listener);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private updateState(record: ActiveJob, state: DownloadState): void {
|
|
411
|
+
record.job.state = state;
|
|
412
|
+
record.job.updatedAt = new Date().toISOString();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private loadTerminalDownloads(): void {
|
|
416
|
+
try {
|
|
417
|
+
const raw = fs.readFileSync(terminalDownloadsPath(), "utf8");
|
|
418
|
+
const parsed = JSON.parse(raw) as TerminalDownloadsFile;
|
|
419
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.jobs)) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
for (const job of parsed.jobs) {
|
|
423
|
+
if (
|
|
424
|
+
job &&
|
|
425
|
+
typeof job.modelId === "string" &&
|
|
426
|
+
(job.state === "completed" ||
|
|
427
|
+
job.state === "failed" ||
|
|
428
|
+
job.state === "cancelled")
|
|
429
|
+
) {
|
|
430
|
+
this.terminal.set(job.modelId, { ...job });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch {
|
|
434
|
+
// Missing or malformed terminal-download state should not block
|
|
435
|
+
// local inference. New terminal states will rewrite the file.
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private persistTerminalDownloads(): void {
|
|
440
|
+
try {
|
|
441
|
+
fs.mkdirSync(localInferenceRoot(), { recursive: true });
|
|
442
|
+
const jobs = [...this.terminal.values()]
|
|
443
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
444
|
+
.slice(0, TERMINAL_DOWNLOAD_LIMIT);
|
|
445
|
+
const payload: TerminalDownloadsFile = { version: 1, jobs };
|
|
446
|
+
fs.writeFileSync(
|
|
447
|
+
terminalDownloadsPath(),
|
|
448
|
+
JSON.stringify(payload, null, 2),
|
|
449
|
+
"utf8",
|
|
450
|
+
);
|
|
451
|
+
} catch {
|
|
452
|
+
// Terminal status is useful for chat/UI telemetry but is not allowed to
|
|
453
|
+
// fail the download path.
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private rememberTerminalDownload(job: DownloadJob): void {
|
|
458
|
+
this.terminal.set(job.modelId, { ...job });
|
|
459
|
+
const ordered = [...this.terminal.values()].sort((left, right) =>
|
|
460
|
+
right.updatedAt.localeCompare(left.updatedAt),
|
|
461
|
+
);
|
|
462
|
+
this.terminal.clear();
|
|
463
|
+
for (const terminalJob of ordered.slice(0, TERMINAL_DOWNLOAD_LIMIT)) {
|
|
464
|
+
this.terminal.set(terminalJob.modelId, terminalJob);
|
|
465
|
+
}
|
|
466
|
+
this.persistTerminalDownloads();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private clearTerminalDownload(modelId: string): void {
|
|
470
|
+
if (!this.terminal.delete(modelId)) return;
|
|
471
|
+
this.persistTerminalDownloads();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private throttleEmit(record: ActiveJob): void {
|
|
475
|
+
const now = Date.now();
|
|
476
|
+
const last = this.lastEmit.get(record.job.modelId) ?? 0;
|
|
477
|
+
if (now - last < PROGRESS_THROTTLE_MS) return;
|
|
478
|
+
this.lastEmit.set(record.job.modelId, now);
|
|
479
|
+
this.emit({ type: "progress", job: { ...record.job } });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private async runJob(
|
|
483
|
+
catalogEntry: CatalogModel,
|
|
484
|
+
record: ActiveJob,
|
|
485
|
+
): Promise<void> {
|
|
486
|
+
try {
|
|
487
|
+
this.updateState(record, "downloading");
|
|
488
|
+
if (catalogEntry.bundleManifestFile) {
|
|
489
|
+
await this.runBundleJob(catalogEntry, record);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const url = buildHuggingFaceResolveUrl(catalogEntry);
|
|
494
|
+
|
|
495
|
+
const httpClient = await this.loadHttpClient();
|
|
496
|
+
const startByte = record.job.received;
|
|
497
|
+
|
|
498
|
+
const headers: Record<string, string> = {
|
|
499
|
+
"user-agent": "Eliza-LocalInference/1.0",
|
|
500
|
+
};
|
|
501
|
+
if (startByte > 0) {
|
|
502
|
+
headers.range = `bytes=${startByte}-`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const response = await httpClient.request(url, {
|
|
506
|
+
method: "GET",
|
|
507
|
+
headers,
|
|
508
|
+
signal: record.abortController.signal,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
if (response.statusCode >= 400) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`HTTP ${response.statusCode} from model hub for ${catalogEntry.hfRepo}`,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
let effectiveStartByte = startByte;
|
|
517
|
+
if (effectiveStartByte > 0 && response.statusCode !== 206) {
|
|
518
|
+
effectiveStartByte = 0;
|
|
519
|
+
record.job.received = 0;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const contentLengthHeader = response.headers["content-length"];
|
|
523
|
+
const contentLength = Array.isArray(contentLengthHeader)
|
|
524
|
+
? Number.parseInt(contentLengthHeader[0] ?? "0", 10)
|
|
525
|
+
: Number.parseInt(contentLengthHeader ?? "0", 10);
|
|
526
|
+
if (Number.isFinite(contentLength) && contentLength > 0) {
|
|
527
|
+
record.job.total = effectiveStartByte + contentLength;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const writeStream: Writable = fs.createWriteStream(record.stagingPath, {
|
|
531
|
+
flags: effectiveStartByte > 0 ? "a" : "w",
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
let lastSampleBytes = record.job.received;
|
|
535
|
+
let lastSampleAt = Date.now();
|
|
536
|
+
|
|
537
|
+
const bodyStream = Readable.from(response.body);
|
|
538
|
+
bodyStream.on("data", (chunk: Buffer) => {
|
|
539
|
+
record.job.received += chunk.length;
|
|
540
|
+
|
|
541
|
+
const now = Date.now();
|
|
542
|
+
const elapsed = now - lastSampleAt;
|
|
543
|
+
if (elapsed >= 1000) {
|
|
544
|
+
record.job.bytesPerSec =
|
|
545
|
+
((record.job.received - lastSampleBytes) * 1000) / elapsed;
|
|
546
|
+
record.job.etaMs =
|
|
547
|
+
record.job.bytesPerSec > 0
|
|
548
|
+
? ((record.job.total - record.job.received) * 1000) /
|
|
549
|
+
record.job.bytesPerSec
|
|
550
|
+
: null;
|
|
551
|
+
lastSampleAt = now;
|
|
552
|
+
lastSampleBytes = record.job.received;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
this.throttleEmit(record);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await pipeline(bodyStream, writeStream);
|
|
559
|
+
|
|
560
|
+
await fsp.rename(record.stagingPath, record.finalPath);
|
|
561
|
+
|
|
562
|
+
const finalStat = await fsp.stat(record.finalPath);
|
|
563
|
+
// Compute SHA256 on commit so we have an integrity baseline. The
|
|
564
|
+
// chunk hasher we maintain during streaming gives the same result
|
|
565
|
+
// but would also have to handle resume-from-partial correctly; for
|
|
566
|
+
// a ~1-20 GB file a second disk pass at the end is simpler and
|
|
567
|
+
// robust. Measured at ~400 MB/s on an NVMe so even the 20 GB
|
|
568
|
+
// catalog entries finish in well under a minute.
|
|
569
|
+
const sha256 = await hashFile(record.finalPath);
|
|
570
|
+
|
|
571
|
+
const installed: InstalledModel = {
|
|
572
|
+
id: catalogEntry.id,
|
|
573
|
+
displayName: catalogEntry.displayName,
|
|
574
|
+
path: record.finalPath,
|
|
575
|
+
sizeBytes: finalStat.size,
|
|
576
|
+
hfRepo: catalogEntry.hfRepo,
|
|
577
|
+
installedAt: new Date().toISOString(),
|
|
578
|
+
lastUsedAt: null,
|
|
579
|
+
source: "eliza-download",
|
|
580
|
+
sha256,
|
|
581
|
+
lastVerifiedAt: new Date().toISOString(),
|
|
582
|
+
...(catalogEntry.runtimeRole
|
|
583
|
+
? { runtimeRole: catalogEntry.runtimeRole }
|
|
584
|
+
: {}),
|
|
585
|
+
};
|
|
586
|
+
await upsertElizaModel(installed);
|
|
587
|
+
|
|
588
|
+
// First-light convenience: only default-eligible Eliza-1 downloads
|
|
589
|
+
// can fill empty slots. Ad-hoc Hugging Face downloads remain
|
|
590
|
+
// explicit opt-in even though Eliza owns the downloaded file.
|
|
591
|
+
if (isDefaultEligibleId(installed.id)) {
|
|
592
|
+
await ensureDefaultAssignment(installed.id);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
this.updateState(record, "completed");
|
|
596
|
+
record.job.received = finalStat.size;
|
|
597
|
+
record.job.total = finalStat.size;
|
|
598
|
+
this.rememberTerminalDownload(record.job);
|
|
599
|
+
this.emit({ type: "completed", job: { ...record.job } });
|
|
600
|
+
} catch (err) {
|
|
601
|
+
if (record.abortController.signal.aborted) {
|
|
602
|
+
this.updateState(record, "cancelled");
|
|
603
|
+
this.rememberTerminalDownload(record.job);
|
|
604
|
+
this.emit({ type: "cancelled", job: { ...record.job } });
|
|
605
|
+
} else {
|
|
606
|
+
this.updateState(record, "failed");
|
|
607
|
+
record.job.error = err instanceof Error ? err.message : String(err);
|
|
608
|
+
this.rememberTerminalDownload(record.job);
|
|
609
|
+
this.emit({ type: "failed", job: { ...record.job } });
|
|
610
|
+
}
|
|
611
|
+
} finally {
|
|
612
|
+
this.active.delete(record.job.modelId);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private async runBundleJob(
|
|
617
|
+
catalogEntry: CatalogModel,
|
|
618
|
+
record: ActiveJob,
|
|
619
|
+
): Promise<void> {
|
|
620
|
+
if (!catalogEntry.bundleManifestFile) {
|
|
621
|
+
throw new Error(
|
|
622
|
+
`[local-inference] ${catalogEntry.id} has no bundle manifest`,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const bundleRoot = path.join(
|
|
627
|
+
elizaModelsDir(),
|
|
628
|
+
bundleDirname(catalogEntry.id),
|
|
629
|
+
);
|
|
630
|
+
await fsp.mkdir(bundleRoot, { recursive: true });
|
|
631
|
+
|
|
632
|
+
const manifestPath = bundleTargetPath(
|
|
633
|
+
bundleRoot,
|
|
634
|
+
catalogEntry.bundleManifestFile,
|
|
635
|
+
);
|
|
636
|
+
const manifestDownloaded = await this.downloadRemotePath(
|
|
637
|
+
catalogEntry,
|
|
638
|
+
catalogEntry.bundleManifestFile,
|
|
639
|
+
path.join(
|
|
640
|
+
downloadsStagingDir(),
|
|
641
|
+
bundleStagingFilename(catalogEntry.id, catalogEntry.bundleManifestFile),
|
|
642
|
+
),
|
|
643
|
+
manifestPath,
|
|
644
|
+
record,
|
|
645
|
+
0,
|
|
646
|
+
catalogEntry.bundleManifestSha256,
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
const manifest = parseBundleManifestOrThrow(
|
|
650
|
+
JSON.parse(await fsp.readFile(manifestPath, "utf8")),
|
|
651
|
+
catalogEntry,
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
// §7: schema version, RAM budget, and kernel-backend availability are
|
|
655
|
+
// checked against this device BEFORE any weight byte is fetched. An
|
|
656
|
+
// incompatible bundle aborts here — there is no "download anyway" path.
|
|
657
|
+
assertBundleInstallable(manifest, await this.probeDeviceCaps());
|
|
658
|
+
|
|
659
|
+
let completedBytes = manifestDownloaded.sizeBytes;
|
|
660
|
+
const downloaded = new Map<string, DownloadedFile>();
|
|
661
|
+
for (const { entry } of collectBundleFiles(manifest)) {
|
|
662
|
+
const finalPath = bundleTargetPath(bundleRoot, entry.path);
|
|
663
|
+
const result = await this.downloadRemotePath(
|
|
664
|
+
catalogEntry,
|
|
665
|
+
entry.path,
|
|
666
|
+
path.join(
|
|
667
|
+
downloadsStagingDir(),
|
|
668
|
+
bundleStagingFilename(catalogEntry.id, entry.path),
|
|
669
|
+
),
|
|
670
|
+
finalPath,
|
|
671
|
+
record,
|
|
672
|
+
completedBytes,
|
|
673
|
+
entry.sha256,
|
|
674
|
+
);
|
|
675
|
+
downloaded.set(entry.path, result);
|
|
676
|
+
completedBytes += result.sizeBytes;
|
|
677
|
+
record.job.received = completedBytes;
|
|
678
|
+
record.job.total = Math.max(record.job.total, completedBytes);
|
|
679
|
+
this.throttleEmit(record);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const textEntry = manifest.files.text.find(
|
|
683
|
+
(entry) => entry.path === catalogEntry.ggufFile,
|
|
684
|
+
);
|
|
685
|
+
if (!textEntry) {
|
|
686
|
+
throw new Error(
|
|
687
|
+
`[local-inference] Bundle missing primary text file ${catalogEntry.ggufFile}`,
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const textFile = downloaded.get(textEntry.path);
|
|
691
|
+
if (!textFile) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
`[local-inference] Bundle did not install text file ${textEntry.path}`,
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// §7: materialize the bundle, then run the one-time verify-on-device
|
|
698
|
+
// pass before the bundle is treated as ready. The hook is injected by
|
|
699
|
+
// the service layer so the downloader stays decoupled from the engine.
|
|
700
|
+
// When no hook is wired, `bundleVerifiedAt` stays unset and the bundle
|
|
701
|
+
// is registered but does NOT auto-fill an empty default slot.
|
|
702
|
+
let bundleVerifiedAt: string | undefined;
|
|
703
|
+
if (this.verifyOnDevice) {
|
|
704
|
+
await this.verifyOnDevice({
|
|
705
|
+
modelId: catalogEntry.id,
|
|
706
|
+
bundleRoot,
|
|
707
|
+
manifestPath,
|
|
708
|
+
textGgufPath: textFile.path,
|
|
709
|
+
});
|
|
710
|
+
bundleVerifiedAt = new Date().toISOString();
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const now = new Date().toISOString();
|
|
714
|
+
const bundleMeta = {
|
|
715
|
+
bundleRoot,
|
|
716
|
+
manifestPath,
|
|
717
|
+
manifestSha256: manifestDownloaded.sha256,
|
|
718
|
+
bundleVersion: manifest.version,
|
|
719
|
+
bundleSizeBytes: completedBytes,
|
|
720
|
+
...(bundleVerifiedAt ? { bundleVerifiedAt } : {}),
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
const installed: InstalledModel = {
|
|
724
|
+
id: catalogEntry.id,
|
|
725
|
+
displayName: catalogEntry.displayName,
|
|
726
|
+
path: textFile.path,
|
|
727
|
+
sizeBytes: textFile.sizeBytes,
|
|
728
|
+
hfRepo: catalogEntry.hfRepo,
|
|
729
|
+
installedAt: now,
|
|
730
|
+
lastUsedAt: null,
|
|
731
|
+
source: "eliza-download",
|
|
732
|
+
sha256: textFile.sha256,
|
|
733
|
+
lastVerifiedAt: now,
|
|
734
|
+
...bundleMeta,
|
|
735
|
+
};
|
|
736
|
+
await upsertElizaModel(installed);
|
|
737
|
+
|
|
738
|
+
// An empty default slot is filled only after the on-device verify pass
|
|
739
|
+
// succeeds. Without a verify hook the bundle is installed and visible,
|
|
740
|
+
// but it is not allowed to auto-fill defaults.
|
|
741
|
+
if (isDefaultEligibleId(installed.id) && bundleVerifiedAt !== undefined) {
|
|
742
|
+
await ensureDefaultAssignment(installed.id);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
this.updateState(record, "completed");
|
|
746
|
+
record.job.received = completedBytes;
|
|
747
|
+
record.job.total = completedBytes;
|
|
748
|
+
this.rememberTerminalDownload(record.job);
|
|
749
|
+
this.emit({ type: "completed", job: { ...record.job } });
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
private async downloadRemotePath(
|
|
753
|
+
catalogEntry: CatalogModel,
|
|
754
|
+
remotePath: string,
|
|
755
|
+
stagingPath: string,
|
|
756
|
+
finalPath: string,
|
|
757
|
+
record: ActiveJob,
|
|
758
|
+
baseBytes: number,
|
|
759
|
+
expectedSha256?: string,
|
|
760
|
+
): Promise<DownloadedFile> {
|
|
761
|
+
if (expectedSha256) {
|
|
762
|
+
try {
|
|
763
|
+
const stat = await fsp.stat(finalPath);
|
|
764
|
+
if (stat.isFile()) {
|
|
765
|
+
const currentSha256 = await hashFile(finalPath);
|
|
766
|
+
if (currentSha256 === expectedSha256) {
|
|
767
|
+
record.job.received = baseBytes + stat.size;
|
|
768
|
+
return {
|
|
769
|
+
path: finalPath,
|
|
770
|
+
sizeBytes: stat.size,
|
|
771
|
+
sha256: currentSha256,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
await fsp.rm(finalPath, { force: true });
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
777
|
+
// Missing files are downloaded below; unreadable stale files are
|
|
778
|
+
// treated as invalid and replaced by the fresh bundle artifact.
|
|
779
|
+
}
|
|
780
|
+
} else {
|
|
781
|
+
await fsp.rm(stagingPath, { force: true }).catch(() => undefined);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
await fsp.mkdir(path.dirname(finalPath), { recursive: true });
|
|
785
|
+
await fsp.mkdir(path.dirname(stagingPath), { recursive: true });
|
|
786
|
+
|
|
787
|
+
let startByte = expectedSha256 ? await partialSize(stagingPath) : 0;
|
|
788
|
+
record.job.received = baseBytes + startByte;
|
|
789
|
+
|
|
790
|
+
const headers: Record<string, string> = {
|
|
791
|
+
"user-agent": "Eliza-LocalInference/1.0",
|
|
792
|
+
};
|
|
793
|
+
if (startByte > 0) {
|
|
794
|
+
headers.range = `bytes=${startByte}-`;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const httpClient = await this.loadHttpClient();
|
|
798
|
+
const url = buildHuggingFaceResolveUrlForPath(catalogEntry, remotePath);
|
|
799
|
+
const response = await httpClient.request(url, {
|
|
800
|
+
method: "GET",
|
|
801
|
+
headers,
|
|
802
|
+
signal: record.abortController.signal,
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
if (response.statusCode >= 400) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`HTTP ${response.statusCode} from model hub for ${catalogEntry.hfRepo}/${remotePath}`,
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
if (startByte > 0 && response.statusCode !== 206) {
|
|
811
|
+
startByte = 0;
|
|
812
|
+
record.job.received = baseBytes;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const contentLengthHeader = response.headers["content-length"];
|
|
816
|
+
const contentLength = Array.isArray(contentLengthHeader)
|
|
817
|
+
? Number.parseInt(contentLengthHeader[0] ?? "0", 10)
|
|
818
|
+
: Number.parseInt(contentLengthHeader ?? "0", 10);
|
|
819
|
+
if (Number.isFinite(contentLength) && contentLength > 0) {
|
|
820
|
+
record.job.total = Math.max(
|
|
821
|
+
record.job.total,
|
|
822
|
+
baseBytes + startByte + contentLength,
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const writeStream: Writable = fs.createWriteStream(stagingPath, {
|
|
827
|
+
flags: startByte > 0 ? "a" : "w",
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
let lastSampleBytes = record.job.received;
|
|
831
|
+
let lastSampleAt = Date.now();
|
|
832
|
+
const bodyStream = Readable.from(response.body);
|
|
833
|
+
bodyStream.on("data", (chunk: Buffer) => {
|
|
834
|
+
record.job.received += chunk.length;
|
|
835
|
+
|
|
836
|
+
const now = Date.now();
|
|
837
|
+
const elapsed = now - lastSampleAt;
|
|
838
|
+
if (elapsed >= 1000) {
|
|
839
|
+
record.job.bytesPerSec =
|
|
840
|
+
((record.job.received - lastSampleBytes) * 1000) / elapsed;
|
|
841
|
+
record.job.etaMs =
|
|
842
|
+
record.job.bytesPerSec > 0
|
|
843
|
+
? ((record.job.total - record.job.received) * 1000) /
|
|
844
|
+
record.job.bytesPerSec
|
|
845
|
+
: null;
|
|
846
|
+
lastSampleAt = now;
|
|
847
|
+
lastSampleBytes = record.job.received;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
this.throttleEmit(record);
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
await pipeline(bodyStream, writeStream);
|
|
854
|
+
await fsp.rename(stagingPath, finalPath);
|
|
855
|
+
|
|
856
|
+
const stat = await fsp.stat(finalPath);
|
|
857
|
+
const sha256 = await hashFile(finalPath);
|
|
858
|
+
if (expectedSha256 && sha256 !== expectedSha256) {
|
|
859
|
+
await fsp.rm(finalPath, { force: true });
|
|
860
|
+
throw new Error(`SHA256 mismatch for bundle file ${remotePath}`);
|
|
861
|
+
}
|
|
862
|
+
return { path: finalPath, sizeBytes: stat.size, sha256 };
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
private async loadHttpClient(): Promise<{
|
|
866
|
+
request: (
|
|
867
|
+
url: string,
|
|
868
|
+
options: {
|
|
869
|
+
method: string;
|
|
870
|
+
headers: Record<string, string>;
|
|
871
|
+
signal: AbortSignal;
|
|
872
|
+
},
|
|
873
|
+
) => Promise<{
|
|
874
|
+
statusCode: number;
|
|
875
|
+
headers: Record<string, string | string[] | undefined>;
|
|
876
|
+
body: AsyncIterable<Buffer>;
|
|
877
|
+
}>;
|
|
878
|
+
}> {
|
|
879
|
+
const fetchImpl = globalThis.fetch;
|
|
880
|
+
return {
|
|
881
|
+
request: async (url, options) => {
|
|
882
|
+
const response = await fetchImpl(url, {
|
|
883
|
+
method: options.method,
|
|
884
|
+
headers: options.headers,
|
|
885
|
+
signal: options.signal,
|
|
886
|
+
redirect: "follow",
|
|
887
|
+
});
|
|
888
|
+
if (!response.body) {
|
|
889
|
+
throw new Error(`Empty response body from ${url}`);
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
statusCode: response.status,
|
|
893
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
894
|
+
body: readFetchBody(response.body),
|
|
895
|
+
};
|
|
896
|
+
},
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
}
|