dv-pipecat-flows 0.0.0.dev2764__tar.gz → 0.0.0.dev2766__tar.gz

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 (127) hide show
  1. {dv_pipecat_flows-0.0.0.dev2764/src/dv_pipecat_flows.egg-info → dv_pipecat_flows-0.0.0.dev2766}/PKG-INFO +1 -1
  2. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766/src/dv_pipecat_flows.egg-info}/PKG-INFO +1 -1
  3. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/dv_pipecat_flows.egg-info/scm_file_list.json +1 -0
  4. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/dv_pipecat_flows.egg-info/scm_version.json +2 -2
  5. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/processors/user_turn_observer.py +32 -11
  6. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.agents/skills/loki-logs/SKILL.md +0 -0
  7. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.agents/skills/loki-logs/query-reference.md +0 -0
  8. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.claude/skills/loki-logs/SKILL.md +0 -0
  9. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.claude/skills/loki-logs/query-reference.md +0 -0
  10. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.gitattributes +0 -0
  11. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.gitignore +0 -0
  12. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.pre-commit-config.yaml +0 -0
  13. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.python-version +0 -0
  14. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/.readthedocs.yaml +0 -0
  15. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/AGENTS.md +0 -0
  16. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/CHANGELOG.md +0 -0
  17. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/CLAUDE.md +0 -0
  18. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/CONTRIBUTING.md +0 -0
  19. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/LICENSE +0 -0
  20. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/MANIFEST.in +0 -0
  21. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/README.md +0 -0
  22. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/.env.example +0 -0
  23. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/.gitignore +0 -0
  24. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/README.md +0 -0
  25. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/ARCHITECTURE.md +0 -0
  26. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/CODE_MAP.md +0 -0
  27. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/CONFIG.md +0 -0
  28. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/CONTRACTS.md +0 -0
  29. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/COPILOT_TODO.md +0 -0
  30. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/DISTRIBUTION.md +0 -0
  31. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/HLD.md +0 -0
  32. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/METHODS.md +0 -0
  33. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/docs/VOICE_E2E.md +0 -0
  34. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/ANALYSIS_stagehand_vs_hybrid.md +0 -0
  35. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/RESULTS.md +0 -0
  36. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/_env.py +0 -0
  37. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/bench.py +0 -0
  38. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/bench_browseruse.py +0 -0
  39. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/bench_stagehand.py +0 -0
  40. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/inspect_browseruse.py +0 -0
  41. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/inspect_browseruse_showcase.py +0 -0
  42. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/inspect_stagehand_prompt.py +0 -0
  43. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/pages/checkout.html +0 -0
  44. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/pages/dashboard.html +0 -0
  45. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/pages/signup.html +0 -0
  46. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/results_browseruse.json +0 -0
  47. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/results_perception.json +0 -0
  48. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/results_stagehand.json +0 -0
  49. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/tasks.py +0 -0
  50. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/try_hybrid.py +0 -0
  51. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/try_hybrid_multistep.py +0 -0
  52. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/eval/try_hybrid_oneshot.py +0 -0
  53. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/pyproject.toml +0 -0
  54. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/scripts/dev-stack.sh +0 -0
  55. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/browser_copilot/uv.lock +0 -0
  56. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/dev-requirements.txt +0 -0
  57. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/docker-compose.dev.yml +0 -0
  58. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/.eslintrc.json +0 -0
  59. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/.prettierrc +0 -0
  60. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/css/tailwind.css +0 -0
  61. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/examples/food_ordering.json +0 -0
  62. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/examples/movie_explorer.json +0 -0
  63. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/examples/patient_intake.json +0 -0
  64. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/examples/restaurant_reservation.json +0 -0
  65. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/examples/travel_planner.json +0 -0
  66. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/favicon.png +0 -0
  67. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/favicon.svg +0 -0
  68. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/index.html +0 -0
  69. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/editor/canvas.js +0 -0
  70. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/editor/editorState.js +0 -0
  71. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/editor/sidePanel.js +0 -0
  72. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/editor/toolbar.js +0 -0
  73. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/main.js +0 -0
  74. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/baseNode.js +0 -0
  75. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/endNode.js +0 -0
  76. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/flowNode.js +0 -0
  77. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/functionNode.js +0 -0
  78. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/index.js +0 -0
  79. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/mergeNode.js +0 -0
  80. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/nodes/startNode.js +0 -0
  81. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/types.js +0 -0
  82. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/utils/export.js +0 -0
  83. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/utils/helpers.js +0 -0
  84. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/utils/import.js +0 -0
  85. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/js/utils/validation.js +0 -0
  86. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/jsdoc.json +0 -0
  87. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/package-lock.json +0 -0
  88. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/package.json +0 -0
  89. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/postcss.config.cjs +0 -0
  90. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/public/favicon.png +0 -0
  91. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/public/favicon.svg +0 -0
  92. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/tailwind.config.cjs +0 -0
  93. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/vercel.json +0 -0
  94. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/editor/vite.config.js +0 -0
  95. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/engine_primitives_plan.md +0 -0
  96. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/env.example +0 -0
  97. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/images/food-ordering-flow.png +0 -0
  98. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/pipecat-flows.png +0 -0
  99. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/pipecat_upgrade.md +0 -0
  100. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/pyproject.toml +0 -0
  101. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/remote-asterisk-code/README.md +0 -0
  102. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/remote-asterisk-code/extensions.conf +0 -0
  103. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/remote-asterisk-code/rtp.conf +0 -0
  104. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/requirements.txt +0 -0
  105. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/scripts/azure_latency_probe.py +0 -0
  106. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/scripts/check-pypi-package.py +0 -0
  107. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/scripts/fix-ruff.sh +0 -0
  108. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/scripts/langfuse_export.py +0 -0
  109. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/scripts/pre-commit.sh +0 -0
  110. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/setup.cfg +0 -0
  111. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/dv_pipecat_flows.egg-info/SOURCES.txt +0 -0
  112. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/dv_pipecat_flows.egg-info/dependency_links.txt +0 -0
  113. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/dv_pipecat_flows.egg-info/requires.txt +0 -0
  114. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/dv_pipecat_flows.egg-info/top_level.txt +0 -0
  115. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/__init__.py +0 -0
  116. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/actions.py +0 -0
  117. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/adapters.py +0 -0
  118. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/condition_evaluator.py +0 -0
  119. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/exceptions.py +0 -0
  120. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/flow_validator.py +0 -0
  121. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/manager.py +0 -0
  122. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/processors/__init__.py +0 -0
  123. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/processors/router_mode_guard.py +0 -0
  124. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/processors/speak_interruption_guard.py +0 -0
  125. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/router_mode.py +0 -0
  126. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/src/pipecat_flows/types.py +0 -0
  127. {dv_pipecat_flows-0.0.0.dev2764 → dv_pipecat_flows-0.0.0.dev2766}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-flows
