@elizaos/plugin-local-inference 2.0.0-beta.1 → 2.0.3-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (701) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/package.json +82 -15
  4. package/src/actions/generate-media.d.ts +59 -0
  5. package/src/actions/generate-media.d.ts.map +1 -0
  6. package/src/actions/generate-media.ts +647 -0
  7. package/src/actions/identify-speaker.d.ts +23 -0
  8. package/src/actions/identify-speaker.d.ts.map +1 -0
  9. package/src/actions/identify-speaker.ts +171 -0
  10. package/src/actions/transcription-control.d.ts +29 -0
  11. package/src/actions/transcription-control.d.ts.map +1 -0
  12. package/src/actions/transcription-control.test.ts +100 -0
  13. package/src/actions/transcription-control.ts +127 -0
  14. package/src/adapters/capacitor-llama/__tests__/compat-behavior.test.ts +218 -0
  15. package/src/adapters/capacitor-llama/__tests__/index.test.ts +68 -0
  16. package/src/adapters/capacitor-llama/__tests__/structured-output.test.ts +215 -0
  17. package/src/adapters/capacitor-llama/__tests__/text-streaming.test.ts +174 -0
  18. package/src/adapters/capacitor-llama/environment.ts +71 -0
  19. package/src/adapters/capacitor-llama/index.browser.ts +83 -0
  20. package/src/adapters/capacitor-llama/index.ts +807 -0
  21. package/src/adapters/capacitor-llama/loader.ts +109 -0
  22. package/src/adapters/capacitor-llama/structured-output.ts +165 -0
  23. package/src/adapters/capacitor-llama/text-streaming.ts +227 -0
  24. package/src/adapters/capacitor-llama/types.ts +374 -0
  25. package/src/backends/apple-foundation.ts +127 -0
  26. package/src/index.d.ts +8 -0
  27. package/src/index.d.ts.map +1 -0
  28. package/src/index.ts +62 -0
  29. package/src/local-inference-routes.d.ts +38 -0
  30. package/src/local-inference-routes.d.ts.map +1 -0
  31. package/src/local-inference-routes.test.ts +344 -0
  32. package/src/local-inference-routes.ts +1543 -0
  33. package/src/provider.d.ts +21 -0
  34. package/src/provider.d.ts.map +1 -0
  35. package/src/provider.ts +1082 -0
  36. package/src/routes/compat-helpers.d.ts +18 -0
  37. package/src/routes/compat-helpers.d.ts.map +1 -0
  38. package/src/routes/compat-helpers.ts +274 -0
  39. package/src/routes/family-member-route.d.ts +62 -0
  40. package/src/routes/family-member-route.d.ts.map +1 -0
  41. package/src/routes/family-member-route.ts +353 -0
  42. package/src/routes/index.d.ts +19 -0
  43. package/src/routes/index.d.ts.map +1 -0
  44. package/src/routes/index.ts +60 -0
  45. package/src/routes/live-diarization-route.d.ts +26 -0
  46. package/src/routes/live-diarization-route.d.ts.map +1 -0
  47. package/src/routes/live-diarization-route.test.ts +213 -0
  48. package/src/routes/live-diarization-route.ts +122 -0
  49. package/src/routes/local-inference-asr-route.d.ts +4 -0
  50. package/src/routes/local-inference-asr-route.d.ts.map +1 -0
  51. package/src/routes/local-inference-asr-route.test.ts +205 -0
  52. package/src/routes/local-inference-asr-route.ts +163 -0
  53. package/src/routes/local-inference-asr-transcribe.d.ts +20 -0
  54. package/src/routes/local-inference-asr-transcribe.d.ts.map +1 -0
  55. package/src/routes/local-inference-asr-transcribe.test.ts +118 -0
  56. package/src/routes/local-inference-asr-transcribe.ts +97 -0
  57. package/src/routes/local-inference-compat-routes.d.ts +16 -0
  58. package/src/routes/local-inference-compat-routes.d.ts.map +1 -0
  59. package/src/routes/local-inference-compat-routes.test.ts +485 -0
  60. package/src/routes/local-inference-compat-routes.ts +808 -0
  61. package/src/routes/local-inference-tts-route.d.ts +7 -0
  62. package/src/routes/local-inference-tts-route.d.ts.map +1 -0
  63. package/src/routes/local-inference-tts-route.test.ts +179 -0
  64. package/src/routes/local-inference-tts-route.ts +230 -0
  65. package/src/routes/transcript-audio-store.d.ts +15 -0
  66. package/src/routes/transcript-audio-store.d.ts.map +1 -0
  67. package/src/routes/transcript-audio-store.ts +27 -0
  68. package/src/routes/transcripts-routes.d.ts +36 -0
  69. package/src/routes/transcripts-routes.d.ts.map +1 -0
  70. package/src/routes/transcripts-routes.test.ts +144 -0
  71. package/src/routes/transcripts-routes.ts +159 -0
  72. package/src/routes/voice-first-run-routes.d.ts +62 -0
  73. package/src/routes/voice-first-run-routes.d.ts.map +1 -0
  74. package/src/routes/voice-first-run-routes.ts +524 -0
  75. package/src/routes/voice-models-routes.d.ts +62 -0
  76. package/src/routes/voice-models-routes.d.ts.map +1 -0
  77. package/src/routes/voice-models-routes.ts +554 -0
  78. package/src/routes/voice-profile-plugin-routes.d.ts +19 -0
  79. package/src/routes/voice-profile-plugin-routes.d.ts.map +1 -0
  80. package/src/routes/voice-profile-plugin-routes.ts +138 -0
  81. package/src/routes/voice-profiles-management-routes.d.ts +52 -0
  82. package/src/routes/voice-profiles-management-routes.d.ts.map +1 -0
  83. package/src/routes/voice-profiles-management-routes.ts +476 -0
  84. package/src/routes/voice-speaker-profile-routes.d.ts +57 -0
  85. package/src/routes/voice-speaker-profile-routes.d.ts.map +1 -0
  86. package/src/routes/voice-speaker-profile-routes.ts +199 -0
  87. package/src/runtime/aosp-llama-loader-selection.test.ts +80 -0
  88. package/src/runtime/capacitor-llama.d.ts +25 -0
  89. package/src/runtime/embedding-manager-support.d.ts +77 -0
  90. package/src/runtime/embedding-manager-support.d.ts.map +1 -0
  91. package/src/runtime/embedding-manager-support.ts +497 -0
  92. package/src/runtime/embedding-presets.d.ts +16 -0
  93. package/src/runtime/embedding-presets.d.ts.map +1 -0
  94. package/src/runtime/embedding-presets.ts +81 -0
  95. package/src/runtime/embedding-warmup-policy.d.ts +14 -0
  96. package/src/runtime/embedding-warmup-policy.d.ts.map +1 -0
  97. package/src/runtime/embedding-warmup-policy.test.ts +53 -0
  98. package/src/runtime/embedding-warmup-policy.ts +48 -0
  99. package/src/runtime/ensure-local-inference-handler.d.ts +62 -0
  100. package/src/runtime/ensure-local-inference-handler.d.ts.map +1 -0
  101. package/src/runtime/ensure-local-inference-handler.test.ts +528 -0
  102. package/src/runtime/ensure-local-inference-handler.ts +1448 -0
  103. package/src/runtime/index.d.ts +15 -0
  104. package/src/runtime/index.d.ts.map +1 -0
  105. package/src/runtime/index.ts +33 -0
  106. package/src/runtime/mobile-local-inference-gate.d.ts +31 -0
  107. package/src/runtime/mobile-local-inference-gate.d.ts.map +1 -0
  108. package/src/runtime/mobile-local-inference-gate.test.ts +69 -0
  109. package/src/runtime/mobile-local-inference-gate.ts +44 -0
  110. package/src/runtime/voice-entity-binding.d.ts +103 -0
  111. package/src/runtime/voice-entity-binding.d.ts.map +1 -0
  112. package/src/runtime/voice-entity-binding.transcript.test.ts +69 -0
  113. package/src/runtime/voice-entity-binding.ts +328 -0
  114. package/src/services/README.md +71 -0
  115. package/src/services/__tests__/backend-selector.test.ts +101 -0
  116. package/src/services/__tests__/checkpoint-manager.test.ts +376 -0
  117. package/src/services/__tests__/gpu-autotune.test.ts +400 -0
  118. package/src/services/__tests__/llm-streaming-binding.test.ts +85 -0
  119. package/src/services/__tests__/planner-grammar.test.ts +372 -0
  120. package/src/services/__tests__/runtime-target.test.ts +176 -0
  121. package/src/services/active-model-switch-rollback.test.ts +183 -0
  122. package/src/services/active-model.d.ts +282 -0
  123. package/src/services/active-model.d.ts.map +1 -0
  124. package/src/services/active-model.ts +1213 -0
  125. package/src/services/assignments.d.ts +71 -0
  126. package/src/services/assignments.d.ts.map +1 -0
  127. package/src/services/assignments.test.ts +80 -0
  128. package/src/services/assignments.ts +230 -0
  129. package/src/services/backend-selector.ts +95 -0
  130. package/src/services/backend.d.ts +346 -0
  131. package/src/services/backend.d.ts.map +1 -0
  132. package/src/services/backend.ts +612 -0
  133. package/src/services/bionic-host-loader.d.ts +46 -0
  134. package/src/services/bionic-host-loader.d.ts.map +1 -0
  135. package/src/services/bionic-host-loader.test.ts +133 -0
  136. package/src/services/bionic-host-loader.ts +180 -0
  137. package/src/services/bundled-models.d.ts +34 -0
  138. package/src/services/bundled-models.d.ts.map +1 -0
  139. package/src/services/bundled-models.ts +129 -0
  140. package/src/services/cache-bridge.d.ts +206 -0
  141. package/src/services/cache-bridge.d.ts.map +1 -0
  142. package/src/services/cache-bridge.test.ts +516 -0
  143. package/src/services/cache-bridge.ts +423 -0
  144. package/src/services/catalog.d.ts +10 -0
  145. package/src/services/catalog.d.ts.map +1 -0
  146. package/src/services/catalog.test.ts +238 -0
  147. package/src/services/catalog.ts +27 -0
  148. package/src/services/checkpoint-client.d.ts +109 -0
  149. package/src/services/checkpoint-client.d.ts.map +1 -0
  150. package/src/services/checkpoint-client.ts +258 -0
  151. package/src/services/checkpoint-manager.ts +474 -0
  152. package/src/services/cloud-fallback.d.ts +102 -0
  153. package/src/services/cloud-fallback.d.ts.map +1 -0
  154. package/src/services/cloud-fallback.ts +230 -0
  155. package/src/services/conversation-registry.d.ts +142 -0
  156. package/src/services/conversation-registry.d.ts.map +1 -0
  157. package/src/services/conversation-registry.test.ts +235 -0
  158. package/src/services/conversation-registry.ts +264 -0
  159. package/src/services/desktop-fused-ffi-backend-runtime.d.ts +95 -0
  160. package/src/services/desktop-fused-ffi-backend-runtime.d.ts.map +1 -0
  161. package/src/services/desktop-fused-ffi-backend-runtime.ts +339 -0
  162. package/src/services/device-bridge.d.ts +188 -0
  163. package/src/services/device-bridge.d.ts.map +1 -0
  164. package/src/services/device-bridge.ts +1237 -0
  165. package/src/services/device-resource-metrics.d.ts +149 -0
  166. package/src/services/device-resource-metrics.d.ts.map +1 -0
  167. package/src/services/device-resource-metrics.test.ts +98 -0
  168. package/src/services/device-resource-metrics.ts +346 -0
  169. package/src/services/device-tier.d.ts +115 -0
  170. package/src/services/device-tier.d.ts.map +1 -0
  171. package/src/services/device-tier.test.ts +371 -0
  172. package/src/services/device-tier.ts +410 -0
  173. package/src/services/downloader.d.ts +82 -0
  174. package/src/services/downloader.d.ts.map +1 -0
  175. package/src/services/downloader.test.ts +747 -0
  176. package/src/services/downloader.ts +925 -0
  177. package/src/services/engine-direct-bundle.test.ts +58 -0
  178. package/src/services/engine-streaming.test.ts +80 -0
  179. package/src/services/engine.d.ts +540 -0
  180. package/src/services/engine.d.ts.map +1 -0
  181. package/src/services/engine.ts +1909 -0
  182. package/src/services/ensure-local-artifacts.integration.test.ts +273 -0
  183. package/src/services/ensure-local-artifacts.test.ts +368 -0
  184. package/src/services/ensure-local-artifacts.ts +351 -0
  185. package/src/services/external-scanner.d.ts +17 -0
  186. package/src/services/external-scanner.d.ts.map +1 -0
  187. package/src/services/external-scanner.ts +312 -0
  188. package/src/services/ffi-llm-mock.ts +354 -0
  189. package/src/services/ffi-llm-streaming-abi.ts +442 -0
  190. package/src/services/ffi-streaming-backend.d.ts +180 -0
  191. package/src/services/ffi-streaming-backend.d.ts.map +1 -0
  192. package/src/services/ffi-streaming-backend.ts +382 -0
  193. package/src/services/ffi-streaming-runner.d.ts +122 -0
  194. package/src/services/ffi-streaming-runner.d.ts.map +1 -0
  195. package/src/services/ffi-streaming-runner.test.ts +60 -0
  196. package/src/services/ffi-streaming-runner.ts +354 -0
  197. package/src/services/ffi-unload-ordering.test.ts +162 -0
  198. package/src/services/gpu-autotune.ts +534 -0
  199. package/src/services/gpu-detect.d.ts +56 -0
  200. package/src/services/gpu-detect.d.ts.map +1 -0
  201. package/src/services/gpu-detect.ts +139 -0
  202. package/src/services/handler-registry.d.ts +72 -0
  203. package/src/services/handler-registry.d.ts.map +1 -0
  204. package/src/services/handler-registry.ts +240 -0
  205. package/src/services/hardware.d.ts +63 -0
  206. package/src/services/hardware.d.ts.map +1 -0
  207. package/src/services/hardware.test.ts +231 -0
  208. package/src/services/hardware.ts +410 -0
  209. package/src/services/hf-search.d.ts +26 -0
  210. package/src/services/hf-search.d.ts.map +1 -0
  211. package/src/services/hf-search.test.ts +69 -0
  212. package/src/services/hf-search.ts +420 -0
  213. package/src/services/image-description-runtime.d.ts +14 -0
  214. package/src/services/image-description-runtime.d.ts.map +1 -0
  215. package/src/services/image-description-runtime.test.ts +61 -0
  216. package/src/services/image-description-runtime.ts +118 -0
  217. package/src/services/imagegen/aosp-unavailable.d.ts +134 -0
  218. package/src/services/imagegen/aosp-unavailable.d.ts.map +1 -0
  219. package/src/services/imagegen/aosp-unavailable.ts +229 -0
  220. package/src/services/imagegen/backend-selector.d.ts +118 -0
  221. package/src/services/imagegen/backend-selector.d.ts.map +1 -0
  222. package/src/services/imagegen/backend-selector.ts +277 -0
  223. package/src/services/imagegen/coreml-unavailable.d.ts +105 -0
  224. package/src/services/imagegen/coreml-unavailable.d.ts.map +1 -0
  225. package/src/services/imagegen/coreml-unavailable.ts +237 -0
  226. package/src/services/imagegen/errors.d.ts +16 -0
  227. package/src/services/imagegen/errors.d.ts.map +1 -0
  228. package/src/services/imagegen/errors.ts +40 -0
  229. package/src/services/imagegen/index.d.ts +58 -0
  230. package/src/services/imagegen/index.d.ts.map +1 -0
  231. package/src/services/imagegen/index.ts +144 -0
  232. package/src/services/imagegen/mflux.d.ts +74 -0
  233. package/src/services/imagegen/mflux.d.ts.map +1 -0
  234. package/src/services/imagegen/mflux.ts +313 -0
  235. package/src/services/imagegen/sd-cpp.d.ts +180 -0
  236. package/src/services/imagegen/sd-cpp.d.ts.map +1 -0
  237. package/src/services/imagegen/sd-cpp.ts +718 -0
  238. package/src/services/imagegen/tensorrt-unavailable.d.ts +83 -0
  239. package/src/services/imagegen/tensorrt-unavailable.d.ts.map +1 -0
  240. package/src/services/imagegen/tensorrt-unavailable.ts +295 -0
  241. package/src/services/imagegen/types.d.ts +181 -0
  242. package/src/services/imagegen/types.d.ts.map +1 -0
  243. package/src/services/imagegen/types.ts +193 -0
  244. package/src/services/index.d.ts +29 -0
  245. package/src/services/index.d.ts.map +1 -0
  246. package/src/services/index.ts +211 -0
  247. package/src/services/inference-capabilities.d.ts +132 -0
  248. package/src/services/inference-capabilities.d.ts.map +1 -0
  249. package/src/services/inference-capabilities.test.ts +75 -0
  250. package/src/services/inference-capabilities.ts +204 -0
  251. package/src/services/inference-telemetry.d.ts +59 -0
  252. package/src/services/inference-telemetry.d.ts.map +1 -0
  253. package/src/services/inference-telemetry.ts +143 -0
  254. package/src/services/ios-llama-streaming.ts +248 -0
  255. package/src/services/kv-spill.d.ts +189 -0
  256. package/src/services/kv-spill.d.ts.map +1 -0
  257. package/src/services/kv-spill.test.ts +222 -0
  258. package/src/services/kv-spill.ts +356 -0
  259. package/src/services/latency-trace.d.ts +346 -0
  260. package/src/services/latency-trace.d.ts.map +1 -0
  261. package/src/services/latency-trace.test.ts +266 -0
  262. package/src/services/latency-trace.ts +844 -0
  263. package/src/services/llama-server-metrics.ts +304 -0
  264. package/src/services/llm-streaming-binding.d.ts +96 -0
  265. package/src/services/llm-streaming-binding.d.ts.map +1 -0
  266. package/src/services/llm-streaming-binding.ts +136 -0
  267. package/src/services/load-args.d.ts +82 -0
  268. package/src/services/load-args.d.ts.map +1 -0
  269. package/src/services/load-args.ts +81 -0
  270. package/src/services/manifest/eliza-1.manifest.v1.json +708 -0
  271. package/src/services/manifest/index.d.ts +4 -0
  272. package/src/services/manifest/index.d.ts.map +1 -0
  273. package/src/services/manifest/index.ts +66 -0
  274. package/src/services/manifest/manifest.test.ts +689 -0
  275. package/src/services/manifest/schema.d.ts +713 -0
  276. package/src/services/manifest/schema.d.ts.map +1 -0
  277. package/src/services/manifest/schema.ts +653 -0
  278. package/src/services/manifest/types.d.ts +30 -0
  279. package/src/services/manifest/types.d.ts.map +1 -0
  280. package/src/services/manifest/types.ts +55 -0
  281. package/src/services/manifest/validator.d.ts +66 -0
  282. package/src/services/manifest/validator.d.ts.map +1 -0
  283. package/src/services/manifest/validator.ts +567 -0
  284. package/src/services/memory-arbiter.d.ts +318 -0
  285. package/src/services/memory-arbiter.d.ts.map +1 -0
  286. package/src/services/memory-arbiter.test.ts +419 -0
  287. package/src/services/memory-arbiter.ts +925 -0
  288. package/src/services/memory-monitor.d.ts +122 -0
  289. package/src/services/memory-monitor.d.ts.map +1 -0
  290. package/src/services/memory-monitor.test.ts +208 -0
  291. package/src/services/memory-monitor.ts +297 -0
  292. package/src/services/memory-pressure.d.ts +130 -0
  293. package/src/services/memory-pressure.d.ts.map +1 -0
  294. package/src/services/memory-pressure.ts +414 -0
  295. package/src/services/mtp-doctor.d.ts +13 -0
  296. package/src/services/mtp-doctor.d.ts.map +1 -0
  297. package/src/services/mtp-doctor.ts +78 -0
  298. package/src/services/network-policy.d.ts +127 -0
  299. package/src/services/network-policy.d.ts.map +1 -0
  300. package/src/services/network-policy.ts +346 -0
  301. package/src/services/paths.d.ts +6 -0
  302. package/src/services/paths.d.ts.map +1 -0
  303. package/src/services/paths.ts +25 -0
  304. package/src/services/planner-skeleton.d.ts +124 -0
  305. package/src/services/planner-skeleton.d.ts.map +1 -0
  306. package/src/services/planner-skeleton.ts +175 -0
  307. package/src/services/providers.d.ts +38 -0
  308. package/src/services/providers.d.ts.map +1 -0
  309. package/src/services/providers.ts +507 -0
  310. package/src/services/ram-budget-cache.test.ts +163 -0
  311. package/src/services/ram-budget.d.ts +110 -0
  312. package/src/services/ram-budget.d.ts.map +1 -0
  313. package/src/services/ram-budget.ts +0 -0
  314. package/src/services/readiness.d.ts +9 -0
  315. package/src/services/readiness.d.ts.map +1 -0
  316. package/src/services/readiness.test.ts +87 -0
  317. package/src/services/readiness.ts +238 -0
  318. package/src/services/recommendation.d.ts +111 -0
  319. package/src/services/recommendation.d.ts.map +1 -0
  320. package/src/services/recommendation.ts +671 -0
  321. package/src/services/registry.d.ts +35 -0
  322. package/src/services/registry.d.ts.map +1 -0
  323. package/src/services/registry.ts +151 -0
  324. package/src/services/router-handler.d.ts +92 -0
  325. package/src/services/router-handler.d.ts.map +1 -0
  326. package/src/services/router-handler.test.ts +45 -0
  327. package/src/services/router-handler.ts +407 -0
  328. package/src/services/routing-policy.d.ts +69 -0
  329. package/src/services/routing-policy.d.ts.map +1 -0
  330. package/src/services/routing-policy.test.ts +164 -0
  331. package/src/services/routing-policy.ts +297 -0
  332. package/src/services/routing-preferences.d.ts +8 -0
  333. package/src/services/routing-preferences.d.ts.map +1 -0
  334. package/src/services/routing-preferences.ts +17 -0
  335. package/src/services/runtime-target.d.ts +98 -0
  336. package/src/services/runtime-target.d.ts.map +1 -0
  337. package/src/services/runtime-target.ts +154 -0
  338. package/src/services/service.d.ts +128 -0
  339. package/src/services/service.d.ts.map +1 -0
  340. package/src/services/service.test.ts +223 -0
  341. package/src/services/service.ts +735 -0
  342. package/src/services/session-pool.d.ts +72 -0
  343. package/src/services/session-pool.d.ts.map +1 -0
  344. package/src/services/session-pool.ts +153 -0
  345. package/src/services/structured-output/deterministic-repair.d.ts +23 -0
  346. package/src/services/structured-output/deterministic-repair.d.ts.map +1 -0
  347. package/src/services/structured-output/deterministic-repair.test.ts +169 -0
  348. package/src/services/structured-output/deterministic-repair.ts +443 -0
  349. package/src/services/structured-output/index.ts +4 -0
  350. package/src/services/structured-output.d.ts +311 -0
  351. package/src/services/structured-output.d.ts.map +1 -0
  352. package/src/services/structured-output.test.ts +483 -0
  353. package/src/services/structured-output.ts +712 -0
  354. package/src/services/system-memory.d.ts +33 -0
  355. package/src/services/system-memory.d.ts.map +1 -0
  356. package/src/services/system-memory.test.ts +47 -0
  357. package/src/services/system-memory.ts +67 -0
  358. package/src/services/transcription-priority.test.ts +211 -0
  359. package/src/services/types.d.ts +19 -0
  360. package/src/services/types.d.ts.map +1 -0
  361. package/src/services/types.ts +55 -0
  362. package/src/services/verify-on-device.d.ts +34 -0
  363. package/src/services/verify-on-device.d.ts.map +1 -0
  364. package/src/services/verify-on-device.test.ts +87 -0
  365. package/src/services/verify-on-device.ts +127 -0
  366. package/src/services/verify.d.ts +8 -0
  367. package/src/services/verify.d.ts.map +1 -0
  368. package/src/services/verify.ts +13 -0
  369. package/src/services/vision/aosp-unavailable.d.ts +115 -0
  370. package/src/services/vision/aosp-unavailable.d.ts.map +1 -0
  371. package/src/services/vision/aosp-unavailable.ts +163 -0
  372. package/src/services/vision/capacitor-llama.d.ts +99 -0
  373. package/src/services/vision/capacitor-llama.d.ts.map +1 -0
  374. package/src/services/vision/capacitor-llama.ts +255 -0
  375. package/src/services/vision/cloud-fallback.d.ts +47 -0
  376. package/src/services/vision/cloud-fallback.d.ts.map +1 -0
  377. package/src/services/vision/cloud-fallback.test.ts +243 -0
  378. package/src/services/vision/cloud-fallback.ts +268 -0
  379. package/src/services/vision/fallback-chain.test.ts +86 -0
  380. package/src/services/vision/hash.d.ts +71 -0
  381. package/src/services/vision/hash.d.ts.map +1 -0
  382. package/src/services/vision/hash.ts +157 -0
  383. package/src/services/vision/index.d.ts +95 -0
  384. package/src/services/vision/index.d.ts.map +1 -0
  385. package/src/services/vision/index.ts +251 -0
  386. package/src/services/vision/llama-server.d.ts +73 -0
  387. package/src/services/vision/llama-server.d.ts.map +1 -0
  388. package/src/services/vision/llama-server.ts +177 -0
  389. package/src/services/vision/types.d.ts +153 -0
  390. package/src/services/vision/types.d.ts.map +1 -0
  391. package/src/services/vision/types.ts +154 -0
  392. package/src/services/vision/vast-fallback.d.ts +18 -0
  393. package/src/services/vision/vast-fallback.d.ts.map +1 -0
  394. package/src/services/vision/vast-fallback.ts +127 -0
  395. package/src/services/vision-embedding-cache.d.ts +98 -0
  396. package/src/services/vision-embedding-cache.d.ts.map +1 -0
  397. package/src/services/vision-embedding-cache.ts +189 -0
  398. package/src/services/voice/VOICE_WORKBENCH.md +88 -0
  399. package/src/services/voice/__test-helpers__/fake-ffi.ts +94 -0
  400. package/src/services/voice/__test-helpers__/synthetic-speech.ts +124 -0
  401. package/src/services/voice/__tests__/checkpoint-manager.test.ts +241 -0
  402. package/src/services/voice/__tests__/checkpoint-policy.test.ts +270 -0
  403. package/src/services/voice/__tests__/eager-context-builder.test.ts +257 -0
  404. package/src/services/voice/__tests__/eliza1-eot-scorer.test.ts +288 -0
  405. package/src/services/voice/__tests__/eot-classifier.test.ts +431 -0
  406. package/src/services/voice/__tests__/optimistic-rollback.test.ts +312 -0
  407. package/src/services/voice/__tests__/prefill-client.test.ts +266 -0
  408. package/src/services/voice/__tests__/prefix-preserving-queue.test.ts +208 -0
  409. package/src/services/voice/__tests__/streaming-asr.test.ts +450 -0
  410. package/src/services/voice/__tests__/streaming-transcriber.test.ts +339 -0
  411. package/src/services/voice/__tests__/turn-detector-resolver.test.ts +195 -0
  412. package/src/services/voice/__tests__/voice-state-machine-prefill.test.ts +275 -0
  413. package/src/services/voice/__tests__/voice-state-machine.test.ts +354 -0
  414. package/src/services/voice/asr-timed.real.test.ts +141 -0
  415. package/src/services/voice/audio-frame-consumer.d.ts +212 -0
  416. package/src/services/voice/audio-frame-consumer.d.ts.map +1 -0
  417. package/src/services/voice/audio-frame-consumer.test.ts +343 -0
  418. package/src/services/voice/audio-frame-consumer.ts +491 -0
  419. package/src/services/voice/barge-in.d.ts +112 -0
  420. package/src/services/voice/barge-in.d.ts.map +1 -0
  421. package/src/services/voice/barge-in.test.ts +244 -0
  422. package/src/services/voice/barge-in.ts +336 -0
  423. package/src/services/voice/cancellation-coordinator.d.ts +127 -0
  424. package/src/services/voice/cancellation-coordinator.d.ts.map +1 -0
  425. package/src/services/voice/cancellation-coordinator.test.ts +196 -0
  426. package/src/services/voice/cancellation-coordinator.ts +269 -0
  427. package/src/services/voice/checkpoint-manager.d.ts +199 -0
  428. package/src/services/voice/checkpoint-manager.d.ts.map +1 -0
  429. package/src/services/voice/checkpoint-manager.ts +401 -0
  430. package/src/services/voice/checkpoint-policy.ts +336 -0
  431. package/src/services/voice/composite-eot-classifier.test.ts +59 -0
  432. package/src/services/voice/e2e-harness.test.ts +182 -0
  433. package/src/services/voice/e2e-harness.ts +743 -0
  434. package/src/services/voice/eager-context-builder.d.ts +170 -0
  435. package/src/services/voice/eager-context-builder.d.ts.map +1 -0
  436. package/src/services/voice/eager-context-builder.ts +262 -0
  437. package/src/services/voice/eliza1-eot-scorer.d.ts +124 -0
  438. package/src/services/voice/eliza1-eot-scorer.d.ts.map +1 -0
  439. package/src/services/voice/eliza1-eot-scorer.ts +242 -0
  440. package/src/services/voice/embedding-server.ts +200 -0
  441. package/src/services/voice/embedding.d.ts +133 -0
  442. package/src/services/voice/embedding.d.ts.map +1 -0
  443. package/src/services/voice/embedding.test.ts +131 -0
  444. package/src/services/voice/embedding.ts +243 -0
  445. package/src/services/voice/emotion-attribution.d.ts +68 -0
  446. package/src/services/voice/emotion-attribution.d.ts.map +1 -0
  447. package/src/services/voice/emotion-attribution.test.ts +129 -0
  448. package/src/services/voice/emotion-attribution.ts +361 -0
  449. package/src/services/voice/engine-bridge-cancellation.test.ts +422 -0
  450. package/src/services/voice/engine-bridge.d.ts +759 -0
  451. package/src/services/voice/engine-bridge.d.ts.map +1 -0
  452. package/src/services/voice/engine-bridge.test.ts +384 -0
  453. package/src/services/voice/engine-bridge.ts +2302 -0
  454. package/src/services/voice/eot-classifier-ggml.d.ts +179 -0
  455. package/src/services/voice/eot-classifier-ggml.d.ts.map +1 -0
  456. package/src/services/voice/eot-classifier-ggml.ts +566 -0
  457. package/src/services/voice/eot-classifier.d.ts +214 -0
  458. package/src/services/voice/eot-classifier.d.ts.map +1 -0
  459. package/src/services/voice/eot-classifier.ts +533 -0
  460. package/src/services/voice/errors.d.ts +20 -0
  461. package/src/services/voice/errors.d.ts.map +1 -0
  462. package/src/services/voice/errors.ts +32 -0
  463. package/src/services/voice/expressive-tags.d.ts +158 -0
  464. package/src/services/voice/expressive-tags.d.ts.map +1 -0
  465. package/src/services/voice/expressive-tags.ts +405 -0
  466. package/src/services/voice/ffi-bindings.d.ts +674 -0
  467. package/src/services/voice/ffi-bindings.d.ts.map +1 -0
  468. package/src/services/voice/ffi-bindings.test.ts +728 -0
  469. package/src/services/voice/ffi-bindings.ts +3225 -0
  470. package/src/services/voice/first-line-cache.d.ts +181 -0
  471. package/src/services/voice/first-line-cache.d.ts.map +1 -0
  472. package/src/services/voice/first-line-cache.ts +725 -0
  473. package/src/services/voice/fused-eot-scorer.d.ts +51 -0
  474. package/src/services/voice/fused-eot-scorer.d.ts.map +1 -0
  475. package/src/services/voice/fused-eot-scorer.ts +135 -0
  476. package/src/services/voice/index.d.ts +91 -0
  477. package/src/services/voice/index.d.ts.map +1 -0
  478. package/src/services/voice/index.ts +481 -0
  479. package/src/services/voice/kokoro/__tests__/kokoro-backend.test.ts +151 -0
  480. package/src/services/voice/kokoro/__tests__/kokoro-engine-bridge.real.test.ts +151 -0
  481. package/src/services/voice/kokoro/__tests__/kokoro-engine-bridge.test.ts +60 -0
  482. package/src/services/voice/kokoro/__tests__/kokoro-engine-discovery.test.ts +277 -0
  483. package/src/services/voice/kokoro/__tests__/kokoro-ffi-runtime.test.ts +235 -0
  484. package/src/services/voice/kokoro/__tests__/kokoro-runtime.test.ts +95 -0
  485. package/src/services/voice/kokoro/__tests__/phonemizer.test.ts +53 -0
  486. package/src/services/voice/kokoro/__tests__/runtime-selection.test.ts +231 -0
  487. package/src/services/voice/kokoro/__tests__/voices.test.ts +57 -0
  488. package/src/services/voice/kokoro/index.ts +79 -0
  489. package/src/services/voice/kokoro/kokoro-backend.d.ts +72 -0
  490. package/src/services/voice/kokoro/kokoro-backend.d.ts.map +1 -0
  491. package/src/services/voice/kokoro/kokoro-backend.ts +207 -0
  492. package/src/services/voice/kokoro/kokoro-engine-discovery.d.ts +58 -0
  493. package/src/services/voice/kokoro/kokoro-engine-discovery.d.ts.map +1 -0
  494. package/src/services/voice/kokoro/kokoro-engine-discovery.ts +177 -0
  495. package/src/services/voice/kokoro/kokoro-ffi-runtime.d.ts +75 -0
  496. package/src/services/voice/kokoro/kokoro-ffi-runtime.d.ts.map +1 -0
  497. package/src/services/voice/kokoro/kokoro-ffi-runtime.ts +233 -0
  498. package/src/services/voice/kokoro/kokoro-runtime.d.ts +100 -0
  499. package/src/services/voice/kokoro/kokoro-runtime.d.ts.map +1 -0
  500. package/src/services/voice/kokoro/kokoro-runtime.ts +170 -0
  501. package/src/services/voice/kokoro/phoneme-stream.ts +123 -0
  502. package/src/services/voice/kokoro/phonemizer.d.ts +50 -0
  503. package/src/services/voice/kokoro/phonemizer.d.ts.map +1 -0
  504. package/src/services/voice/kokoro/phonemizer.ts +344 -0
  505. package/src/services/voice/kokoro/pick-runtime.d.ts +61 -0
  506. package/src/services/voice/kokoro/pick-runtime.d.ts.map +1 -0
  507. package/src/services/voice/kokoro/pick-runtime.test.ts +91 -0
  508. package/src/services/voice/kokoro/pick-runtime.ts +130 -0
  509. package/src/services/voice/kokoro/runtime-selection.d.ts +92 -0
  510. package/src/services/voice/kokoro/runtime-selection.d.ts.map +1 -0
  511. package/src/services/voice/kokoro/runtime-selection.ts +237 -0
  512. package/src/services/voice/kokoro/types.d.ts +82 -0
  513. package/src/services/voice/kokoro/types.d.ts.map +1 -0
  514. package/src/services/voice/kokoro/types.ts +95 -0
  515. package/src/services/voice/kokoro/voice-presets.d.ts +23 -0
  516. package/src/services/voice/kokoro/voice-presets.d.ts.map +1 -0
  517. package/src/services/voice/kokoro/voice-presets.ts +129 -0
  518. package/src/services/voice/kokoro/voices.d.ts +30 -0
  519. package/src/services/voice/kokoro/voices.d.ts.map +1 -0
  520. package/src/services/voice/kokoro/voices.ts +64 -0
  521. package/src/services/voice/lifecycle.d.ts +135 -0
  522. package/src/services/voice/lifecycle.d.ts.map +1 -0
  523. package/src/services/voice/lifecycle.test.ts +315 -0
  524. package/src/services/voice/lifecycle.ts +301 -0
  525. package/src/services/voice/live-diarization-session.d.ts +96 -0
  526. package/src/services/voice/live-diarization-session.d.ts.map +1 -0
  527. package/src/services/voice/live-diarization-session.ts +289 -0
  528. package/src/services/voice/mic-source.d.ts +136 -0
  529. package/src/services/voice/mic-source.d.ts.map +1 -0
  530. package/src/services/voice/mic-source.test.ts +210 -0
  531. package/src/services/voice/mic-source.ts +503 -0
  532. package/src/services/voice/optimistic-policy.d.ts +109 -0
  533. package/src/services/voice/optimistic-policy.d.ts.map +1 -0
  534. package/src/services/voice/optimistic-policy.test.ts +101 -0
  535. package/src/services/voice/optimistic-policy.ts +192 -0
  536. package/src/services/voice/optimistic-rollback.ts +343 -0
  537. package/src/services/voice/partial-stabilizer.d.ts +73 -0
  538. package/src/services/voice/partial-stabilizer.d.ts.map +1 -0
  539. package/src/services/voice/partial-stabilizer.test.ts +68 -0
  540. package/src/services/voice/partial-stabilizer.ts +140 -0
  541. package/src/services/voice/phoneme-tokenizer.d.ts +49 -0
  542. package/src/services/voice/phoneme-tokenizer.d.ts.map +1 -0
  543. package/src/services/voice/phoneme-tokenizer.ts +158 -0
  544. package/src/services/voice/phrase-cache.d.ts +76 -0
  545. package/src/services/voice/phrase-cache.d.ts.map +1 -0
  546. package/src/services/voice/phrase-cache.test.ts +242 -0
  547. package/src/services/voice/phrase-cache.ts +186 -0
  548. package/src/services/voice/phrase-chunker.d.ts +62 -0
  549. package/src/services/voice/phrase-chunker.d.ts.map +1 -0
  550. package/src/services/voice/phrase-chunker.test.ts +239 -0
  551. package/src/services/voice/phrase-chunker.ts +281 -0
  552. package/src/services/voice/pipeline-impls.d.ts +151 -0
  553. package/src/services/voice/pipeline-impls.d.ts.map +1 -0
  554. package/src/services/voice/pipeline-impls.l6.test.ts +110 -0
  555. package/src/services/voice/pipeline-impls.test.ts +292 -0
  556. package/src/services/voice/pipeline-impls.ts +315 -0
  557. package/src/services/voice/pipeline.d.ts +216 -0
  558. package/src/services/voice/pipeline.d.ts.map +1 -0
  559. package/src/services/voice/pipeline.ts +505 -0
  560. package/src/services/voice/prefill-client.d.ts +123 -0
  561. package/src/services/voice/prefill-client.d.ts.map +1 -0
  562. package/src/services/voice/prefill-client.ts +316 -0
  563. package/src/services/voice/prefix-preserving-queue.d.ts +113 -0
  564. package/src/services/voice/prefix-preserving-queue.d.ts.map +1 -0
  565. package/src/services/voice/prefix-preserving-queue.ts +162 -0
  566. package/src/services/voice/profile-store.d.ts +248 -0
  567. package/src/services/voice/profile-store.d.ts.map +1 -0
  568. package/src/services/voice/profile-store.ts +887 -0
  569. package/src/services/voice/real-audio-decode.test.ts +148 -0
  570. package/src/services/voice/ring-buffer.d.ts +40 -0
  571. package/src/services/voice/ring-buffer.d.ts.map +1 -0
  572. package/src/services/voice/ring-buffer.test.ts +129 -0
  573. package/src/services/voice/ring-buffer.ts +123 -0
  574. package/src/services/voice/rollback-queue.d.ts +24 -0
  575. package/src/services/voice/rollback-queue.d.ts.map +1 -0
  576. package/src/services/voice/rollback-queue.ts +74 -0
  577. package/src/services/voice/samantha-preset-placeholder.d.ts +67 -0
  578. package/src/services/voice/samantha-preset-placeholder.d.ts.map +1 -0
  579. package/src/services/voice/samantha-preset-placeholder.test.ts +97 -0
  580. package/src/services/voice/samantha-preset-placeholder.ts +148 -0
  581. package/src/services/voice/samantha-preset-regenerator.d.ts +87 -0
  582. package/src/services/voice/samantha-preset-regenerator.d.ts.map +1 -0
  583. package/src/services/voice/samantha-preset-regenerator.ts +393 -0
  584. package/src/services/voice/scheduler.d.ts +146 -0
  585. package/src/services/voice/scheduler.d.ts.map +1 -0
  586. package/src/services/voice/scheduler.t2.test.ts +141 -0
  587. package/src/services/voice/scheduler.ts +927 -0
  588. package/src/services/voice/shared-resources.d.ts +190 -0
  589. package/src/services/voice/shared-resources.d.ts.map +1 -0
  590. package/src/services/voice/shared-resources.ts +320 -0
  591. package/src/services/voice/speaker/attribution-pipeline.d.ts +74 -0
  592. package/src/services/voice/speaker/attribution-pipeline.d.ts.map +1 -0
  593. package/src/services/voice/speaker/attribution-pipeline.ts +386 -0
  594. package/src/services/voice/speaker/diarizer-fused.d.ts +59 -0
  595. package/src/services/voice/speaker/diarizer-fused.d.ts.map +1 -0
  596. package/src/services/voice/speaker/diarizer-fused.real.test.ts +100 -0
  597. package/src/services/voice/speaker/diarizer-fused.ts +154 -0
  598. package/src/services/voice/speaker/diarizer.d.ts +75 -0
  599. package/src/services/voice/speaker/diarizer.d.ts.map +1 -0
  600. package/src/services/voice/speaker/diarizer.ts +218 -0
  601. package/src/services/voice/speaker/encoder-fused.d.ts +60 -0
  602. package/src/services/voice/speaker/encoder-fused.d.ts.map +1 -0
  603. package/src/services/voice/speaker/encoder-fused.real.test.ts +113 -0
  604. package/src/services/voice/speaker/encoder-fused.ts +138 -0
  605. package/src/services/voice/speaker/encoder-ggml.d.ts +33 -0
  606. package/src/services/voice/speaker/encoder-ggml.d.ts.map +1 -0
  607. package/src/services/voice/speaker/encoder-ggml.ts +79 -0
  608. package/src/services/voice/speaker/encoder.d.ts +37 -0
  609. package/src/services/voice/speaker/encoder.d.ts.map +1 -0
  610. package/src/services/voice/speaker/encoder.ts +105 -0
  611. package/src/services/voice/speaker-imprint.d.ts +83 -0
  612. package/src/services/voice/speaker-imprint.d.ts.map +1 -0
  613. package/src/services/voice/speaker-imprint.test.ts +185 -0
  614. package/src/services/voice/speaker-imprint.ts +312 -0
  615. package/src/services/voice/speaker-preset-cache.d.ts +77 -0
  616. package/src/services/voice/speaker-preset-cache.d.ts.map +1 -0
  617. package/src/services/voice/speaker-preset-cache.test.ts +154 -0
  618. package/src/services/voice/speaker-preset-cache.ts +195 -0
  619. package/src/services/voice/streaming-asr/streaming-pipeline-adapter.ts +292 -0
  620. package/src/services/voice/system-audio-sink.d.ts +73 -0
  621. package/src/services/voice/system-audio-sink.d.ts.map +1 -0
  622. package/src/services/voice/system-audio-sink.test.ts +29 -0
  623. package/src/services/voice/system-audio-sink.ts +366 -0
  624. package/src/services/voice/transcriber.d.ts +244 -0
  625. package/src/services/voice/transcriber.d.ts.map +1 -0
  626. package/src/services/voice/transcriber.test.ts +392 -0
  627. package/src/services/voice/transcriber.ts +704 -0
  628. package/src/services/voice/transcript-knowledge.d.ts +37 -0
  629. package/src/services/voice/transcript-knowledge.d.ts.map +1 -0
  630. package/src/services/voice/transcript-knowledge.test.ts +68 -0
  631. package/src/services/voice/transcript-knowledge.ts +75 -0
  632. package/src/services/voice/transcript-service.d.ts +41 -0
  633. package/src/services/voice/transcript-service.d.ts.map +1 -0
  634. package/src/services/voice/transcript-service.test.ts +137 -0
  635. package/src/services/voice/transcript-service.ts +141 -0
  636. package/src/services/voice/transcript-store.d.ts +53 -0
  637. package/src/services/voice/transcript-store.d.ts.map +1 -0
  638. package/src/services/voice/transcript-store.test.ts +153 -0
  639. package/src/services/voice/transcript-store.ts +132 -0
  640. package/src/services/voice/turn-controller.d.ts +183 -0
  641. package/src/services/voice/turn-controller.d.ts.map +1 -0
  642. package/src/services/voice/turn-controller.test.ts +575 -0
  643. package/src/services/voice/turn-controller.ts +596 -0
  644. package/src/services/voice/types.d.ts +643 -0
  645. package/src/services/voice/types.d.ts.map +1 -0
  646. package/src/services/voice/types.ts +699 -0
  647. package/src/services/voice/vad.d.ts +282 -0
  648. package/src/services/voice/vad.d.ts.map +1 -0
  649. package/src/services/voice/vad.test.ts +480 -0
  650. package/src/services/voice/vad.ts +827 -0
  651. package/src/services/voice/vad.v1-v4.test.ts +222 -0
  652. package/src/services/voice/voice-budget.d.ts +241 -0
  653. package/src/services/voice/voice-budget.d.ts.map +1 -0
  654. package/src/services/voice/voice-budget.test.ts +418 -0
  655. package/src/services/voice/voice-budget.ts +635 -0
  656. package/src/services/voice/voice-duet.test.ts +375 -0
  657. package/src/services/voice/voice-emotion-classifier.d.ts +95 -0
  658. package/src/services/voice/voice-emotion-classifier.d.ts.map +1 -0
  659. package/src/services/voice/voice-emotion-classifier.test.ts +210 -0
  660. package/src/services/voice/voice-emotion-classifier.ts +273 -0
  661. package/src/services/voice/voice-preset-format.d.ts +158 -0
  662. package/src/services/voice/voice-preset-format.d.ts.map +1 -0
  663. package/src/services/voice/voice-preset-format.ts +700 -0
  664. package/src/services/voice/voice-preset-generator.test.ts +89 -0
  665. package/src/services/voice/voice-profile-artifact.d.ts +116 -0
  666. package/src/services/voice/voice-profile-artifact.d.ts.map +1 -0
  667. package/src/services/voice/voice-profile-artifact.test.ts +138 -0
  668. package/src/services/voice/voice-profile-artifact.ts +518 -0
  669. package/src/services/voice/voice-profile-routes.d.ts +83 -0
  670. package/src/services/voice/voice-profile-routes.d.ts.map +1 -0
  671. package/src/services/voice/voice-profile-routes.test.ts +429 -0
  672. package/src/services/voice/voice-profile-routes.ts +425 -0
  673. package/src/services/voice/voice-scenario.ts +154 -0
  674. package/src/services/voice/voice-settings.d.ts +82 -0
  675. package/src/services/voice/voice-settings.d.ts.map +1 -0
  676. package/src/services/voice/voice-settings.ts +172 -0
  677. package/src/services/voice/voice-state-machine.d.ts +364 -0
  678. package/src/services/voice/voice-state-machine.d.ts.map +1 -0
  679. package/src/services/voice/voice-state-machine.ts +727 -0
  680. package/src/services/voice/voice-workbench-report.test.ts +168 -0
  681. package/src/services/voice/voice-workbench-report.ts +326 -0
  682. package/src/services/voice/voice-workbench.test.ts +158 -0
  683. package/src/services/voice/voice.test.ts +1070 -0
  684. package/src/services/voice/wake-word-ggml.d.ts +101 -0
  685. package/src/services/voice/wake-word-ggml.d.ts.map +1 -0
  686. package/src/services/voice/wake-word-ggml.ts +320 -0
  687. package/src/services/voice/wake-word.d.ts +255 -0
  688. package/src/services/voice/wake-word.d.ts.map +1 -0
  689. package/src/services/voice/wake-word.test.ts +298 -0
  690. package/src/services/voice/wake-word.ts +554 -0
  691. package/src/services/voice/wrap-with-first-line-cache.d.ts +70 -0
  692. package/src/services/voice/wrap-with-first-line-cache.d.ts.map +1 -0
  693. package/src/services/voice/wrap-with-first-line-cache.ts +267 -0
  694. package/src/services/voice-model-updater.d.ts +240 -0
  695. package/src/services/voice-model-updater.d.ts.map +1 -0
  696. package/src/services/voice-model-updater.ts +724 -0
  697. package/src/services/voice-prewarm.d.ts +3 -0
  698. package/src/services/voice-prewarm.d.ts.map +1 -0
  699. package/src/services/voice-prewarm.ts +51 -0
  700. package/dist/index.d.ts +0 -37
  701. 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
+ }