@pipedog/core 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (698) hide show
  1. package/LICENSE +25 -0
  2. package/NOTICE +9 -0
  3. package/dist/adapters/base_llm_adapter.d.ts +266 -0
  4. package/dist/adapters/base_llm_adapter.js +489 -0
  5. package/dist/adapters/index.d.ts +2 -0
  6. package/dist/adapters/index.js +8 -0
  7. package/dist/adapters/schemas/direct_function.d.ts +202 -0
  8. package/dist/adapters/schemas/direct_function.js +302 -0
  9. package/dist/adapters/schemas/function_schema.d.ts +67 -0
  10. package/dist/adapters/schemas/function_schema.js +72 -0
  11. package/dist/adapters/schemas/index.d.ts +2 -0
  12. package/dist/adapters/schemas/index.js +8 -0
  13. package/dist/adapters/schemas/tools_schema.d.ts +57 -0
  14. package/dist/adapters/schemas/tools_schema.js +79 -0
  15. package/dist/adapters/services/index.d.ts +2 -0
  16. package/dist/adapters/services/index.js +8 -0
  17. package/dist/adapters/services/open_ai_adapter.d.ts +147 -0
  18. package/dist/adapters/services/open_ai_adapter.js +250 -0
  19. package/dist/audio/dtmf/dtmf_data.d.ts +3 -0
  20. package/dist/audio/dtmf/dtmf_data.js +22 -0
  21. package/dist/audio/dtmf/index.d.ts +2 -0
  22. package/dist/audio/dtmf/index.js +8 -0
  23. package/dist/audio/dtmf/types.d.ts +31 -0
  24. package/dist/audio/dtmf/types.js +35 -0
  25. package/dist/audio/dtmf/utils.d.ts +13 -0
  26. package/dist/audio/dtmf/utils.js +62 -0
  27. package/dist/audio/filters/base_audio_filter.d.ts +51 -0
  28. package/dist/audio/filters/base_audio_filter.js +17 -0
  29. package/dist/audio/filters/index.d.ts +2 -0
  30. package/dist/audio/filters/index.js +8 -0
  31. package/dist/audio/index.d.ts +2 -0
  32. package/dist/audio/index.js +8 -0
  33. package/dist/audio/krisp_instance.d.ts +95 -0
  34. package/dist/audio/krisp_instance.js +229 -0
  35. package/dist/audio/mixers/base_audio_mixer.d.ts +56 -0
  36. package/dist/audio/mixers/base_audio_mixer.js +16 -0
  37. package/dist/audio/mixers/index.d.ts +2 -0
  38. package/dist/audio/mixers/index.js +8 -0
  39. package/dist/audio/resamplers/base_audio_resampler.d.ts +46 -0
  40. package/dist/audio/resamplers/base_audio_resampler.js +35 -0
  41. package/dist/audio/resamplers/index.d.ts +2 -0
  42. package/dist/audio/resamplers/index.js +8 -0
  43. package/dist/audio/resamplers/resampy_resampler.d.ts +10 -0
  44. package/dist/audio/resamplers/resampy_resampler.js +15 -0
  45. package/dist/audio/resamplers/soxr_resampler.d.ts +54 -0
  46. package/dist/audio/resamplers/soxr_resampler.js +69 -0
  47. package/dist/audio/resamplers/soxr_stream_resampler.d.ts +75 -0
  48. package/dist/audio/resamplers/soxr_stream_resampler.js +146 -0
  49. package/dist/audio/turn/base_turn_analyzer.d.ts +84 -0
  50. package/dist/audio/turn/base_turn_analyzer.js +61 -0
  51. package/dist/audio/turn/index.d.ts +2 -0
  52. package/dist/audio/turn/index.js +8 -0
  53. package/dist/audio/turn/smart_turn/_whisper_features.d.ts +30 -0
  54. package/dist/audio/turn/smart_turn/_whisper_features.js +225 -0
  55. package/dist/audio/turn/smart_turn/base_smart_turn.d.ts +94 -0
  56. package/dist/audio/turn/smart_turn/base_smart_turn.js +239 -0
  57. package/dist/audio/turn/smart_turn/local_smart_turn_v3.d.ts +76 -0
  58. package/dist/audio/turn/smart_turn/local_smart_turn_v3.js +143 -0
  59. package/dist/audio/utils.d.ts +224 -0
  60. package/dist/audio/utils.js +634 -0
  61. package/dist/audio/vad/data/index.d.ts +2 -0
  62. package/dist/audio/vad/data/index.js +8 -0
  63. package/dist/audio/vad/index.d.ts +2 -0
  64. package/dist/audio/vad/index.js +8 -0
  65. package/dist/audio/vad/vad_analyzer.d.ts +126 -0
  66. package/dist/audio/vad/vad_analyzer.js +211 -0
  67. package/dist/audio/vad/vad_controller.d.ts +143 -0
  68. package/dist/audio/vad/vad_controller.js +228 -0
  69. package/dist/audio/volume.d.ts +38 -0
  70. package/dist/audio/volume.js +75 -0
  71. package/dist/bus/adapters/base.d.ts +35 -0
  72. package/dist/bus/adapters/base.js +19 -0
  73. package/dist/bus/adapters/index.d.ts +10 -0
  74. package/dist/bus/adapters/index.js +15 -0
  75. package/dist/bus/adapters/llm_context_adapter.d.ts +36 -0
  76. package/dist/bus/adapters/llm_context_adapter.js +82 -0
  77. package/dist/bus/adapters/tools_schema_adapter.d.ts +25 -0
  78. package/dist/bus/adapters/tools_schema_adapter.js +70 -0
  79. package/dist/bus/bridge_processor.d.ts +111 -0
  80. package/dist/bus/bridge_processor.js +166 -0
  81. package/dist/bus/bus.d.ts +110 -0
  82. package/dist/bus/bus.js +200 -0
  83. package/dist/bus/index.d.ts +22 -0
  84. package/dist/bus/index.js +27 -0
  85. package/dist/bus/local/async_queue.d.ts +12 -0
  86. package/dist/bus/local/async_queue.js +21 -0
  87. package/dist/bus/local/index.d.ts +3 -0
  88. package/dist/bus/local/index.js +8 -0
  89. package/dist/bus/messages.d.ts +378 -0
  90. package/dist/bus/messages.js +357 -0
  91. package/dist/bus/network/index.d.ts +16 -0
  92. package/dist/bus/network/index.js +21 -0
  93. package/dist/bus/queue.d.ts +27 -0
  94. package/dist/bus/queue.js +51 -0
  95. package/dist/bus/serializers/base.d.ts +25 -0
  96. package/dist/bus/serializers/base.js +14 -0
  97. package/dist/bus/serializers/index.d.ts +9 -0
  98. package/dist/bus/serializers/index.js +14 -0
  99. package/dist/bus/serializers/json.d.ts +77 -0
  100. package/dist/bus/serializers/json.js +367 -0
  101. package/dist/bus/subscriber.d.ts +46 -0
  102. package/dist/bus/subscriber.js +27 -0
  103. package/dist/bus/ui/index.d.ts +3 -0
  104. package/dist/bus/ui/index.js +8 -0
  105. package/dist/bus/ui/messages.d.ts +186 -0
  106. package/dist/bus/ui/messages.js +171 -0
  107. package/dist/classifiers/base_classifier.d.ts +263 -0
  108. package/dist/classifiers/base_classifier.js +346 -0
  109. package/dist/classifiers/cloudflare/clef/classifier.d.ts +70 -0
  110. package/dist/classifiers/cloudflare/clef/classifier.js +204 -0
  111. package/dist/classifiers/cloudflare/clef/client.d.ts +104 -0
  112. package/dist/classifiers/cloudflare/clef/client.js +234 -0
  113. package/dist/classifiers/cloudflare/clef/index.d.ts +2 -0
  114. package/dist/classifiers/cloudflare/clef/index.js +8 -0
  115. package/dist/classifiers/cloudflare/index.d.ts +2 -0
  116. package/dist/classifiers/cloudflare/index.js +8 -0
  117. package/dist/classifiers/http_client.d.ts +70 -0
  118. package/dist/classifiers/http_client.js +161 -0
  119. package/dist/classifiers/index.d.ts +2 -0
  120. package/dist/classifiers/index.js +8 -0
  121. package/dist/classifiers/jev/classifier.d.ts +8 -0
  122. package/dist/classifiers/jev/classifier.js +13 -0
  123. package/dist/classifiers/jev/client.d.ts +8 -0
  124. package/dist/classifiers/jev/client.js +13 -0
  125. package/dist/classifiers/jev/index.d.ts +2 -0
  126. package/dist/classifiers/jev/index.js +8 -0
  127. package/dist/classifiers/llm/classifier.d.ts +72 -0
  128. package/dist/classifiers/llm/classifier.js +304 -0
  129. package/dist/classifiers/llm/index.d.ts +2 -0
  130. package/dist/classifiers/llm/index.js +8 -0
  131. package/dist/classifiers/openai/decisions/classifier.d.ts +77 -0
  132. package/dist/classifiers/openai/decisions/classifier.js +241 -0
  133. package/dist/classifiers/openai/decisions/client.d.ts +108 -0
  134. package/dist/classifiers/openai/decisions/client.js +234 -0
  135. package/dist/classifiers/openai/decisions/index.d.ts +2 -0
  136. package/dist/classifiers/openai/decisions/index.js +8 -0
  137. package/dist/classifiers/openai/index.d.ts +2 -0
  138. package/dist/classifiers/openai/index.js +8 -0
  139. package/dist/classifiers/typesafe/index.d.ts +2 -0
  140. package/dist/classifiers/typesafe/index.js +8 -0
  141. package/dist/classifiers/typesafe/jev/classifier.d.ts +70 -0
  142. package/dist/classifiers/typesafe/jev/classifier.js +220 -0
  143. package/dist/classifiers/typesafe/jev/client.d.ts +105 -0
  144. package/dist/classifiers/typesafe/jev/client.js +233 -0
  145. package/dist/classifiers/typesafe/jev/index.d.ts +2 -0
  146. package/dist/classifiers/typesafe/jev/index.js +8 -0
  147. package/dist/clocks/base_clock.d.ts +13 -0
  148. package/dist/clocks/base_clock.js +14 -0
  149. package/dist/clocks/index.d.ts +2 -0
  150. package/dist/clocks/index.js +8 -0
  151. package/dist/clocks/system_clock.d.ts +15 -0
  152. package/dist/clocks/system_clock.js +25 -0
  153. package/dist/frames/frames.d.ts +2533 -0
  154. package/dist/frames/frames.js +2485 -0
  155. package/dist/frames/index.d.ts +2 -0
  156. package/dist/frames/index.js +8 -0
  157. package/dist/frames/protobufs/frames_pb2.d.ts +229 -0
  158. package/dist/frames/protobufs/frames_pb2.js +466 -0
  159. package/dist/index.d.ts +11 -0
  160. package/dist/index.js +56 -0
  161. package/dist/metrics/index.d.ts +2 -0
  162. package/dist/metrics/index.js +8 -0
  163. package/dist/metrics/metrics.d.ts +276 -0
  164. package/dist/metrics/metrics.js +254 -0
  165. package/dist/observers/base_observer.d.ts +173 -0
  166. package/dist/observers/base_observer.js +151 -0
  167. package/dist/observers/error_observer.d.ts +75 -0
  168. package/dist/observers/error_observer.js +87 -0
  169. package/dist/observers/function_call_observer.d.ts +157 -0
  170. package/dist/observers/function_call_observer.js +222 -0
  171. package/dist/observers/index.d.ts +2 -0
  172. package/dist/observers/index.js +8 -0
  173. package/dist/observers/loggers/debug_log_observer.d.ts +91 -0
  174. package/dist/observers/loggers/debug_log_observer.js +228 -0
  175. package/dist/observers/loggers/index.d.ts +2 -0
  176. package/dist/observers/loggers/index.js +8 -0
  177. package/dist/observers/loggers/llm_log_observer.d.ts +24 -0
  178. package/dist/observers/loggers/llm_log_observer.js +110 -0
  179. package/dist/observers/loggers/metrics_log_observer.d.ts +85 -0
  180. package/dist/observers/loggers/metrics_log_observer.js +186 -0
  181. package/dist/observers/loggers/transcription_log_observer.d.ts +23 -0
  182. package/dist/observers/loggers/transcription_log_observer.js +74 -0
  183. package/dist/observers/service_metrics_observer.d.ts +168 -0
  184. package/dist/observers/service_metrics_observer.js +236 -0
  185. package/dist/observers/speaking_observer.d.ts +104 -0
  186. package/dist/observers/speaking_observer.js +150 -0
  187. package/dist/observers/startup_timing_observer.d.ts +221 -0
  188. package/dist/observers/startup_timing_observer.js +365 -0
  189. package/dist/observers/turn_tracking_observer.d.ts +78 -0
  190. package/dist/observers/turn_tracking_observer.js +189 -0
  191. package/dist/observers/user_bot_latency_observer.d.ts +411 -0
  192. package/dist/observers/user_bot_latency_observer.js +974 -0
  193. package/dist/pipeline/base_pipeline.d.ts +49 -0
  194. package/dist/pipeline/base_pipeline.js +101 -0
  195. package/dist/pipeline/capabilities.d.ts +76 -0
  196. package/dist/pipeline/capabilities.js +97 -0
  197. package/dist/pipeline/index.d.ts +2 -0
  198. package/dist/pipeline/index.js +8 -0
  199. package/dist/pipeline/job_context.d.ts +317 -0
  200. package/dist/pipeline/job_context.js +450 -0
  201. package/dist/pipeline/job_decorator.d.ts +54 -0
  202. package/dist/pipeline/job_decorator.js +92 -0
  203. package/dist/pipeline/llm_switcher.d.ts +84 -0
  204. package/dist/pipeline/llm_switcher.js +107 -0
  205. package/dist/pipeline/llm_with_backend.d.ts +435 -0
  206. package/dist/pipeline/llm_with_backend.js +806 -0
  207. package/dist/pipeline/parallel_pipeline.d.ts +83 -0
  208. package/dist/pipeline/parallel_pipeline.js +200 -0
  209. package/dist/pipeline/pipeline.d.ts +126 -0
  210. package/dist/pipeline/pipeline.js +196 -0
  211. package/dist/pipeline/service_switcher.d.ts +287 -0
  212. package/dist/pipeline/service_switcher.js +509 -0
  213. package/dist/pipeline/sync_parallel_pipeline.d.ts +192 -0
  214. package/dist/pipeline/sync_parallel_pipeline.js +387 -0
  215. package/dist/pipeline/worker.d.ts +814 -0
  216. package/dist/pipeline/worker.js +1639 -0
  217. package/dist/pipeline/worker_observer.d.ts +131 -0
  218. package/dist/pipeline/worker_observer.js +274 -0
  219. package/dist/pipeline/worker_ready_decorator.d.ts +45 -0
  220. package/dist/pipeline/worker_ready_decorator.js +76 -0
  221. package/dist/processors/aggregators/async_tool_messages.d.ts +146 -0
  222. package/dist/processors/aggregators/async_tool_messages.js +291 -0
  223. package/dist/processors/aggregators/dtmf_aggregator.d.ts +61 -0
  224. package/dist/processors/aggregators/dtmf_aggregator.js +144 -0
  225. package/dist/processors/aggregators/gated.d.ts +43 -0
  226. package/dist/processors/aggregators/gated.js +86 -0
  227. package/dist/processors/aggregators/gated_llm_context.d.ts +42 -0
  228. package/dist/processors/aggregators/gated_llm_context.js +86 -0
  229. package/dist/processors/aggregators/index.d.ts +2 -0
  230. package/dist/processors/aggregators/index.js +8 -0
  231. package/dist/processors/aggregators/llm_context.d.ts +370 -0
  232. package/dist/processors/aggregators/llm_context.js +580 -0
  233. package/dist/processors/aggregators/llm_context_summarizer.d.ts +219 -0
  234. package/dist/processors/aggregators/llm_context_summarizer.js +447 -0
  235. package/dist/processors/aggregators/llm_response.d.ts +37 -0
  236. package/dist/processors/aggregators/llm_response.js +71 -0
  237. package/dist/processors/aggregators/llm_response_universal.d.ts +966 -0
  238. package/dist/processors/aggregators/llm_response_universal.js +2394 -0
  239. package/dist/processors/aggregators/llm_text_processor.d.ts +53 -0
  240. package/dist/processors/aggregators/llm_text_processor.js +102 -0
  241. package/dist/processors/aggregators/sentence.d.ts +39 -0
  242. package/dist/processors/aggregators/sentence.js +69 -0
  243. package/dist/processors/async_generator.d.ts +42 -0
  244. package/dist/processors/async_generator.js +76 -0
  245. package/dist/processors/audio/audio_buffer_processor.d.ts +241 -0
  246. package/dist/processors/audio/audio_buffer_processor.js +565 -0
  247. package/dist/processors/audio/index.d.ts +2 -0
  248. package/dist/processors/audio/index.js +8 -0
  249. package/dist/processors/audio/vad_processor.d.ts +67 -0
  250. package/dist/processors/audio/vad_processor.js +103 -0
  251. package/dist/processors/consumer_processor.d.ts +49 -0
  252. package/dist/processors/consumer_processor.js +85 -0
  253. package/dist/processors/filters/frame_filter.d.ts +39 -0
  254. package/dist/processors/filters/frame_filter.js +52 -0
  255. package/dist/processors/filters/function_filter.d.ts +59 -0
  256. package/dist/processors/filters/function_filter.js +78 -0
  257. package/dist/processors/filters/identity_filter.d.ts +33 -0
  258. package/dist/processors/filters/identity_filter.js +37 -0
  259. package/dist/processors/filters/index.d.ts +2 -0
  260. package/dist/processors/filters/index.js +8 -0
  261. package/dist/processors/filters/null_filter.d.ts +34 -0
  262. package/dist/processors/filters/null_filter.js +47 -0
  263. package/dist/processors/filters/wake_notifier_filter.d.ts +48 -0
  264. package/dist/processors/filters/wake_notifier_filter.js +49 -0
  265. package/dist/processors/frame_processor.d.ts +360 -0
  266. package/dist/processors/frame_processor.js +932 -0
  267. package/dist/processors/frameworks/index.d.ts +2 -0
  268. package/dist/processors/frameworks/index.js +8 -0
  269. package/dist/processors/frameworks/rtvi/frames.d.ts +241 -0
  270. package/dist/processors/frameworks/rtvi/frames.js +259 -0
  271. package/dist/processors/frameworks/rtvi/index.d.ts +6 -0
  272. package/dist/processors/frameworks/rtvi/index.js +11 -0
  273. package/dist/processors/frameworks/rtvi/models.d.ts +1741 -0
  274. package/dist/processors/frameworks/rtvi/models.js +1807 -0
  275. package/dist/processors/frameworks/rtvi/observer.d.ts +340 -0
  276. package/dist/processors/frameworks/rtvi/observer.js +1113 -0
  277. package/dist/processors/frameworks/rtvi/processor.d.ts +182 -0
  278. package/dist/processors/frameworks/rtvi/processor.js +676 -0
  279. package/dist/processors/idle_frame_processor.d.ts +44 -0
  280. package/dist/processors/idle_frame_processor.js +86 -0
  281. package/dist/processors/index.d.ts +2 -0
  282. package/dist/processors/index.js +8 -0
  283. package/dist/processors/logger.d.ts +42 -0
  284. package/dist/processors/logger.js +87 -0
  285. package/dist/processors/metrics/frame_processor_metrics.d.ts +172 -0
  286. package/dist/processors/metrics/frame_processor_metrics.js +372 -0
  287. package/dist/processors/metrics/index.d.ts +2 -0
  288. package/dist/processors/metrics/index.js +8 -0
  289. package/dist/processors/producer_processor.d.ts +68 -0
  290. package/dist/processors/producer_processor.js +78 -0
  291. package/dist/processors/text_transformer.d.ts +30 -0
  292. package/dist/processors/text_transformer.js +48 -0
  293. package/dist/registry/index.d.ts +3 -0
  294. package/dist/registry/index.js +8 -0
  295. package/dist/registry/registry.d.ts +69 -0
  296. package/dist/registry/registry.js +149 -0
  297. package/dist/registry/types.d.ts +47 -0
  298. package/dist/registry/types.js +39 -0
  299. package/dist/serializers/base_serializer.d.ts +94 -0
  300. package/dist/serializers/base_serializer.js +82 -0
  301. package/dist/serializers/index.d.ts +2 -0
  302. package/dist/serializers/index.js +8 -0
  303. package/dist/services/ai_service.d.ts +145 -0
  304. package/dist/services/ai_service.js +262 -0
  305. package/dist/services/anthropic/index.d.ts +2 -0
  306. package/dist/services/anthropic/index.js +7 -0
  307. package/dist/services/assemblyai/index.d.ts +2 -0
  308. package/dist/services/assemblyai/index.js +7 -0
  309. package/dist/services/asyncai/index.d.ts +2 -0
  310. package/dist/services/asyncai/index.js +7 -0
  311. package/dist/services/aws/index.d.ts +2 -0
  312. package/dist/services/aws/index.js +7 -0
  313. package/dist/services/azure/index.d.ts +2 -0
  314. package/dist/services/azure/index.js +7 -0
  315. package/dist/services/baseten/index.d.ts +2 -0
  316. package/dist/services/baseten/index.js +7 -0
  317. package/dist/services/bland/index.d.ts +2 -0
  318. package/dist/services/bland/index.js +7 -0
  319. package/dist/services/camb/index.d.ts +2 -0
  320. package/dist/services/camb/index.js +7 -0
  321. package/dist/services/cartesia/index.d.ts +2 -0
  322. package/dist/services/cartesia/index.js +7 -0
  323. package/dist/services/cerebras/index.d.ts +2 -0
  324. package/dist/services/cerebras/index.js +7 -0
  325. package/dist/services/crusoe/index.d.ts +2 -0
  326. package/dist/services/crusoe/index.js +7 -0
  327. package/dist/services/deepgram/index.d.ts +2 -0
  328. package/dist/services/deepgram/index.js +7 -0
  329. package/dist/services/deepseek/index.d.ts +2 -0
  330. package/dist/services/deepseek/index.js +7 -0
  331. package/dist/services/elevenlabs/index.d.ts +2 -0
  332. package/dist/services/elevenlabs/index.js +7 -0
  333. package/dist/services/fal/index.d.ts +2 -0
  334. package/dist/services/fal/index.js +7 -0
  335. package/dist/services/fireworks/index.d.ts +2 -0
  336. package/dist/services/fireworks/index.js +7 -0
  337. package/dist/services/fish/index.d.ts +2 -0
  338. package/dist/services/fish/index.js +7 -0
  339. package/dist/services/funasr/index.d.ts +3 -0
  340. package/dist/services/funasr/index.js +8 -0
  341. package/dist/services/gladia/index.d.ts +2 -0
  342. package/dist/services/gladia/index.js +7 -0
  343. package/dist/services/google/index.d.ts +2 -0
  344. package/dist/services/google/index.js +7 -0
  345. package/dist/services/gradium/index.d.ts +2 -0
  346. package/dist/services/gradium/index.js +7 -0
  347. package/dist/services/grok/index.d.ts +2 -0
  348. package/dist/services/grok/index.js +7 -0
  349. package/dist/services/groq/index.d.ts +2 -0
  350. package/dist/services/groq/index.js +7 -0
  351. package/dist/services/heygen/index.d.ts +2 -0
  352. package/dist/services/heygen/index.js +7 -0
  353. package/dist/services/hume/index.d.ts +2 -0
  354. package/dist/services/hume/index.js +7 -0
  355. package/dist/services/image_service.d.ts +52 -0
  356. package/dist/services/image_service.js +60 -0
  357. package/dist/services/inception/index.d.ts +2 -0
  358. package/dist/services/inception/index.js +7 -0
  359. package/dist/services/index.d.ts +2 -0
  360. package/dist/services/index.js +8 -0
  361. package/dist/services/inworld/index.d.ts +2 -0
  362. package/dist/services/inworld/index.js +7 -0
  363. package/dist/services/keenable/index.d.ts +2 -0
  364. package/dist/services/keenable/index.js +7 -0
  365. package/dist/services/kokoro/index.d.ts +2 -0
  366. package/dist/services/kokoro/index.js +7 -0
  367. package/dist/services/llm_service.d.ts +1106 -0
  368. package/dist/services/llm_service.js +2231 -0
  369. package/dist/services/mem0/index.d.ts +2 -0
  370. package/dist/services/mem0/index.js +7 -0
  371. package/dist/services/meta/index.d.ts +2 -0
  372. package/dist/services/meta/index.js +7 -0
  373. package/dist/services/minimax/index.d.ts +2 -0
  374. package/dist/services/minimax/index.js +7 -0
  375. package/dist/services/mistral/index.d.ts +2 -0
  376. package/dist/services/mistral/index.js +7 -0
  377. package/dist/services/moondream/index.d.ts +2 -0
  378. package/dist/services/moondream/index.js +7 -0
  379. package/dist/services/moonshine/index.d.ts +2 -0
  380. package/dist/services/moonshine/index.js +7 -0
  381. package/dist/services/nebius/index.d.ts +2 -0
  382. package/dist/services/nebius/index.js +7 -0
  383. package/dist/services/neuphonic/index.d.ts +2 -0
  384. package/dist/services/neuphonic/index.js +7 -0
  385. package/dist/services/novita/index.d.ts +2 -0
  386. package/dist/services/novita/index.js +7 -0
  387. package/dist/services/nvidia/index.d.ts +2 -0
  388. package/dist/services/nvidia/index.js +11 -0
  389. package/dist/services/ollama/index.d.ts +2 -0
  390. package/dist/services/ollama/index.js +7 -0
  391. package/dist/services/openai/index.d.ts +2 -0
  392. package/dist/services/openai/index.js +7 -0
  393. package/dist/services/openclaw/index.d.ts +14 -0
  394. package/dist/services/openclaw/index.js +19 -0
  395. package/dist/services/openrouter/index.d.ts +2 -0
  396. package/dist/services/openrouter/index.js +7 -0
  397. package/dist/services/perplexity/index.d.ts +2 -0
  398. package/dist/services/perplexity/index.js +7 -0
  399. package/dist/services/piper/index.d.ts +2 -0
  400. package/dist/services/piper/index.js +7 -0
  401. package/dist/services/pocket_tts/index.d.ts +2 -0
  402. package/dist/services/pocket_tts/index.js +7 -0
  403. package/dist/services/qwen/index.d.ts +2 -0
  404. package/dist/services/qwen/index.js +7 -0
  405. package/dist/services/resembleai/index.d.ts +2 -0
  406. package/dist/services/resembleai/index.js +7 -0
  407. package/dist/services/rime/index.d.ts +2 -0
  408. package/dist/services/rime/index.js +7 -0
  409. package/dist/services/sambanova/index.d.ts +2 -0
  410. package/dist/services/sambanova/index.js +7 -0
  411. package/dist/services/sarvam/index.d.ts +2 -0
  412. package/dist/services/sarvam/index.js +7 -0
  413. package/dist/services/settings.d.ts +356 -0
  414. package/dist/services/settings.js +437 -0
  415. package/dist/services/simli/index.d.ts +2 -0
  416. package/dist/services/simli/index.js +7 -0
  417. package/dist/services/smallest/index.d.ts +2 -0
  418. package/dist/services/smallest/index.js +7 -0
  419. package/dist/services/soniox/index.d.ts +2 -0
  420. package/dist/services/soniox/index.js +7 -0
  421. package/dist/services/speechify/index.d.ts +2 -0
  422. package/dist/services/speechify/index.js +7 -0
  423. package/dist/services/speechmatics/index.d.ts +2 -0
  424. package/dist/services/speechmatics/index.js +7 -0
  425. package/dist/services/stt_latency.d.ts +62 -0
  426. package/dist/services/stt_latency.js +71 -0
  427. package/dist/services/stt_service.d.ts +601 -0
  428. package/dist/services/stt_service.js +1044 -0
  429. package/dist/services/tavus/index.d.ts +2 -0
  430. package/dist/services/tavus/index.js +7 -0
  431. package/dist/services/together/index.d.ts +2 -0
  432. package/dist/services/together/index.js +7 -0
  433. package/dist/services/tts_service.d.ts +909 -0
  434. package/dist/services/tts_service.js +2018 -0
  435. package/dist/services/ultravox/index.d.ts +2 -0
  436. package/dist/services/ultravox/index.js +9 -0
  437. package/dist/services/vision_service.d.ts +56 -0
  438. package/dist/services/vision_service.js +62 -0
  439. package/dist/services/websocket_service.d.ts +213 -0
  440. package/dist/services/websocket_service.js +654 -0
  441. package/dist/services/whisper/index.d.ts +2 -0
  442. package/dist/services/whisper/index.js +9 -0
  443. package/dist/services/xai/index.d.ts +2 -0
  444. package/dist/services/xai/index.js +9 -0
  445. package/dist/services/xtts/index.d.ts +2 -0
  446. package/dist/services/xtts/index.js +9 -0
  447. package/dist/tests/index.d.ts +2 -0
  448. package/dist/tests/index.js +8 -0
  449. package/dist/tests/utils.d.ts +123 -0
  450. package/dist/tests/utils.js +212 -0
  451. package/dist/transcriptions/index.d.ts +2 -0
  452. package/dist/transcriptions/index.js +8 -0
  453. package/dist/transcriptions/language.d.ts +376 -0
  454. package/dist/transcriptions/language.js +543 -0
  455. package/dist/transports/base_input.d.ts +149 -0
  456. package/dist/transports/base_input.js +338 -0
  457. package/dist/transports/base_output.d.ts +432 -0
  458. package/dist/transports/base_output.js +1231 -0
  459. package/dist/transports/base_transport.d.ts +223 -0
  460. package/dist/transports/base_transport.js +167 -0
  461. package/dist/transports/daily/index.d.ts +2 -0
  462. package/dist/transports/daily/index.js +9 -0
  463. package/dist/transports/heygen/index.d.ts +2 -0
  464. package/dist/transports/heygen/index.js +9 -0
  465. package/dist/transports/index.d.ts +2 -0
  466. package/dist/transports/index.js +8 -0
  467. package/dist/transports/lemonslice/index.d.ts +2 -0
  468. package/dist/transports/lemonslice/index.js +9 -0
  469. package/dist/transports/livekit/index.d.ts +2 -0
  470. package/dist/transports/livekit/index.js +9 -0
  471. package/dist/transports/local/index.d.ts +2 -0
  472. package/dist/transports/local/index.js +9 -0
  473. package/dist/transports/moq/index.d.ts +2 -0
  474. package/dist/transports/moq/index.js +9 -0
  475. package/dist/transports/smallwebrtc/index.d.ts +2 -0
  476. package/dist/transports/smallwebrtc/index.js +9 -0
  477. package/dist/transports/tavus/index.d.ts +2 -0
  478. package/dist/transports/tavus/index.js +9 -0
  479. package/dist/transports/video_in_sampler.d.ts +157 -0
  480. package/dist/transports/video_in_sampler.js +258 -0
  481. package/dist/transports/vonage/index.d.ts +2 -0
  482. package/dist/transports/vonage/index.js +9 -0
  483. package/dist/transports/websocket/index.d.ts +2 -0
  484. package/dist/transports/websocket/index.js +9 -0
  485. package/dist/transports/whatsapp/index.d.ts +2 -0
  486. package/dist/transports/whatsapp/index.js +9 -0
  487. package/dist/turns/eager_end_of_turn_mixin.d.ts +70 -0
  488. package/dist/turns/eager_end_of_turn_mixin.js +122 -0
  489. package/dist/turns/empty_user_turn.d.ts +55 -0
  490. package/dist/turns/empty_user_turn.js +53 -0
  491. package/dist/turns/index.d.ts +2 -0
  492. package/dist/turns/index.js +8 -0
  493. package/dist/turns/speculation_gate.d.ts +169 -0
  494. package/dist/turns/speculation_gate.js +294 -0
  495. package/dist/turns/types.d.ts +33 -0
  496. package/dist/turns/types.js +35 -0
  497. package/dist/turns/user_idle_controller.d.ts +94 -0
  498. package/dist/turns/user_idle_controller.js +187 -0
  499. package/dist/turns/user_mute/always_user_mute_strategy.d.ts +15 -0
  500. package/dist/turns/user_mute/always_user_mute_strategy.js +29 -0
  501. package/dist/turns/user_mute/base_user_mute_strategy.d.ts +39 -0
  502. package/dist/turns/user_mute/base_user_mute_strategy.js +45 -0
  503. package/dist/turns/user_mute/first_speech_user_mute_strategy.d.ts +28 -0
  504. package/dist/turns/user_mute/first_speech_user_mute_strategy.js +52 -0
  505. package/dist/turns/user_mute/function_call_user_mute_strategy.d.ts +25 -0
  506. package/dist/turns/user_mute/function_call_user_mute_strategy.js +50 -0
  507. package/dist/turns/user_mute/index.d.ts +6 -0
  508. package/dist/turns/user_mute/index.js +11 -0
  509. package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.d.ts +43 -0
  510. package/dist/turns/user_mute/mute_until_first_bot_complete_user_mute_strategy.js +76 -0
  511. package/dist/turns/user_start/base_user_turn_start_strategy.d.ts +135 -0
  512. package/dist/turns/user_start/base_user_turn_start_strategy.js +134 -0
  513. package/dist/turns/user_start/external_user_turn_start_strategy.d.ts +45 -0
  514. package/dist/turns/user_start/external_user_turn_start_strategy.js +64 -0
  515. package/dist/turns/user_start/index.d.ts +8 -0
  516. package/dist/turns/user_start/index.js +16 -0
  517. package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.d.ts +133 -0
  518. package/dist/turns/user_start/krisp_viva_ip_user_turn_start_strategy.js +243 -0
  519. package/dist/turns/user_start/min_words_user_turn_start_strategy.d.ts +60 -0
  520. package/dist/turns/user_start/min_words_user_turn_start_strategy.js +102 -0
  521. package/dist/turns/user_start/transcription_user_turn_start_strategy.d.ts +29 -0
  522. package/dist/turns/user_start/transcription_user_turn_start_strategy.js +43 -0
  523. package/dist/turns/user_start/vad_user_turn_start_strategy.d.ts +20 -0
  524. package/dist/turns/user_start/vad_user_turn_start_strategy.js +31 -0
  525. package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.d.ts +141 -0
  526. package/dist/turns/user_start/wake_phrase_user_turn_start_strategy.js +238 -0
  527. package/dist/turns/user_stop/base_user_turn_stop_strategy.d.ts +174 -0
  528. package/dist/turns/user_stop/base_user_turn_stop_strategy.js +170 -0
  529. package/dist/turns/user_stop/deferred_user_turn_stop_strategy.d.ts +81 -0
  530. package/dist/turns/user_stop/deferred_user_turn_stop_strategy.js +107 -0
  531. package/dist/turns/user_stop/eager_match_policy.d.ts +36 -0
  532. package/dist/turns/user_stop/eager_match_policy.js +50 -0
  533. package/dist/turns/user_stop/eager_user_turn_stop_strategy.d.ts +84 -0
  534. package/dist/turns/user_stop/eager_user_turn_stop_strategy.js +154 -0
  535. package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.d.ts +45 -0
  536. package/dist/turns/user_stop/external_user_turn_completion_stop_strategy.js +55 -0
  537. package/dist/turns/user_stop/external_user_turn_stop_strategy.d.ts +115 -0
  538. package/dist/turns/user_stop/external_user_turn_stop_strategy.js +205 -0
  539. package/dist/turns/user_stop/index.d.ts +10 -0
  540. package/dist/turns/user_stop/index.js +15 -0
  541. package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.d.ts +44 -0
  542. package/dist/turns/user_stop/llm_turn_completion_user_turn_stop_strategy.js +59 -0
  543. package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.d.ts +140 -0
  544. package/dist/turns/user_stop/speech_timeout_user_turn_stop_strategy.js +303 -0
  545. package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.d.ts +135 -0
  546. package/dist/turns/user_stop/turn_analyzer_user_turn_stop_strategy.js +332 -0
  547. package/dist/turns/user_turn_completion_mixin.d.ts +147 -0
  548. package/dist/turns/user_turn_completion_mixin.js +591 -0
  549. package/dist/turns/user_turn_controller.d.ts +134 -0
  550. package/dist/turns/user_turn_controller.js +335 -0
  551. package/dist/turns/user_turn_processor.d.ts +87 -0
  552. package/dist/turns/user_turn_processor.js +180 -0
  553. package/dist/turns/user_turn_strategies.d.ts +187 -0
  554. package/dist/turns/user_turn_strategies.js +222 -0
  555. package/dist/utils/async_tool_cancellation.d.ts +35 -0
  556. package/dist/utils/async_tool_cancellation.js +78 -0
  557. package/dist/utils/asyncio/index.d.ts +2 -0
  558. package/dist/utils/asyncio/index.js +7 -0
  559. package/dist/utils/asyncio/task_manager.d.ts +49 -0
  560. package/dist/utils/asyncio/task_manager.js +105 -0
  561. package/dist/utils/base_object.d.ts +76 -0
  562. package/dist/utils/base_object.js +154 -0
  563. package/dist/utils/context/aggregated_frame_sequencer.d.ts +491 -0
  564. package/dist/utils/context/aggregated_frame_sequencer.js +803 -0
  565. package/dist/utils/context/audio_context_manager.d.ts +131 -0
  566. package/dist/utils/context/audio_context_manager.js +233 -0
  567. package/dist/utils/context/index.d.ts +2 -0
  568. package/dist/utils/context/index.js +7 -0
  569. package/dist/utils/context/llm_context_summarization.d.ts +258 -0
  570. package/dist/utils/context/llm_context_summarization.js +511 -0
  571. package/dist/utils/context/text_segment_map.d.ts +611 -0
  572. package/dist/utils/context/text_segment_map.js +1132 -0
  573. package/dist/utils/context/word_completion_tracker.d.ts +273 -0
  574. package/dist/utils/context/word_completion_tracker.js +441 -0
  575. package/dist/utils/context/word_timestamp_tracker.d.ts +72 -0
  576. package/dist/utils/context/word_timestamp_tracker.js +98 -0
  577. package/dist/utils/deprecation.d.ts +28 -0
  578. package/dist/utils/deprecation.js +75 -0
  579. package/dist/utils/env.d.ts +38 -0
  580. package/dist/utils/env.js +62 -0
  581. package/dist/utils/errors.d.ts +75 -0
  582. package/dist/utils/errors.js +202 -0
  583. package/dist/utils/file_resolver.d.ts +126 -0
  584. package/dist/utils/file_resolver.js +314 -0
  585. package/dist/utils/file_storage.d.ts +99 -0
  586. package/dist/utils/file_storage.js +100 -0
  587. package/dist/utils/frame_queue.d.ts +36 -0
  588. package/dist/utils/frame_queue.js +66 -0
  589. package/dist/utils/http.d.ts +39 -0
  590. package/dist/utils/http.js +53 -0
  591. package/dist/utils/image.d.ts +15 -0
  592. package/dist/utils/image.js +30 -0
  593. package/dist/utils/index.d.ts +2 -0
  594. package/dist/utils/index.js +8 -0
  595. package/dist/utils/log_config.d.ts +34 -0
  596. package/dist/utils/log_config.js +76 -0
  597. package/dist/utils/logger.d.ts +40 -0
  598. package/dist/utils/logger.js +91 -0
  599. package/dist/utils/mixin.d.ts +14 -0
  600. package/dist/utils/mixin.js +63 -0
  601. package/dist/utils/network.d.ts +73 -0
  602. package/dist/utils/network.js +90 -0
  603. package/dist/utils/security/allowed_origins.d.ts +16 -0
  604. package/dist/utils/security/allowed_origins.js +34 -0
  605. package/dist/utils/security/index.d.ts +2 -0
  606. package/dist/utils/security/index.js +7 -0
  607. package/dist/utils/security/redaction.d.ts +27 -0
  608. package/dist/utils/security/redaction.js +123 -0
  609. package/dist/utils/security/ssrf.d.ts +79 -0
  610. package/dist/utils/security/ssrf.js +327 -0
  611. package/dist/utils/shared.d.ts +35 -0
  612. package/dist/utils/shared.js +136 -0
  613. package/dist/utils/startup.d.ts +17 -0
  614. package/dist/utils/startup.js +130 -0
  615. package/dist/utils/string.d.ts +116 -0
  616. package/dist/utils/string.js +460 -0
  617. package/dist/utils/sync/base_notifier.d.ts +25 -0
  618. package/dist/utils/sync/base_notifier.js +16 -0
  619. package/dist/utils/sync/event_notifier.d.ts +38 -0
  620. package/dist/utils/sync/event_notifier.js +51 -0
  621. package/dist/utils/sync/index.d.ts +2 -0
  622. package/dist/utils/sync/index.js +7 -0
  623. package/dist/utils/text/alnum_utils.d.ts +109 -0
  624. package/dist/utils/text/alnum_utils.js +262 -0
  625. package/dist/utils/text/base_text_aggregator.d.ts +146 -0
  626. package/dist/utils/text/base_text_aggregator.js +102 -0
  627. package/dist/utils/text/base_text_filter.d.ts +54 -0
  628. package/dist/utils/text/base_text_filter.js +50 -0
  629. package/dist/utils/text/index.d.ts +2 -0
  630. package/dist/utils/text/index.js +7 -0
  631. package/dist/utils/text/markdown_text_filter.d.ts +147 -0
  632. package/dist/utils/text/markdown_text_filter.js +522 -0
  633. package/dist/utils/text/markup_utils.d.ts +54 -0
  634. package/dist/utils/text/markup_utils.js +156 -0
  635. package/dist/utils/text/pattern_pair_aggregator.d.ts +251 -0
  636. package/dist/utils/text/pattern_pair_aggregator.js +478 -0
  637. package/dist/utils/text/phonemes.d.ts +60 -0
  638. package/dist/utils/text/phonemes.js +172 -0
  639. package/dist/utils/text/simple_text_aggregator.d.ts +135 -0
  640. package/dist/utils/text/simple_text_aggregator.js +254 -0
  641. package/dist/utils/text/skip_tags_aggregator.d.ts +85 -0
  642. package/dist/utils/text/skip_tags_aggregator.js +147 -0
  643. package/dist/utils/text/transforms/pronunciations.d.ts +65 -0
  644. package/dist/utils/text/transforms/pronunciations.js +111 -0
  645. package/dist/utils/text/word_timestamp_utils.d.ts +26 -0
  646. package/dist/utils/text/word_timestamp_utils.js +52 -0
  647. package/dist/utils/time.d.ts +13 -0
  648. package/dist/utils/time.js +31 -0
  649. package/dist/utils/tracing/index.d.ts +3 -0
  650. package/dist/utils/tracing/index.js +8 -0
  651. package/dist/utils/tracing/service_attributes.d.ts +198 -0
  652. package/dist/utils/tracing/service_attributes.js +434 -0
  653. package/dist/utils/tracing/service_decorators.d.ts +214 -0
  654. package/dist/utils/tracing/service_decorators.js +1662 -0
  655. package/dist/utils/tracing/setup.d.ts +95 -0
  656. package/dist/utils/tracing/setup.js +110 -0
  657. package/dist/utils/tracing/tracing_context.d.ts +55 -0
  658. package/dist/utils/tracing/tracing_context.js +97 -0
  659. package/dist/utils/tracing/turn_trace_observer.d.ts +90 -0
  660. package/dist/utils/tracing/turn_trace_observer.js +219 -0
  661. package/dist/utils/types.d.ts +80 -0
  662. package/dist/utils/types.js +108 -0
  663. package/dist/utils/utils.d.ts +5 -0
  664. package/dist/utils/utils.js +22 -0
  665. package/dist/utils/yaml.d.ts +64 -0
  666. package/dist/utils/yaml.js +41 -0
  667. package/dist/version.d.ts +3 -0
  668. package/dist/version.js +7 -0
  669. package/dist/workers/base_worker.d.ts +772 -0
  670. package/dist/workers/base_worker.js +1298 -0
  671. package/dist/workers/index.d.ts +2 -0
  672. package/dist/workers/index.js +8 -0
  673. package/dist/workers/llm/backend_llm_worker.d.ts +552 -0
  674. package/dist/workers/llm/backend_llm_worker.js +1055 -0
  675. package/dist/workers/llm/index.d.ts +6 -0
  676. package/dist/workers/llm/index.js +11 -0
  677. package/dist/workers/llm/llm_context_worker.d.ts +66 -0
  678. package/dist/workers/llm/llm_context_worker.js +72 -0
  679. package/dist/workers/llm/llm_worker.d.ts +180 -0
  680. package/dist/workers/llm/llm_worker.js +323 -0
  681. package/dist/workers/llm/tool_decorator.d.ts +68 -0
  682. package/dist/workers/llm/tool_decorator.js +60 -0
  683. package/dist/workers/runner.d.ts +268 -0
  684. package/dist/workers/runner.js +489 -0
  685. package/dist/workers/ui/index.d.ts +22 -0
  686. package/dist/workers/ui/index.js +27 -0
  687. package/dist/workers/ui/ui_event_decorator.d.ts +43 -0
  688. package/dist/workers/ui/ui_event_decorator.js +75 -0
  689. package/dist/workers/ui/ui_job_context.d.ts +12 -0
  690. package/dist/workers/ui/ui_job_context.js +17 -0
  691. package/dist/workers/ui/ui_prompts.d.ts +21 -0
  692. package/dist/workers/ui/ui_prompts.js +41 -0
  693. package/dist/workers/ui/ui_tools.d.ts +32 -0
  694. package/dist/workers/ui/ui_tools.js +112 -0
  695. package/dist/workers/ui/ui_worker.d.ts +571 -0
  696. package/dist/workers/ui/ui_worker.js +1156 -0
  697. package/package.json +46 -3
  698. package/README.md +0 -5