3
- Version: 0.0.0.dev2764
3
+ Version: 0.0.0.dev2766
4
4
  Summary: Conversation Flow management for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat-flows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-flows
3
- Version: 0.0.0.dev2764
3
+ Version: 0.0.0.dev2766
4
4
  Summary: Conversation Flow management for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat-flows
@@ -642,6 +642,7 @@
642
642
  "tests/test_router_mode_guard.py",
643
643
  "tests/test_router_no_unclear.py",
644
644
  "tests/test_tts_split_cache.py",
645
+ "tests/test_user_turn_observer.py",
645
646
  "tests/test_voicemail_detector.py",
646
647
  "uv.lock"
647
648
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "0.0.0.dev0",
3
- "distance": 2764,
4
- "node": "gb247b6b890a5ece19939aba5a216415b690f8e87",
3
+ "distance": 2766,
4
+ "node": "g2f9ead0f53048f6c04d0647e37cff236bd770e14",
5
5
  "dirty": false,
6
6
  "branch": "dv-stage",
7
7
  "node_date": "2026-08-08"
@@ -1,4 +1,5 @@
1
- """Observer that timestamps :class:`OpenAILLMContextFrame` so router-mode
1
+ """Observer that timestamps the user-turn-committed context frame
2
+ (:class:`OpenAILLMContextFrame` **or** :class:`LLMContextFrame`) so router-mode
2
3
  nodes can detect "user turn is now committed to context" without reaching
3
4
  into the user context aggregator's private state.
4
5
 
@@ -7,22 +8,27 @@ Why an observer, not a FrameProcessor:
7
8
  public ``PipelineTask.add_observer`` API.
8
9
  * We only need a passive timestamp + an ``asyncio.Event`` — no frame routing.
9
10
 
10
- Why ``OpenAILLMContextFrame`` and not ``UserStoppedSpeakingFrame``:
11
+ Why a context frame and not ``UserStoppedSpeakingFrame``:
11
12
  * ``UserStoppedSpeakingFrame`` is raw VAD — it fires the instant the mic
12
13
  detects silence, BEFORE the STT final lands and BEFORE the user-context
13
14
  aggregator appends the turn. Router gates that release on this signal
14
15
  classify against an empty/stale context.
15
- * ``OpenAILLMContextFrame`` is pushed by ``LLMUserContextAggregator``
16
+ * The context frame is pushed by ``LLMUserContextAggregator``
16
17
  (``llm_response._process_aggregation``) only AFTER the final transcript
