@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,974 @@
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/observers/user_bot_latency_observer.py
6
+ /**
7
+ * Observer for tracking user-to-bot response latency.
8
+ *
9
+ * Provides an observer that monitors the time between when a user stops
10
+ * speaking and when the bot starts speaking, emitting events when latency is
11
+ * measured. When `enableMetrics` is true it also collects a breakdown of the
12
+ * interval: per-service metrics (TTFB, text aggregation), and contributions
13
+ * that name each part of the timeline, including the parts no service
14
+ * measures.
15
+ */
16
+ import { time } from "@pipedog/asyncio";
17
+ import { BotStartedSpeakingFrame, ClientConnectedFrame, FunctionCallInProgressFrame, FunctionCallResultFrame, InterruptionFrame, LLMFullResponseStartFrame, LLMMarkerFrame, LLMTextFrame, MetricsFrame, TranscriptionFrame, TTSAudioRawFrame, UserStoppedSpeakingFrame, VADUserStartedSpeakingFrame, VADUserStoppedSpeakingFrame, } from "../frames/frames.js";
18
+ import { TextAggregationMetricsData, TTFBMetricsData } from "../metrics/metrics.js";
19
+ import { FrameDirection } from "../processors/frame_processor.js";
20
+ import { BaseObserver } from "./base_observer.js";
21
+ /**
22
+ * TTFB measurement with timestamp for timeline placement.
23
+ *
24
+ * - `processor`: name of the processor that reported the TTFB.
25
+ * - `model`: optional model name associated with the metric.
26
+ * - `startTime`: Unix timestamp (seconds) when the TTFB measurement started.
27
+ * - `durationSecs`: TTFB duration in seconds.
28
+ */
29
+ export class TTFBBreakdownMetrics {
30
+ processor;
31
+ model;
32
+ startTime;
33
+ durationSecs;
34
+ constructor(options) {
35
+ this.processor = options.processor;
36
+ this.model = options.model ?? null;
37
+ this.startTime = options.startTime;
38
+ this.durationSecs = options.durationSecs;
39
+ }
40
+ }
41
+ /**
42
+ * Text aggregation measurement with timestamp for timeline placement.
43
+ *
44
+ * - `processor`: name of the processor that reported the metric.
45
+ * - `startTime`: Unix timestamp (seconds) when text aggregation started.
46
+ * - `durationSecs`: aggregation duration in seconds.
47
+ */
48
+ export class TextAggregationBreakdownMetrics {
49
+ processor;
50
+ startTime;
51
+ durationSecs;
52
+ constructor(options) {
53
+ this.processor = options.processor;
54
+ this.startTime = options.startTime;
55
+ this.durationSecs = options.durationSecs;
56
+ }
57
+ }
58
+ /**
59
+ * Latency for a single function call execution.
60
+ *
61
+ * - `functionName`: name of the function that was called.
62
+ * - `startTime`: Unix timestamp (seconds) when execution started.
63
+ * - `durationSecs`: time in seconds from execution start to result.
64
+ */
65
+ export class FunctionCallMetrics {
66
+ functionName;
67
+ startTime;
68
+ durationSecs;
69
+ constructor(options) {
70
+ this.functionName = options.functionName;
71
+ this.startTime = options.startTime;
72
+ this.durationSecs = options.durationSecs;
73
+ }
74
+ }
75
+ /**
76
+ * Anything shorter than this is a frame hop rather than work worth naming, so
77
+ * it is rolled into the single pipeline contribution.
78
+ */
79
+ export const MIN_CONTRIBUTION_SECS = 0.005;
80
+ /**
81
+ * Half of the last digit `turnContributionLines()` prints, below which a span
82
+ * would show as zero and so is not part of the timeline at all.
83
+ */
84
+ export const PRINTS_AS_ZERO_SECS = 0.0005;
85
+ /**
86
+ * A point in a user-to-bot cycle that a contribution can start or end at.
87
+ *
88
+ * Most are observed as frames. `LLM_CHUNK` and `SENTENCE` are worked back from
89
+ * a service's own metric, because no frame marks them.
90
+ */
91
+ const _MomentKind = {
92
+ SILENCE: "silence",
93
+ READY: "ready",
94
+ VAD_STOP: "vad stop",
95
+ TRANSCRIPT: "transcript",
96
+ LLM_REQUEST: "llm request",
97
+ LLM_CHUNK: "llm chunk",
98
+ MARKER_COMPLETE: "marker complete",
99
+ MARKER_INCOMPLETE: "marker incomplete",
100
+ HANDLERS_START: "handlers start",
101
+ HANDLERS_END: "handlers end",
102
+ FIRST_TEXT: "first text",
103
+ SENTENCE: "sentence",
104
+ FIRST_AUDIO: "first audio",
105
+ BOT_SPEAKING: "bot speaking",
106
+ };
107
+ /**
108
+ * One moment in a cycle.
109
+ *
110
+ * - `at`: Unix timestamp (seconds) of the moment.
111
+ * - `kind`: what happened.
112
+ * - `source`: name of whatever produced it, used when a span takes its owner
113
+ * from one end.
114
+ * - `derived`: whether it was worked back from a metric rather than observed
115
+ * as a frame. A derived moment sits at or before the frame that reported
116
+ * it, which decides the order when the two share a time.
117
+ */
118
+ class _Moment {
119
+ at;
120
+ kind;
121
+ source;
122
+ derived;
123
+ constructor(options) {
124
+ this.at = options.at;
125
+ this.kind = options.kind;
126
+ this.source = options.source ?? "";
127
+ this.derived = options.derived ?? false;
128
+ }
129
+ }
130
+ const OPENER = Symbol("OPENER");
131
+ const CLOSER = Symbol("CLOSER");
132
+ /** Which end of a span supplies its owner, when a setting does not. */
133
+ const _Owner = { OPENER, CLOSER };
134
+ const _TURN_COMPLETION = "config: filter_incomplete_user_turns";
135
+ /**
136
+ * One stretch of a cycle, named by the moments at its ends.
137
+ *
138
+ * - `start`: the moment the stretch runs from.
139
+ * - `end`: the moment it runs to.
140
+ * - `key`: stable identifier, which survives a label being reworded.
141
+ * - `label`: how the stretch reads in a timeline.
142
+ * - `owner`: the setting that governs the time, the bot whose code spends it,
143
+ * or which end of the span supplies the processor that does.
144
+ * - `core`: whether this is a core stage, which stays listed however brief so
145
+ * the timeline keeps its shape from one turn to the next.
146
+ */
147
+ class _Span {
148
+ start;
149
+ end;
150
+ key;
151
+ label;
152
+ owner;
153
+ core;
154
+ constructor(options) {
155
+ this.start = options.start;
156
+ this.end = options.end;
157
+ this.key = options.key;
158
+ this.label = options.label;
159
+ this.owner = options.owner;
160
+ this.core = options.core;
161
+ }
162
+ }
163
+ // Each pair of adjacent moments names the span between them, and a pair that
164
+ // is absent leaves that stretch unnamed, to be reported as pipeline time.
165
+ const _SPANS = [
166
+ // Silence the VAD had to hear before it would call the turn over. It is a
167
+ // setting rather than a service, and shortening it trades latency for
168
+ // transcription accuracy.
169
+ new _Span({
170
+ start: _MomentKind.SILENCE,
171
+ end: _MomentKind.VAD_STOP,
172
+ key: "endpointing_wait",
173
+ label: "endpointing wait",
174
+ owner: "config: VAD stop_secs",
175
+ core: true,
176
+ }),
177
+ // From a pipeline that is ready with a client on it, to the bot asking for
178
+ // something to say: the application's connected handler, and the frame it
179
+ // queues reaching the LLM.
180
+ new _Span({
181
+ start: _MomentKind.READY,
182
+ end: _MomentKind.LLM_REQUEST,
183
+ key: "first_request",
184
+ label: "first request",
185
+ owner: "bot",
186
+ core: true,
187
+ }),
188
+ new _Span({
189
+ start: _MomentKind.VAD_STOP,
190
+ end: _MomentKind.TRANSCRIPT,
191
+ key: "transcription",
192
+ label: "transcription",
193
+ owner: _Owner.CLOSER,
194
+ core: true,
195
+ }),
196
+ // Whatever decides the user is done: a fixed speech timeout, a smart-turn
197
+ // model's inference, or an external signal.
198
+ new _Span({
199
+ start: _MomentKind.TRANSCRIPT,
200
+ end: _MomentKind.LLM_REQUEST,
201
+ key: "turn_detection",
202
+ label: "turn detection",
203
+ owner: "config: user turn strategies",
204
+ core: true,
205
+ }),
206
+ new _Span({
207
+ start: _MomentKind.LLM_REQUEST,
208
+ end: _MomentKind.LLM_CHUNK,
209
+ key: "llm_inference",
210
+ label: "LLM inference",
211
+ owner: _Owner.OPENER,
212
+ core: true,
213
+ }),
214
+ // The gate reading the verdict, whichever way it went.
215
+ new _Span({
216
+ start: _MomentKind.LLM_CHUNK,
217
+ end: _MomentKind.MARKER_COMPLETE,
218
+ key: "turn_completion",
219
+ label: "turn completion",
220
+ owner: _TURN_COMPLETION,
221
+ core: false,
222
+ }),
223
+ new _Span({
224
+ start: _MomentKind.LLM_CHUNK,
225
+ end: _MomentKind.MARKER_INCOMPLETE,
226
+ key: "turn_completion",
227
+ label: "turn completion",
228
+ owner: _TURN_COMPLETION,
229
+ core: false,
230
+ }),
231
+ // The token the marker occupies before anything can be spoken.
232
+ new _Span({
233
+ start: _MomentKind.MARKER_COMPLETE,
234
+ end: _MomentKind.FIRST_TEXT,
235
+ key: "turn_completion",
236
+ label: "turn completion",
237
+ owner: _TURN_COMPLETION,
238
+ core: false,
239
+ }),
240
+ new _Span({
241
+ start: _MomentKind.MARKER_INCOMPLETE,
242
+ end: _MomentKind.LLM_REQUEST,
243
+ key: "waiting_for_user",
244
+ label: "waiting for user",
245
+ owner: _TURN_COMPLETION,
246
+ core: false,
247
+ }),
248
+ // Between the LLM's first chunk and the call being dispatched, the LLM is
249
+ // still writing the call.
250
+ new _Span({
251
+ start: _MomentKind.LLM_CHUNK,
252
+ end: _MomentKind.HANDLERS_START,
253
+ key: "llm_tool_call",
254
+ label: "LLM tool call",
255
+ owner: _Owner.OPENER,
256
+ core: false,
257
+ }),
258
+ new _Span({
259
+ start: _MomentKind.HANDLERS_START,
260
+ end: _MomentKind.HANDLERS_END,
261
+ key: "function_handler",
262
+ label: "function handler",
263
+ owner: _Owner.CLOSER,
264
+ core: false,
265
+ }),
266
+ // Waiting for a full sentence before speaking any of it.
267
+ new _Span({
268
+ start: _MomentKind.FIRST_TEXT,
269
+ end: _MomentKind.SENTENCE,
270
+ key: "sentence_aggregation",
271
+ label: "sentence aggregation",
272
+ owner: "config: text_aggregation_mode",
273
+ core: false,
274
+ }),
275
+ new _Span({
276
+ start: _MomentKind.SENTENCE,
277
+ end: _MomentKind.FIRST_AUDIO,
278
+ key: "speech_synthesis",
279
+ label: "speech synthesis",
280
+ owner: _Owner.CLOSER,
281
+ core: true,
282
+ }),
283
+ new _Span({
284
+ start: _MomentKind.FIRST_TEXT,
285
+ end: _MomentKind.FIRST_AUDIO,
286
+ key: "speech_synthesis",
287
+ label: "speech synthesis",
288
+ owner: _Owner.CLOSER,
289
+ core: true,
290
+ }),
291
+ new _Span({
292
+ start: _MomentKind.FIRST_AUDIO,
293
+ end: _MomentKind.BOT_SPEAKING,
294
+ key: "output_transport",
295
+ label: "output transport",
296
+ owner: _Owner.CLOSER,
297
+ core: false,
298
+ }),
299
+ ];
300
+ function spanKey(start, end) {
301
+ return `${start}\u0000${end}`;
302
+ }
303
+ const _SPANS_BY_MOMENTS = new Map(_SPANS.map((span) => [spanKey(span.start, span.end), span]));
304
+ // One stretch is two spans rather than one, so it is chosen rather than looked
305
+ // up: where turn completion is in use, a response that carried no marker is
306
+ // buffered whole before anything can be spoken; where it is not, the same wait
307
+ // is the LLM still streaming, so its inference covers it.
308
+ const _AWAITING_SPEAKABLE_TEXT = new _Span({
309
+ start: _MomentKind.LLM_CHUNK,
310
+ end: _MomentKind.FIRST_TEXT,
311
+ key: "awaiting_speakable_text",
312
+ label: "awaiting speakable text",
313
+ owner: _Owner.CLOSER,
314
+ core: false,
315
+ });
316
+ const _LLM_INFERENCE_UNTIL_TEXT = new _Span({
317
+ start: _MomentKind.LLM_CHUNK,
318
+ end: _MomentKind.FIRST_TEXT,
319
+ key: "llm_inference",
320
+ label: "LLM inference",
321
+ owner: _Owner.OPENER,
322
+ core: true,
323
+ });
324
+ /**
325
+ * Where a measured interval was anchored.
326
+ *
327
+ * A greeting is timed from the client connecting, a turn from the user
328
+ * falling silent, so a partial interval is never mistaken for a whole one.
329
+ */
330
+ export const MeasuredFrom = {
331
+ USER_SILENCE: "user_silence",
332
+ CLIENT_CONNECTED: "client_connected",
333
+ };
334
+ /**
335
+ * What kind of thing a contribution's owner is.
336
+ *
337
+ * Grouping on this answers where a turn's time went without matching on the
338
+ * owner's name: a service that could be swapped, a setting the bot chose, the
339
+ * bot's own code, or the pipeline between them.
340
+ */
341
+ export const LatencyOwnerKind = {
342
+ SERVICE: "service",
343
+ SETTING: "setting",
344
+ BOT: "bot",
345
+ PIPELINE: "pipeline",
346
+ };
347
+ /**
348
+ * One named part of the user-to-bot interval.
349
+ *
350
+ * Contributions account for the whole interval, so their durations sum to the
351
+ * measured latency. Unlike a TTFB, which reports what a service spent once it
352
+ * was asked, a contribution can also name time no service is measuring: the
353
+ * silence a VAD waits out, the tokens a turn-completion marker occupies before
354
+ * any speakable text, or a hold while the pipeline waits for the user to
355
+ * finish a sentence.
356
+ *
357
+ * - `key`: stable identifier for this part of a turn. Unlike `label`, it is
358
+ * safe to group on: it survives the label being reworded.
359
+ * - `label`: what the time was spent on.
360
+ * - `owner`: what spent it — a processor name, or a `config:` tag naming the
361
+ * setting that governs it.
362
+ * - `ownerKind`: what kind of thing the owner is.
363
+ * - `startTime`: Unix timestamp (seconds) when it started.
364
+ * - `durationSecs`: how long it took, in seconds.
365
+ */
366
+ export class LatencyContribution {
367
+ key;
368
+ label;
369
+ owner;
370
+ ownerKind;
371
+ startTime;
372
+ durationSecs;
373
+ constructor(options) {
374
+ this.key = options.key;
375
+ this.label = options.label;
376
+ this.owner = options.owner;
377
+ this.ownerKind = options.ownerKind;
378
+ this.startTime = options.startTime;
379
+ this.durationSecs = options.durationSecs;
380
+ }
381
+ }
382
+ /**
383
+ * Per-service latency breakdown for a single user-to-bot cycle.
384
+ *
385
+ * Collected between `VADUserStoppedSpeakingFrame` and
386
+ * `BotStartedSpeakingFrame` when `enableMetrics` is true in `PipelineParams`.
387
+ *
388
+ * - `ttfb`: time-to-first-byte metrics from each service in the pipeline.
389
+ * - `textAggregation`: first text aggregation measurement, representing the
390
+ * latency cost of sentence aggregation in the TTS pipeline.
391
+ * - `userTurnStartTime`: Unix timestamp when the user turn started (actual
392
+ * user silence, adjusted for VAD stop_secs). null if no
393
+ * `VADUserStoppedSpeakingFrame` was observed.
394
+ * - `userTurnSecs`: duration in seconds of the user's turn, measured from
395
+ * when the user actually stopped speaking to when the turn was released
396
+ * (`UserStoppedSpeakingFrame`). This includes VAD silence detection, STT
397
+ * finalization, and any turn analyzer wait. null if no
398
+ * `UserStoppedSpeakingFrame` was observed (e.g. no turn analyzer
399
+ * configured).
400
+ * - `functionCalls`: latency for each function call executed during this
401
+ * cycle. Empty if no function calls occurred.
402
+ * - `contributions`: named parts of the interval, in chronological order,
403
+ * summing to the measured latency. A part is listed only if it happened, so
404
+ * a bot without turn completion has no marker or wait entries.
405
+ * - `measuredFrom`: where the interval was anchored, so a greeting is not
406
+ * compared with a turn.
407
+ * - `totalSecs`: the measured interval, which the contributions sum to.
408
+ */
409
+ export class LatencyBreakdown {
410
+ ttfb;
411
+ contributions;
412
+ measuredFrom;
413
+ totalSecs;
414
+ textAggregation;
415
+ userTurnStartTime;
416
+ userTurnSecs;
417
+ functionCalls;
418
+ constructor(options = {}) {
419
+ this.ttfb = options.ttfb ?? [];
420
+ this.contributions = options.contributions ?? [];
421
+ this.measuredFrom = options.measuredFrom ?? null;
422
+ this.totalSecs = options.totalSecs ?? 0.0;
423
+ this.textAggregation = options.textAggregation ?? null;
424
+ this.userTurnStartTime = options.userTurnStartTime ?? null;
425
+ this.userTurnSecs = options.userTurnSecs ?? null;
426
+ this.functionCalls = options.functionCalls ?? [];
427
+ }
428
+ /**
429
+ * Format the contributions for logging, one per line plus a total.
430
+ *
431
+ * One breakdown covers one user-to-bot cycle, so these lines describe a
432
+ * single turn — or, for the first thing the bot says, the wait before it
433
+ * from the pipeline being ready with a client on it, which the total line
434
+ * names.
435
+ *
436
+ * @param options.byCost Order by duration, largest first, rather than in
437
+ * the order things happened. Useful when the question is what to optimize
438
+ * rather than what the turn did.
439
+ * @returns One formatted line per contribution, then a total line. Empty if
440
+ * no contributions were collected.
441
+ */
442
+ turnContributionLines(options = {}) {
443
+ if (this.contributions.length === 0) {
444
+ return [];
445
+ }
446
+ const ordered = options.byCost
447
+ ? [...this.contributions].sort((a, b) => b.durationSecs - a.durationSecs)
448
+ : this.contributions;
449
+ const lines = ordered.map((c) => `${formatSecs(c.durationSecs)}s ${c.label.padEnd(20)} [${c.owner}]`);
450
+ const total = this.contributions.reduce((sum, c) => sum + c.durationSecs, 0);
451
+ const anchor = this.measuredFrom === MeasuredFrom.CLIENT_CONNECTED ? " (from client connected)" : "";
452
+ lines.push(`${formatSecs(total)}s TOTAL${anchor}`);
453
+ return lines;
454
+ }
455
+ }
456
+ /** Python's `f"{secs:6.3f}"`. */
457
+ function formatSecs(secs) {
458
+ return secs.toFixed(3).padStart(6);
459
+ }
460
+ /**
461
+ * Observer that tracks user-to-bot response latency.
462
+ *
463
+ * Measures the time between when a user stops speaking
464
+ * (VADUserStoppedSpeakingFrame) and when the bot starts speaking
465
+ * (BotStartedSpeakingFrame). Emits events when latency is measured, allowing
466
+ * consumers to log, trace, or otherwise process the latency data.
467
+ *
468
+ * When `enableMetrics` is true in pipeline params, also collects a latency
469
+ * breakdown and emits an `on_latency_breakdown` event alongside the existing
470
+ * latency measurement. The breakdown carries per-service metrics (TTFB, text
471
+ * aggregation) and, in `contributions`, the whole interval named part by part:
472
+ *
473
+ * @example
474
+ * for (const line of breakdown.turnContributionLines()) {
475
+ * logger.info(line);
476
+ * }
477
+ * // 0.200s endpointing wait [config: VAD stop_secs]
478
+ * // 0.125s transcription [DeepgramSTTService#0]
479
+ * // 0.336s LLM inference [OpenAILLMService#0]
480
+ * // 0.359s speech synthesis [CartesiaTTSService#0]
481
+ * // 1.020s TOTAL
482
+ *
483
+ * This observer follows the composition pattern used by
484
+ * TurnTrackingObserver, acting as a reusable component for latency
485
+ * measurement.
486
+ *
487
+ * Events:
488
+ *
489
+ * - on_latency_measured(observer, latencySeconds): emitted when
490
+ * time-to-first-bot-speech is calculated. Measures the time from when the
491
+ * user stopped speaking to when the bot starts speaking.
492
+ * - on_latency_breakdown(observer, breakdown): emitted at each
493
+ * `BotStartedSpeakingFrame` with a `LatencyBreakdown` containing
494
+ * per-service metrics collected during the user→bot cycle.
495
+ * - on_first_bot_speech_latency(observer, latencySeconds): emitted once, the
496
+ * first time `BotStartedSpeakingFrame` arrives after `ClientConnectedFrame`.
497
+ * Measures the time from client connection to the first bot speech.
498
+ */
499
+ export class UserBotLatencyObserver extends BaseObserver {
500
+ _minContributionSecs;
501
+ _now;
502
+ _userStoppedTime = null;
503
+ _userTurnStartTime = null;
504
+ _userTurn = null;
505
+ // First bot speech tracking
506
+ _clientConnectedTime = null;
507
+ _pipelineStartedTime = null;
508
+ _firstBotSpeechMeasured = false;
509
+ // The moments of the cycle, in the order they were observed.
510
+ _moments = [];
511
+ _llmRequest = null;
512
+ // Whether this bot uses turn completion at all, which a marker frame
513
+ // proves. Not per-cycle: a bot that emits markers keeps using them.
514
+ _markersSeen = false;
515
+ // Per-cycle metric accumulators
516
+ _ttfb = [];
517
+ _textAggregation = null;
518
+ _functionCallStarts = new Map();
519
+ _functionCallMetrics = [];
520
+ /**
521
+ * Initialize the user-bot latency observer.
522
+ *
523
+ * @param options.minContributionSecs Contributions shorter than this are
524
+ * rolled into the single pipeline entry rather than listed.
525
+ * @param options.timeSource Reads the current time in seconds.
526
+ */
527
+ constructor(options = {}) {
528
+ super({ ...options, observeEveryPush: false });
529
+ this._minContributionSecs = options.minContributionSecs ?? MIN_CONTRIBUTION_SECS;
530
+ this._now = options.timeSource ?? time;
531
+ this._registerEventHandler("on_latency_measured");
532
+ this._registerEventHandler("on_latency_breakdown");
533
+ this._registerEventHandler("on_first_bot_speech_latency");
534
+ }
535
+ /**
536
+ * Record that the pipeline finished starting.
537
+ *
538
+ * The `StartFrame` has reached every processor by now, which is where
539
+ * `StartupTimingObserver` stops measuring. A greeting is timed from here so
540
+ * the two reports meet rather than overlap.
541
+ */
542
+ async onPipelineStarted() {
543
+ if (this._pipelineStartedTime === null) {
544
+ this._pipelineStartedTime = this._now();
545
+ }
546
+ }
547
+ /**
548
+ * Process frames to track speech timing and calculate latency.
549
+ *
550
+ * Tracks VAD events and bot speaking events to measure the time between
551
+ * user stopping speech and bot starting speech. Also accumulates metrics
552
+ * from MetricsFrame for the latency breakdown.
553
+ *
554
+ * @param data Frame push event containing the frame and direction information.
555
+ */
556
+ async onPushFrame(data) {
557
+ // Only process downstream frames
558
+ if (data.direction !== FrameDirection.DOWNSTREAM) {
559
+ return;
560
+ }
561
+ const frame = data.frame;
562
+ // Track client connection (first occurrence only)
563
+ if (frame instanceof ClientConnectedFrame) {
564
+ if (this._clientConnectedTime === null) {
565
+ this._clientConnectedTime = this._now();
566
+ }
567
+ return;
568
+ }
569
+ // Track speech and pipeline events for latency
570
+ if (frame instanceof VADUserStartedSpeakingFrame) {
571
+ // Reset when user starts speaking
572
+ this._userStoppedTime = null;
573
+ this._userTurnStartTime = null;
574
+ this._userTurn = null;
575
+ this._resetAccumulators();
576
+ // If user speaks before the bot's first speech, abandon the
577
+ // first-bot-speech measurement — it's only meaningful for greetings.
578
+ this._firstBotSpeechMeasured = true;
579
+ }
580
+ else if (frame instanceof VADUserStoppedSpeakingFrame) {
581
+ // Record the actual time the user stopped speaking, which is the VAD
582
+ // determination time minus the stop_secs silence duration that had to
583
+ // elapse before the VAD confirmed speech ended.
584
+ this._userStoppedTime = frame.timestamp - frame.stopSecs;
585
+ this._userTurnStartTime = this._userStoppedTime;
586
+ this._mark(_MomentKind.VAD_STOP, { at: frame.timestamp });
587
+ }
588
+ else if (frame instanceof UserStoppedSpeakingFrame) {
589
+ // Measure the user turn duration: from actual user silence to turn
590
+ // release. Includes VAD silence detection, STT finalization, and any
591
+ // turn analyzer wait.
592
+ if (this._userStoppedTime !== null) {
593
+ this._userTurn = this._now() - this._userStoppedTime;
594
+ }
595
+ }
596
+ else if (frame instanceof TranscriptionFrame) {
597
+ // A service can finalize an utterance in several pieces. The turn is
598
+ // not detectable until the last of them, which is also where the
599
+ // service stops its own TTFB clock, so a later one replaces the earlier
600
+ // until the LLM is asked.
601
+ if (!this._seen(_MomentKind.LLM_REQUEST)) {
602
+ this._moments = this._moments.filter((m) => m.kind !== _MomentKind.TRANSCRIPT);
603
+ this._mark(_MomentKind.TRANSCRIPT, { source: data.source.name });
604
+ }
605
+ }
606
+ else if (frame instanceof LLMFullResponseStartFrame) {
607
+ this._llmRequest = this._mark(_MomentKind.LLM_REQUEST, { source: data.source.name });
608
+ }
609
+ else if (frame instanceof LLMMarkerFrame) {
610
+ // A stand-alone marker holds the turn open; one that prefixes a
611
+ // response is the completion the pipeline was waiting for.
612
+ this._markersSeen = true;
613
+ // A turn can be held more than once before it completes, so every
614
+ // incomplete verdict is recorded; only the completion is kept once.
615
+ this._mark(frame.appendToContextImmediately
616
+ ? _MomentKind.MARKER_INCOMPLETE
617
+ : _MomentKind.MARKER_COMPLETE, { once: !frame.appendToContextImmediately });
618
+ }
619
+ else if (frame instanceof LLMTextFrame) {
620
+ this._mark(_MomentKind.FIRST_TEXT, { source: data.source.name, once: true });
621
+ }
622
+ else if (frame instanceof TTSAudioRawFrame) {
623
+ this._mark(_MomentKind.FIRST_AUDIO, { source: data.source.name, once: true });
624
+ }
625
+ else if (frame instanceof InterruptionFrame) {
626
+ // Discard stale metrics from cancelled LLM/TTS cycles
627
+ this._resetAccumulators();
628
+ }
629
+ else if (frame instanceof FunctionCallInProgressFrame) {
630
+ this._functionCallStarts.set(frame.toolCallId, [frame.functionName, this._now()]);
631
+ }
632
+ else if (frame instanceof FunctionCallResultFrame) {
633
+ const start = this._functionCallStarts.get(frame.toolCallId);
634
+ this._functionCallStarts.delete(frame.toolCallId);
635
+ if (start !== undefined) {
636
+ const [functionName, startTime] = start;
637
+ this._functionCallMetrics.push(new FunctionCallMetrics({
638
+ functionName,
639
+ startTime,
640
+ durationSecs: this._now() - startTime,
641
+ }));
642
+ }
643
+ }
644
+ else if (frame instanceof MetricsFrame) {
645
+ this._handleMetricsFrame(frame);
646
+ }
647
+ else if (frame instanceof BotStartedSpeakingFrame) {
648
+ this._mark(_MomentKind.BOT_SPEAKING, { source: data.source.name, once: true });
649
+ await this._handleBotStartedSpeaking();
650
+ }
651
+ }
652
+ /**
653
+ * Record a moment, if a cycle is being measured.
654
+ *
655
+ * @param kind What happened.
656
+ * @param options.at When, defaulting to now. Derived moments pass their own time.
657
+ * @param options.source Name of whatever produced it.
658
+ * @param options.once Keep only the first of this kind, for frames that
659
+ * repeat within a cycle such as text and audio.
660
+ * @param options.derived Whether it comes from a metric rather than from a frame.
661
+ * @returns The recorded moment, or null if it was not recorded.
662
+ */
663
+ _mark(kind, options = {}) {
664
+ if (!this._measuring) {
665
+ return null;
666
+ }
667
+ if (options.once && this._seen(kind)) {
668
+ return null;
669
+ }
670
+ const moment = new _Moment({
671
+ at: options.at ?? this._now(),
672
+ kind,
673
+ source: options.source ?? "",
674
+ derived: options.derived ?? false,
675
+ });
676
+ this._moments.push(moment);
677
+ return moment;
678
+ }
679
+ /** Whether a moment of this kind has been recorded this cycle. */
680
+ _seen(kind) {
681
+ return this._moments.some((m) => m.kind === kind);
682
+ }
683
+ /** Whether a cycle is open: a user turn, or the wait for the greeting. */
684
+ get _measuring() {
685
+ if (this._userStoppedTime !== null) {
686
+ return true;
687
+ }
688
+ return this._clientConnectedTime !== null && !this._firstBotSpeechMeasured;
689
+ }
690
+ /**
691
+ * Moments that come from metrics rather than from frames.
692
+ *
693
+ * Handlers dispatched together run concurrently, so they become one pair of
694
+ * moments named for the one the wait actually depends on: listing each
695
+ * would count the same stretch of wall clock more than once.
696
+ *
697
+ * @returns The sentence-aggregation and function-handler moments, if any.
698
+ */
699
+ _derivedMoments() {
700
+ const moments = [];
701
+ const firstText = this._moments.find((m) => m.kind === _MomentKind.FIRST_TEXT);
702
+ if (firstText && this._textAggregation && this._textAggregation.durationSecs > 0) {
703
+ moments.push(new _Moment({
704
+ at: firstText.at + this._textAggregation.durationSecs,
705
+ kind: _MomentKind.SENTENCE,
706
+ derived: true,
707
+ }));
708
+ }
709
+ for (const group of this._concurrentHandlers()) {
710
+ // max() keeps the first of equal values.
711
+ let slowest = group[0];
712
+ for (const c of group) {
713
+ if (c.durationSecs > slowest.durationSecs)
714
+ slowest = c;
715
+ }
716
+ let owner = slowest.functionName;
717
+ if (group.length > 1) {
718
+ owner += ` +${group.length - 1}`;
719
+ }
720
+ moments.push(new _Moment({
721
+ at: Math.min(...group.map((c) => c.startTime)),
722
+ kind: _MomentKind.HANDLERS_START,
723
+ derived: true,
724
+ }));
725
+ moments.push(new _Moment({
726
+ at: Math.max(...group.map((c) => c.startTime + c.durationSecs)),
727
+ kind: _MomentKind.HANDLERS_END,
728
+ source: owner,
729
+ derived: true,
730
+ }));
731
+ }
732
+ return moments;
733
+ }
734
+ /**
735
+ * Group function handlers whose executions overlap in time.
736
+ *
737
+ * @returns Groups of overlapping calls, in the order they started.
738
+ */
739
+ _concurrentHandlers() {
740
+ const groups = [];
741
+ const sorted = [...this._functionCallMetrics].sort((a, b) => a.startTime - b.startTime);
742
+ for (const call of sorted) {
743
+ const last = groups[groups.length - 1];
744
+ if (last && call.startTime <= Math.max(...last.map((c) => c.startTime + c.durationSecs))) {
745
+ last.push(call);
746
+ }
747
+ else {
748
+ groups.push([call]);
749
+ }
750
+ }
751
+ return groups;
752
+ }
753
+ /**
754
+ * Name each stretch between one moment and the next.
755
+ *
756
+ * Spans run from one moment to the following one, so they tile the cycle
757
+ * and cannot overlap. A stretch whose pair of moments has no entry in the
758
+ * table is left unnamed and reported as pipeline time, which is what makes
759
+ * a gap in the accounting visible rather than silently absorbed.
760
+ *
761
+ * @returns Contributions in chronological order, or empty if the cycle was
762
+ * never measured.
763
+ */
764
+ _buildContributions() {
765
+ let start = this._userTurnStartTime;
766
+ if (start === null) {
767
+ // A greeting begins once the pipeline is ready and someone is there to
768
+ // hear it, whichever came second: before the pipeline started, the
769
+ // startup report has the time; before a client connected, the bot was
770
+ // waiting on a person rather than working.
771
+ const ready = [this._clientConnectedTime, this._pipelineStartedTime].filter((moment) => moment !== null);
772
+ start = ready.length > 0 ? Math.max(...ready) : null;
773
+ }
774
+ if (start === null || !this._seen(_MomentKind.BOT_SPEAKING)) {
775
+ return [];
776
+ }
777
+ const cycleStart = start;
778
+ const moments = [
779
+ new _Moment({
780
+ at: cycleStart,
781
+ kind: this._userTurnStartTime !== null ? _MomentKind.SILENCE : _MomentKind.READY,
782
+ }),
783
+ ...this._moments.filter((m) => m.at >= cycleStart),
784
+ ...this._derivedMoments(),
785
+ ].sort((a, b) => a.at - b.at || Number(!a.derived) - Number(!b.derived));
786
+ let spans = [];
787
+ const core = new Set();
788
+ for (let i = 0; i + 1 < moments.length; i++) {
789
+ const opener = moments[i];
790
+ const closer = moments[i + 1];
791
+ if (closer.at <= opener.at) {
792
+ continue;
793
+ }
794
+ let span;
795
+ if (opener.kind === _MomentKind.LLM_CHUNK && closer.kind === _MomentKind.FIRST_TEXT) {
796
+ span = this._markersSeen ? _AWAITING_SPEAKABLE_TEXT : _LLM_INFERENCE_UNTIL_TEXT;
797
+ }
798
+ else {
799
+ span = _SPANS_BY_MOMENTS.get(spanKey(opener.kind, closer.kind));
800
+ }
801
+ if (span === undefined) {
802
+ continue;
803
+ }
804
+ let owner;
805
+ let ownerKind = LatencyOwnerKind.SERVICE;
806
+ if (span.owner === _Owner.OPENER) {
807
+ owner = opener.source;
808
+ }
809
+ else if (span.owner === _Owner.CLOSER) {
810
+ owner = closer.source;
811
+ }
812
+ else {
813
+ owner = span.owner;
814
+ // A literal owner is not a processor: either the setting that governs
815
+ // the time, or the bot's own code.
816
+ ownerKind = owner.startsWith("config: ") ? LatencyOwnerKind.SETTING : LatencyOwnerKind.BOT;
817
+ }
818
+ if (span.core) {
819
+ core.add(span.label);
820
+ }
821
+ spans.push(new LatencyContribution({
822
+ key: span.key,
823
+ label: span.label,
824
+ owner,
825
+ ownerKind,
826
+ startTime: opener.at,
827
+ durationSecs: closer.at - opener.at,
828
+ }));
829
+ }
830
+ spans = UserBotLatencyObserver._mergeAdjacent(spans);
831
+ // A core stage stays listed however brief, but nothing is listed that
832
+ // would print as zero: a stage that took no measurable time is not part of
833
+ // the timeline.
834
+ const named = spans.filter((c) => c.durationSecs >= this._minContributionSecs ||
835
+ (core.has(c.label) && c.durationSecs >= PRINTS_AS_ZERO_SECS));
836
+ // Time the named stages did not cover, which includes the spans the
837
+ // threshold filtered out. It is spread across the cycle rather than
838
+ // sitting anywhere in it, so it is listed last, and it is listed whenever
839
+ // there is any of it, so the contributions always sum to the interval
840
+ // they describe.
841
+ const last = moments[moments.length - 1];
842
+ const pipelineSecs = last.at - cycleStart - named.reduce((sum, c) => sum + c.durationSecs, 0);
843
+ if (pipelineSecs >= PRINTS_AS_ZERO_SECS) {
844
+ named.push(new LatencyContribution({
845
+ key: "pipeline",
846
+ label: "pipeline",
847
+ owner: "pipecat",
848
+ ownerKind: LatencyOwnerKind.PIPELINE,
849
+ startTime: cycleStart,
850
+ durationSecs: pipelineSecs,
851
+ }));
852
+ }
853
+ return named;
854
+ }
855
+ /**
856
+ * Join neighbouring spans that say the same thing.
857
+ *
858
+ * A marker sits in the middle of what a reader thinks of as one stretch, so
859
+ * the table names both halves the same way and they are joined here. Only
860
+ * spans that meet are joined: two of the same name either side of an
861
+ * unnamed gap stay apart, so the gap is reported rather than absorbed.
862
+ *
863
+ * @param spans Contributions in chronological order.
864
+ * @returns The same coverage, with runs of one label and owner joined.
865
+ */
866
+ static _mergeAdjacent(spans) {
867
+ const merged = [];
868
+ for (const span of spans) {
869
+ const previous = merged.length > 0 ? merged[merged.length - 1] : null;
870
+ const touching = previous !== null &&
871
+ span.startTime - (previous.startTime + previous.durationSecs) < PRINTS_AS_ZERO_SECS;
872
+ if (previous && touching && previous.label === span.label && previous.owner === span.owner) {
873
+ previous.durationSecs = span.startTime + span.durationSecs - previous.startTime;
874
+ }
875
+ else {
876
+ merged.push(span);
877
+ }
878
+ }
879
+ return merged;
880
+ }
881
+ /** Handle BotStartedSpeakingFrame to emit latency and breakdown. */
882
+ async _handleBotStartedSpeaking() {
883
+ let emitBreakdown = false;
884
+ // One-time first bot speech measurement (client connect → first speech)
885
+ if (this._clientConnectedTime !== null && !this._firstBotSpeechMeasured) {
886
+ this._firstBotSpeechMeasured = true;
887
+ const latency = this._now() - this._clientConnectedTime;
888
+ await this._callEventHandler("on_first_bot_speech_latency", latency);
889
+ emitBreakdown = true;
890
+ }
891
+ if (this._userStoppedTime !== null) {
892
+ const latency = this._now() - this._userStoppedTime;
893
+ this._userStoppedTime = null;
894
+ await this._callEventHandler("on_latency_measured", latency);
895
+ emitBreakdown = true;
896
+ }
897
+ if (emitBreakdown) {
898
+ const contributions = this._buildContributions();
899
+ const breakdown = new LatencyBreakdown({
900
+ ttfb: [...this._ttfb],
901
+ contributions,
902
+ measuredFrom: contributions.length > 0
903
+ ? this._userTurnStartTime !== null
904
+ ? MeasuredFrom.USER_SILENCE
905
+ : MeasuredFrom.CLIENT_CONNECTED
906
+ : null,
907
+ totalSecs: contributions.reduce((sum, c) => sum + c.durationSecs, 0),
908
+ textAggregation: this._textAggregation,
909
+ userTurnStartTime: this._userTurnStartTime,
910
+ userTurnSecs: this._userTurn,
911
+ functionCalls: [...this._functionCallMetrics],
912
+ });
913
+ await this._callEventHandler("on_latency_breakdown", breakdown);
914
+ this._resetAccumulators();
915
+ }
916
+ }
917
+ /**
918
+ * Extract latency metrics from a MetricsFrame.
919
+ *
920
+ * Accumulates metrics when a measurement is in progress: either a user→bot
921
+ * cycle (after `VADUserStoppedSpeakingFrame`) or the first-bot-speech
922
+ * window (after `ClientConnectedFrame`).
923
+ */
924
+ _handleMetricsFrame(frame) {
925
+ const waitingForFirstSpeech = this._clientConnectedTime !== null && !this._firstBotSpeechMeasured;
926
+ if (this._userStoppedTime === null && !waitingForFirstSpeech) {
927
+ return;
928
+ }
929
+ const now = this._now();
930
+ for (const metricsData of frame.data) {
931
+ if (metricsData instanceof TTFBMetricsData && metricsData.value > 0) {
932
+ this._ttfb.push(new TTFBBreakdownMetrics({
933
+ processor: metricsData.processor,
934
+ model: metricsData.model,
935
+ startTime: now - metricsData.value,
936
+ durationSecs: metricsData.value,
937
+ }));
938
+ if (this._llmRequest && metricsData.processor === this._llmRequest.source) {
939
+ // The first chunk landed before this metric was pushed, so cap it
940
+ // there rather than letting a derived moment sort after the frames
941
+ // that followed it.
942
+ this._mark(_MomentKind.LLM_CHUNK, {
943
+ at: Math.min(this._llmRequest.at + metricsData.value, now),
944
+ source: this._llmRequest.source,
945
+ derived: true,
946
+ });
947
+ }
948
+ }
949
+ else if (metricsData instanceof TextAggregationMetricsData) {
950
+ // Only keep the first measurement — it's the one that impacts the
951
+ // initial speaking latency.
952
+ if (this._textAggregation === null) {
953
+ this._textAggregation = new TextAggregationBreakdownMetrics({
954
+ processor: metricsData.processor,
955
+ startTime: now - metricsData.value,
956
+ durationSecs: metricsData.value,
957
+ });
958
+ }
959
+ }
960
+ }
961
+ }
962
+ /** Clear per-cycle metric accumulators. */
963
+ _resetAccumulators() {
964
+ this._moments = [];
965
+ this._llmRequest = null;
966
+ this._ttfb = [];
967
+ this._textAggregation = null;
968
+ this._userTurnStartTime = null;
969
+ this._userTurn = null;
970
+ this._functionCallStarts = new Map();
971
+ this._functionCallMetrics = [];
972
+ }
973
+ }
974
+ //# sourceMappingURL=user_bot_latency_observer.js.map