@@ -0,0 +1,1044 @@
1
+ // Copyright (c) 2024-2026, Daily
2
+ // Copyright (c) 2026, pipedog contributors
3
+ // SPDX-License-Identifier: BSD-2-Clause
4
+ //
5
+ // Ported from pipecat: src/pipecat/services/stt_service.py
6
+ /** Base classes for Speech-to-Text services with continuous and segmented processing. */
7
+ import { monotonic, Queue, rethrowIfCancelled, sleep, time } from "@pipedog/asyncio";
8
+ import { pcmToWav } from "../audio/utils.js";
9
+ import { InputAudioRawFrame, InterruptionFrame, LLMContextAssistantTurnFrame, STTMetadataFrame, STTMuteFrame, STTUpdateSettingsFrame, TranscriptionFrame, UserAudioRawFrame, UserStoppedSpeakingFrame, VADUserStartedSpeakingFrame, VADUserStoppedSpeakingFrame, } from "../frames/frames.js";
10
+ import { STTUsage } from "../metrics/metrics.js";
11
+ import { FrameDirection } from "../processors/frame_processor.js";
12
+ import { isLanguage } from "../transcriptions/language.js";
13
+ import { logger } from "../utils/logger.js";
14
+ import { isGiven } from "../utils/types.js";
15
+ import { AIService } from "./ai_service.js";
16
+ import { STTSettings } from "./settings.js";
17
+ import { DEFAULT_TTFS_P99 } from "./stt_latency.js";
18
+ import { State, WebsocketService } from "./websocket_service.js";
19
+ // Duration in seconds of silent audio sent for WebSocket keepalive (100ms).
20
+ const KEEPALIVE_SILENCE_DURATION = 0.1;
21
+ function errorMessage(e) {
22
+ return e instanceof Error ? e.message : String(e);
23
+ }
24
+ /**
25
+ * Base class for speech-to-text services.
26
+ *
27
+ * Provides common functionality for STT services including audio
28
+ * passthrough, muting, settings management, and audio processing. Subclasses
29
+ * must implement the `runStt` method to provide actual speech recognition.
30
+ *
31
+ * Includes an optional keepalive mechanism that sends silent audio when no
32
+ * real audio has been sent for a configurable timeout, preventing servers from
33
+ * closing idle connections (e.g. when behind a ServiceSwitcher). Subclasses
34
+ * that enable keepalive must override `_sendKeepalive()` to deliver the
35
+ * silence in the appropriate service-specific protocol.
36
+ *
37
+ * A streaming STT reports latency through TTFB — speech end to final
38
+ * transcript — and not through processing metrics. Audio arrives
39
+ * continuously, so there is no discrete request whose duration a
40
+ * `FrameProcessor.startProcessingMetrics()` window could measure; anchoring
41
+ * one to a speech or turn boundary measures how long the user talked.
42
+ * SegmentedSTTService does issue a discrete request per utterance, so its
43
+ * subclasses time that call and report both.
44
+ *
45
+ * Event handlers:
46
+ *
47
+ * - on_connected: called when connected to the STT service.
48
+ * - on_disconnected: called when disconnected from the STT service.
49
+ * - on_connection_error: called when a connection to the STT service error
50
+ * occurs.
51
+ *
52
+ * @example
53
+ * stt.addEventHandler("on_connected", async (stt: STTService) => {
54
+ * logger.debug("STT connected");
55
+ * });
56
+ *
57
+ * stt.addEventHandler("on_disconnected", async (stt: STTService) => {
58
+ * logger.debug("STT disconnected");
59
+ * });
60
+ *
61
+ * stt.addEventHandler("on_connection_error", async (stt: STTService, error: string) => {
62
+ * logger.error(`STT connection error: ${error}`);
63
+ * });
64
+ */
65
+ export class STTService extends AIService {
66
+ _audioPassthrough;
67
+ _initSampleRate;
68
+ _sampleRate = 0;
69
+ _muted = false;
70
+ _userId = "";
71
+ _ttfsP99Latency;
72
+ // STT TTFB tracking state
73
+ _sttTtfbTimeout;
74
+ _ttfbTimeoutTask = null;
75
+ _userSpeaking = false;
76
+ _finalizePending = false;
77
+ _finalizeRequested = false;
78
+ _lastTranscriptTime = 0;
79
+ // Keepalive state
80
+ _keepaliveTimeout;
81
+ _keepaliveInterval;
82
+ _keepaliveTask = null;
83
+ _lastAudioTime = 0;
84
+ // Client-measured audio seconds submitted to the service since the last
85
+ // usage report (see emitSttUsageMetrics).
86
+ _sttUsagePendingSeconds = 0.0;
87
+ // VAD-aware reconnect state
88
+ // Whether it is safe to reconnect right now (false while the user is speaking).
89
+ _canReconnect = true;
90
+ // Whether a reconnect has been requested but deferred until speaking ends.
91
+ _needReconnect = false;
92
+ // Whether a reconnect cycle is currently in progress.
93
+ _reconnecting = false;
94
+ // Audio frames received while _reconnecting is true, replayed after reconnect.
95
+ _reconnectAudioBuffer = [];
96
+ /**
97
+ * Initialize the STT service.
98
+ *
99
+ * @param options.audioPassthrough Whether to pass audio frames downstream
100
+ * after processing. Defaults to true.
101
+ * @param options.sampleRate The sample rate for audio input. If null, will
102
+ * be determined from the start frame.
103
+ * @param options.sttTtfbTimeout Time in seconds to wait after VAD stop
104
+ * before reporting TTFB. This delay allows the final transcription to
105
+ * arrive. Defaults to 2.0. Note: STT "TTFB" differs from traditional TTFB
106
+ * (which measures from a discrete request to first response byte). Since
107
+ * STT receives continuous audio, we measure from when the user stops
108
+ * speaking to when the final transcript arrives—capturing the latency that
109
+ * matters for voice AI applications.
110
+ * @param options.ttfsP99Latency P99 latency from speech end to final
111
+ * transcript in seconds. This is broadcast via STTMetadataFrame at
112
+ * pipeline start for downstream processors (e.g., turn strategies) to
113
+ * optimize timing. Subclasses provide measured defaults; pass a value here
114
+ * to override for your deployment. Turn-based services where the server
115
+ * defines turn boundaries should override `supportsTtfs` to return false
116
+ * instead of supplying a value.
117
+ * @param options.keepaliveTimeout Seconds of no audio before sending
118
+ * silence to keep the connection alive. null disables keepalive. Useful
119
+ * for services that close idle connections (e.g. behind a
120
+ * ServiceSwitcher).
121
+ * @param options.keepaliveInterval Seconds between idle checks when
122
+ * keepalive is enabled.
123
+ * @param options.settings The runtime-updatable settings for the STT
124
+ * service. Other options are passed to the parent AIService.
125
+ */
126
+ constructor(options = {}) {
127
+ super({
128
+ ...options,
129
+ settings: options.settings ||
130
+ // Here in case subclass doesn't implement more specific settings
131
+ // (which hopefully should be rare)
132
+ new STTSettings(),
133
+ });
134
+ // Convert Language enum to service-specific format at init time.
135
+ // Runtime updates are handled by _updateSettings(), but init-time
136
+ // settings bypass that path and need explicit conversion.
137
+ // Raw strings (e.g. "de-DE") are first checked against the Language
138
+ // codes so they go through the same resolution logic.
139
+ const language = this._settings.language;
140
+ if (typeof language === "string" && !isLanguage(language)) {
141
+ logger.debug(`Language string '${language}' is not a recognized ` +
142
+ "Language code. It will be passed to the service as-is.");
143
+ }
144
+ if (typeof language === "string" && isLanguage(language)) {
145
+ const converted = this.languageToServiceLanguage(language);
146
+ if (converted !== null) {
147
+ this._settings.language = converted;
148
+ }
149
+ }
150
+ this._audioPassthrough = options.audioPassthrough ?? true;
151
+ this._initSampleRate = options.sampleRate ?? null;
152
+ this._ttfsP99Latency = options.ttfsP99Latency ?? null;
153
+ this._sttTtfbTimeout = options.sttTtfbTimeout ?? 2.0;
154
+ this._keepaliveTimeout = options.keepaliveTimeout ?? null;
155
+ this._keepaliveInterval = options.keepaliveInterval ?? 5.0;
156
+ this._registerEventHandler("on_connected");
157
+ this._registerEventHandler("on_disconnected");
158
+ this._registerEventHandler("on_connection_error");
159
+ }
160
+ /** The service's current settings, for reading. */
161
+ get settings() {
162
+ return this._settings;
163
+ }
164
+ /** Whether the STT service is currently muted and will not process audio. */
165
+ get isMuted() {
166
+ return this._muted;
167
+ }
168
+ /**
169
+ * Mark that a finalize request has been sent, awaiting server confirmation.
170
+ *
171
+ * For providers that have explicit server confirmation of finalization
172
+ * (e.g., Deepgram's from_finalize field), call this when sending the
173
+ * finalize request. Then call `confirmFinalize()` when the server confirms.
174
+ *
175
+ * For providers without server confirmation, don't call this method - just
176
+ * send the finalize/flush/commit command and rely on the TTFB timeout.
177
+ */
178
+ requestFinalize() {
179
+ this._finalizeRequested = true;
180
+ }
181
+ /**
182
+ * Confirm that the server has acknowledged the finalize request.
183
+ *
184
+ * Call this when the server response confirms finalization (e.g.,
185
+ * Deepgram's from_finalize=True). The next TranscriptionFrame pushed will be
186
+ * marked as finalized.
187
+ *
188
+ * Only has effect if `requestFinalize()` was previously called.
189
+ */
190
+ confirmFinalize() {
191
+ if (this._finalizeRequested) {
192
+ this._finalizePending = true;
193
+ this._finalizeRequested = false;
194
+ }
195
+ }
196
+ /** The current sample rate for audio processing, in Hz. */
197
+ get sampleRate() {
198
+ return this._sampleRate;
199
+ }
200
+ /**
201
+ * Accumulate client-measured audio seconds for usage metrics.
202
+ *
203
+ * @param audio Raw 16-bit mono PCM audio bytes submitted to the service.
204
+ */
205
+ _recordSttAudioUsage(audio) {
206
+ if (this.sampleRate > 0) {
207
+ this._sttUsagePendingSeconds += audio.length / (this.sampleRate * 2);
208
+ }
209
+ }
210
+ /**
211
+ * Emit an incremental STT usage metrics event.
212
+ *
213
+ * Reports the audio seconds accumulated since the last report as an
214
+ * `STTUsageMetricsData` in a `MetricsFrame`. Services call this once per
215
+ * billing event (e.g. on each final transcript); the base class calls it a
216
+ * final time on stop/cancel to flush trailing audio. Does nothing if there
217
+ * is no usage to report.
218
+ */
219
+ async emitSttUsageMetrics() {
220
+ const audioSeconds = this._sttUsagePendingSeconds;
221
+ this._sttUsagePendingSeconds = 0.0;
222
+ if (audioSeconds === 0.0) {
223
+ return;
224
+ }
225
+ await this.startSttUsageMetrics(new STTUsage({ audioSeconds }));
226
+ }
227
+ /** Flush any trailing audio usage not yet reported by a billing event. */
228
+ async _flushSttUsageMetrics() {
229
+ await this.emitSttUsageMetrics();
230
+ }
231
+ /**
232
+ * Convert a language to the service-specific language format.
233
+ *
234
+ * @param language The language to convert.
235
+ * @returns The service-specific language identifier. Return `null` to
236
+ * indicate an unsupported language. This optional return is an extension
237
+ * hook for future or third-party subclasses; as of 2026-04-28,
238
+ * first-party services return a string.
239
+ */
240
+ languageToServiceLanguage(language) {
241
+ return language;
242
+ }
243
+ /**
244
+ * Called when the assistant's turn completes with the aggregated reply text.
245
+ *
246
+ * Override in subclasses to react to each completed bot reply — for
247
+ * example, to feed the text to a provider-side context carryover API. The
248
+ * default implementation is a no-op.
249
+ *
250
+ * @param text The assistant's aggregated spoken text for this turn.
251
+ */
252
+ async _processAssistantTurn(_text) { }
253
+ /**
254
+ * Set up the service.
255
+ *
256
+ * @param setup Configuration object containing setup parameters.
257
+ */
258
+ async setup(setup) {
259
+ await super.setup(setup);
260
+ this._sampleRate = this._initSampleRate || setup.audioInSampleRate;
261
+ }
262
+ /** Clean up STT service resources. */
263
+ async cleanup() {
264
+ await super.cleanup();
265
+ await this._cancelTtfbTimeout();
266
+ await this._cancelKeepaliveTask();
267
+ this._reconnectAudioBuffer.length = 0;
268
+ }
269
+ /**
270
+ * Stop the STT service on a graceful end.
271
+ *
272
+ * @param frame The end frame.
273
+ */
274
+ async stop(frame) {
275
+ await super.stop(frame);
276
+ await this._flushSttUsageMetrics();
277
+ }
278
+ /**
279
+ * Cancel the STT service immediately.
280
+ *
281
+ * @param frame The cancel frame.
282
+ */
283
+ async cancel(frame) {
284
+ await super.cancel(frame);
285
+ await this._flushSttUsageMetrics();
286
+ }
287
+ /**
288
+ * Apply an STT settings delta.
289
+ *
290
+ * Handles `model` (via parent). Translates `Language` values before
291
+ * applying so the stored value is a service-specific string. Concrete
292
+ * services should override this method and handle language changes
293
+ * (including any reconnect logic) based on the returned changed-field
294
+ * record.
295
+ *
296
+ * @param delta An STT settings delta.
297
+ * @returns Record mapping changed field names to their previous values.
298
+ */
299
+ async _updateSettings(delta) {
300
+ // Translate language *before* applying so the stored value is canonical.
301
+ // Raw strings are first checked against the Language codes for proper resolution.
302
+ const language = delta.language;
303
+ if (isGiven(language) && typeof language === "string" && !isLanguage(language)) {
304
+ logger.debug(`Language string '${language}' is not a recognized ` +
305
+ "Language code. It will be passed to the service as-is.");
306
+ }
307
+ if (isGiven(language) && typeof language === "string" && isLanguage(language)) {
308
+ const converted = this.languageToServiceLanguage(language);
309
+ if (converted !== null) {
310
+ delta.language = converted;
311
+ }
312
+ }
313
+ const changed = await super._updateSettings(delta);
314
+ return changed;
315
+ }
316
+ /**
317
+ * Process an audio frame for speech recognition.
318
+ *
319
+ * If a reconnect is in progress, the frame is buffered and replayed once
320
+ * the connection is restored. If the service is muted, the frame is
321
+ * dropped. Otherwise the frame is sent to the STT service and, if a userId
322
+ * is present, it is stored for use in transcription results.
323
+ *
324
+ * @param frame The audio frame to process.
325
+ * @param direction The direction of frame processing.
326
+ */
327
+ async processAudioFrame(frame, direction) {
328
+ if (this._reconnecting) {
329
+ this._reconnectAudioBuffer.push([frame, direction]);
330
+ return;
331
+ }
332
+ if (this._muted) {
333
+ return;
334
+ }
335
+ // A service that can no longer work can't transcribe anything, and
336
+ // services that connect on demand would attempt a handshake per chunk.
337
+ if (!this.isUsable) {
338
+ return;
339
+ }
340
+ this._lastAudioTime = monotonic();
341
+ // UserAudioRawFrame contains a userId (e.g. Daily, Livekit)
342
+ if (frame instanceof UserAudioRawFrame) {
343
+ this._userId = frame.userId;
344
+ }
345
+ // InputAudioRawFrame does not have a userId (e.g. SmallWebRTCTransport, websockets)
346
+ else {
347
+ this._userId = "";
348
+ }
349
+ if (!frame.audio || frame.audio.length === 0) {
350
+ // Ignoring in case we don't have audio to transcribe.
351
+ logger.warning(`Empty audio frame received for STT service: ${this.name} ${frame.numFrames}`);
352
+ return;
353
+ }
354
+ this._recordSttAudioUsage(frame.audio);
355
+ await this.processGenerator(this.runStt(frame.audio));
356
+ }
357
+ /**
358
+ * Process frames, handling VAD events and audio segmentation.
359
+ *
360
+ * @param frame The frame to process.
361
+ * @param direction The direction of frame processing.
362
+ */
363
+ async processFrame(frame, direction) {
364
+ await super.processFrame(frame, direction);
365
+ if (frame instanceof InputAudioRawFrame) {
366
+ // In this service we accumulate audio internally and at the end we
367
+ // push a TextFrame. We also push audio downstream in case someone
368
+ // else needs it.
369
+ await this.processAudioFrame(frame, direction);
370
+ if (this._audioPassthrough) {
371
+ await this.pushFrame(frame, direction);
372
+ }
373
+ }
374
+ else if (frame instanceof VADUserStartedSpeakingFrame) {
375
+ await this._handleVadUserStartedSpeaking(frame);
376
+ await this.pushFrame(frame, direction);
377
+ }
378
+ else if (frame instanceof VADUserStoppedSpeakingFrame) {
379
+ await this._handleVadUserStoppedSpeaking(frame);
380
+ await this.pushFrame(frame, direction);
381
+ }
382
+ else if (frame instanceof UserStoppedSpeakingFrame) {
383
+ await this._maybeReconnectOnUserStoppedSpeaking();
384
+ await this.pushFrame(frame, direction);
385
+ }
386
+ else if (frame instanceof STTUpdateSettingsFrame) {
387
+ if (frame.service !== null && frame.service !== this) {
388
+ await this.pushFrame(frame, direction);
389
+ }
390
+ else if (frame.delta !== null) {
391
+ await this._updateSettings(frame.delta);
392
+ }
393
+ }
394
+ else if (frame instanceof STTMuteFrame) {
395
+ this._muted = frame.mute;
396
+ logger.debug(`STT service ${frame.mute ? "muted" : "unmuted"}`);
397
+ }
398
+ else if (frame instanceof InterruptionFrame) {
399
+ await this._resetSttTtfbState();
400
+ await this.pushFrame(frame, direction);
401
+ }
402
+ else if (frame instanceof LLMContextAssistantTurnFrame) {
403
+ await this._processAssistantTurn(frame.text);
404
+ await this.pushFrame(frame, direction);
405
+ }
406
+ else {
407
+ await this.pushFrame(frame, direction);
408
+ }
409
+ }
410
+ /**
411
+ * Push a frame downstream, tracking TranscriptionFrame timestamps for TTFB.
412
+ *
413
+ * Stores the timestamp of each TranscriptionFrame for TTFB calculation. If
414
+ * the frame is marked as finalized (via requestFinalize/confirmFinalize),
415
+ * reports TTFB immediately and cancels any pending timeout. Otherwise, TTFB
416
+ * is reported after a timeout.
417
+ *
418
+ * @param frame The frame to push.
419
+ * @param direction The direction to push the frame.
420
+ */
421
+ async pushFrame(frame, direction = FrameDirection.DOWNSTREAM) {
422
+ if (frame instanceof TranscriptionFrame) {
423
+ // Store the transcript time for TTFB calculation
424
+ this._lastTranscriptTime = time();
425
+ // Set finalized from pending state and auto-reset
426
+ if (this._finalizePending) {
427
+ frame.finalized = true;
428
+ this._finalizePending = false;
429
+ }
430
+ // If this is a finalized transcription, report TTFB immediately
431
+ if (frame.finalized) {
432
+ await this.stopTtfbMetrics();
433
+ // Cancel the timeout since we've already reported
434
+ await this._cancelTtfbTimeout();
435
+ }
436
+ }
437
+ await super.pushFrame(frame, direction);
438
+ }
439
+ /**
440
+ * Whether this STT service has a meaningful TTFS-to-final-transcript metric.
441
+ *
442
+ * Returns false for turn-based STTs where the server defines the turn
443
+ * boundary, so there is no separate "speech end → final transcript"
444
+ * interval to measure. Downstream turn-stop strategies that consume
445
+ * STTMetadataFrame treat a 0 latency as "no extra wait."
446
+ */
447
+ get supportsTtfs() {
448
+ return true;
449
+ }
450
+ /**
451
+ * Build the STT metadata frame broadcast at start.
452
+ *
453
+ * Overrides `AIService.serviceMetadataFrame()` to return an
454
+ * STTMetadataFrame carrying the service's TTFS P99 latency. A service that
455
+ * does its own server-side end-of-turn detection overrides this (calling
456
+ * `super`) to set `userTurnStrategies` on the returned frame.
457
+ */
458
+ serviceMetadataFrame() {
459
+ let ttfs;
460
+ if (!this.supportsTtfs) {
461
+ ttfs = 0.0;
462
+ }
463
+ else {
464
+ const latency = this._ttfsP99Latency;
465
+ if (latency === null) {
466
+ ttfs = DEFAULT_TTFS_P99;
467
+ logger.warning(`${this.name}: ttfs_p99_latency not set, using default ${ttfs}s`);
468
+ }
469
+ else {
470
+ ttfs = latency;
471
+ }
472
+ }
473
+ return new STTMetadataFrame({ serviceName: this.name, ttfsP99Latency: ttfs });
474
+ }
475
+ /** Cancel any pending TTFB timeout task. */
476
+ async _cancelTtfbTimeout() {
477
+ if (this._ttfbTimeoutTask) {
478
+ await this.cancelTask(this._ttfbTimeoutTask);
479
+ this._ttfbTimeoutTask = null;
480
+ }
481
+ }
482
+ /**
483
+ * Reset STT TTFB measurement state.
484
+ *
485
+ * Called when starting a new utterance or on interruption to ensure we
486
+ * don't use stale state for TTFB calculations. This specifically guards
487
+ * against the case where a TranscriptionFrame is received without
488
+ * corresponding VADUserStartedSpeakingFrame and VADUserStoppedSpeakingFrame
489
+ * frames.
490
+ *
491
+ * Note: does not reset `_userSpeaking` since InterruptionFrame can arrive
492
+ * while user is still speaking.
493
+ */
494
+ async _resetSttTtfbState() {
495
+ await this._cancelTtfbTimeout();
496
+ await this.cancelTtfbMetrics();
497
+ }
498
+ /**
499
+ * Handle VAD user started speaking frame to start tracking transcriptions.
500
+ *
501
+ * Cancels any pending TTFB timeout, resets TTFB tracking state, and marks
502
+ * user as speaking. Also resets finalization state to prevent stale
503
+ * finalization from a previous utterance.
504
+ *
505
+ * @param frame The VAD user started speaking frame.
506
+ */
507
+ async _handleVadUserStartedSpeaking(_frame) {
508
+ await this._resetSttTtfbState();
509
+ this._userSpeaking = true;
510
+ this._canReconnect = false;
511
+ this._finalizeRequested = false;
512
+ this._finalizePending = false;
513
+ this._lastTranscriptTime = 0;
514
+ }
515
+ /**
516
+ * Check if reconnection is needed after the user stops speaking.
517
+ *
518
+ * Called when the user's full turn has ended and the transcription has been
519
+ * received. Re-enables reconnection and triggers any deferred reconnect
520
+ * that was requested while the user was speaking.
521
+ */
522
+ async _maybeReconnectOnUserStoppedSpeaking() {
523
+ this._canReconnect = true;
524
+ if (this._needReconnect) {
525
+ await this._reconnect();
526
+ }
527
+ }
528
+ /**
529
+ * Handle VAD user stopped speaking frame.
530
+ *
531
+ * Calculates the actual speech end time and starts a timeout task to wait
532
+ * for the final transcription before reporting TTFB.
533
+ *
534
+ * @param frame The VAD user stopped speaking frame.
535
+ */
536
+ async _handleVadUserStoppedSpeaking(frame) {
537
+ this._userSpeaking = false;
538
+ // Skip TTFB measurement if stopSecs is not set
539
+ if (frame.stopSecs === 0.0) {
540
+ return;
541
+ }
542
+ // Calculate the actual speech end time (current time minus VAD stop delay).
543
+ // This approximates when the last user audio was sent to the STT service,
544
+ // which we use to measure against the eventual transcription response.
545
+ const speechEndTime = frame.timestamp - frame.stopSecs;
546
+ await this.startTtfbMetrics({ startTime: speechEndTime });
547
+ // Start timeout task (any previous timeout was cancelled by VADUserStartedSpeakingFrame
548
+ // or InterruptionFrame)
549
+ this._ttfbTimeoutTask = this.createTask(() => this._ttfbTimeoutHandler(), "stt_ttfb_timeout");
550
+ }
551
+ /**
552
+ * Wait for timeout then report TTFB using the last transcript timestamp.
553
+ *
554
+ * This timeout allows the final transcription to arrive before we
555
+ * calculate and report TTFB. Uses `_lastTranscriptTime` as the end time so
556
+ * we measure to when the transcript actually arrived, not when the timeout
557
+ * fired.
558
+ *
559
+ * A transcript that predates the end of speech belongs to an earlier
560
+ * segment the service finalized on its own endpointing; the metrics
561
+ * collector refuses it rather than report the negative interval it would
562
+ * produce.
563
+ */
564
+ async _ttfbTimeoutHandler() {
565
+ try {
566
+ await sleep(this._sttTtfbTimeout);
567
+ if (this._lastTranscriptTime > 0) {
568
+ await this.stopTtfbMetrics({ endTime: this._lastTranscriptTime });
569
+ }
570
+ else {
571
+ // No transcript at all, so there is no end time to measure to.
572
+ // Close the measurement rather than leave it open for the next
573
+ // transcript to be measured against.
574
+ await this.cancelTtfbMetrics();
575
+ }
576
+ }
577
+ finally {
578
+ // A cancellation here (new utterance or interruption) is expected
579
+ // behavior; it propagates so the task ends as cancelled.
580
+ this._ttfbTimeoutTask = null;
581
+ }
582
+ }
583
+ /** Start the keepalive task if keepalive is enabled. */
584
+ _createKeepaliveTask() {
585
+ if (this._keepaliveTimeout !== null) {
586
+ this._lastAudioTime = monotonic();
587
+ this._keepaliveTask = this.createTask(() => this._keepaliveTaskHandler(), "keepalive");
588
+ }
589
+ }
590
+ /** Stop the keepalive task if running. */
591
+ async _cancelKeepaliveTask() {
592
+ if (this._keepaliveTask) {
593
+ await this.cancelTask(this._keepaliveTask);
594
+ this._keepaliveTask = null;
595
+ }
596
+ }
597
+ /**
598
+ * Perform a full reconnect cycle with audio buffering.
599
+ *
600
+ * Sets `_reconnecting` so incoming audio frames are buffered rather than
601
+ * sent to a dead connection. Delegates the actual connection reset to
602
+ * `_doReconnect()`. After the new connection is established all buffered
603
+ * frames are replayed. On failure the error is reported via `pushError`
604
+ * and the `on_connection_error` event handler.
605
+ */
606
+ async _reconnect() {
607
+ logger.info(`${this} reconnecting...`);
608
+ this._reconnectAudioBuffer.length = 0;
609
+ this._reconnecting = true;
610
+ this._needReconnect = false;
611
+ try {
612
+ await this._doReconnect();
613
+ }
614
+ catch (e) {
615
+ rethrowIfCancelled(e);
616
+ logger.error(`${this} reconnect failed: ${errorMessage(e)}`);
617
+ await this._callEventHandler("on_connection_error", errorMessage(e));
618
+ await this.pushError(`${this} reconnect failed: ${errorMessage(e)}`, { exception: e });
619
+ return;
620
+ }
621
+ finally {
622
+ this._reconnecting = false;
623
+ }
624
+ // Replay audio frames that arrived while the connection was down.
625
+ for (const [bufferedFrame, bufferedDirection] of this._reconnectAudioBuffer) {
626
+ await this.processAudioFrame(bufferedFrame, bufferedDirection);
627
+ }
628
+ this._reconnectAudioBuffer.length = 0;
629
+ }
630
+ /**
631
+ * Perform the service-specific connection reset.
632
+ *
633
+ * Called by `_reconnect()` inside the reconnecting guard. The default
634
+ * implementation is a no-op. Subclasses that support explicit reconnection
635
+ * (e.g. WebsocketSTTService) should override this to tear down and
636
+ * re-establish their connection.
637
+ */
638
+ async _doReconnect() { }
639
+ /**
640
+ * Reconnect immediately if safe, or defer until after the current user turn.
641
+ *
642
+ * Reconnection is unsafe while the user is speaking because the service is
643
+ * actively receiving audio. Calling this method while the user is speaking
644
+ * schedules a reconnect that fires as soon as `UserStoppedSpeakingFrame` is
645
+ * received.
646
+ */
647
+ async _requestReconnect() {
648
+ logger.debug(`${this} requesting to reconnect!`);
649
+ if (this._canReconnect) {
650
+ await this._reconnect();
651
+ }
652
+ else {
653
+ this._needReconnect = true;
654
+ }
655
+ }
656
+ /**
657
+ * Send periodic silent audio to prevent the server from closing the
658
+ * connection.
659
+ *
660
+ * When keepalive is enabled, this task checks periodically if the
661
+ * connection has been idle (no audio sent) for longer than
662
+ * keepaliveTimeout seconds. If so, it generates silent 16-bit mono PCM
663
+ * audio and passes it to `_sendKeepalive()` for service-specific formatting
664
+ * and sending. A failed send is logged and the loop keeps going.
665
+ */
666
+ async _keepaliveTaskHandler() {
667
+ // This task is only started when a keepalive timeout is configured.
668
+ const keepaliveTimeout = this._keepaliveTimeout;
669
+ if (keepaliveTimeout === null) {
670
+ throw new Error("keepalive task started without a keepalive timeout");
671
+ }
672
+ while (true) {
673
+ await sleep(this._keepaliveInterval);
674
+ try {
675
+ if (!this._isKeepaliveReady()) {
676
+ continue;
677
+ }
678
+ const elapsed = monotonic() - this._lastAudioTime;
679
+ if (elapsed < keepaliveTimeout) {
680
+ continue;
681
+ }
682
+ const numSamples = Math.trunc(this.sampleRate * KEEPALIVE_SILENCE_DURATION);
683
+ const silence = new Uint8Array(numSamples * 2);
684
+ await this._sendKeepalive(silence);
685
+ this._lastAudioTime = monotonic();
686
+ logger.trace(`${this} sent keepalive silence`);
687
+ }
688
+ catch (e) {
689
+ rethrowIfCancelled(e);
690
+ logger.warning(`${this} keepalive error: ${errorMessage(e)}`);
691
+ }
692
+ }
693
+ }
694
+ /**
695
+ * Check if the service is ready to send keepalive.
696
+ *
697
+ * Subclasses should override this to check their connection state.
698
+ *
699
+ * @returns True if keepalive can be sent.
700
+ */
701
+ _isKeepaliveReady() {
702
+ return true;
703
+ }
704
+ /**
705
+ * Send silent audio to keep the connection alive.
706
+ *
707
+ * Subclasses that enable keepalive must override this to deliver silence in
708
+ * their service-specific protocol.
709
+ *
710
+ * @param silence Silent 16-bit mono PCM audio bytes.
711
+ */
712
+ async _sendKeepalive(_silence) {
713
+ throw new Error("Subclasses must override _sendKeepalive");
714
+ }
715
+ }
716
+ /**
717
+ * STT service that processes speech in segments using VAD events.
718
+ *
719
+ * Uses Voice Activity Detection (VAD) events to detect speech segments and
720
+ * runs speech-to-text only on those segments, rather than continuously.
721
+ *
722
+ * Requires VAD to be enabled in the pipeline to function properly. Maintains
723
+ * a small audio buffer to account for the delay between actual speech start
724
+ * and VAD detection.
725
+ *
726
+ * The buffered segment is passed to `runStt` as a WAV container by default,
727
+ * which is what cloud providers want for their upload APIs. Local models that
728
+ * consume raw 16-bit PCM directly override `wantsWavSegments` to return false
729
+ * so they receive the unwrapped buffer instead. This is a subclass-level
730
+ * contract, not a user-configurable option: the format is dictated by what
731
+ * the model expects.
732
+ *
733
+ * A segment ends right where the VAD stopped, and models tend to drop or
734
+ * garble the final word when the audio ends that abruptly, so each segment is
735
+ * padded with `trailingSilenceSecs` of silence before transcription.
736
+ *
737
+ * Transcription runs off the audio path. When the VAD stops, the segment is
738
+ * queued and one background task transcribes queued segments in order,
739
+ * pushing each transcript as it completes; meanwhile audio and other frames
740
+ * keep flowing through the service, as they do through a streaming STT whose
741
+ * transcripts arrive asynchronously. A graceful stop transcribes what is
742
+ * queued before the service stops; a cancel drops it.
743
+ */
744
+ export class SegmentedSTTService extends STTService {
745
+ _trailingSilenceSecs;
746
+ _content = null;
747
+ _wave = null;
748
+ _audioBuffer = new Uint8Array(0);
749
+ _audioBufferSize1s = 0;
750
+ // Segments awaiting transcription, drained in order by _segmentTask;
751
+ // null tells the task to finish.
752
+ _segmentQueue = new Queue();
753
+ _segmentTask = null;
754
+ /**
755
+ * Initialize the segmented STT service.
756
+ *
757
+ * @param options.sampleRate The sample rate for audio input. If null, will
758
+ * be determined from the start frame.
759
+ * @param options.trailingSilenceSecs Seconds of silence appended to each
760
+ * segment before it is transcribed, so the model hears the end of speech
761
+ * and can finish the last word. Set to 0 to disable. Other options are
762
+ * passed to the parent STTService.
763
+ */
764
+ constructor(options = {}) {
765
+ super(options);
766
+ this._trailingSilenceSecs = options.trailingSilenceSecs ?? 0.5;
767
+ this._userSpeaking = false;
768
+ }
769
+ /**
770
+ * Set up the service.
771
+ *
772
+ * @param setup Configuration object containing setup parameters.
773
+ */
774
+ async setup(setup) {
775
+ await super.setup(setup);
776
+ this._audioBufferSize1s = this.sampleRate * 2;
777
+ }
778
+ /**
779
+ * Start the service and its transcription task.
780
+ *
781
+ * @param frame The start frame.
782
+ */
783
+ async start(frame) {
784
+ await super.start(frame);
785
+ this._segmentTask = this.createTask(() => this._segmentTaskHandler());
786
+ }
787
+ /**
788
+ * Transcribe the queued segments, then stop the service.
789
+ *
790
+ * @param frame The end frame.
791
+ */
792
+ async stop(frame) {
793
+ if (this._segmentTask !== null) {
794
+ await this._segmentQueue.put(null);
795
+ await this._segmentTask;
796
+ this._segmentTask = null;
797
+ }
798
+ await super.stop(frame);
799
+ }
800
+ /**
801
+ * Stop the service at once, dropping any queued segments.
802
+ *
803
+ * @param frame The cancel frame.
804
+ */
805
+ async cancel(frame) {
806
+ await this._cancelSegmentTask();
807
+ await super.cancel(frame);
808
+ }
809
+ /**
810
+ * Whether segments are passed to `runStt` as a WAV container.
811
+ *
812
+ * Returns true (the default) for cloud providers whose upload APIs expect a
813
+ * WAV file. Local models that read the buffer as raw 16-bit PCM override
814
+ * this to return false; otherwise they would misinterpret the 44-byte WAV
815
+ * header as audio samples.
816
+ */
817
+ get wantsWavSegments() {
818
+ return true;
819
+ }
820
+ /**
821
+ * Push a frame, marking TranscriptionFrames as finalized.
822
+ *
823
+ * Segmented STT services process complete speech segments and return a
824
+ * single TranscriptionFrame per segment, so every transcription is
825
+ * inherently finalized.
826
+ *
827
+ * @param frame The frame to push.
828
+ * @param direction The direction of frame flow in the pipeline.
829
+ */
830
+ async pushFrame(frame, direction = FrameDirection.DOWNSTREAM) {
831
+ if (frame instanceof TranscriptionFrame) {
832
+ frame.finalized = true;
833
+ }
834
+ await super.pushFrame(frame, direction);
835
+ }
836
+ /** Process frames, handling VAD events and audio segmentation. */
837
+ async processFrame(frame, direction) {
838
+ await super.processFrame(frame, direction);
839
+ if (frame instanceof VADUserStartedSpeakingFrame) {
840
+ await this._handleUserStartedSpeaking(frame);
841
+ }
842
+ else if (frame instanceof VADUserStoppedSpeakingFrame) {
843
+ await this._handleUserStoppedSpeaking(frame);
844
+ }
845
+ }
846
+ async _handleUserStartedSpeaking(_frame) {
847
+ this._userSpeaking = true;
848
+ }
849
+ async _handleUserStoppedSpeaking(_frame) {
850
+ this._userSpeaking = false;
851
+ // A service that can no longer work can't transcribe this segment.
852
+ if (!this.isUsable) {
853
+ this._audioBuffer = new Uint8Array(0);
854
+ return;
855
+ }
856
+ const trailing = this._trailingSilence();
857
+ const pcm = new Uint8Array(this._audioBuffer.length + trailing.length);
858
+ pcm.set(this._audioBuffer, 0);
859
+ pcm.set(trailing, this._audioBuffer.length);
860
+ this._audioBuffer = new Uint8Array(0);
861
+ // The padded segment is what the provider receives, so it is what
862
+ // usage measures. Usage is reported before transcription so tracing
863
+ // can attach it to the STT span the resulting TranscriptionFrame closes.
864
+ this._recordSttAudioUsage(pcm);
865
+ await this.emitSttUsageMetrics();
866
+ let audio;
867
+ if (this.wantsWavSegments) {
868
+ audio = pcmToWav(pcm, this.sampleRate);
869
+ }
870
+ else {
871
+ // Local models read the buffer as raw 16-bit PCM; wrapping it in a
872
+ // WAV container would make them misread the 44-byte header as audio.
873
+ audio = pcm;
874
+ }
875
+ await this._segmentQueue.put(audio);
876
+ }
877
+ /** Transcribe queued segments one at a time, in order, until told to finish. */
878
+ async _segmentTaskHandler() {
879
+ let running = true;
880
+ while (running) {
881
+ const audio = await this._segmentQueue.get();
882
+ running = audio !== null;
883
+ if (audio && audio.length > 0) {
884
+ try {
885
+ await this.processGenerator(this.runStt(audio));
886
+ }
887
+ catch (e) {
888
+ rethrowIfCancelled(e);
889
+ await this.pushError(`${this}: transcription failed: ${errorMessage(e)}`, {
890
+ exception: e,
891
+ });
892
+ }
893
+ }
894
+ }
895
+ }
896
+ async _cancelSegmentTask() {
897
+ if (this._segmentTask !== null) {
898
+ await this.cancelTask(this._segmentTask);
899
+ this._segmentTask = null;
900
+ }
901
+ }
902
+ /** Silence appended to a segment, as 16-bit mono PCM at the service sample rate. */
903
+ _trailingSilence() {
904
+ const numSamples = Math.max(0, Math.trunc(this.sampleRate * this._trailingSilenceSecs));
905
+ return new Uint8Array(numSamples * 2);
906
+ }
907
+ /**
908
+ * Process audio frames by buffering them for segmented transcription.
909
+ *
910
+ * Continuously buffers audio, growing the buffer while user is speaking and
911
+ * maintaining a small buffer when not speaking to account for VAD delay.
912
+ *
913
+ * If the frame has a userId, it is stored for later use in transcription.
914
+ *
915
+ * @param frame The audio frame to process.
916
+ * @param direction The direction of frame processing.
917
+ */
918
+ async processAudioFrame(frame, _direction) {
919
+ // UserAudioRawFrame contains a userId (e.g. Daily, Livekit)
920
+ if (frame instanceof UserAudioRawFrame) {
921
+ this._userId = frame.userId;
922
+ }
923
+ // InputAudioRawFrame does not have a userId (e.g. SmallWebRTCTransport, websockets)
924
+ else {
925
+ this._userId = "";
926
+ }
927
+ // If the user is speaking the audio buffer will keep growing.
928
+ const grown = new Uint8Array(this._audioBuffer.length + frame.audio.length);
929
+ grown.set(this._audioBuffer, 0);
930
+ grown.set(frame.audio, this._audioBuffer.length);
931
+ this._audioBuffer = grown;
932
+ // If the user is not speaking we keep just a little bit of audio.
933
+ if (!this._userSpeaking && this._audioBuffer.length > this._audioBufferSize1s) {
934
+ const discarded = this._audioBuffer.length - this._audioBufferSize1s;
935
+ this._audioBuffer = this._audioBuffer.slice(discarded);
936
+ }
937
+ }
938
+ }
939
+ /**
940
+ * Base class for websocket-based STT services.
941
+ *
942
+ * Combines STT functionality with websocket connectivity, providing
943
+ * automatic error handling, reconnection capabilities, and optional
944
+ * silence-based keepalive.
945
+ *
946
+ * The keepalive feature (inherited from STTService) sends silent audio when
947
+ * no real audio has been sent for a configurable timeout, preventing servers
948
+ * from closing idle connections (e.g. when behind a ServiceSwitcher).
949
+ * Subclasses can override `_sendKeepalive()` to wrap the silence in a
950
+ * service-specific protocol.
951
+ */
952
+ export class WebsocketSTTService extends WebsocketService(STTService) {
953
+ /**
954
+ * Initialize the Websocket STT service.
955
+ *
956
+ * @param options.reconnectOnError Whether to automatically reconnect on
957
+ * websocket errors. Other options are passed to the parent classes
958
+ * (including keepaliveTimeout and keepaliveInterval for STTService).
959
+ */
960
+ constructor(options = {}) {
961
+ const merged = {
962
+ ...options,
963
+ reconnectOnError: options.reconnectOnError ?? true,
964
+ };
965
+ super(merged);
966
+ }
967
+ /**
968
+ * Stop the websocket STT service on a graceful end.
969
+ *
970
+ * @param frame The end frame.
971
+ */
972
+ async stop(frame) {
973
+ await super.stop(frame);
974
+ await this._disconnect();
975
+ }
976
+ /**
977
+ * Cancel the websocket STT service immediately.
978
+ *
979
+ * Disconnecting here is the prompt teardown: the websocket receive loop
980
+ * runs independently and keeps reading transcripts from the provider until
981
+ * the socket is closed.
982
+ *
983
+ * @param frame The cancel frame.
984
+ */
985
+ async cancel(frame) {
986
+ await super.cancel(frame);
987
+ await this._disconnect();
988
+ }
989
+ /** Release websocket STT resources at teardown. */
990
+ async cleanup() {
991
+ await super.cleanup();
992
+ await this._disconnect();
993
+ }
994
+ /** Connect and start keepalive task if enabled. */
995
+ async _connect() {
996
+ await super._connect();
997
+ this._createKeepaliveTask();
998
+ }
999
+ /** Disconnect and cancel keepalive task. */
1000
+ async _disconnect() {
1001
+ await super._disconnect();
1002
+ await this._cancelKeepaliveTask();
1003
+ }
1004
+ /**
1005
+ * Disconnect and reconnect the websocket.
1006
+ *
1007
+ * Called by `STTService._reconnect()` inside the reconnecting guard. Tears
1008
+ * down the current websocket connection and re-establishes it. Keepalive
1009
+ * management is handled by `_connect` / `_disconnect`.
1010
+ */
1011
+ async _doReconnect() {
1012
+ await this._disconnect();
1013
+ await this._connect();
1014
+ }
1015
+ /** Check if the websocket is open and ready for keepalive. */
1016
+ _isKeepaliveReady() {
1017
+ return this._websocket !== null && this._websocket.state === State.OPEN;
1018
+ }
1019
+ /**
1020
+ * Send silent audio over the websocket to keep the connection alive.
1021
+ *
1022
+ * The default implementation sends raw PCM bytes directly. Subclasses can
1023
+ * override this to wrap the silence in a service-specific protocol.
1024
+ *
1025
+ * @param silence Silent 16-bit mono PCM audio bytes.
1026
+ */
1027
+ async _sendKeepalive(silence) {
1028
+ if (this._websocket === null) {
1029
+ // should never happen — caller should gate on _isKeepaliveReady()
1030
+ return;
1031
+ }
1032
+ await this._websocket.send(silence);
1033
+ // Keepalive silence is real audio submitted to the service, so it
1034
+ // counts toward usage.
1035
+ this._recordSttAudioUsage(silence);
1036
+ }
1037
+ async _reportError(error, options = {}) {
1038
+ await this._callEventHandler("on_connection_error", error.error);
1039
+ await this.pushErrorFrame(error, {
1040
+ forceTreatAsPermanent: options.forceTreatAsPermanent ?? false,
1041
+ });
1042
+ }
1043
+ }
1044
+ //# sourceMappingURL=stt_service.js.map