17
18
  is appended to the LLM context. It is the canonical "user turn is now
18
19
  in the context the router will read" event.
19
20
  * Router-mode never queues ``LLMRunFrame`` for the router node itself, so
20
- the only ``OpenAILLMContextFrame`` produced during a router invocation
21
- comes from the aggregator's turn-complete path — no competing source.
21
+ the only context frame produced during a router invocation comes from
22
+ the aggregator's turn-complete path — no competing source.
23
+
24
+ BOTH context-frame flavours must be matched (see
25
+ ``_USER_TURN_COMMITTED_FRAME_TYPES``): the aggregator emits
26
+ ``LLMContextFrame`` on the Responses-API / google / gemma paths and
27
+ ``OpenAILLMContextFrame`` on the Chat Completions path.
22
28
 
23
29
  State exposed:
24
30
  * ``last_user_context_pushed_ts``: monotonic time of the latest
25
- ``OpenAILLMContextFrame`` (user turn committed to context).
31
+ user-turn context frame (user turn committed to context).
26
32
  * ``last_user_stopped_ts``: backwards-compatible alias kept in sync with
27
33
  ``last_user_context_pushed_ts`` so existing callers keep working.
28
34
  * ``event``: ``asyncio.Event`` set on every such frame; the consumer is
@@ -38,26 +44,41 @@ from typing import List, Tuple
38
44
 
39
45
  from pipecat.frames.frames import (
40
46
  InterimTranscriptionFrame,
47
+ LLMContextFrame,
41
48
  TranscriptionFrame,
42
49
  UserStartedSpeakingFrame,
43
50
  )
44
51
  from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame
45
52
  from pipecat.observers.base_observer import BaseObserver, FramePushed
46
53
 
54
+ # Both context-frame flavours mean the same thing here: "the user's turn is now
55
+ # committed to the context the router will read". Which one the aggregator emits
56
+ # is decided by one switch in the bot:
57
+ #
58
+ # context = LLMContext() if (_use_responses or _use_universal_for_google)
59
+ # else OpenAILLMContext()
60
+ #
61
+ # They are SIBLINGS (both extend Frame directly, neither subclasses the other),
62
+ # so matching one does NOT match the other. Keying on only OpenAILLMContextFrame
63
+ # left every Responses-API / google / gemma agent's router waiting the full
64
+ # ``router_wait_for_user_timeout`` (15s) after the caller had already replied.
65
+ _USER_TURN_COMMITTED_FRAME_TYPES = (OpenAILLMContextFrame, LLMContextFrame)
66
+
47
67
  # Bound the per-call transcript log so a very long call can't grow it without
48
68
  # limit; far more than any single Collect dictation needs.
49
69
  _MAX_TRANSCRIPT_LOG = 256
50
70
 
51
71
 
52
72
  class UserTurnObserver(BaseObserver):
53
- """Tracks the timestamp of the most recent ``OpenAILLMContextFrame``.
73
+ """Tracks the timestamp of the most recent user-turn context frame.
54
74
 
55
75
  Router-mode uses this to (a) detect a user reply already committed to
56
76
  context BEFORE the router was entered (barge-in during the preceding
57
77
  Speak's TTS) and (b) await the NEXT user reply when none is available.
58
78
 
59
- The observer is keyed on ``OpenAILLMContextFrame`` rather than the raw
60
- VAD ``UserStoppedSpeakingFrame`` because only the former guarantees the
79
+ The observer is keyed on the context frame (either flavour, see
80
+ ``_USER_TURN_COMMITTED_FRAME_TYPES``) rather than the raw VAD
81
+ ``UserStoppedSpeakingFrame`` because only the former guarantees the
61
82
  user's transcript has been appended to the LLM context the router reads.
62
83
  """
63
84
 
@@ -93,7 +114,7 @@ class UserTurnObserver(BaseObserver):
93
114
 
94
115
  @property
95
116
  def event(self) -> asyncio.Event:
96
- """The asyncio.Event set on each ``OpenAILLMContextFrame``.
117
+ """The asyncio.Event set on each user-turn context frame.
97
118
 
98
119
  Callers MUST ``.clear()`` before awaiting if they want to wait for the
99
120
  NEXT frame (vs. immediately returning on a stale set state).
@@ -101,7 +122,7 @@ class UserTurnObserver(BaseObserver):
101
122
  return self._event
102
123
 
103
124
  async def on_push_frame(self, data: FramePushed) -> None:
104
- if isinstance(data.frame, OpenAILLMContextFrame):
125
+ if isinstance(data.frame, _USER_TURN_COMMITTED_FRAME_TYPES):
105
126
  now = time.monotonic()
106
127
  self.last_user_context_pushed_ts = now
107
128
  self.last_user_stopped_ts = now