@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,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `TRANSCRIPTION` model handler priority + provider hint
|
|
3
|
+
* semantics on `AgentRuntime`. These exercise the same `registerModel`
|
|
4
|
+
* machinery used by plugin-elizacloud (remote OpenAI Whisper API) and the
|
|
5
|
+
* local fused Qwen3-ASR handler so callers know exactly:
|
|
6
|
+
*
|
|
7
|
+
* - which handler wins when priorities tie (first-registered),
|
|
8
|
+
* - how an explicit `priority` overrides that,
|
|
9
|
+
* - that a `provider` hint to `useModel` overrides priority entirely,
|
|
10
|
+
* - that the runtime does NOT auto-fall-through on handler errors,
|
|
11
|
+
* - and how a caller can implement an explicit try/catch fallback.
|
|
12
|
+
*
|
|
13
|
+
* The test is intentionally provider-agnostic — it registers fake handlers
|
|
14
|
+
* named "local" and "openai" so the contract is observable without booting
|
|
15
|
+
* any real ASR backend.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, expect, it } from "vitest";
|
|
19
|
+
|
|
20
|
+
import { InMemoryDatabaseAdapter } from "../../../../packages/core/src/database/inMemoryAdapter";
|
|
21
|
+
import { AgentRuntime } from "../../../../packages/core/src/runtime";
|
|
22
|
+
import { ModelType } from "../../../../packages/core/src/types";
|
|
23
|
+
|
|
24
|
+
interface TranscriptionParams {
|
|
25
|
+
audio?: Float32Array | Uint8Array | Buffer | string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TestRuntimeCtx {
|
|
29
|
+
runtime: AgentRuntime;
|
|
30
|
+
calls: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeRuntime(): TestRuntimeCtx {
|
|
34
|
+
const runtime = new AgentRuntime({
|
|
35
|
+
character: {
|
|
36
|
+
name: "TranscriptionPriorityTest",
|
|
37
|
+
bio: "asr-priority test",
|
|
38
|
+
settings: {},
|
|
39
|
+
} as never,
|
|
40
|
+
adapter: new InMemoryDatabaseAdapter(),
|
|
41
|
+
logLevel: "fatal",
|
|
42
|
+
});
|
|
43
|
+
const calls: string[] = [];
|
|
44
|
+
return { runtime, calls };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeHandler(
|
|
48
|
+
calls: string[],
|
|
49
|
+
label: string,
|
|
50
|
+
result: string | (() => string),
|
|
51
|
+
options: { throws?: boolean } = {},
|
|
52
|
+
) {
|
|
53
|
+
return async (_runtime: unknown, _params: unknown): Promise<string> => {
|
|
54
|
+
calls.push(label);
|
|
55
|
+
if (options.throws) {
|
|
56
|
+
throw new Error(`${label} handler failed`);
|
|
57
|
+
}
|
|
58
|
+
return typeof result === "function" ? result() : result;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("TRANSCRIPTION handler priority on AgentRuntime", () => {
|
|
63
|
+
it("local Qwen3-ASR wins over remote OpenAI Whisper by registration order when priorities tie", async () => {
|
|
64
|
+
const { runtime, calls } = makeRuntime();
|
|
65
|
+
runtime.registerModel(
|
|
66
|
+
ModelType.TRANSCRIPTION,
|
|
67
|
+
makeHandler(calls, "local", "local transcript"),
|
|
68
|
+
"eliza-local-inference",
|
|
69
|
+
0,
|
|
70
|
+
);
|
|
71
|
+
runtime.registerModel(
|
|
72
|
+
ModelType.TRANSCRIPTION,
|
|
73
|
+
makeHandler(calls, "openai", "openai transcript"),
|
|
74
|
+
"openai",
|
|
75
|
+
0,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const text = await runtime.useModel(ModelType.TRANSCRIPTION, {
|
|
79
|
+
audio: new Float32Array(160),
|
|
80
|
+
} as TranscriptionParams as never);
|
|
81
|
+
|
|
82
|
+
expect(text).toBe("local transcript");
|
|
83
|
+
expect(calls).toEqual(["local"]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("explicit higher priority always wins regardless of registration order", async () => {
|
|
87
|
+
const { runtime, calls } = makeRuntime();
|
|
88
|
+
runtime.registerModel(
|
|
89
|
+
ModelType.TRANSCRIPTION,
|
|
90
|
+
makeHandler(calls, "remote", "remote transcript"),
|
|
91
|
+
"openai",
|
|
92
|
+
100,
|
|
93
|
+
);
|
|
94
|
+
runtime.registerModel(
|
|
95
|
+
ModelType.TRANSCRIPTION,
|
|
96
|
+
makeHandler(calls, "local", "local transcript"),
|
|
97
|
+
"eliza-local-inference",
|
|
98
|
+
0,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Remote wins because priority 100 > 0.
|
|
102
|
+
let text = await runtime.useModel(ModelType.TRANSCRIPTION, {
|
|
103
|
+
audio: new Float32Array(160),
|
|
104
|
+
} as TranscriptionParams as never);
|
|
105
|
+
expect(text).toBe("remote transcript");
|
|
106
|
+
expect(calls).toEqual(["remote"]);
|
|
107
|
+
|
|
108
|
+
// Re-register local with a higher priority — now local wins.
|
|
109
|
+
runtime.registerModel(
|
|
110
|
+
ModelType.TRANSCRIPTION,
|
|
111
|
+
makeHandler(calls, "local-priority", "local-priority transcript"),
|
|
112
|
+
"eliza-local-inference",
|
|
113
|
+
200,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
text = await runtime.useModel(ModelType.TRANSCRIPTION, {
|
|
117
|
+
audio: new Float32Array(160),
|
|
118
|
+
} as TranscriptionParams as never);
|
|
119
|
+
expect(text).toBe("local-priority transcript");
|
|
120
|
+
expect(calls).toEqual(["remote", "local-priority"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("provider hint to useModel overrides priority", async () => {
|
|
124
|
+
const { runtime, calls } = makeRuntime();
|
|
125
|
+
runtime.registerModel(
|
|
126
|
+
ModelType.TRANSCRIPTION,
|
|
127
|
+
makeHandler(calls, "local", "local transcript"),
|
|
128
|
+
"eliza-local-inference",
|
|
129
|
+
200,
|
|
130
|
+
);
|
|
131
|
+
runtime.registerModel(
|
|
132
|
+
ModelType.TRANSCRIPTION,
|
|
133
|
+
makeHandler(calls, "openai", "openai transcript"),
|
|
134
|
+
"openai",
|
|
135
|
+
0,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Default useModel: local wins (priority 200 > 0).
|
|
139
|
+
let text = await runtime.useModel(ModelType.TRANSCRIPTION, {
|
|
140
|
+
audio: new Float32Array(160),
|
|
141
|
+
} as TranscriptionParams as never);
|
|
142
|
+
expect(text).toBe("local transcript");
|
|
143
|
+
|
|
144
|
+
// Explicit provider hint flips it to openai despite the priority gap.
|
|
145
|
+
text = await runtime.useModel(
|
|
146
|
+
ModelType.TRANSCRIPTION,
|
|
147
|
+
{ audio: new Float32Array(160) } as TranscriptionParams as never,
|
|
148
|
+
"openai",
|
|
149
|
+
);
|
|
150
|
+
expect(text).toBe("openai transcript");
|
|
151
|
+
expect(calls).toEqual(["local", "openai"]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("runtime does NOT auto-fall-through on handler errors", async () => {
|
|
155
|
+
const { runtime, calls } = makeRuntime();
|
|
156
|
+
runtime.registerModel(
|
|
157
|
+
ModelType.TRANSCRIPTION,
|
|
158
|
+
makeHandler(calls, "local", "should never reach this", { throws: true }),
|
|
159
|
+
"eliza-local-inference",
|
|
160
|
+
200,
|
|
161
|
+
);
|
|
162
|
+
runtime.registerModel(
|
|
163
|
+
ModelType.TRANSCRIPTION,
|
|
164
|
+
makeHandler(calls, "openai", "openai transcript"),
|
|
165
|
+
"openai",
|
|
166
|
+
0,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
await expect(
|
|
170
|
+
runtime.useModel(ModelType.TRANSCRIPTION, {
|
|
171
|
+
audio: new Float32Array(160),
|
|
172
|
+
} as TranscriptionParams as never),
|
|
173
|
+
).rejects.toThrow(/local handler failed/);
|
|
174
|
+
// The remote handler should NOT have been invoked — there is no
|
|
175
|
+
// implicit fallback chain on the runtime.
|
|
176
|
+
expect(calls).toEqual(["local"]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("caller can implement fallback via try/catch + provider hint", async () => {
|
|
180
|
+
const { runtime, calls } = makeRuntime();
|
|
181
|
+
runtime.registerModel(
|
|
182
|
+
ModelType.TRANSCRIPTION,
|
|
183
|
+
makeHandler(calls, "local", "boom", { throws: true }),
|
|
184
|
+
"eliza-local-inference",
|
|
185
|
+
200,
|
|
186
|
+
);
|
|
187
|
+
runtime.registerModel(
|
|
188
|
+
ModelType.TRANSCRIPTION,
|
|
189
|
+
makeHandler(calls, "openai", "openai transcript"),
|
|
190
|
+
"openai",
|
|
191
|
+
0,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
let result: string;
|
|
195
|
+
try {
|
|
196
|
+
result = (await runtime.useModel(ModelType.TRANSCRIPTION, {
|
|
197
|
+
audio: new Float32Array(160),
|
|
198
|
+
} as TranscriptionParams as never)) as string;
|
|
199
|
+
} catch {
|
|
200
|
+
// Caller-controlled fallback: explicit retry with provider hint.
|
|
201
|
+
result = (await runtime.useModel(
|
|
202
|
+
ModelType.TRANSCRIPTION,
|
|
203
|
+
{ audio: new Float32Array(160) } as TranscriptionParams as never,
|
|
204
|
+
"openai",
|
|
205
|
+
)) as string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
expect(result).toBe("openai transcript");
|
|
209
|
+
expect(calls).toEqual(["local", "openai"]);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error every TTS backend throws when it can't serve a
|
|
3
|
+
* request. Mirrors `ImageGenBackendUnavailableError` so the arbiter / WS5
|
|
4
|
+
* provider handler can surface a single typed failure mode upward.
|
|
5
|
+
*
|
|
6
|
+
* `unsupported_request` is the only reason callers retry against a
|
|
7
|
+
* different backend (e.g. unknown voice id on Kokoro → fall through to
|
|
8
|
+
* Edge TTS via the runtime model registry priority list). All other
|
|
9
|
+
* reasons indicate a missing local install and the caller should surface
|
|
10
|
+
* the actionable message verbatim — no silent fallback to a cloud
|
|
11
|
+
* provider for installer / weights issues (AGENTS.md §3).
|
|
12
|
+
*/
|
|
13
|
+
export class TtsBackendUnavailableError extends Error {
|
|
14
|
+
readonly code = "TTS_BACKEND_UNAVAILABLE";
|
|
15
|
+
constructor(
|
|
16
|
+
readonly backendId: string,
|
|
17
|
+
readonly reason:
|
|
18
|
+
| "binary_missing"
|
|
19
|
+
| "binary_version_mismatch"
|
|
20
|
+
| "model_missing"
|
|
21
|
+
| "tokenizer_missing"
|
|
22
|
+
| "voice_preset_missing"
|
|
23
|
+
| "voice_unknown"
|
|
24
|
+
| "binding_unavailable"
|
|
25
|
+
| "unsupported_runtime"
|
|
26
|
+
| "unsupported_request"
|
|
27
|
+
| "subprocess_failed",
|
|
28
|
+
message: string,
|
|
29
|
+
options?: { cause?: unknown },
|
|
30
|
+
) {
|
|
31
|
+
super(message, options);
|
|
32
|
+
this.name = "TtsBackendUnavailableError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Tells callers whether a thrown error came from a backend availability check. */
|
|
37
|
+
export function isTtsUnavailable(
|
|
38
|
+
err: unknown,
|
|
39
|
+
): err is TtsBackendUnavailableError {
|
|
40
|
+
return (
|
|
41
|
+
err instanceof TtsBackendUnavailableError ||
|
|
42
|
+
(typeof err === "object" &&
|
|
43
|
+
err !== null &&
|
|
44
|
+
(err as { code?: unknown }).code === "TTS_BACKEND_UNAVAILABLE")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local text-to-speech capability (WS5) — public entry point.
|
|
3
|
+
*
|
|
4
|
+
* This module is what `provider.ts` (`createTextToSpeechHandler`), the
|
|
5
|
+
* voice lifecycle service, and TTS skill imports use to register
|
|
6
|
+
* the capability with the WS1 `MemoryArbiter`.
|
|
7
|
+
*
|
|
8
|
+
* Wiring:
|
|
9
|
+
*
|
|
10
|
+
* const arbiter = service.getMemoryArbiter();
|
|
11
|
+
* const registration = createTtsCapabilityRegistration({
|
|
12
|
+
* loader: createDefaultTtsLoader({ ... }),
|
|
13
|
+
* cache: arbiterTtsCache,
|
|
14
|
+
* });
|
|
15
|
+
* arbiter.registerCapability(registration);
|
|
16
|
+
*
|
|
17
|
+
* `createTtsCapabilityRegistration` wraps the underlying backend so the
|
|
18
|
+
* arbiter's `run(request)` path:
|
|
19
|
+
*
|
|
20
|
+
* 1. Hashes the request (`hashTtsRequest`) against the loaded model id.
|
|
21
|
+
* 2. Checks the capability-level audio cache (`tts-audio` namespace).
|
|
22
|
+
* On hit it short-circuits the backend and returns the cached bytes
|
|
23
|
+
* with `metadata.cacheHit = true`.
|
|
24
|
+
* 3. On miss: calls `backend.supports(request)`. If false the arbiter
|
|
25
|
+
* throws `TtsBackendUnavailableError` with `unsupported_request` so
|
|
26
|
+
* the runtime model registry can fall through to the next provider
|
|
27
|
+
* in priority order (typically `plugin-edge-tts` as the cloud
|
|
28
|
+
* fallback). No silent fallback inside the capability — the runtime
|
|
29
|
+
* owns provider-cascade routing, AGENTS.md §3.
|
|
30
|
+
* 4. Calls `backend.synthesize(request)`, populates the cache, and
|
|
31
|
+
* returns the result.
|
|
32
|
+
*
|
|
33
|
+
* The capability registers with the default `"tts"` resident role
|
|
34
|
+
* (`memory-arbiter.ts#CAPABILITY_ROLE.speak === "tts"`). Co-evicts with
|
|
35
|
+
* NOTHING in particular — TTS has its own role in the `RESIDENT_ROLE_PRIORITY`
|
|
36
|
+
* table and is the first non-text bucket the arbiter drops under critical
|
|
37
|
+
* pressure. That ordering is deliberate: voice output is the cheapest
|
|
38
|
+
* audible feature to defer; vision/image-gen weights take longer to
|
|
39
|
+
* re-load.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export { isTtsUnavailable, TtsBackendUnavailableError } from "./errors";
|
|
43
|
+
export {
|
|
44
|
+
hashTtsRequest,
|
|
45
|
+
TtsAudioCache,
|
|
46
|
+
type TtsAudioCacheConfig,
|
|
47
|
+
type TtsAudioEntry,
|
|
48
|
+
} from "./tts-audio-cache";
|
|
49
|
+
export type {
|
|
50
|
+
TtsBackend,
|
|
51
|
+
TtsBackendLoader,
|
|
52
|
+
TtsLoadArgs,
|
|
53
|
+
TtsMimeType,
|
|
54
|
+
TtsRequest,
|
|
55
|
+
TtsResult,
|
|
56
|
+
} from "./types";
|
|
57
|
+
|
|
58
|
+
import type {
|
|
59
|
+
ArbiterCapability,
|
|
60
|
+
CapabilityRegistration,
|
|
61
|
+
} from "../memory-arbiter";
|
|
62
|
+
import { TtsBackendUnavailableError } from "./errors";
|
|
63
|
+
import { hashTtsRequest, type TtsAudioEntry } from "./tts-audio-cache";
|
|
64
|
+
import type {
|
|
65
|
+
TtsBackend,
|
|
66
|
+
TtsBackendLoader,
|
|
67
|
+
TtsMimeType,
|
|
68
|
+
TtsRequest,
|
|
69
|
+
TtsResult,
|
|
70
|
+
} from "./types";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Minimal cache shape the registration needs. Lets tests inject a fake
|
|
74
|
+
* cache without instantiating the full `TtsAudioCache`.
|
|
75
|
+
*/
|
|
76
|
+
export interface TtsAudioCacheLike {
|
|
77
|
+
get(hash: string): TtsAudioEntry | null;
|
|
78
|
+
set(
|
|
79
|
+
hash: string,
|
|
80
|
+
entry: {
|
|
81
|
+
audio: Uint8Array;
|
|
82
|
+
mime: TtsMimeType;
|
|
83
|
+
sampleRate: number;
|
|
84
|
+
voice: string;
|
|
85
|
+
model: string;
|
|
86
|
+
},
|
|
87
|
+
ttlMs?: number,
|
|
88
|
+
): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface CreateTtsCapabilityRegistrationOptions {
|
|
92
|
+
loader: TtsBackendLoader;
|
|
93
|
+
/**
|
|
94
|
+
* Optional audio cache. When provided the wrapper performs hash →
|
|
95
|
+
* cache lookup before calling the backend's `synthesize`. The cache
|
|
96
|
+
* is keyed on `(provider, model, voice, text, speed, sampleRate)` via
|
|
97
|
+
* {@link hashTtsRequest}; the provider name is fixed to
|
|
98
|
+
* `"eliza-local-inference"` so cache entries don't collide with a
|
|
99
|
+
* different runtime's TTS cache living in the same process.
|
|
100
|
+
*/
|
|
101
|
+
cache?: TtsAudioCacheLike;
|
|
102
|
+
/**
|
|
103
|
+
* Default provider name used in the cache key. Defaults to
|
|
104
|
+
* `"eliza-local-inference"`. Tests may override to assert keying.
|
|
105
|
+
*/
|
|
106
|
+
provider?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Best-effort RAM footprint estimate for the loaded weights. The
|
|
109
|
+
* arbiter only uses this for telemetry; eviction is by priority.
|
|
110
|
+
* Defaults: Kokoro ≈ 350 MB int8 / 410 MB fp32; OmniVoice ranges
|
|
111
|
+
* 800–1700 MB across the Q3..Q8 ladder. The right call site is
|
|
112
|
+
* `loader.estimatedMb` once it's resolved against the active tier.
|
|
113
|
+
*/
|
|
114
|
+
estimatedMb?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Optional override for the cache TTL on insert. Defaults to the
|
|
117
|
+
* `TtsAudioCache` default (10 min).
|
|
118
|
+
*/
|
|
119
|
+
cacheTtlMs?: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Build a `CapabilityRegistration` ready to feed to
|
|
124
|
+
* `arbiter.registerCapability()`. Mirrors
|
|
125
|
+
* `createVisionCapabilityRegistration` from WS2 and
|
|
126
|
+
* `createImageGenCapabilityRegistration` from WS3.
|
|
127
|
+
*/
|
|
128
|
+
export function createTtsCapabilityRegistration(
|
|
129
|
+
opts: CreateTtsCapabilityRegistrationOptions,
|
|
130
|
+
): CapabilityRegistration<TtsBackend, TtsRequest, TtsResult> {
|
|
131
|
+
const capability: ArbiterCapability = "speak";
|
|
132
|
+
const loader = opts.loader;
|
|
133
|
+
const cache = opts.cache;
|
|
134
|
+
const provider = opts.provider ?? "eliza-local-inference";
|
|
135
|
+
const cacheTtlMs = opts.cacheTtlMs;
|
|
136
|
+
return {
|
|
137
|
+
capability,
|
|
138
|
+
estimatedMb: opts.estimatedMb ?? 600,
|
|
139
|
+
async load(modelKey: string): Promise<TtsBackend> {
|
|
140
|
+
return await loader(modelKey);
|
|
141
|
+
},
|
|
142
|
+
async unload(backend: TtsBackend): Promise<void> {
|
|
143
|
+
await backend.dispose();
|
|
144
|
+
},
|
|
145
|
+
async run(backend: TtsBackend, request: TtsRequest): Promise<TtsResult> {
|
|
146
|
+
// 1. Reject unsupported requests cleanly — no silent fallback.
|
|
147
|
+
if (!backend.supports(request)) {
|
|
148
|
+
throw new TtsBackendUnavailableError(
|
|
149
|
+
backend.id,
|
|
150
|
+
"unsupported_request",
|
|
151
|
+
`[tts] backend "${backend.id}" does not support this request (voice=${request.voice ?? "default"} sampleRate=${request.sampleRate ?? "native"})`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. Cache lookup (if a cache is wired).
|
|
156
|
+
let cacheKey: string | null = null;
|
|
157
|
+
if (cache) {
|
|
158
|
+
cacheKey = hashTtsRequest({
|
|
159
|
+
provider,
|
|
160
|
+
model: backend.id,
|
|
161
|
+
voice: request.voice ?? "default",
|
|
162
|
+
text: request.text,
|
|
163
|
+
...(typeof request.speed === "number"
|
|
164
|
+
? { speed: request.speed }
|
|
165
|
+
: {}),
|
|
166
|
+
...(typeof request.sampleRate === "number"
|
|
167
|
+
? { sampleRate: request.sampleRate }
|
|
168
|
+
: {}),
|
|
169
|
+
});
|
|
170
|
+
const hit = cache.get(cacheKey);
|
|
171
|
+
if (hit && hit.live !== false) {
|
|
172
|
+
return {
|
|
173
|
+
audio: hit.audio,
|
|
174
|
+
mime: hit.mime,
|
|
175
|
+
sampleRate: hit.sampleRate,
|
|
176
|
+
metadata: {
|
|
177
|
+
model: hit.model,
|
|
178
|
+
voice: hit.voice,
|
|
179
|
+
text: request.text,
|
|
180
|
+
inferenceTimeMs: 0,
|
|
181
|
+
cacheHit: true,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 3. Miss — synthesize through the backend.
|
|
188
|
+
const result = await backend.synthesize(request);
|
|
189
|
+
|
|
190
|
+
// 4. Populate cache (best-effort).
|
|
191
|
+
if (cache && cacheKey) {
|
|
192
|
+
cache.set(
|
|
193
|
+
cacheKey,
|
|
194
|
+
{
|
|
195
|
+
audio: result.audio,
|
|
196
|
+
mime: result.mime,
|
|
197
|
+
sampleRate: result.sampleRate,
|
|
198
|
+
voice: result.metadata.voice,
|
|
199
|
+
model: result.metadata.model,
|
|
200
|
+
},
|
|
201
|
+
cacheTtlMs,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
...result,
|
|
207
|
+
metadata: {
|
|
208
|
+
...result.metadata,
|
|
209
|
+
cacheHit: false,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-hashed cache for synthesized TTS audio. WS5 capability-level
|
|
3
|
+
* cache that sits between the WS1 `MemoryArbiter` and the concrete TTS
|
|
4
|
+
* backend (OmniVoice / Kokoro / fake).
|
|
5
|
+
*
|
|
6
|
+
* Cache namespace: `tts-audio`.
|
|
7
|
+
*
|
|
8
|
+
* Why a capability-level cache (in addition to the existing
|
|
9
|
+
* `wrap-with-first-line-cache.ts`):
|
|
10
|
+
*
|
|
11
|
+
* - `wrap-with-first-line-cache.ts` is a *first-sentence* cache. It
|
|
12
|
+
* accelerates the audible TTFA on the planner's typical "short
|
|
13
|
+
* acknowledgement" leading sentence. It's keyed on `(provider,
|
|
14
|
+
* voiceId, voiceRevision, codec, voiceSettingsFingerprint,
|
|
15
|
+
* normalizedFirstSentence)`.
|
|
16
|
+
*
|
|
17
|
+
* - This cache is a *full-utterance* cache. It accelerates repeated
|
|
18
|
+
* synthesis of identical inputs across turns (system prompts, idle
|
|
19
|
+
* fillers, the user replaying a previously-spoken sentence). It's
|
|
20
|
+
* keyed on `sha256(provider || model || voice || text)`. Hits short-
|
|
21
|
+
* circuit `backend.synthesize` entirely; the arbiter returns the
|
|
22
|
+
* cached bytes and records `metadata.cacheHit = true`.
|
|
23
|
+
*
|
|
24
|
+
* The two layers are independent. A request can hit the first-line cache,
|
|
25
|
+
* a partially-matching prefix can hit this cache, both can miss — and the
|
|
26
|
+
* keying spaces don't collide because the first-line cache keys on the
|
|
27
|
+
* *first sentence's* normalized text, while this one keys on the full
|
|
28
|
+
* text. Wire both at boot for maximum hit rate.
|
|
29
|
+
*
|
|
30
|
+
* Contract:
|
|
31
|
+
* - Caller computes a stable hash from a deterministic key (the request
|
|
32
|
+
* plus the active model). The hash is the cache key.
|
|
33
|
+
* - Caller pairs the hash with the synthesized bytes AND the
|
|
34
|
+
* `(mime, sampleRate, voice, model)` quadruple so a reader can
|
|
35
|
+
* reproduce a full `TtsResult` on hit.
|
|
36
|
+
* - `get(hash)` returns `null` on miss or expiry, the entry on hit. A
|
|
37
|
+
* hit also "touches" the entry to keep it warm under LRU.
|
|
38
|
+
* - `set(hash, entry, ttlMs?)` inserts with a TTL (default 10 min);
|
|
39
|
+
* if the LRU is full, the coldest entry is evicted.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { createHash } from "node:crypto";
|
|
43
|
+
import type { TtsMimeType } from "./types";
|
|
44
|
+
|
|
45
|
+
interface CacheEntry {
|
|
46
|
+
audio: Uint8Array;
|
|
47
|
+
mime: TtsMimeType;
|
|
48
|
+
sampleRate: number;
|
|
49
|
+
voice: string;
|
|
50
|
+
model: string;
|
|
51
|
+
expiresAtMs: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface TtsAudioEntry {
|
|
55
|
+
audio: Uint8Array;
|
|
56
|
+
mime: TtsMimeType;
|
|
57
|
+
sampleRate: number;
|
|
58
|
+
voice: string;
|
|
59
|
+
model: string;
|
|
60
|
+
/** True when this entry is still within its TTL. */
|
|
61
|
+
live: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TtsAudioCacheConfig {
|
|
65
|
+
/** Max entries retained. LRU evicts beyond this. Default 64. */
|
|
66
|
+
maxEntries: number;
|
|
67
|
+
/** Default TTL when `set()` is called without one. Default 10 min. */
|
|
68
|
+
defaultTtlMs: number;
|
|
69
|
+
/** Optional max total bytes; an LRU sweep enforces this on insert. Default 64 MB. */
|
|
70
|
+
maxBytes: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const DEFAULTS: TtsAudioCacheConfig = {
|
|
74
|
+
maxEntries: 64,
|
|
75
|
+
defaultTtlMs: 10 * 60_000,
|
|
76
|
+
maxBytes: 64 * 1024 * 1024,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the canonical cache key for a TTS request + active model. The
|
|
81
|
+
* fields are joined with a separator that can't appear in any of them
|
|
82
|
+
* (a NUL byte) and SHA-256'd. The model id participates so a tier swap
|
|
83
|
+
* doesn't yield phantom hits from a different voice family.
|
|
84
|
+
*/
|
|
85
|
+
export function hashTtsRequest(args: {
|
|
86
|
+
provider: string;
|
|
87
|
+
model: string;
|
|
88
|
+
voice: string;
|
|
89
|
+
text: string;
|
|
90
|
+
speed?: number;
|
|
91
|
+
sampleRate?: number;
|
|
92
|
+
}): string {
|
|
93
|
+
const h = createHash("sha256");
|
|
94
|
+
h.update(args.provider);
|
|
95
|
+
h.update("\0");
|
|
96
|
+
h.update(args.model);
|
|
97
|
+
h.update("\0");
|
|
98
|
+
h.update(args.voice);
|
|
99
|
+
h.update("\0");
|
|
100
|
+
h.update(args.text);
|
|
101
|
+
h.update("\0");
|
|
102
|
+
h.update(typeof args.speed === "number" ? String(args.speed) : "");
|
|
103
|
+
h.update("\0");
|
|
104
|
+
h.update(typeof args.sampleRate === "number" ? String(args.sampleRate) : "");
|
|
105
|
+
return h.digest("hex");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class TtsAudioCache {
|
|
109
|
+
private readonly config: TtsAudioCacheConfig;
|
|
110
|
+
/**
|
|
111
|
+
* `Map` preserves insertion order; we re-insert on hit to bubble
|
|
112
|
+
* entries to the back, so the first key in iteration order is the
|
|
113
|
+
* LRU candidate.
|
|
114
|
+
*/
|
|
115
|
+
private readonly entries = new Map<string, CacheEntry>();
|
|
116
|
+
private readonly now: () => number;
|
|
117
|
+
private totalBytes = 0;
|
|
118
|
+
|
|
119
|
+
constructor(
|
|
120
|
+
opts: {
|
|
121
|
+
config?: Partial<TtsAudioCacheConfig>;
|
|
122
|
+
now?: () => number;
|
|
123
|
+
} = {},
|
|
124
|
+
) {
|
|
125
|
+
this.config = {
|
|
126
|
+
maxEntries: Math.max(1, opts.config?.maxEntries ?? DEFAULTS.maxEntries),
|
|
127
|
+
defaultTtlMs: Math.max(
|
|
128
|
+
0,
|
|
129
|
+
opts.config?.defaultTtlMs ?? DEFAULTS.defaultTtlMs,
|
|
130
|
+
),
|
|
131
|
+
maxBytes: Math.max(1024, opts.config?.maxBytes ?? DEFAULTS.maxBytes),
|
|
132
|
+
};
|
|
133
|
+
this.now = opts.now ?? (() => Date.now());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get(hash: string): TtsAudioEntry | null {
|
|
137
|
+
const found = this.entries.get(hash);
|
|
138
|
+
if (!found) return null;
|
|
139
|
+
const live = found.expiresAtMs > this.now();
|
|
140
|
+
if (!live) {
|
|
141
|
+
this.entries.delete(hash);
|
|
142
|
+
this.totalBytes -= found.audio.byteLength;
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// Bubble to MRU position.
|
|
146
|
+
this.entries.delete(hash);
|
|
147
|
+
this.entries.set(hash, found);
|
|
148
|
+
return {
|
|
149
|
+
audio: found.audio,
|
|
150
|
+
mime: found.mime,
|
|
151
|
+
sampleRate: found.sampleRate,
|
|
152
|
+
voice: found.voice,
|
|
153
|
+
model: found.model,
|
|
154
|
+
live: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
set(
|
|
159
|
+
hash: string,
|
|
160
|
+
entry: {
|
|
161
|
+
audio: Uint8Array;
|
|
162
|
+
mime: TtsMimeType;
|
|
163
|
+
sampleRate: number;
|
|
164
|
+
voice: string;
|
|
165
|
+
model: string;
|
|
166
|
+
},
|
|
167
|
+
ttlMs?: number,
|
|
168
|
+
): void {
|
|
169
|
+
const ttl = typeof ttlMs === "number" ? ttlMs : this.config.defaultTtlMs;
|
|
170
|
+
if (ttl <= 0) return;
|
|
171
|
+
const existing = this.entries.get(hash);
|
|
172
|
+
if (existing) this.totalBytes -= existing.audio.byteLength;
|
|
173
|
+
const record: CacheEntry = {
|
|
174
|
+
audio: entry.audio,
|
|
175
|
+
mime: entry.mime,
|
|
176
|
+
sampleRate: entry.sampleRate,
|
|
177
|
+
voice: entry.voice,
|
|
178
|
+
model: entry.model,
|
|
179
|
+
expiresAtMs: this.now() + ttl,
|
|
180
|
+
};
|
|
181
|
+
this.entries.set(hash, record);
|
|
182
|
+
this.totalBytes += entry.audio.byteLength;
|
|
183
|
+
this.enforceBudget();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
has(hash: string): boolean {
|
|
187
|
+
const found = this.entries.get(hash);
|
|
188
|
+
if (!found) return false;
|
|
189
|
+
if (found.expiresAtMs <= this.now()) {
|
|
190
|
+
this.entries.delete(hash);
|
|
191
|
+
this.totalBytes -= found.audio.byteLength;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
purgeExpired(nowMs: number = this.now()): number {
|
|
198
|
+
let removed = 0;
|
|
199
|
+
for (const [key, value] of this.entries) {
|
|
200
|
+
if (value.expiresAtMs <= nowMs) {
|
|
201
|
+
this.entries.delete(key);
|
|
202
|
+
this.totalBytes -= value.audio.byteLength;
|
|
203
|
+
removed += 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return removed;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
clear(): void {
|
|
210
|
+
this.entries.clear();
|
|
211
|
+
this.totalBytes = 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
size(): number {
|
|
215
|
+
return this.entries.size;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
bytes(): number {
|
|
219
|
+
return this.totalBytes;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private enforceBudget(): void {
|
|
223
|
+
while (
|
|
224
|
+
this.entries.size > this.config.maxEntries ||
|
|
225
|
+
this.totalBytes > this.config.maxBytes
|
|
226
|
+
) {
|
|
227
|
+
const oldest = this.entries.keys().next();
|
|
228
|
+
if (oldest.done) break;
|
|
229
|
+
const key = oldest.value;
|
|
230
|
+
const removed = this.entries.get(key);
|
|
231
|
+
this.entries.delete(key);
|
|
232
|
+
if (removed) this.totalBytes -= removed.audio.byteLength;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|