@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,724 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice sub-model auto-updater (per R5-versioning §3).
|
|
3
|
+
*
|
|
4
|
+
* Watches for newer versions of the voice sub-models declared in
|
|
5
|
+
* `@elizaos/shared/local-inference/voice-models` (`VOICE_MODEL_VERSIONS`)
|
|
6
|
+
* and recommends downloads when the published-side history advertises a
|
|
7
|
+
* strictly newer semver, the publish gate set `netImprovement === true`,
|
|
8
|
+
* and the user has not pinned the installed model.
|
|
9
|
+
*
|
|
10
|
+
* Source cascade (first that responds wins):
|
|
11
|
+
*
|
|
12
|
+
* 1. **Eliza Cloud catalog** — `GET <cloudBaseUrl>/api/v1/voice-models/catalog`
|
|
13
|
+
* (signed Ed25519, current+next-key rotation). Preferred when the device
|
|
14
|
+
* is linked to Cloud.
|
|
15
|
+
* 2. **GitHub Releases of `elizaOS/eliza-1-voice-models`** — one release per
|
|
16
|
+
* `<voiceModelId>-v<version>` tag. Public, unauthenticated (60/h rate
|
|
17
|
+
* limit; cache aggressively).
|
|
18
|
+
* 3. **HuggingFace tree-listing** — `GET https://huggingface.co/api/models/<hfRepo>/tree/<revision>?recursive=true`.
|
|
19
|
+
* Final-truth probe; used when the catalog can't be reached.
|
|
20
|
+
*
|
|
21
|
+
* The local in-binary `VOICE_MODEL_VERSIONS` is always consulted alongside
|
|
22
|
+
* the remote sources so a build that ships a newer-than-remote version
|
|
23
|
+
* still wins the comparison (we never auto-downgrade).
|
|
24
|
+
*
|
|
25
|
+
* Cadence: 4 hours (`ELIZA_VOICE_UPDATE_INTERVAL_MS` overrides; defaults to
|
|
26
|
+
* 14_400_000 ms, mirroring `update-checker.ts`).
|
|
27
|
+
*
|
|
28
|
+
* Decision: see `shouldAutoUpdateVoiceModel`.
|
|
29
|
+
*
|
|
30
|
+
* Atomic swap: downloads write to `<staging>/<id>-<version>.part`, hash
|
|
31
|
+
* against the catalog sha256, then `fsp.rename` into the bundle voice dir.
|
|
32
|
+
* On verify failure the staging file is unlinked and the failure is
|
|
33
|
+
* surfaced; no automatic retry (avoid update loops on a flaky CDN).
|
|
34
|
+
*
|
|
35
|
+
* This module is platform-agnostic — it does NOT call the network-policy
|
|
36
|
+
* bridge directly; callers compose the policy decision before invoking
|
|
37
|
+
* `downloadVoiceModel`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import fsp from "node:fs/promises";
|
|
41
|
+
import path from "node:path";
|
|
42
|
+
import {
|
|
43
|
+
compareVoiceModelSemver,
|
|
44
|
+
type Ed25519PublicKey,
|
|
45
|
+
type NetworkPolicyDecision,
|
|
46
|
+
VOICE_MODEL_VERSIONS,
|
|
47
|
+
type VoiceModelId,
|
|
48
|
+
type VoiceModelVersion,
|
|
49
|
+
verifyManifestSignatureText,
|
|
50
|
+
} from "@elizaos/shared";
|
|
51
|
+
import { hashFile } from "./verify";
|
|
52
|
+
|
|
53
|
+
const DEFAULT_CHECK_INTERVAL_MS = 14_400_000; // 4 hours
|
|
54
|
+
const DEFAULT_HTTP_TIMEOUT_MS = 8_000;
|
|
55
|
+
const CATALOG_SIGNATURE_HEADER = "X-Eliza-Signature";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One remote source consulted by the cascade. Tests inject custom shapes;
|
|
59
|
+
* production wires the three sources below.
|
|
60
|
+
*/
|
|
61
|
+
export interface VoiceModelCatalogSource {
|
|
62
|
+
readonly id: "cloud" | "github" | "huggingface" | (string & {});
|
|
63
|
+
fetchAll(signal: AbortSignal): Promise<ReadonlyArray<VoiceModelVersion>>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface VoiceModelPinPolicy {
|
|
67
|
+
/** Set of `VoiceModelId`s pinned to their currently-installed version. */
|
|
68
|
+
readonly pinned: ReadonlySet<VoiceModelId>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface VoiceModelUpdateCheckResult {
|
|
72
|
+
readonly id: VoiceModelId;
|
|
73
|
+
readonly installedVersion: string | null;
|
|
74
|
+
readonly latestVersion: VoiceModelVersion | null;
|
|
75
|
+
readonly updateAvailable: boolean;
|
|
76
|
+
readonly pinned: boolean;
|
|
77
|
+
readonly reason:
|
|
78
|
+
| "up-to-date"
|
|
79
|
+
| "pinned"
|
|
80
|
+
| "not-installed"
|
|
81
|
+
| "net-regression"
|
|
82
|
+
| "bundle-incompatible"
|
|
83
|
+
| "unpublished"
|
|
84
|
+
| "update-available";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface VoiceModelUpdaterOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Catalog sources in priority order. The first source that returns a
|
|
90
|
+
* non-empty list wins; sources that throw or return empty are skipped.
|
|
91
|
+
* Defaults to `[cloud(cloudBaseUrl), github(), huggingface()]`.
|
|
92
|
+
*/
|
|
93
|
+
readonly sources?: ReadonlyArray<VoiceModelCatalogSource>;
|
|
94
|
+
/** Eliza Cloud root URL (e.g. `https://cloud.elizaos.ai`). */
|
|
95
|
+
readonly cloudBaseUrl?: string;
|
|
96
|
+
/** Ed25519 public keys accepted for the Cloud catalog. */
|
|
97
|
+
readonly publicKeys?: ReadonlyArray<Ed25519PublicKey>;
|
|
98
|
+
/** Optional logger for trace events. */
|
|
99
|
+
readonly logger?: {
|
|
100
|
+
info: (msg: string) => void;
|
|
101
|
+
warn: (msg: string) => void;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decide whether a candidate version should auto-update over the installed
|
|
107
|
+
* one. Three required gates:
|
|
108
|
+
*
|
|
109
|
+
* 1. candidate version > installed version (semver compare > 0).
|
|
110
|
+
* 2. `candidate.evalDeltas.netImprovement === true`.
|
|
111
|
+
* 3. installed bundle.version ≥ `candidate.minBundleVersion` (caller passes
|
|
112
|
+
* `bundleVersion`; pass an empty string to skip the bundle gate, e.g.
|
|
113
|
+
* during pure UI listing).
|
|
114
|
+
*
|
|
115
|
+
* A pinned id always declines.
|
|
116
|
+
*/
|
|
117
|
+
export function shouldAutoUpdateVoiceModel(args: {
|
|
118
|
+
installedVersion: string | null;
|
|
119
|
+
candidate: VoiceModelVersion;
|
|
120
|
+
bundleVersion: string;
|
|
121
|
+
pinned: boolean;
|
|
122
|
+
}): { allow: boolean; reason: VoiceModelUpdateCheckResult["reason"] } {
|
|
123
|
+
if (args.pinned) return { allow: false, reason: "pinned" };
|
|
124
|
+
if (args.installedVersion === null) {
|
|
125
|
+
return { allow: false, reason: "not-installed" };
|
|
126
|
+
}
|
|
127
|
+
const cmp = compareVoiceModelSemver(
|
|
128
|
+
args.candidate.version,
|
|
129
|
+
args.installedVersion,
|
|
130
|
+
);
|
|
131
|
+
if (cmp === null || cmp <= 0) {
|
|
132
|
+
return { allow: false, reason: "up-to-date" };
|
|
133
|
+
}
|
|
134
|
+
// A "pending" revision (or a placeholder carrying no downloadable assets)
|
|
135
|
+
// marks a catalogued-but-unpublished release: its HF tree can't be fetched,
|
|
136
|
+
// so approving it would 404 the download. Never auto-update to one — even
|
|
137
|
+
// when it is a newer semver with a net improvement (e.g. vad@0.2.0, whose
|
|
138
|
+
// HF revision is not yet pinned).
|
|
139
|
+
if (
|
|
140
|
+
args.candidate.hfRevision === "pending" ||
|
|
141
|
+
args.candidate.ggufAssets.length === 0
|
|
142
|
+
) {
|
|
143
|
+
return { allow: false, reason: "unpublished" };
|
|
144
|
+
}
|
|
145
|
+
if (!args.candidate.evalDeltas.netImprovement) {
|
|
146
|
+
return { allow: false, reason: "net-regression" };
|
|
147
|
+
}
|
|
148
|
+
if (args.bundleVersion !== "") {
|
|
149
|
+
const bundleCmp = compareVoiceModelSemver(
|
|
150
|
+
args.bundleVersion,
|
|
151
|
+
args.candidate.minBundleVersion,
|
|
152
|
+
);
|
|
153
|
+
if (bundleCmp === null || bundleCmp < 0) {
|
|
154
|
+
return { allow: false, reason: "bundle-incompatible" };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { allow: true, reason: "update-available" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Walk the cascade in order, return the first source that yields a
|
|
162
|
+
* non-empty list. All sources are awaited with `signal` so the caller can
|
|
163
|
+
* cancel the whole check. Sources that throw are logged and skipped.
|
|
164
|
+
*
|
|
165
|
+
* Re-exported as `fetchVoiceModelCatalog` for tests; production callers go
|
|
166
|
+
* through `VoiceModelUpdater.check`.
|
|
167
|
+
*/
|
|
168
|
+
export async function fetchVoiceModelCatalog(
|
|
169
|
+
sources: ReadonlyArray<VoiceModelCatalogSource>,
|
|
170
|
+
signal: AbortSignal,
|
|
171
|
+
logger?: VoiceModelUpdaterOptions["logger"],
|
|
172
|
+
): Promise<{
|
|
173
|
+
source: string;
|
|
174
|
+
versions: ReadonlyArray<VoiceModelVersion>;
|
|
175
|
+
} | null> {
|
|
176
|
+
for (const source of sources) {
|
|
177
|
+
if (signal.aborted) return null;
|
|
178
|
+
try {
|
|
179
|
+
const versions = await source.fetchAll(signal);
|
|
180
|
+
if (versions.length > 0) {
|
|
181
|
+
logger?.info(
|
|
182
|
+
`[voice-model-updater] catalog from ${source.id}: ${versions.length} versions`,
|
|
183
|
+
);
|
|
184
|
+
return { source: source.id, versions };
|
|
185
|
+
}
|
|
186
|
+
logger?.info(`[voice-model-updater] ${source.id} returned empty`);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
logger?.warn(
|
|
189
|
+
`[voice-model-updater] ${source.id} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Cloud catalog source (R5 §3.1.1). Fetches the signed JSON manifest and
|
|
198
|
+
* verifies it before parsing. Strict — a body whose signature does not
|
|
199
|
+
* verify is treated as a fetch failure so the cascade moves on rather than
|
|
200
|
+
* silently accepting an unsigned response.
|
|
201
|
+
*/
|
|
202
|
+
export function cloudCatalogSource(args: {
|
|
203
|
+
baseUrl: string;
|
|
204
|
+
publicKeys: ReadonlyArray<Ed25519PublicKey>;
|
|
205
|
+
authToken?: string;
|
|
206
|
+
timeoutMs?: number;
|
|
207
|
+
}): VoiceModelCatalogSource {
|
|
208
|
+
return {
|
|
209
|
+
id: "cloud",
|
|
210
|
+
async fetchAll(
|
|
211
|
+
signal: AbortSignal,
|
|
212
|
+
): Promise<ReadonlyArray<VoiceModelVersion>> {
|
|
213
|
+
const url = `${args.baseUrl.replace(/\/$/, "")}/api/v1/voice-models/catalog`;
|
|
214
|
+
const headers: Record<string, string> = {
|
|
215
|
+
Accept: "application/json",
|
|
216
|
+
};
|
|
217
|
+
if (args.authToken) {
|
|
218
|
+
headers.Authorization = `Bearer ${args.authToken}`;
|
|
219
|
+
}
|
|
220
|
+
const timed = withTimeout(
|
|
221
|
+
signal,
|
|
222
|
+
args.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
|
|
223
|
+
);
|
|
224
|
+
try {
|
|
225
|
+
const res = await fetch(url, { headers, signal: timed.signal });
|
|
226
|
+
if (!res.ok) {
|
|
227
|
+
throw new Error(`HTTP ${res.status}`);
|
|
228
|
+
}
|
|
229
|
+
const signature = res.headers.get(CATALOG_SIGNATURE_HEADER);
|
|
230
|
+
if (!signature) {
|
|
231
|
+
throw new Error(`missing ${CATALOG_SIGNATURE_HEADER} header`);
|
|
232
|
+
}
|
|
233
|
+
const body = await res.text();
|
|
234
|
+
await verifyManifestSignatureText(body, signature, args.publicKeys);
|
|
235
|
+
const parsed = JSON.parse(body) as { versions?: VoiceModelVersion[] };
|
|
236
|
+
return Array.isArray(parsed.versions) ? parsed.versions : [];
|
|
237
|
+
} finally {
|
|
238
|
+
timed.dispose();
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* GitHub releases source (R5 §3.1.2). One release per `<id>-v<version>`
|
|
246
|
+
* tag in `elizaOS/eliza-1-voice-models`. Each release asset includes a
|
|
247
|
+
* `manifest.json` matching the `VoiceModelVersion` shape.
|
|
248
|
+
*/
|
|
249
|
+
export function githubReleasesSource(args: {
|
|
250
|
+
owner?: string;
|
|
251
|
+
repo?: string;
|
|
252
|
+
timeoutMs?: number;
|
|
253
|
+
authToken?: string;
|
|
254
|
+
}): VoiceModelCatalogSource {
|
|
255
|
+
const owner = args.owner ?? "elizaOS";
|
|
256
|
+
const repo = args.repo ?? "eliza-1-voice-models";
|
|
257
|
+
return {
|
|
258
|
+
id: "github",
|
|
259
|
+
async fetchAll(
|
|
260
|
+
signal: AbortSignal,
|
|
261
|
+
): Promise<ReadonlyArray<VoiceModelVersion>> {
|
|
262
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`;
|
|
263
|
+
const headers: Record<string, string> = {
|
|
264
|
+
Accept: "application/vnd.github+json",
|
|
265
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
266
|
+
};
|
|
267
|
+
if (args.authToken) {
|
|
268
|
+
headers.Authorization = `Bearer ${args.authToken}`;
|
|
269
|
+
}
|
|
270
|
+
const timed = withTimeout(
|
|
271
|
+
signal,
|
|
272
|
+
args.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
|
|
273
|
+
);
|
|
274
|
+
try {
|
|
275
|
+
const res = await fetch(url, { headers, signal: timed.signal });
|
|
276
|
+
if (!res.ok) {
|
|
277
|
+
throw new Error(`HTTP ${res.status}`);
|
|
278
|
+
}
|
|
279
|
+
const releases = (await res.json()) as Array<{
|
|
280
|
+
tag_name: string;
|
|
281
|
+
body?: string;
|
|
282
|
+
assets?: Array<{ name: string; browser_download_url: string }>;
|
|
283
|
+
}>;
|
|
284
|
+
const out: VoiceModelVersion[] = [];
|
|
285
|
+
for (const release of releases) {
|
|
286
|
+
const manifestAsset = release.assets?.find(
|
|
287
|
+
(a) => a.name === "manifest.json",
|
|
288
|
+
);
|
|
289
|
+
if (!manifestAsset) continue;
|
|
290
|
+
try {
|
|
291
|
+
const mRes = await fetch(manifestAsset.browser_download_url, {
|
|
292
|
+
signal: timed.signal,
|
|
293
|
+
headers,
|
|
294
|
+
});
|
|
295
|
+
if (!mRes.ok) continue;
|
|
296
|
+
const parsed = (await mRes.json()) as VoiceModelVersion;
|
|
297
|
+
if (parsed && typeof parsed.id === "string") {
|
|
298
|
+
out.push(parsed);
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
// One bad release should not poison the whole list.
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
} finally {
|
|
306
|
+
timed.dispose();
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* HuggingFace tree-listing source (R5 §3.1.3). Final-truth probe. Walks
|
|
314
|
+
* every model id in `VOICE_MODEL_VERSIONS` against its `hfRepo` + `main`
|
|
315
|
+
* revision and re-confirms file shas. This source is best-effort — when
|
|
316
|
+
* HF responds with the file list but no `lfs.sha256` field, the candidate
|
|
317
|
+
* is dropped (we never auto-update on a partially-trusted source).
|
|
318
|
+
*/
|
|
319
|
+
export function huggingFaceSource(args?: {
|
|
320
|
+
timeoutMs?: number;
|
|
321
|
+
baseUrl?: string;
|
|
322
|
+
}): VoiceModelCatalogSource {
|
|
323
|
+
const baseUrl = args?.baseUrl ?? "https://huggingface.co";
|
|
324
|
+
return {
|
|
325
|
+
id: "huggingface",
|
|
326
|
+
async fetchAll(
|
|
327
|
+
signal: AbortSignal,
|
|
328
|
+
): Promise<ReadonlyArray<VoiceModelVersion>> {
|
|
329
|
+
const seenRepos = new Set<string>();
|
|
330
|
+
const out: VoiceModelVersion[] = [];
|
|
331
|
+
const timed = withTimeout(
|
|
332
|
+
signal,
|
|
333
|
+
args?.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
|
|
334
|
+
);
|
|
335
|
+
try {
|
|
336
|
+
for (const v of VOICE_MODEL_VERSIONS) {
|
|
337
|
+
const key = `${v.hfRepo}@${v.hfRevision}`;
|
|
338
|
+
if (seenRepos.has(key)) continue;
|
|
339
|
+
seenRepos.add(key);
|
|
340
|
+
const url = `${baseUrl}/api/models/${v.hfRepo}/tree/${encodeURIComponent(v.hfRevision)}?recursive=true`;
|
|
341
|
+
try {
|
|
342
|
+
const res = await fetch(url, { signal: timed.signal });
|
|
343
|
+
if (!res.ok) continue;
|
|
344
|
+
const files = (await res.json()) as Array<{
|
|
345
|
+
path: string;
|
|
346
|
+
size?: number;
|
|
347
|
+
lfs?: { sha256?: string };
|
|
348
|
+
}>;
|
|
349
|
+
const assets = files
|
|
350
|
+
.filter((f) => f.lfs?.sha256 && f.size !== undefined)
|
|
351
|
+
.map((f) => ({
|
|
352
|
+
filename: f.path,
|
|
353
|
+
sha256: String(f.lfs?.sha256),
|
|
354
|
+
sizeBytes: Number(f.size ?? 0),
|
|
355
|
+
quant: "fp16" as const,
|
|
356
|
+
}));
|
|
357
|
+
if (assets.length === 0) continue;
|
|
358
|
+
out.push({
|
|
359
|
+
...v,
|
|
360
|
+
ggufAssets: assets,
|
|
361
|
+
});
|
|
362
|
+
} catch {
|
|
363
|
+
// continue
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
} finally {
|
|
368
|
+
timed.dispose();
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Compose a default cascade if the caller doesn't provide one. */
|
|
375
|
+
export function defaultVoiceModelSources(
|
|
376
|
+
opts: VoiceModelUpdaterOptions,
|
|
377
|
+
): ReadonlyArray<VoiceModelCatalogSource> {
|
|
378
|
+
const out: VoiceModelCatalogSource[] = [];
|
|
379
|
+
if (opts.cloudBaseUrl && opts.publicKeys && opts.publicKeys.length > 0) {
|
|
380
|
+
out.push(
|
|
381
|
+
cloudCatalogSource({
|
|
382
|
+
baseUrl: opts.cloudBaseUrl,
|
|
383
|
+
publicKeys: opts.publicKeys,
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
out.push(githubReleasesSource({}));
|
|
388
|
+
out.push(huggingFaceSource());
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Merge the local in-binary `VOICE_MODEL_VERSIONS` with the remote
|
|
394
|
+
* catalog. Local entries that match `(id, version)` are replaced by the
|
|
395
|
+
* remote (remote is the source of truth for shas + sizes); local entries
|
|
396
|
+
* with no remote match are kept so a build can ship a strictly-newer
|
|
397
|
+
* voice model that hasn't been uploaded yet.
|
|
398
|
+
*/
|
|
399
|
+
export function mergeCatalogs(
|
|
400
|
+
local: ReadonlyArray<VoiceModelVersion>,
|
|
401
|
+
remote: ReadonlyArray<VoiceModelVersion>,
|
|
402
|
+
): ReadonlyArray<VoiceModelVersion> {
|
|
403
|
+
const remoteKeys = new Set(remote.map((v) => `${v.id}@${v.version}`));
|
|
404
|
+
const out: VoiceModelVersion[] = [...remote];
|
|
405
|
+
for (const v of local) {
|
|
406
|
+
if (!remoteKeys.has(`${v.id}@${v.version}`)) {
|
|
407
|
+
out.push(v);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Pick the highest-semver version per id from a merged catalog. */
|
|
414
|
+
export function latestPerId(
|
|
415
|
+
versions: ReadonlyArray<VoiceModelVersion>,
|
|
416
|
+
): Map<VoiceModelId, VoiceModelVersion> {
|
|
417
|
+
const out = new Map<VoiceModelId, VoiceModelVersion>();
|
|
418
|
+
for (const v of versions) {
|
|
419
|
+
const cur = out.get(v.id);
|
|
420
|
+
if (cur === undefined) {
|
|
421
|
+
out.set(v.id, v);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const cmp = compareVoiceModelSemver(v.version, cur.version);
|
|
425
|
+
if (cmp !== null && cmp > 0) {
|
|
426
|
+
out.set(v.id, v);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Resolve the cadence in ms — env override > argument > default. Returns
|
|
434
|
+
* a strictly positive number. The interval governs background ticks; the
|
|
435
|
+
* UI "check now" button always bypasses.
|
|
436
|
+
*/
|
|
437
|
+
export function resolveCheckIntervalMs(override?: number): number {
|
|
438
|
+
const env = process.env.ELIZA_VOICE_UPDATE_INTERVAL_MS;
|
|
439
|
+
if (env !== undefined) {
|
|
440
|
+
const n = Number(env);
|
|
441
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
442
|
+
}
|
|
443
|
+
if (override !== undefined && Number.isFinite(override) && override > 0) {
|
|
444
|
+
return override;
|
|
445
|
+
}
|
|
446
|
+
return DEFAULT_CHECK_INTERVAL_MS;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export interface VoiceModelDownloadInputs {
|
|
450
|
+
readonly version: VoiceModelVersion;
|
|
451
|
+
/** Bundle voice directory where the final file lives. */
|
|
452
|
+
readonly bundleVoiceDir: string;
|
|
453
|
+
/** Staging directory for `.part` files. */
|
|
454
|
+
readonly stagingDir: string;
|
|
455
|
+
/**
|
|
456
|
+
* Index into `version.ggufAssets` selecting the asset to fetch. The
|
|
457
|
+
* caller's quant-selection policy (R8) decides which.
|
|
458
|
+
*/
|
|
459
|
+
readonly assetIndex: number;
|
|
460
|
+
/** Network policy decision attested by the caller. */
|
|
461
|
+
readonly networkPolicy: NetworkPolicyDecision;
|
|
462
|
+
/** AbortSignal — required so the cancel button stops downloads. */
|
|
463
|
+
readonly signal: AbortSignal;
|
|
464
|
+
/** HF resolve-URL builder; defaults to standard HF resolve path. */
|
|
465
|
+
readonly resolveUrl?: (
|
|
466
|
+
repo: string,
|
|
467
|
+
revision: string,
|
|
468
|
+
file: string,
|
|
469
|
+
) => string;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export class VoiceModelDownloadError extends Error {
|
|
473
|
+
readonly code: string;
|
|
474
|
+
constructor(message: string, code: string) {
|
|
475
|
+
super(message);
|
|
476
|
+
this.name = "VoiceModelDownloadError";
|
|
477
|
+
this.code = code;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const buildHfResolveUrl = (
|
|
482
|
+
repo: string,
|
|
483
|
+
revision: string,
|
|
484
|
+
file: string,
|
|
485
|
+
): string =>
|
|
486
|
+
`https://huggingface.co/${repo}/resolve/${encodeURIComponent(revision)}/${file}`;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Atomic-swap downloader for a single voice asset.
|
|
490
|
+
*
|
|
491
|
+
* Refuses to proceed when `networkPolicy.allow === false` so headless
|
|
492
|
+
* environments and pre-OWNER cellular skip cleanly. Streams to a
|
|
493
|
+
* `<id>-<version>.<filename>.part` file in the staging dir, hashes,
|
|
494
|
+
* verifies against the catalog sha256, then renames into the bundle voice
|
|
495
|
+
* dir using a `<id>-<version>.<filename>` final name so old + new
|
|
496
|
+
* versions coexist briefly during the swap (R5 §6.2).
|
|
497
|
+
*/
|
|
498
|
+
export async function downloadVoiceModel(
|
|
499
|
+
args: VoiceModelDownloadInputs,
|
|
500
|
+
): Promise<{ finalPath: string; sha256: string; sizeBytes: number }> {
|
|
501
|
+
if (!args.networkPolicy.allow) {
|
|
502
|
+
throw new VoiceModelDownloadError(
|
|
503
|
+
`network policy refused download (reason=${args.networkPolicy.reason})`,
|
|
504
|
+
"ELIZA_VOICE_NET_POLICY_REFUSED",
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
const asset = args.version.ggufAssets[args.assetIndex];
|
|
508
|
+
if (!asset) {
|
|
509
|
+
throw new VoiceModelDownloadError(
|
|
510
|
+
`asset index ${args.assetIndex} out of range (have ${args.version.ggufAssets.length})`,
|
|
511
|
+
"ELIZA_VOICE_ASSET_INDEX",
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
await fsp.mkdir(args.stagingDir, { recursive: true });
|
|
515
|
+
await fsp.mkdir(args.bundleVoiceDir, { recursive: true });
|
|
516
|
+
const stageName = `${args.version.id}-${args.version.version}-${path.basename(asset.filename)}.part`;
|
|
517
|
+
const stagePath = path.join(args.stagingDir, stageName);
|
|
518
|
+
const finalName = `${args.version.id}-${args.version.version}-${path.basename(asset.filename)}`;
|
|
519
|
+
const finalPath = path.join(args.bundleVoiceDir, finalName);
|
|
520
|
+
|
|
521
|
+
const resolveUrl = args.resolveUrl ?? buildHfResolveUrl;
|
|
522
|
+
const url = resolveUrl(
|
|
523
|
+
args.version.hfRepo,
|
|
524
|
+
args.version.hfRevision,
|
|
525
|
+
asset.filename,
|
|
526
|
+
);
|
|
527
|
+
const res = await fetch(url, { signal: args.signal });
|
|
528
|
+
if (!res.ok) {
|
|
529
|
+
throw new VoiceModelDownloadError(
|
|
530
|
+
`HTTP ${res.status} fetching ${url}`,
|
|
531
|
+
"ELIZA_VOICE_HTTP",
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const body = res.body;
|
|
535
|
+
if (!body) {
|
|
536
|
+
throw new VoiceModelDownloadError(
|
|
537
|
+
`empty response body for ${url}`,
|
|
538
|
+
"ELIZA_VOICE_EMPTY_BODY",
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Materialise the streamed bytes into the staging file. We re-implement
|
|
543
|
+
// the small piece of streaming we need rather than pulling in the full
|
|
544
|
+
// downloader.ts here, because that module is tied to the catalog and
|
|
545
|
+
// bundle layer.
|
|
546
|
+
const buffer = new Uint8Array(await new Response(body).arrayBuffer());
|
|
547
|
+
await fsp.writeFile(stagePath, buffer);
|
|
548
|
+
|
|
549
|
+
const computed = await hashFile(stagePath);
|
|
550
|
+
if (computed !== asset.sha256) {
|
|
551
|
+
await fsp.rm(stagePath, { force: true });
|
|
552
|
+
throw new VoiceModelDownloadError(
|
|
553
|
+
`sha256 mismatch for ${asset.filename}: expected ${asset.sha256}, got ${computed}`,
|
|
554
|
+
"ELIZA_VOICE_SHA_MISMATCH",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
const stat = await fsp.stat(stagePath);
|
|
558
|
+
if (asset.sizeBytes > 0 && stat.size !== asset.sizeBytes) {
|
|
559
|
+
await fsp.rm(stagePath, { force: true });
|
|
560
|
+
throw new VoiceModelDownloadError(
|
|
561
|
+
`size mismatch for ${asset.filename}: expected ${asset.sizeBytes}, got ${stat.size}`,
|
|
562
|
+
"ELIZA_VOICE_SIZE_MISMATCH",
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
await fsp.rename(stagePath, finalPath);
|
|
566
|
+
return { finalPath, sha256: computed, sizeBytes: stat.size };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Computed status for one model id. Surfaced by `VoiceModelUpdater.check`
|
|
571
|
+
* and by the `/api/local-inference/voice-models/status` route.
|
|
572
|
+
*/
|
|
573
|
+
export interface VoiceModelStatus {
|
|
574
|
+
readonly id: VoiceModelId;
|
|
575
|
+
readonly installedVersion: string | null;
|
|
576
|
+
readonly latestKnown: VoiceModelVersion | null;
|
|
577
|
+
readonly pinned: boolean;
|
|
578
|
+
readonly decision: ReturnType<typeof shouldAutoUpdateVoiceModel>;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export interface VoiceModelInstallState {
|
|
582
|
+
/** `VoiceModelId` → currently-installed semver (null = not installed). */
|
|
583
|
+
readonly installed: ReadonlyMap<VoiceModelId, string | null>;
|
|
584
|
+
/** Bundle version this device is currently running (e.g. manifest `version`). */
|
|
585
|
+
readonly bundleVersion: string;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export class VoiceModelUpdater {
|
|
589
|
+
readonly options: VoiceModelUpdaterOptions;
|
|
590
|
+
private lastCheckAt: number | null = null;
|
|
591
|
+
private lastResult: ReadonlyArray<VoiceModelStatus> | null = null;
|
|
592
|
+
private inFlight: Promise<ReadonlyArray<VoiceModelStatus>> | null = null;
|
|
593
|
+
|
|
594
|
+
constructor(options: VoiceModelUpdaterOptions = {}) {
|
|
595
|
+
this.options = options;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** Sources used by this updater (defaults composed from options). */
|
|
599
|
+
get sources(): ReadonlyArray<VoiceModelCatalogSource> {
|
|
600
|
+
return this.options.sources ?? defaultVoiceModelSources(this.options);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Run a check against the cascade, merge with the local in-binary
|
|
605
|
+
* catalog, and return a per-id status.
|
|
606
|
+
*
|
|
607
|
+
* `force === false` (default) returns the cached result when the last
|
|
608
|
+
* check was within `resolveCheckIntervalMs()`. `force === true` always
|
|
609
|
+
* re-fetches.
|
|
610
|
+
*/
|
|
611
|
+
async check(
|
|
612
|
+
install: VoiceModelInstallState,
|
|
613
|
+
pinPolicy: VoiceModelPinPolicy,
|
|
614
|
+
options?: { force?: boolean; signal?: AbortSignal },
|
|
615
|
+
): Promise<ReadonlyArray<VoiceModelStatus>> {
|
|
616
|
+
const intervalMs = resolveCheckIntervalMs();
|
|
617
|
+
if (
|
|
618
|
+
!options?.force &&
|
|
619
|
+
this.lastResult &&
|
|
620
|
+
this.lastCheckAt !== null &&
|
|
621
|
+
Date.now() - this.lastCheckAt < intervalMs
|
|
622
|
+
) {
|
|
623
|
+
return this.lastResult;
|
|
624
|
+
}
|
|
625
|
+
// De-dup concurrent callers — second caller waits on the first.
|
|
626
|
+
if (this.inFlight) return this.inFlight;
|
|
627
|
+
this.inFlight = this.runCheck(install, pinPolicy, options?.signal);
|
|
628
|
+
try {
|
|
629
|
+
const result = await this.inFlight;
|
|
630
|
+
this.lastResult = result;
|
|
631
|
+
this.lastCheckAt = Date.now();
|
|
632
|
+
return result;
|
|
633
|
+
} finally {
|
|
634
|
+
this.inFlight = null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
private async runCheck(
|
|
639
|
+
install: VoiceModelInstallState,
|
|
640
|
+
pinPolicy: VoiceModelPinPolicy,
|
|
641
|
+
signal: AbortSignal | undefined,
|
|
642
|
+
): Promise<ReadonlyArray<VoiceModelStatus>> {
|
|
643
|
+
const ctl = new AbortController();
|
|
644
|
+
// Use a single named handler so the removeEventListener call below
|
|
645
|
+
// actually removes the listener it registered. `addEventListener`
|
|
646
|
+
// returns void; the previous `detach = signal?.addEventListener(...)`
|
|
647
|
+
// pattern leaked listeners + the captured `ctl` controller when the
|
|
648
|
+
// caller's signal never fired.
|
|
649
|
+
const abortHandler = () => ctl.abort();
|
|
650
|
+
if (signal) {
|
|
651
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const fetched = await fetchVoiceModelCatalog(
|
|
655
|
+
this.sources,
|
|
656
|
+
ctl.signal,
|
|
657
|
+
this.options.logger,
|
|
658
|
+
);
|
|
659
|
+
const remote = fetched?.versions ?? [];
|
|
660
|
+
const merged = mergeCatalogs(VOICE_MODEL_VERSIONS, remote);
|
|
661
|
+
const latest = latestPerId(merged);
|
|
662
|
+
const out: VoiceModelStatus[] = [];
|
|
663
|
+
const ids = new Set<VoiceModelId>([
|
|
664
|
+
...install.installed.keys(),
|
|
665
|
+
...latest.keys(),
|
|
666
|
+
]);
|
|
667
|
+
for (const id of ids) {
|
|
668
|
+
const installedVersion = install.installed.get(id) ?? null;
|
|
669
|
+
const candidate = latest.get(id) ?? null;
|
|
670
|
+
const pinned = pinPolicy.pinned.has(id);
|
|
671
|
+
const decision = candidate
|
|
672
|
+
? shouldAutoUpdateVoiceModel({
|
|
673
|
+
installedVersion,
|
|
674
|
+
candidate,
|
|
675
|
+
bundleVersion: install.bundleVersion,
|
|
676
|
+
pinned,
|
|
677
|
+
})
|
|
678
|
+
: { allow: false, reason: "up-to-date" as const };
|
|
679
|
+
out.push({
|
|
680
|
+
id,
|
|
681
|
+
installedVersion,
|
|
682
|
+
latestKnown: candidate,
|
|
683
|
+
pinned,
|
|
684
|
+
decision,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
688
|
+
return out;
|
|
689
|
+
} finally {
|
|
690
|
+
if (signal) {
|
|
691
|
+
signal.removeEventListener("abort", abortHandler);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/** Pure helper for tests; returns the merged + per-id-latest map. */
|
|
697
|
+
static computeLatestPerId(
|
|
698
|
+
remote: ReadonlyArray<VoiceModelVersion>,
|
|
699
|
+
): Map<VoiceModelId, VoiceModelVersion> {
|
|
700
|
+
return latestPerId(mergeCatalogs(VOICE_MODEL_VERSIONS, remote));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Compose an AbortSignal that fires when either the caller's signal fires
|
|
706
|
+
* or the timeout elapses. Returns a `dispose` function so the timer can be
|
|
707
|
+
* cleared on the happy path.
|
|
708
|
+
*/
|
|
709
|
+
function withTimeout(
|
|
710
|
+
signal: AbortSignal,
|
|
711
|
+
timeoutMs: number,
|
|
712
|
+
): { signal: AbortSignal; dispose: () => void } {
|
|
713
|
+
const ctl = new AbortController();
|
|
714
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
715
|
+
const onAbort = () => ctl.abort();
|
|
716
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
717
|
+
return {
|
|
718
|
+
signal: ctl.signal,
|
|
719
|
+
dispose: () => {
|
|
720
|
+
clearTimeout(timer);
|
|
721
|
+
signal.removeEventListener("abort", onAbort);
|
|
722
|
+
},
|
|
723
|
+
};
|
|
724
|
+
}
|