iCode-TUI 0.29.1__py3-none-any.whl

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 (1138) hide show
  1. chrys/__init__.py +16 -0
  2. chrys/app/__init__.py +3 -0
  3. chrys/app/acp/__init__.py +3 -0
  4. chrys/app/acp/bridge.py +664 -0
  5. chrys/app/acp/content.py +132 -0
  6. chrys/app/acp/doc/frontend-api.md +425 -0
  7. chrys/app/acp/history.py +585 -0
  8. chrys/app/acp/server.py +1796 -0
  9. chrys/app/acp/session_manager.py +1815 -0
  10. chrys/app/acp/tool_status.py +45 -0
  11. chrys/app/cli/__init__.py +3 -0
  12. chrys/app/cli/acp.py +178 -0
  13. chrys/app/cli/app.py +165 -0
  14. chrys/app/cli/approvals.py +101 -0
  15. chrys/app/cli/headless.py +223 -0
  16. chrys/app/cli/launch_cwd.py +42 -0
  17. chrys/app/cli/profiles.py +298 -0
  18. chrys/app/cli/progress.py +857 -0
  19. chrys/app/cli/run.py +425 -0
  20. chrys/app/cli/serve.py +1593 -0
  21. chrys/app/cli/tool_summary.py +225 -0
  22. chrys/app/cli/trajectory.py +201 -0
  23. chrys/app/cli/workflow.py +601 -0
  24. chrys/app/features/__init__.py +3 -0
  25. chrys/app/features/buddy/__init__.py +3 -0
  26. chrys/app/features/buddy/actions.py +68 -0
  27. chrys/app/features/buddy/animation.py +76 -0
  28. chrys/app/features/buddy/commands.py +196 -0
  29. chrys/app/features/buddy/docs/design.md +187 -0
  30. chrys/app/features/buddy/hatchery.py +80 -0
  31. chrys/app/features/buddy/lifecycle.py +41 -0
  32. chrys/app/features/buddy/model.py +265 -0
  33. chrys/app/features/buddy/pixel_renderer.py +113 -0
  34. chrys/app/features/buddy/pixel_sprites.py +279 -0
  35. chrys/app/features/buddy/portrait.py +76 -0
  36. chrys/app/features/buddy/progression.py +58 -0
  37. chrys/app/features/buddy/replies.py +191 -0
  38. chrys/app/features/buddy/sprites/alien.toml +70 -0
  39. chrys/app/features/buddy/sprites/axolotl.toml +71 -0
  40. chrys/app/features/buddy/sprites/bat.toml +69 -0
  41. chrys/app/features/buddy/sprites/bee.toml +69 -0
  42. chrys/app/features/buddy/sprites/cactus.toml +73 -0
  43. chrys/app/features/buddy/sprites/capybara.toml +70 -0
  44. chrys/app/features/buddy/sprites/cat.toml +69 -0
  45. chrys/app/features/buddy/sprites/crab.toml +70 -0
  46. chrys/app/features/buddy/sprites/dragon.toml +71 -0
  47. chrys/app/features/buddy/sprites/duck.toml +69 -0
  48. chrys/app/features/buddy/sprites/fox.toml +69 -0
  49. chrys/app/features/buddy/sprites/frog.toml +70 -0
  50. chrys/app/features/buddy/sprites/ghost.toml +70 -0
  51. chrys/app/features/buddy/sprites/goose.toml +69 -0
  52. chrys/app/features/buddy/sprites/jelly.toml +70 -0
  53. chrys/app/features/buddy/sprites/llama.toml +70 -0
  54. chrys/app/features/buddy/sprites/mushroom.toml +69 -0
  55. chrys/app/features/buddy/sprites/octopus.toml +70 -0
  56. chrys/app/features/buddy/sprites/owl.toml +70 -0
  57. chrys/app/features/buddy/sprites/panda.toml +71 -0
  58. chrys/app/features/buddy/sprites/penguin.toml +69 -0
  59. chrys/app/features/buddy/sprites/rabbit.toml +70 -0
  60. chrys/app/features/buddy/sprites/robot.toml +72 -0
  61. chrys/app/features/buddy/sprites/shark.toml +70 -0
  62. chrys/app/features/buddy/sprites/snail.toml +69 -0
  63. chrys/app/features/buddy/sprites/snake.toml +69 -0
  64. chrys/app/features/buddy/sprites/tabby.toml +69 -0
  65. chrys/app/features/buddy/sprites/turtle.toml +69 -0
  66. chrys/app/features/buddy/store.py +143 -0
  67. chrys/app/features/session_title/__init__.py +7 -0
  68. chrys/app/features/session_title/updater.py +582 -0
  69. chrys/app/installer.py +651 -0
  70. chrys/app/parsing.py +17 -0
  71. chrys/app/tui/__init__.py +3 -0
  72. chrys/app/tui/app.py +1287 -0
  73. chrys/app/tui/behaviors/__init__.py +3 -0
  74. chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
  75. chrys/app/tui/behaviors/insert_clipboard.py +73 -0
  76. chrys/app/tui/behaviors/right_click_copy.py +146 -0
  77. chrys/app/tui/binding_display.py +139 -0
  78. chrys/app/tui/buddy_messages.py +17 -0
  79. chrys/app/tui/buddy_reply.py +86 -0
  80. chrys/app/tui/chrys.tcss +549 -0
  81. chrys/app/tui/clipboard.py +109 -0
  82. chrys/app/tui/copy_messages.py +10 -0
  83. chrys/app/tui/i18n.py +230 -0
  84. chrys/app/tui/language.py +58 -0
  85. chrys/app/tui/notifications/__init__.py +12 -0
  86. chrys/app/tui/notifications/drivers.py +180 -0
  87. chrys/app/tui/notifications/service.py +126 -0
  88. chrys/app/tui/notifications/settings.py +141 -0
  89. chrys/app/tui/screens/__init__.py +3 -0
  90. chrys/app/tui/screens/agents/__init__.py +8 -0
  91. chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
  92. chrys/app/tui/screens/agents/config.py +2142 -0
  93. chrys/app/tui/screens/agents/config.tcss +169 -0
  94. chrys/app/tui/screens/agents/panels/__init__.py +3 -0
  95. chrys/app/tui/screens/agents/panels/acp.py +1176 -0
  96. chrys/app/tui/screens/agents/panels/basic.py +497 -0
  97. chrys/app/tui/screens/agents/panels/compaction.py +231 -0
  98. chrys/app/tui/screens/agents/panels/config_card.py +218 -0
  99. chrys/app/tui/screens/agents/panels/instructions.py +91 -0
  100. chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
  101. chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
  102. chrys/app/tui/screens/agents/panels/memory.py +684 -0
  103. chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
  104. chrys/app/tui/screens/agents/panels/skills.py +806 -0
  105. chrys/app/tui/screens/agents/panels/subagents.py +652 -0
  106. chrys/app/tui/screens/agents/panels/tools.py +205 -0
  107. chrys/app/tui/screens/agents/picker.py +103 -0
  108. chrys/app/tui/screens/agents/skill_paths.py +38 -0
  109. chrys/app/tui/screens/agents/validation_messages.py +452 -0
  110. chrys/app/tui/screens/dialogs/__init__.py +35 -0
  111. chrys/app/tui/screens/dialogs/agent_load.py +834 -0
  112. chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
  113. chrys/app/tui/screens/dialogs/app_mode.py +55 -0
  114. chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
  115. chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
  116. chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
  117. chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
  118. chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
  119. chrys/app/tui/screens/dialogs/approval/body.py +110 -0
  120. chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
  121. chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
  122. chrys/app/tui/screens/dialogs/ask_user.py +148 -0
  123. chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
  124. chrys/app/tui/screens/dialogs/base.py +84 -0
  125. chrys/app/tui/screens/dialogs/confirm.py +184 -0
  126. chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
  127. chrys/app/tui/screens/dialogs/connection_test.py +183 -0
  128. chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
  129. chrys/app/tui/screens/dialogs/diagram.py +102 -0
  130. chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
  131. chrys/app/tui/screens/dialogs/editor.py +378 -0
  132. chrys/app/tui/screens/dialogs/editor.tcss +132 -0
  133. chrys/app/tui/screens/dialogs/file_picker.py +582 -0
  134. chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
  135. chrys/app/tui/screens/dialogs/fork_session.py +219 -0
  136. chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
  137. chrys/app/tui/screens/dialogs/image_compression.py +102 -0
  138. chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
  139. chrys/app/tui/screens/dialogs/man_page.py +172 -0
  140. chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
  141. chrys/app/tui/screens/dialogs/new_folder.py +277 -0
  142. chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
  143. chrys/app/tui/screens/dialogs/notice.tcss +14 -0
  144. chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
  145. chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
  146. chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
  147. chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
  148. chrys/app/tui/screens/dialogs/session_title.py +110 -0
  149. chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
  150. chrys/app/tui/screens/dialogs/tool_view.py +207 -0
  151. chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
  152. chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
  153. chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
  154. chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
  155. chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
  156. chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
  157. chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
  158. chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
  159. chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
  160. chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
  161. chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
  162. chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
  163. chrys/app/tui/screens/diff/__init__.py +22 -0
  164. chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
  165. chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
  166. chrys/app/tui/screens/diff/screen.py +1532 -0
  167. chrys/app/tui/screens/diff/screen.tcss +104 -0
  168. chrys/app/tui/screens/guides/__init__.py +7 -0
  169. chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
  170. chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
  171. chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
  172. chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
  173. chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
  174. chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
  175. chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
  176. chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
  177. chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
  178. chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
  179. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
  180. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
  181. chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
  182. chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
  183. chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
  184. chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
  185. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
  186. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
  187. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
  188. chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
  189. chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
  190. chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
  191. chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
  192. chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
  193. chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
  194. chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
  195. chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
  196. chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
  197. chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
  198. chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
  199. chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
  200. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
  201. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
  202. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
  203. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
  204. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
  205. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
  206. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
  207. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
  208. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
  209. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
  210. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
  211. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
  212. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
  213. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
  214. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
  215. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
  216. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
  217. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
  218. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
  219. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
  220. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
  221. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
  222. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
  223. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
  224. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
  225. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
  226. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
  227. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
  228. chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
  229. chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
  230. chrys/app/tui/screens/guides/index.py +215 -0
  231. chrys/app/tui/screens/guides/reader.py +173 -0
  232. chrys/app/tui/screens/guides/screen.py +442 -0
  233. chrys/app/tui/screens/guides/screen.tcss +52 -0
  234. chrys/app/tui/screens/logs/__init__.py +7 -0
  235. chrys/app/tui/screens/logs/screen.py +504 -0
  236. chrys/app/tui/screens/logs/screen.tcss +45 -0
  237. chrys/app/tui/screens/main/__init__.py +7 -0
  238. chrys/app/tui/screens/main/action_availability.py +30 -0
  239. chrys/app/tui/screens/main/buddy_command.py +143 -0
  240. chrys/app/tui/screens/main/commands.py +1073 -0
  241. chrys/app/tui/screens/main/config_actions.py +464 -0
  242. chrys/app/tui/screens/main/copy_actions.py +117 -0
  243. chrys/app/tui/screens/main/dialog_controllers.py +826 -0
  244. chrys/app/tui/screens/main/dialog_gateway.py +166 -0
  245. chrys/app/tui/screens/main/diff_controller.py +478 -0
  246. chrys/app/tui/screens/main/engine_read_model.py +184 -0
  247. chrys/app/tui/screens/main/event_handlers.py +2014 -0
  248. chrys/app/tui/screens/main/input_flow.py +241 -0
  249. chrys/app/tui/screens/main/live_diff.py +244 -0
  250. chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
  251. chrys/app/tui/screens/main/model_indicator.py +217 -0
  252. chrys/app/tui/screens/main/navigation.py +343 -0
  253. chrys/app/tui/screens/main/ports.py +571 -0
  254. chrys/app/tui/screens/main/recent_dirs.py +123 -0
  255. chrys/app/tui/screens/main/rollback_controller.py +586 -0
  256. chrys/app/tui/screens/main/runtime_info.py +71 -0
  257. chrys/app/tui/screens/main/screen.py +2265 -0
  258. chrys/app/tui/screens/main/screen.tcss +21 -0
  259. chrys/app/tui/screens/main/session_handlers.py +1127 -0
  260. chrys/app/tui/screens/main/session_title.py +239 -0
  261. chrys/app/tui/screens/main/settings_coordinator.py +365 -0
  262. chrys/app/tui/screens/main/settings_persistence.py +184 -0
  263. chrys/app/tui/screens/main/shell_mode.py +112 -0
  264. chrys/app/tui/screens/main/state.py +289 -0
  265. chrys/app/tui/screens/main/submit_state.py +11 -0
  266. chrys/app/tui/screens/main/subscriptions.py +177 -0
  267. chrys/app/tui/screens/main/suggestions.py +982 -0
  268. chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
  269. chrys/app/tui/screens/main/view_adapter.py +1567 -0
  270. chrys/app/tui/screens/main/workflow_browser.py +747 -0
  271. chrys/app/tui/screens/main/workflow_content.py +248 -0
  272. chrys/app/tui/screens/main/workflow_controller.py +240 -0
  273. chrys/app/tui/screens/main/workflow_feedback.py +137 -0
  274. chrys/app/tui/screens/main/workflow_flow.py +49 -0
  275. chrys/app/tui/screens/main/workflow_run_control.py +456 -0
  276. chrys/app/tui/screens/main/workflow_session_view.py +316 -0
  277. chrys/app/tui/screens/main/workspace_actions.py +272 -0
  278. chrys/app/tui/screens/main/workspace_branch.py +194 -0
  279. chrys/app/tui/screens/menus/__init__.py +8 -0
  280. chrys/app/tui/screens/menus/languages.py +114 -0
  281. chrys/app/tui/screens/models/__init__.py +7 -0
  282. chrys/app/tui/screens/models/picker.py +147 -0
  283. chrys/app/tui/screens/models/screen.py +2043 -0
  284. chrys/app/tui/screens/models/screen.tcss +255 -0
  285. chrys/app/tui/screens/sessions/__init__.py +8 -0
  286. chrys/app/tui/screens/sessions/presenter.py +586 -0
  287. chrys/app/tui/screens/sessions/screen.py +779 -0
  288. chrys/app/tui/screens/sessions/screen.tcss +127 -0
  289. chrys/app/tui/screens/settings/__init__.py +29 -0
  290. chrys/app/tui/screens/settings/danger.py +26 -0
  291. chrys/app/tui/screens/settings/dialog.py +281 -0
  292. chrys/app/tui/screens/settings/layout.py +519 -0
  293. chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
  294. chrys/app/tui/screens/settings/panes/__init__.py +3 -0
  295. chrys/app/tui/screens/settings/panes/notifications.py +248 -0
  296. chrys/app/tui/screens/settings/panes/sessions.py +148 -0
  297. chrys/app/tui/screens/settings/ports.py +55 -0
  298. chrys/app/tui/screens/settings/provenance.py +102 -0
  299. chrys/app/tui/screens/settings/rows.py +546 -0
  300. chrys/app/tui/screens/settings/settings.tcss +406 -0
  301. chrys/app/tui/screens/themes/__init__.py +8 -0
  302. chrys/app/tui/screens/themes/dialogs.py +330 -0
  303. chrys/app/tui/screens/themes/editor.py +632 -0
  304. chrys/app/tui/screens/themes/editor.tcss +268 -0
  305. chrys/app/tui/screens/themes/messages.py +95 -0
  306. chrys/app/tui/screens/themes/palette.py +694 -0
  307. chrys/app/tui/screens/themes/panel.py +113 -0
  308. chrys/app/tui/screens/themes/picker.py +203 -0
  309. chrys/app/tui/screens/themes/save.py +191 -0
  310. chrys/app/tui/support/__init__.py +3 -0
  311. chrys/app/tui/support/file_manager.py +64 -0
  312. chrys/app/tui/support/gc_freeze.py +1114 -0
  313. chrys/app/tui/support/path_shortcuts.py +50 -0
  314. chrys/app/tui/support/prompt_history.py +298 -0
  315. chrys/app/tui/support/workspace_mru.py +476 -0
  316. chrys/app/tui/terminal/__init__.py +6 -0
  317. chrys/app/tui/terminal/_pty_backend.py +114 -0
  318. chrys/app/tui/terminal/emulator/__init__.py +24 -0
  319. chrys/app/tui/terminal/emulator/buffer.py +456 -0
  320. chrys/app/tui/terminal/emulator/charsets.py +113 -0
  321. chrys/app/tui/terminal/emulator/core.py +963 -0
  322. chrys/app/tui/terminal/emulator/input.py +342 -0
  323. chrys/app/tui/terminal/emulator/parser.py +227 -0
  324. chrys/app/tui/terminal/emulator/pen.py +158 -0
  325. chrys/app/tui/terminal/launcher.py +455 -0
  326. chrys/app/tui/terminal/leaked_reports.py +44 -0
  327. chrys/app/tui/terminal/panel.py +323 -0
  328. chrys/app/tui/terminal/pty_process.py +405 -0
  329. chrys/app/tui/terminal/rendering.py +151 -0
  330. chrys/app/tui/terminal/shell.py +273 -0
  331. chrys/app/tui/terminal/shell_integration.py +199 -0
  332. chrys/app/tui/terminal/title.py +224 -0
  333. chrys/app/tui/terminal/widget.py +654 -0
  334. chrys/app/tui/theme.py +422 -0
  335. chrys/app/tui/theme_loader.py +201 -0
  336. chrys/app/tui/theme_stylesheet.py +49 -0
  337. chrys/app/tui/themes/__init__.py +3 -0
  338. chrys/app/tui/themes/document.py +120 -0
  339. chrys/app/tui/themes/preview.py +170 -0
  340. chrys/app/tui/themes/store.py +176 -0
  341. chrys/app/tui/toasts.py +69 -0
  342. chrys/app/tui/util/__init__.py +3 -0
  343. chrys/app/tui/util/context_pressure.py +146 -0
  344. chrys/app/tui/util/debug_log.py +292 -0
  345. chrys/app/tui/util/diff_entries.py +64 -0
  346. chrys/app/tui/util/formatting.py +53 -0
  347. chrys/app/tui/util/git_branch.py +326 -0
  348. chrys/app/tui/util/invocation_progress.py +57 -0
  349. chrys/app/tui/util/logo.py +32 -0
  350. chrys/app/tui/util/message_gate.py +35 -0
  351. chrys/app/tui/util/mutation_sources.py +14 -0
  352. chrys/app/tui/util/picker_text.py +27 -0
  353. chrys/app/tui/util/removal.py +47 -0
  354. chrys/app/tui/util/rich_style.py +36 -0
  355. chrys/app/tui/util/source_text.py +12 -0
  356. chrys/app/tui/util/static_update.py +88 -0
  357. chrys/app/tui/util/todo_format.py +51 -0
  358. chrys/app/tui/util/visibility.py +214 -0
  359. chrys/app/tui/widgets/__init__.py +60 -0
  360. chrys/app/tui/widgets/ask_user_controls.py +652 -0
  361. chrys/app/tui/widgets/ask_user_markdown.py +17 -0
  362. chrys/app/tui/widgets/ask_user_prompt.py +606 -0
  363. chrys/app/tui/widgets/buttons.py +52 -0
  364. chrys/app/tui/widgets/chat/__init__.py +3 -0
  365. chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
  366. chrys/app/tui/widgets/chat/chrome.py +296 -0
  367. chrys/app/tui/widgets/chat/compaction_card.py +353 -0
  368. chrys/app/tui/widgets/chat/context_fold.py +77 -0
  369. chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
  370. chrys/app/tui/widgets/chat/image_preview.py +214 -0
  371. chrys/app/tui/widgets/chat/live_renderer.py +383 -0
  372. chrys/app/tui/widgets/chat/messages.py +1039 -0
  373. chrys/app/tui/widgets/chat/panel.py +1769 -0
  374. chrys/app/tui/widgets/chat/ports.py +245 -0
  375. chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
  376. chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
  377. chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
  378. chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
  379. chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
  380. chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
  381. chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
  382. chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
  383. chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
  384. chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
  385. chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
  386. chrys/app/tui/widgets/chat/renderers/search.py +348 -0
  387. chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
  388. chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
  389. chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
  390. chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
  391. chrys/app/tui/widgets/chat/renderers/web.py +69 -0
  392. chrys/app/tui/widgets/chat/replay.py +1605 -0
  393. chrys/app/tui/widgets/chat/replay_mount.py +322 -0
  394. chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
  395. chrys/app/tui/widgets/chat/selection_controller.py +435 -0
  396. chrys/app/tui/widgets/chat/session_json.py +508 -0
  397. chrys/app/tui/widgets/chat/toc_model.py +156 -0
  398. chrys/app/tui/widgets/chat/tool_call.py +2478 -0
  399. chrys/app/tui/widgets/chat/tool_registry.py +753 -0
  400. chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
  401. chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
  402. chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
  403. chrys/app/tui/widgets/checkbox.py +24 -0
  404. chrys/app/tui/widgets/chrome/__init__.py +3 -0
  405. chrys/app/tui/widgets/chrome/app_header.py +283 -0
  406. chrys/app/tui/widgets/chrome/commands.py +95 -0
  407. chrys/app/tui/widgets/chrome/file_index.py +68 -0
  408. chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
  409. chrys/app/tui/widgets/chrome/footer.py +284 -0
  410. chrys/app/tui/widgets/chrome/image_paste.py +351 -0
  411. chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
  412. chrys/app/tui/widgets/chrome/status_bar.py +911 -0
  413. chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
  414. chrys/app/tui/widgets/click_affordance.py +24 -0
  415. chrys/app/tui/widgets/color_picker/__init__.py +254 -0
  416. chrys/app/tui/widgets/color_picker/controls.py +171 -0
  417. chrys/app/tui/widgets/color_picker/model.py +89 -0
  418. chrys/app/tui/widgets/dialog_buttons.py +58 -0
  419. chrys/app/tui/widgets/dialog_scroll.py +24 -0
  420. chrys/app/tui/widgets/diff_view/__init__.py +20 -0
  421. chrys/app/tui/widgets/diff_view/cells.py +59 -0
  422. chrys/app/tui/widgets/diff_view/code.py +280 -0
  423. chrys/app/tui/widgets/diff_view/compute.py +286 -0
  424. chrys/app/tui/widgets/diff_view/gutter.py +161 -0
  425. chrys/app/tui/widgets/diff_view/inline.py +77 -0
  426. chrys/app/tui/widgets/diff_view/palette.py +87 -0
  427. chrys/app/tui/widgets/diff_view/protocols.py +24 -0
  428. chrys/app/tui/widgets/diff_view/rows.py +138 -0
  429. chrys/app/tui/widgets/diff_view/unified.py +267 -0
  430. chrys/app/tui/widgets/diff_view/widget.py +209 -0
  431. chrys/app/tui/widgets/editor/__init__.py +79 -0
  432. chrys/app/tui/widgets/editor/coordinates.py +30 -0
  433. chrys/app/tui/widgets/editor/highlighter.py +139 -0
  434. chrys/app/tui/widgets/editor/keymaps.py +357 -0
  435. chrys/app/tui/widgets/editor/types.py +216 -0
  436. chrys/app/tui/widgets/editor/vim.py +1195 -0
  437. chrys/app/tui/widgets/editor/widget.py +401 -0
  438. chrys/app/tui/widgets/file_preview.py +58 -0
  439. chrys/app/tui/widgets/hatch.py +138 -0
  440. chrys/app/tui/widgets/input.py +66 -0
  441. chrys/app/tui/widgets/loading.py +125 -0
  442. chrys/app/tui/widgets/markdown/__init__.py +25 -0
  443. chrys/app/tui/widgets/markdown/_utils.py +21 -0
  444. chrys/app/tui/widgets/markdown/anchors.py +71 -0
  445. chrys/app/tui/widgets/markdown/blocks.py +115 -0
  446. chrys/app/tui/widgets/markdown/copyable.py +163 -0
  447. chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
  448. chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
  449. chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
  450. chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
  451. chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
  452. chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
  453. chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
  454. chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
  455. chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
  456. chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
  457. chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
  458. chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
  459. chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
  460. chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
  461. chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
  462. chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
  463. chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
  464. chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
  465. chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
  466. chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
  467. chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
  468. chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
  469. chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
  470. chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
  471. chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
  472. chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
  473. chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
  474. chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
  475. chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
  476. chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
  477. chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
  478. chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
  479. chrys/app/tui/widgets/markdown/links.py +47 -0
  480. chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
  481. chrys/app/tui/widgets/markdown/math/layout.py +739 -0
  482. chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
  483. chrys/app/tui/widgets/markdown/math/parser.py +325 -0
  484. chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
  485. chrys/app/tui/widgets/markdown/navigator.py +80 -0
  486. chrys/app/tui/widgets/markdown/parser.py +1057 -0
  487. chrys/app/tui/widgets/markdown/stream.py +73 -0
  488. chrys/app/tui/widgets/markdown/toc.py +98 -0
  489. chrys/app/tui/widgets/markdown/viewer.py +146 -0
  490. chrys/app/tui/widgets/markdown/widget.py +2046 -0
  491. chrys/app/tui/widgets/marquee.py +101 -0
  492. chrys/app/tui/widgets/option_menu.py +82 -0
  493. chrys/app/tui/widgets/page_navigator.py +118 -0
  494. chrys/app/tui/widgets/select.py +196 -0
  495. chrys/app/tui/widgets/selection.py +59 -0
  496. chrys/app/tui/widgets/sidebar/__init__.py +3 -0
  497. chrys/app/tui/widgets/sidebar/buddy.py +367 -0
  498. chrys/app/tui/widgets/sidebar/context.py +482 -0
  499. chrys/app/tui/widgets/sidebar/debug.py +195 -0
  500. chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
  501. chrys/app/tui/widgets/sidebar/panel.py +198 -0
  502. chrys/app/tui/widgets/sidebar/tasks.py +198 -0
  503. chrys/app/tui/widgets/sidebar/toc.py +166 -0
  504. chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
  505. chrys/app/tui/widgets/syntax_theme.py +85 -0
  506. chrys/app/tui/widgets/text_area.py +258 -0
  507. chrys/app/tui/widgets/trajectory/__init__.py +8 -0
  508. chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
  509. chrys/app/tui/widgets/trajectory/insights.py +1266 -0
  510. chrys/app/tui/widgets/trajectory/overview.py +1101 -0
  511. chrys/app/tui/widgets/trajectory/panel.py +983 -0
  512. chrys/app/tui/widgets/trajectory/presentation.py +311 -0
  513. chrys/app/tui/widgets/trajectory/session_info.py +124 -0
  514. chrys/app/tui/widgets/trajectory/text_view.py +124 -0
  515. chrys/app/tui/widgets/trajectory/timeline.py +380 -0
  516. chrys/app/tui/widgets/welcome.py +126 -0
  517. chrys/app/tui/widgets/workflow/__init__.py +5 -0
  518. chrys/app/tui/widgets/workflow/edges.py +89 -0
  519. chrys/app/tui/widgets/workflow/graph.py +869 -0
  520. chrys/app/tui/widgets/workflow/info.py +310 -0
  521. chrys/app/tui/widgets/workflow/node_view.py +27 -0
  522. chrys/app/tui/widgets/workflow/output.py +225 -0
  523. chrys/app/tui/widgets/workflow/panel.py +658 -0
  524. chrys/app/tui/widgets/workflow/projector.py +324 -0
  525. chrys/app/tui/widgets/workflow/records.py +69 -0
  526. chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
  527. chrys/app/tui/widgets/workflow/selection.py +151 -0
  528. chrys/app/tui/widgets/workflow/source.py +65 -0
  529. chrys/app/tui/widgets/workflow/text.py +257 -0
  530. chrys/app/tui/widgets/workflow/values.py +317 -0
  531. chrys/foundation/__init__.py +0 -0
  532. chrys/foundation/branding.py +15 -0
  533. chrys/foundation/config/__init__.py +7 -0
  534. chrys/foundation/config/coercion.py +374 -0
  535. chrys/foundation/config/context.py +34 -0
  536. chrys/foundation/config/env_file.py +141 -0
  537. chrys/foundation/config/env_layers.py +370 -0
  538. chrys/foundation/config/migrations.py +521 -0
  539. chrys/foundation/config/notification_events.py +22 -0
  540. chrys/foundation/config/process_settings.py +332 -0
  541. chrys/foundation/config/runtime_pointer.py +133 -0
  542. chrys/foundation/config/settings.py +1952 -0
  543. chrys/foundation/config/settings_store.py +996 -0
  544. chrys/foundation/config/spec.py +396 -0
  545. chrys/foundation/config/user_settings.py +140 -0
  546. chrys/foundation/config/warnings.py +262 -0
  547. chrys/foundation/config/web_values.py +135 -0
  548. chrys/foundation/config/yaml_store.py +487 -0
  549. chrys/foundation/errors/__init__.py +68 -0
  550. chrys/foundation/errors/_legacy.py +117 -0
  551. chrys/foundation/errors/_text.py +13 -0
  552. chrys/foundation/errors/_walk.py +85 -0
  553. chrys/foundation/errors/classify.py +278 -0
  554. chrys/foundation/errors/display.py +274 -0
  555. chrys/foundation/errors/formatting.py +283 -0
  556. chrys/foundation/errors/kinds.py +67 -0
  557. chrys/foundation/errors/network.py +497 -0
  558. chrys/foundation/errors/provider.py +445 -0
  559. chrys/foundation/errors/route.py +85 -0
  560. chrys/foundation/events/__init__.py +8 -0
  561. chrys/foundation/events/bus.py +143 -0
  562. chrys/foundation/events/types.py +1629 -0
  563. chrys/foundation/events/workflow.py +38 -0
  564. chrys/foundation/hosted_tools.py +142 -0
  565. chrys/foundation/i18n/__init__.py +32 -0
  566. chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
  567. chrys/foundation/i18n/catalogs.py +42 -0
  568. chrys/foundation/i18n/formatting.py +202 -0
  569. chrys/foundation/i18n/locale.py +92 -0
  570. chrys/foundation/i18n/localizer.py +187 -0
  571. chrys/foundation/i18n/messages.py +268 -0
  572. chrys/foundation/io/__init__.py +3 -0
  573. chrys/foundation/io/result_content.py +237 -0
  574. chrys/foundation/models/__init__.py +3 -0
  575. chrys/foundation/models/approval_reuse.py +23 -0
  576. chrys/foundation/models/ask_user.py +468 -0
  577. chrys/foundation/models/execution.py +18 -0
  578. chrys/foundation/models/history_markers.py +115 -0
  579. chrys/foundation/models/invocations.py +63 -0
  580. chrys/foundation/models/mutation_scope.py +28 -0
  581. chrys/foundation/models/session_env.py +87 -0
  582. chrys/foundation/models/session_surface.py +29 -0
  583. chrys/foundation/models/todos.py +74 -0
  584. chrys/foundation/models/turns.py +125 -0
  585. chrys/foundation/models/workflow_session.py +89 -0
  586. chrys/foundation/models/workspace.py +59 -0
  587. chrys/foundation/net/__init__.py +3 -0
  588. chrys/foundation/net/address_class.py +57 -0
  589. chrys/foundation/net/route_probe.py +94 -0
  590. chrys/foundation/net/url.py +107 -0
  591. chrys/foundation/observability/__init__.py +3 -0
  592. chrys/foundation/observability/exporters.py +174 -0
  593. chrys/foundation/observability/gate.py +31 -0
  594. chrys/foundation/observability/setup.py +497 -0
  595. chrys/foundation/observability/sink.py +228 -0
  596. chrys/foundation/patches/__init__.py +19 -0
  597. chrys/foundation/patches/mdurl_cache.py +59 -0
  598. chrys/foundation/patches/patcher.py +287 -0
  599. chrys/foundation/patches/staged_members.py +205 -0
  600. chrys/foundation/patches/textual_block_border.py +105 -0
  601. chrys/foundation/patches/textual_button_ansi.py +65 -0
  602. chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
  603. chrys/foundation/patches/textual_callback_cache.py +248 -0
  604. chrys/foundation/patches/textual_callback_dispatch.py +109 -0
  605. chrys/foundation/patches/textual_compositor_cjk.py +566 -0
  606. chrys/foundation/patches/textual_dispatch_cache.py +150 -0
  607. chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
  608. chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
  609. chrys/foundation/patches/textual_lru_acyclic.py +241 -0
  610. chrys/foundation/patches/textual_message_pump.py +136 -0
  611. chrys/foundation/patches/textual_node_diet.py +875 -0
  612. chrys/foundation/patches/textual_one_shot_timer.py +48 -0
  613. chrys/foundation/patches/textual_option_list.py +90 -0
  614. chrys/foundation/patches/textual_precompose.py +89 -0
  615. chrys/foundation/patches/textual_pruned_tabs.py +69 -0
  616. chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
  617. chrys/foundation/patches/textual_removed_node_caches.py +86 -0
  618. chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
  619. chrys/foundation/patches/textual_selection_extract.py +48 -0
  620. chrys/foundation/patches/textual_strip_cycles.py +119 -0
  621. chrys/foundation/patches/textual_tab_selection.py +1611 -0
  622. chrys/foundation/patches/textual_timer_skip.py +70 -0
  623. chrys/foundation/patches/textual_utf8_decoder.py +106 -0
  624. chrys/foundation/patches/textual_win_sleep.py +167 -0
  625. chrys/foundation/patches/watchdog_fsevents.py +70 -0
  626. chrys/foundation/patches/watchdog_windows.py +52 -0
  627. chrys/foundation/platform/__init__.py +556 -0
  628. chrys/foundation/platform/c_api.py +32 -0
  629. chrys/foundation/platform/child_reap.py +107 -0
  630. chrys/foundation/platform/command_line.py +95 -0
  631. chrys/foundation/platform/files.py +1470 -0
  632. chrys/foundation/platform/output_capture.py +113 -0
  633. chrys/foundation/platform/paths.py +193 -0
  634. chrys/foundation/platform/process.py +1468 -0
  635. chrys/foundation/platform/pty_output.py +19 -0
  636. chrys/foundation/platform/runtime_paths.py +281 -0
  637. chrys/foundation/platform/win_console.py +129 -0
  638. chrys/foundation/reasoning_origin.py +67 -0
  639. chrys/foundation/recovery.py +16 -0
  640. chrys/foundation/retry.py +278 -0
  641. chrys/foundation/text/__init__.py +3 -0
  642. chrys/foundation/text/encoding.py +603 -0
  643. chrys/foundation/text/images.py +304 -0
  644. chrys/foundation/text/lines.py +24 -0
  645. chrys/foundation/text/mentions.py +90 -0
  646. chrys/foundation/text/model_json.py +25 -0
  647. chrys/foundation/text/reading_order.py +20 -0
  648. chrys/foundation/text/tokenizer.py +25 -0
  649. chrys/foundation/text/tool_output.py +287 -0
  650. chrys/foundation/text/yaml_io.py +62 -0
  651. chrys/foundation/tool_call_context.py +202 -0
  652. chrys/foundation/tool_execution_stamp.py +184 -0
  653. chrys/foundation/tool_invocation_order.py +48 -0
  654. chrys/foundation/tool_kinds.py +102 -0
  655. chrys/foundation/tool_result_metadata.py +287 -0
  656. chrys/foundation/trajectory/__init__.py +10 -0
  657. chrys/foundation/trajectory/context.py +481 -0
  658. chrys/foundation/trajectory/envelope.py +571 -0
  659. chrys/foundation/trajectory/event_types.py +294 -0
  660. chrys/foundation/trajectory/finalizers.py +46 -0
  661. chrys/foundation/trajectory/fingerprint.py +62 -0
  662. chrys/foundation/trajectory/ids.py +155 -0
  663. chrys/foundation/trajectory/keys.py +99 -0
  664. chrys/foundation/trajectory/lease.py +173 -0
  665. chrys/foundation/trajectory/metadata.py +72 -0
  666. chrys/foundation/trajectory/reader.py +271 -0
  667. chrys/foundation/trajectory/recovery.py +225 -0
  668. chrys/foundation/trajectory/revisions.py +152 -0
  669. chrys/foundation/trajectory/segments.py +225 -0
  670. chrys/foundation/trajectory/tools.py +117 -0
  671. chrys/foundation/trajectory/usage.py +151 -0
  672. chrys/foundation/trajectory/writer.py +972 -0
  673. chrys/foundation/trajectory_timing.py +97 -0
  674. chrys/foundation/util/__init__.py +3 -0
  675. chrys/foundation/util/async_tasks.py +29 -0
  676. chrys/foundation/util/chrys_headers.py +23 -0
  677. chrys/foundation/util/env_templates.py +104 -0
  678. chrys/foundation/util/filenames.py +96 -0
  679. chrys/foundation/util/header_charset.py +138 -0
  680. chrys/foundation/util/httpx_helpers.py +12 -0
  681. chrys/foundation/util/lock.py +236 -0
  682. chrys/foundation/util/once_close.py +46 -0
  683. chrys/foundation/util/session_ids.py +29 -0
  684. chrys/foundation/util/shell_tokens.py +88 -0
  685. chrys/foundation/util/sub_agent_context.py +38 -0
  686. chrys/foundation/util/time.py +35 -0
  687. chrys/foundation/util/unicode_scalars.py +36 -0
  688. chrys/foundation/vendor/__init__.py +43 -0
  689. chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
  690. chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
  691. chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
  692. chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
  693. chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
  694. chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
  695. chrys/kernel/__init__.py +256 -0
  696. chrys/kernel/_content.py +1866 -0
  697. chrys/kernel/_loop_recorder.py +631 -0
  698. chrys/kernel/_null_overlay.py +1026 -0
  699. chrys/kernel/_result_ceiling.py +216 -0
  700. chrys/kernel/_serialization.py +431 -0
  701. chrys/kernel/_tool_arg_errors.py +1629 -0
  702. chrys/kernel/_tool_execution.py +1172 -0
  703. chrys/kernel/_tool_expansion.py +26 -0
  704. chrys/kernel/_types.py +2598 -0
  705. chrys/kernel/agent.py +994 -0
  706. chrys/kernel/client.py +1084 -0
  707. chrys/kernel/compaction.py +1315 -0
  708. chrys/kernel/exceptions.py +211 -0
  709. chrys/kernel/exchanges.py +683 -0
  710. chrys/kernel/identity.py +95 -0
  711. chrys/kernel/images.py +36 -0
  712. chrys/kernel/instrumentation.py +1618 -0
  713. chrys/kernel/loop.py +2308 -0
  714. chrys/kernel/middleware.py +616 -0
  715. chrys/kernel/sessions.py +604 -0
  716. chrys/kernel/tools.py +1061 -0
  717. chrys/kernel/types.py +126 -0
  718. chrys/orchestration/__init__.py +3 -0
  719. chrys/orchestration/engine/__init__.py +3 -0
  720. chrys/orchestration/engine/assembly.py +236 -0
  721. chrys/orchestration/engine/build/__init__.py +3 -0
  722. chrys/orchestration/engine/build/builder.py +1061 -0
  723. chrys/orchestration/engine/build/construction.py +408 -0
  724. chrys/orchestration/engine/build/loaded.py +139 -0
  725. chrys/orchestration/engine/engine.py +789 -0
  726. chrys/orchestration/engine/execution.py +771 -0
  727. chrys/orchestration/engine/loader.py +865 -0
  728. chrys/orchestration/engine/rollback.py +918 -0
  729. chrys/orchestration/engine/run/__init__.py +3 -0
  730. chrys/orchestration/engine/run/active_injection.py +419 -0
  731. chrys/orchestration/engine/run/attachments.py +519 -0
  732. chrys/orchestration/engine/run/bindings.py +1095 -0
  733. chrys/orchestration/engine/run/coordinator.py +1062 -0
  734. chrys/orchestration/engine/run/finalizer.py +417 -0
  735. chrys/orchestration/engine/run/input_refs.py +60 -0
  736. chrys/orchestration/engine/run/prompt_content.py +457 -0
  737. chrys/orchestration/engine/run/resume.py +299 -0
  738. chrys/orchestration/engine/run/retry.py +948 -0
  739. chrys/orchestration/engine/run/runner.py +625 -0
  740. chrys/orchestration/engine/run/runtime_skills.py +146 -0
  741. chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
  742. chrys/orchestration/engine/run/turn_hooks.py +258 -0
  743. chrys/orchestration/engine/run/turn_state.py +149 -0
  744. chrys/orchestration/engine/run/working_dir.py +50 -0
  745. chrys/orchestration/engine/session_lifecycle.py +2013 -0
  746. chrys/orchestration/engine/state/__init__.py +3 -0
  747. chrys/orchestration/engine/state/active_session.py +183 -0
  748. chrys/orchestration/engine/state/controls.py +639 -0
  749. chrys/orchestration/engine/state/current_agent.py +26 -0
  750. chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
  751. chrys/orchestration/engine/state/machine.py +193 -0
  752. chrys/orchestration/engine/state/session_writer.py +434 -0
  753. chrys/orchestration/engine/trajectory.py +714 -0
  754. chrys/orchestration/engine/usage.py +118 -0
  755. chrys/orchestration/invoker/__init__.py +3 -0
  756. chrys/orchestration/invoker/acp.py +727 -0
  757. chrys/orchestration/invoker/acp_protocol.py +1115 -0
  758. chrys/orchestration/invoker/acp_spec.py +79 -0
  759. chrys/orchestration/invoker/attempts.py +994 -0
  760. chrys/orchestration/invoker/child_compaction.py +131 -0
  761. chrys/orchestration/invoker/child_history.py +99 -0
  762. chrys/orchestration/invoker/contracts.py +253 -0
  763. chrys/orchestration/invoker/evidence.py +94 -0
  764. chrys/orchestration/invoker/kernel.py +343 -0
  765. chrys/orchestration/invoker/origin.py +79 -0
  766. chrys/orchestration/invoker/resources.py +343 -0
  767. chrys/orchestration/invoker/runtime.py +299 -0
  768. chrys/orchestration/invoker/usage.py +80 -0
  769. chrys/orchestration/session_hooks.py +117 -0
  770. chrys/orchestration/session_host.py +1023 -0
  771. chrys/orchestration/session_resources.py +95 -0
  772. chrys/orchestration/session_usage.py +219 -0
  773. chrys/orchestration/startup.py +255 -0
  774. chrys/orchestration/sub_agents/__init__.py +3 -0
  775. chrys/orchestration/sub_agents/acp_policy.py +269 -0
  776. chrys/orchestration/sub_agents/kernel_policy.py +848 -0
  777. chrys/orchestration/sub_agents/registration.py +44 -0
  778. chrys/orchestration/sub_agents/shell.py +337 -0
  779. chrys/orchestration/sub_agents/tools.py +2228 -0
  780. chrys/orchestration/workflows/__init__.py +3 -0
  781. chrys/orchestration/workflows/agent_archive.py +156 -0
  782. chrys/orchestration/workflows/agent_node.py +894 -0
  783. chrys/orchestration/workflows/agent_node_build.py +687 -0
  784. chrys/orchestration/workflows/catalog.py +332 -0
  785. chrys/orchestration/workflows/coordinator.py +771 -0
  786. chrys/orchestration/workflows/hooks.py +176 -0
  787. chrys/orchestration/workflows/preview.py +322 -0
  788. chrys/orchestration/workflows/rollback.py +151 -0
  789. chrys/orchestration/workflows/runner.py +986 -0
  790. chrys/orchestration/workflows/session.py +390 -0
  791. chrys/orchestration/workflows/settings.py +97 -0
  792. chrys/orchestration/workflows/validation.py +674 -0
  793. chrys/orchestration/workflows/worker_client.py +1002 -0
  794. chrys/service/__init__.py +3 -0
  795. chrys/service/acp_client/__init__.py +65 -0
  796. chrys/service/acp_client/_transport.py +922 -0
  797. chrys/service/acp_client/client.py +1311 -0
  798. chrys/service/acp_client/errors.py +224 -0
  799. chrys/service/acp_client/protocol.py +220 -0
  800. chrys/service/acp_client/spawn.py +239 -0
  801. chrys/service/acp_client/spec.py +100 -0
  802. chrys/service/agent_middleware/__init__.py +60 -0
  803. chrys/service/agent_middleware/_metadata_keys.py +52 -0
  804. chrys/service/agent_middleware/control/__init__.py +3 -0
  805. chrys/service/agent_middleware/control/approval.py +769 -0
  806. chrys/service/agent_middleware/control/approval_hooks.py +62 -0
  807. chrys/service/agent_middleware/control/ask_user.py +207 -0
  808. chrys/service/agent_middleware/control/interrupt.py +50 -0
  809. chrys/service/agent_middleware/control/sleep.py +118 -0
  810. chrys/service/agent_middleware/control/todo.py +117 -0
  811. chrys/service/agent_middleware/events/__init__.py +3 -0
  812. chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
  813. chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
  814. chrys/service/agent_middleware/events/intermediate_text.py +128 -0
  815. chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
  816. chrys/service/agent_middleware/events/result_persistence.py +98 -0
  817. chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
  818. chrys/service/agent_middleware/events/tool_error_text.py +22 -0
  819. chrys/service/agent_middleware/events/tool_events.py +779 -0
  820. chrys/service/agent_middleware/injection.py +510 -0
  821. chrys/service/agent_middleware/reminders/__init__.py +91 -0
  822. chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
  823. chrys/service/agent_middleware/reminders/base.py +25 -0
  824. chrys/service/agent_middleware/reminders/context_usage.py +74 -0
  825. chrys/service/agent_middleware/reminders/file_change.py +63 -0
  826. chrys/service/agent_middleware/reminders/mcp.py +39 -0
  827. chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
  828. chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
  829. chrys/service/agent_middleware/reminders/skills.py +44 -0
  830. chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
  831. chrys/service/agent_middleware/reminders/todo.py +35 -0
  832. chrys/service/agent_middleware/reminders/turn_line.py +48 -0
  833. chrys/service/agent_middleware/response_validation.py +1380 -0
  834. chrys/service/agent_middleware/system_reminder.py +1107 -0
  835. chrys/service/agent_middleware/validators.py +396 -0
  836. chrys/service/analytics/__init__.py +124 -0
  837. chrys/service/analytics/_actions.py +896 -0
  838. chrys/service/analytics/_context_evidence.py +357 -0
  839. chrys/service/analytics/_critical_path.py +179 -0
  840. chrys/service/analytics/_facts.py +919 -0
  841. chrys/service/analytics/_insights.py +769 -0
  842. chrys/service/analytics/_metric_ops.py +83 -0
  843. chrys/service/analytics/_session_projection.py +460 -0
  844. chrys/service/analytics/_timeline.py +434 -0
  845. chrys/service/analytics/_turn_graph.py +1237 -0
  846. chrys/service/analytics/_turns.py +1377 -0
  847. chrys/service/analytics/_workflows.py +171 -0
  848. chrys/service/analytics/aggregation.py +1474 -0
  849. chrys/service/analytics/classification.py +128 -0
  850. chrys/service/analytics/export.py +803 -0
  851. chrys/service/analytics/findings.py +282 -0
  852. chrys/service/analytics/math.py +79 -0
  853. chrys/service/analytics/model.py +886 -0
  854. chrys/service/analytics/reader.py +321 -0
  855. chrys/service/approval/__init__.py +3 -0
  856. chrys/service/approval/arbitration.py +102 -0
  857. chrys/service/approval/command_identity.py +109 -0
  858. chrys/service/approval/correlation.py +57 -0
  859. chrys/service/approval/grant_store.py +108 -0
  860. chrys/service/approval/judge.py +589 -0
  861. chrys/service/approval/level.py +15 -0
  862. chrys/service/approval/policy.py +209 -0
  863. chrys/service/approval/reuse.py +223 -0
  864. chrys/service/approval/reuse_binding.py +133 -0
  865. chrys/service/approval/safety_classifier.py +536 -0
  866. chrys/service/approval/turn_context.py +49 -0
  867. chrys/service/context/__init__.py +3 -0
  868. chrys/service/context/compaction/__init__.py +38 -0
  869. chrys/service/context/compaction/breaker.py +156 -0
  870. chrys/service/context/compaction/budgets.py +93 -0
  871. chrys/service/context/compaction/calibration.py +77 -0
  872. chrys/service/context/compaction/compression.py +690 -0
  873. chrys/service/context/compaction/current_turn_drop.py +458 -0
  874. chrys/service/context/compaction/events.py +201 -0
  875. chrys/service/context/compaction/exclusions.py +261 -0
  876. chrys/service/context/compaction/groups.py +361 -0
  877. chrys/service/context/compaction/last_words.py +1996 -0
  878. chrys/service/context/compaction/last_words_state.py +752 -0
  879. chrys/service/context/compaction/scoped.py +472 -0
  880. chrys/service/context/compaction/spill.py +1947 -0
  881. chrys/service/context/compaction/strategy.py +1116 -0
  882. chrys/service/context/compaction/summaries.py +319 -0
  883. chrys/service/context/compaction/turns.py +375 -0
  884. chrys/service/context/manager.py +174 -0
  885. chrys/service/context/memory_loader.py +792 -0
  886. chrys/service/context/middleware/__init__.py +3 -0
  887. chrys/service/context/middleware/usage.py +332 -0
  888. chrys/service/context/providers/__init__.py +3 -0
  889. chrys/service/context/providers/context_management.py +432 -0
  890. chrys/service/context/providers/history.py +647 -0
  891. chrys/service/context/providers/memory.py +50 -0
  892. chrys/service/context/tool_content.py +22 -0
  893. chrys/service/hooks/__init__.py +37 -0
  894. chrys/service/hooks/detached_worker.py +118 -0
  895. chrys/service/hooks/docs/README.md +14 -0
  896. chrys/service/hooks/docs/authoring.md +684 -0
  897. chrys/service/hooks/docs/configuration.md +499 -0
  898. chrys/service/hooks/docs/design.md +359 -0
  899. chrys/service/hooks/docs/project-hooks.md +348 -0
  900. chrys/service/hooks/events.py +157 -0
  901. chrys/service/hooks/loader.py +665 -0
  902. chrys/service/hooks/manager.py +807 -0
  903. chrys/service/hooks/matcher.py +111 -0
  904. chrys/service/hooks/outbox.py +294 -0
  905. chrys/service/hooks/runner.py +572 -0
  906. chrys/service/hooks/schema.py +407 -0
  907. chrys/service/llm/__init__.py +7 -0
  908. chrys/service/llm/_structured_outputs.py +192 -0
  909. chrys/service/llm/anthropic_messages/__init__.py +9 -0
  910. chrys/service/llm/anthropic_messages/client.py +254 -0
  911. chrys/service/llm/anthropic_messages/decode.py +331 -0
  912. chrys/service/llm/anthropic_messages/history.py +279 -0
  913. chrys/service/llm/anthropic_messages/request.py +323 -0
  914. chrys/service/llm/anthropic_messages/server_tools.py +319 -0
  915. chrys/service/llm/anthropic_messages/stream.py +297 -0
  916. chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
  917. chrys/service/llm/chat_completions/__init__.py +9 -0
  918. chrys/service/llm/chat_completions/client.py +369 -0
  919. chrys/service/llm/chat_completions/decode.py +338 -0
  920. chrys/service/llm/chat_completions/history.py +657 -0
  921. chrys/service/llm/chat_completions/reasoning.py +244 -0
  922. chrys/service/llm/chat_completions/request.py +192 -0
  923. chrys/service/llm/chat_completions/stream.py +546 -0
  924. chrys/service/llm/chat_completions/validation.py +308 -0
  925. chrys/service/llm/clients.py +613 -0
  926. chrys/service/llm/images.py +65 -0
  927. chrys/service/llm/mock.py +413 -0
  928. chrys/service/llm/observer.py +446 -0
  929. chrys/service/llm/one_shot.py +106 -0
  930. chrys/service/llm/openai_exceptions.py +93 -0
  931. chrys/service/llm/openai_responses/__init__.py +10 -0
  932. chrys/service/llm/openai_responses/client.py +323 -0
  933. chrys/service/llm/openai_responses/decode.py +432 -0
  934. chrys/service/llm/openai_responses/history.py +519 -0
  935. chrys/service/llm/openai_responses/hosted.py +625 -0
  936. chrys/service/llm/openai_responses/replay.py +629 -0
  937. chrys/service/llm/openai_responses/request.py +342 -0
  938. chrys/service/llm/openai_responses/stream.py +847 -0
  939. chrys/service/llm/openai_timestamps.py +57 -0
  940. chrys/service/llm/providers.py +107 -0
  941. chrys/service/llm/proxy_route.py +54 -0
  942. chrys/service/llm/raw_http_log.py +309 -0
  943. chrys/service/llm/route_facts.py +63 -0
  944. chrys/service/llm/route_sessions.py +49 -0
  945. chrys/service/llm/sdk_anthropic.py +18 -0
  946. chrys/service/llm/sdk_headers.py +47 -0
  947. chrys/service/llm/sdk_openai.py +18 -0
  948. chrys/service/llm/sdk_retry.py +59 -0
  949. chrys/service/llm/wire_client.py +246 -0
  950. chrys/service/mcp/__init__.py +3 -0
  951. chrys/service/mcp/_connection.py +235 -0
  952. chrys/service/mcp/_http_transport.py +350 -0
  953. chrys/service/mcp/_progressive.py +459 -0
  954. chrys/service/mcp/_stdio_transport.py +614 -0
  955. chrys/service/mcp/_tool_mixins.py +96 -0
  956. chrys/service/mcp/adapter.py +1031 -0
  957. chrys/service/mcp/cache.py +476 -0
  958. chrys/service/mcp/content.py +32 -0
  959. chrys/service/mcp/errors.py +169 -0
  960. chrys/service/mcp/owned.py +1330 -0
  961. chrys/service/mcp/result_limits.py +168 -0
  962. chrys/service/mcp/thinking_warning.py +95 -0
  963. chrys/service/mcp/validation.py +74 -0
  964. chrys/service/mutations/__init__.py +84 -0
  965. chrys/service/mutations/coordination.py +894 -0
  966. chrys/service/mutations/detector.py +458 -0
  967. chrys/service/mutations/git_calibrator.py +397 -0
  968. chrys/service/mutations/git_state.py +647 -0
  969. chrys/service/mutations/pipeline.py +779 -0
  970. chrys/service/mutations/scanner.py +466 -0
  971. chrys/service/mutations/snapshot_files.py +233 -0
  972. chrys/service/mutations/store.py +625 -0
  973. chrys/service/mutations/tool_names.py +15 -0
  974. chrys/service/mutations/trace.py +246 -0
  975. chrys/service/mutations/tracker.py +1289 -0
  976. chrys/service/mutations/types.py +559 -0
  977. chrys/service/mutations/workspace_changes.py +1583 -0
  978. chrys/service/profiles/__init__.py +2 -0
  979. chrys/service/profiles/agents/__init__.py +3 -0
  980. chrys/service/profiles/agents/builtins/Code.yaml +199 -0
  981. chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
  982. chrys/service/profiles/agents/builtins/General.yaml +114 -0
  983. chrys/service/profiles/agents/builtins/QA.yaml +128 -0
  984. chrys/service/profiles/agents/loader.py +808 -0
  985. chrys/service/profiles/agents/registry.py +727 -0
  986. chrys/service/profiles/agents/schema.py +371 -0
  987. chrys/service/profiles/agents/serializer.py +196 -0
  988. chrys/service/profiles/coercion.py +22 -0
  989. chrys/service/profiles/models/__init__.py +3 -0
  990. chrys/service/profiles/models/env_bridge.py +157 -0
  991. chrys/service/profiles/models/loader.py +282 -0
  992. chrys/service/profiles/models/options.py +438 -0
  993. chrys/service/profiles/models/registry.py +104 -0
  994. chrys/service/profiles/models/resolver.py +286 -0
  995. chrys/service/profiles/models/schema.py +91 -0
  996. chrys/service/profiles/models/serializer.py +76 -0
  997. chrys/service/session/__init__.py +3 -0
  998. chrys/service/session/checkpoint.py +156 -0
  999. chrys/service/session/history.py +1616 -0
  1000. chrys/service/session/message_metadata.py +151 -0
  1001. chrys/service/session/persistence.py +435 -0
  1002. chrys/service/session/runtime_metadata.py +210 -0
  1003. chrys/service/session/sub_agent_logs.py +1069 -0
  1004. chrys/service/session/sub_agent_transcript.py +654 -0
  1005. chrys/service/skills/__init__.py +3 -0
  1006. chrys/service/skills/adapter.py +408 -0
  1007. chrys/service/skills/constants.py +8 -0
  1008. chrys/service/skills/loader.py +575 -0
  1009. chrys/service/skills/model.py +130 -0
  1010. chrys/service/skills/provider.py +841 -0
  1011. chrys/service/skills/runner.py +390 -0
  1012. chrys/service/state/__init__.py +3 -0
  1013. chrys/service/state/_fork.py +710 -0
  1014. chrys/service/state/_session_files.py +175 -0
  1015. chrys/service/state/_session_meta.py +552 -0
  1016. chrys/service/state/locks.py +154 -0
  1017. chrys/service/state/serializers.py +156 -0
  1018. chrys/service/state/session_catalog.py +361 -0
  1019. chrys/service/state/session_listing.py +84 -0
  1020. chrys/service/state/session_migration.py +446 -0
  1021. chrys/service/state/session_mru.py +315 -0
  1022. chrys/service/state/store.py +2132 -0
  1023. chrys/service/state/workflow.py +158 -0
  1024. chrys/service/text_blocks.py +47 -0
  1025. chrys/service/todos/__init__.py +3 -0
  1026. chrys/service/todos/reminder.py +31 -0
  1027. chrys/service/todos/tracker.py +58 -0
  1028. chrys/service/tools/__init__.py +3 -0
  1029. chrys/service/tools/approval_targets.py +92 -0
  1030. chrys/service/tools/builtins/__init__.py +3 -0
  1031. chrys/service/tools/builtins/ask_user.py +326 -0
  1032. chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
  1033. chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
  1034. chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
  1035. chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
  1036. chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
  1037. chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
  1038. chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
  1039. chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
  1040. chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
  1041. chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
  1042. chrys/service/tools/builtins/doc_converter/registry.py +55 -0
  1043. chrys/service/tools/builtins/filesystem.py +1071 -0
  1044. chrys/service/tools/builtins/search.py +1265 -0
  1045. chrys/service/tools/builtins/shell.py +967 -0
  1046. chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
  1047. chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
  1048. chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
  1049. chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
  1050. chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
  1051. chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
  1052. chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
  1053. chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
  1054. chrys/service/tools/builtins/shell_filter.py +24 -0
  1055. chrys/service/tools/builtins/sleep.py +91 -0
  1056. chrys/service/tools/builtins/todo.py +64 -0
  1057. chrys/service/tools/builtins/web/__init__.py +6 -0
  1058. chrys/service/tools/builtins/web/build.py +268 -0
  1059. chrys/service/tools/builtins/web/config.py +338 -0
  1060. chrys/service/tools/builtins/web/credentials.py +37 -0
  1061. chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
  1062. chrys/service/tools/builtins/web/fetch/cache.py +68 -0
  1063. chrys/service/tools/builtins/web/fetch/convert.py +56 -0
  1064. chrys/service/tools/builtins/web/fetch/tool.py +223 -0
  1065. chrys/service/tools/builtins/web/hosted.py +61 -0
  1066. chrys/service/tools/builtins/web/http.py +330 -0
  1067. chrys/service/tools/builtins/web/progress.py +18 -0
  1068. chrys/service/tools/builtins/web/search/__init__.py +3 -0
  1069. chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
  1070. chrys/service/tools/builtins/web/search/html.py +152 -0
  1071. chrys/service/tools/builtins/web/search/json_api.py +227 -0
  1072. chrys/service/tools/builtins/web/search/providers.py +77 -0
  1073. chrys/service/tools/builtins/web/search/tool.py +218 -0
  1074. chrys/service/tools/builtins/web/search/types.py +28 -0
  1075. chrys/service/tools/builtins/web/tls_identity.py +66 -0
  1076. chrys/service/tools/kinds.py +115 -0
  1077. chrys/service/tools/names.py +47 -0
  1078. chrys/service/tools/registry.py +242 -0
  1079. chrys/service/tools/result_metadata.py +103 -0
  1080. chrys/service/tools/session_artifacts.py +196 -0
  1081. chrys/service/tools/spill.py +218 -0
  1082. chrys/service/tools/workspace_paths.py +81 -0
  1083. chrys/service/trajectory/__init__.py +11 -0
  1084. chrys/service/trajectory/approvals.py +201 -0
  1085. chrys/service/trajectory/compaction.py +420 -0
  1086. chrys/service/trajectory/fork.py +75 -0
  1087. chrys/service/trajectory/hooks.py +217 -0
  1088. chrys/service/trajectory/items.py +41 -0
  1089. chrys/service/trajectory/preparation.py +374 -0
  1090. chrys/service/trajectory/retries.py +95 -0
  1091. chrys/service/trajectory/revisions.py +129 -0
  1092. chrys/service/trajectory/segmented.py +163 -0
  1093. chrys/service/trajectory/session.py +762 -0
  1094. chrys/service/trajectory/state.py +83 -0
  1095. chrys/service/trajectory/sub_agents.py +169 -0
  1096. chrys/service/trajectory/tombstone.py +262 -0
  1097. chrys/service/trajectory/tools.py +323 -0
  1098. chrys/service/trajectory/validation.py +130 -0
  1099. chrys/service/trajectory/waits.py +161 -0
  1100. chrys/service/trajectory/workflow.py +148 -0
  1101. chrys/service/vision.py +128 -0
  1102. chrys/service/workflows/__init__.py +3 -0
  1103. chrys/service/workflows/admission.py +151 -0
  1104. chrys/service/workflows/artifacts.py +109 -0
  1105. chrys/service/workflows/asks.py +130 -0
  1106. chrys/service/workflows/builtins/__init__.py +12 -0
  1107. chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
  1108. chrys/service/workflows/builtins/demo-workflow.py +756 -0
  1109. chrys/service/workflows/discovery.py +554 -0
  1110. chrys/service/workflows/environment.py +284 -0
  1111. chrys/service/workflows/graph.py +430 -0
  1112. chrys/service/workflows/history.py +82 -0
  1113. chrys/service/workflows/interpreter.py +107 -0
  1114. chrys/service/workflows/journal.py +334 -0
  1115. chrys/service/workflows/layout.py +56 -0
  1116. chrys/service/workflows/ledger.py +149 -0
  1117. chrys/service/workflows/model_selection.py +18 -0
  1118. chrys/service/workflows/orphans.py +209 -0
  1119. chrys/service/workflows/outcomes.py +46 -0
  1120. chrys/service/workflows/protocol.py +210 -0
  1121. chrys/service/workflows/records.py +142 -0
  1122. chrys/service/workflows/scheduler.py +770 -0
  1123. chrys/service/workflows/sdk/__init__.py +51 -0
  1124. chrys/service/workflows/sdk/_ask.py +167 -0
  1125. chrys/service/workflows/sdk/_builder.py +781 -0
  1126. chrys/service/workflows/sdk/_values.py +92 -0
  1127. chrys/service/workflows/sdk_artifact.py +74 -0
  1128. chrys/service/workflows/store.py +494 -0
  1129. chrys/service/workflows/transcript.py +102 -0
  1130. chrys/service/workflows/values.py +73 -0
  1131. chrys/service/workflows/worker_host.py +1820 -0
  1132. chrys/workflows.py +38 -0
  1133. icode_tui-0.29.1.dist-info/METADATA +104 -0
  1134. icode_tui-0.29.1.dist-info/RECORD +1138 -0
  1135. icode_tui-0.29.1.dist-info/WHEEL +4 -0
  1136. icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
  1137. icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
  1138. icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
@@ -0,0 +1,1618 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3
+ # Contains code adapted from Microsoft Agent Framework (MIT License; see NOTICE).
4
+
5
+ """OpenTelemetry layers for chat calls, agent runs and tool execution.
6
+
7
+ ``ChatTelemetryLayer`` wraps chat clients and ``AgentTelemetryLayer`` wraps
8
+ ``Agent``. Both use the foundation-owned ``TELEMETRY_GATE``, defaulting off.
9
+ Settings parsing and provider setup belong to foundation observability.
10
+
11
+ Non-streaming dedup ContextVars must be set and reset inside the returned
12
+ coroutine. Setting them at call time would make ``create_task`` execution
13
+ reset tokens in a different context. Streaming spans activate around each
14
+ pull; cleanup closes the span before resetting ContextVars so a cross-context
15
+ reset cannot leave it open. Such reset failures are logged at debug level.
16
+
17
+ Tool definitions are canonicalized independently so one malformed declaration
18
+ cannot discard valid neighbors or raise into a run. Message events, span
19
+ attributes, histogram buckets and duration recording use the schema defined
20
+ here. Tracer and meter scope is ``chrys`` with its installed version; agent
21
+ provider names come from the host's ``AGENT_PROVIDER_NAME`` ClassVar, while
22
+ chat spans keep the model provider name.
23
+
24
+ The per-tool duration metric and function-name attribute now use
25
+ ``chrys.function.*``. Dashboards and alerts consuming the earlier per-tool
26
+ metric schema must update their selectors.
27
+
28
+ The tool loop emits function spans and duration metrics around its single
29
+ invoke boundary. Direct ``FunctionTool.invoke`` remains telemetry-free.
30
+ Kernel dependencies stay within this package, allowed third-party packages
31
+ and foundation; the scope version is the package's ``__version__``.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import contextlib
37
+ import contextvars
38
+ import json
39
+ import logging
40
+ import weakref
41
+ from collections.abc import Awaitable, Callable, Generator, Mapping, Sequence
42
+ from enum import Enum
43
+ from time import perf_counter, time_ns
44
+ from typing import TYPE_CHECKING, Any, ClassVar, Final, Literal, Protocol, TypeGuard, cast, overload
45
+
46
+ from opentelemetry import context as otel_context
47
+ from opentelemetry import metrics, trace
48
+
49
+ from chrys import __version__
50
+ from chrys.foundation.observability.gate import TELEMETRY_GATE
51
+
52
+ from ._content import add_usage_details
53
+ from ._types import AgentResponse, ChatResponse, ResponseStream, merge_chat_options, normalize_messages
54
+ from .client import _PreparedRequestObserverClient
55
+ from .tools import FunctionTool, normalize_tools
56
+
57
+ if TYPE_CHECKING:
58
+ from ._content import Content, UsageDetails
59
+ from ._types import (
60
+ AgentResponseUpdate,
61
+ AgentRunInputs,
62
+ ChatResponseUpdate,
63
+ FinishReason,
64
+ FinishReasonLiteral,
65
+ Message,
66
+ ToolTypes,
67
+ )
68
+ from .middleware import ChatMiddleware, FunctionMiddleware
69
+ from .sessions import AgentSession
70
+
71
+ class _ChatTelemetryBase(Protocol):
72
+ def get_response(
73
+ self,
74
+ messages: Sequence[Message],
75
+ *,
76
+ stream: bool = False,
77
+ options: Mapping[str, Any] | None = None,
78
+ compaction_strategy: Any = None,
79
+ tokenizer: Any = None,
80
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
81
+ client_kwargs: Mapping[str, Any] | None = None,
82
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
83
+ ) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
84
+
85
+ class _AgentTelemetryBase(Protocol):
86
+ def run(
87
+ self,
88
+ messages: AgentRunInputs | None = None,
89
+ *,
90
+ stream: bool = False,
91
+ session: AgentSession | None = None,
92
+ middleware: ChatMiddleware
93
+ | FunctionMiddleware
94
+ | Sequence[ChatMiddleware | FunctionMiddleware]
95
+ | None = None,
96
+ tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
97
+ options: Mapping[str, Any] | None = None,
98
+ compaction_strategy: Any = None,
99
+ tokenizer: Any = None,
100
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
101
+ client_kwargs: Mapping[str, Any] | None = None,
102
+ ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
103
+
104
+ else:
105
+ _ChatTelemetryBase = object
106
+ _AgentTelemetryBase = object
107
+
108
+
109
+ def _is_agent_response_stream(
110
+ value: object,
111
+ ) -> TypeGuard[ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]:
112
+ """Narrow the streaming branch of an agent-run result."""
113
+ return isinstance(value, ResponseStream)
114
+
115
+
116
+ __all__ = [
117
+ "FUNCTION_SPAN_EXCLUDED_KWARGS",
118
+ "AgentTelemetryLayer",
119
+ "ChatTelemetryLayer",
120
+ "OtelAttr",
121
+ "capture_exception",
122
+ "create_mcp_client_span",
123
+ "get_function_duration_histogram",
124
+ "get_function_span",
125
+ "get_function_span_attributes",
126
+ "get_meter",
127
+ "get_tracer",
128
+ "set_mcp_span_error",
129
+ ]
130
+
131
+ logger = logging.getLogger(__name__)
132
+
133
+ _SCOPE_NAME: Final[str] = "chrys"
134
+ _SCOPE_VERSION: Final[str] = __version__
135
+
136
+
137
+ # Dedup channel between the agent and inner chat layers so usage and
138
+ # response_id are not double-counted
139
+ # on the agent span.
140
+ INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS: Final[contextvars.ContextVar[set[str] | None]] = contextvars.ContextVar(
141
+ "chrys_inner_response_telemetry_captured_fields", default=None
142
+ )
143
+ INNER_RESPONSE_ID_CAPTURED_FIELD: Final[str] = "response_id"
144
+ INNER_USAGE_CAPTURED_FIELD: Final[str] = "usage"
145
+ INNER_ACCUMULATED_USAGE: Final[contextvars.ContextVar[UsageDetails | None]] = contextvars.ContextVar(
146
+ "chrys_inner_accumulated_usage", default=None
147
+ )
148
+
149
+ # Shared histogram boundaries for token usage and operation duration.
150
+ TOKEN_USAGE_BUCKET_BOUNDARIES: Final[tuple[float, ...]] = (
151
+ 1,
152
+ 4,
153
+ 16,
154
+ 64,
155
+ 256,
156
+ 1024,
157
+ 4096,
158
+ 16384,
159
+ 65536,
160
+ 262144,
161
+ 1048576,
162
+ 4194304,
163
+ 16777216,
164
+ 67108864,
165
+ )
166
+ OPERATION_DURATION_BUCKET_BOUNDARIES: Final[tuple[float, ...]] = (
167
+ 0.01,
168
+ 0.02,
169
+ 0.04,
170
+ 0.08,
171
+ 0.16,
172
+ 0.32,
173
+ 0.64,
174
+ 1.28,
175
+ 2.56,
176
+ 5.12,
177
+ 10.24,
178
+ 20.48,
179
+ 40.96,
180
+ 81.92,
181
+ )
182
+
183
+
184
+ class OtelAttr(str, Enum): # noqa: UP042 - str+Enum preserves value-returning __str__/__repr__ semantics.
185
+ """Attribute keys and metric names emitted by Chrys telemetry."""
186
+
187
+ OPERATION = "gen_ai.operation.name"
188
+ PROVIDER_NAME = "gen_ai.provider.name"
189
+ ERROR_TYPE = "error.type"
190
+ PORT = "server.port"
191
+ ADDRESS = "server.address"
192
+ # Request attributes
193
+ SEED = "gen_ai.request.seed"
194
+ ENCODING_FORMATS = "gen_ai.request.encoding_formats"
195
+ FREQUENCY_PENALTY = "gen_ai.request.frequency_penalty"
196
+ PRESENCE_PENALTY = "gen_ai.request.presence_penalty"
197
+ STOP_SEQUENCES = "gen_ai.request.stop_sequences"
198
+ TOP_K = "gen_ai.request.top_k"
199
+ CHOICE_COUNT = "gen_ai.request.choice.count"
200
+ REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens"
201
+ REQUEST_TEMPERATURE = "gen_ai.request.temperature"
202
+ REQUEST_TOP_P = "gen_ai.request.top_p"
203
+ REQUEST_MODEL = "gen_ai.request.model"
204
+ # Response attributes
205
+ FINISH_REASONS = "gen_ai.response.finish_reasons"
206
+ RESPONSE_ID = "gen_ai.response.id"
207
+ RESPONSE_MODEL = "gen_ai.response.model"
208
+ # Usage attributes
209
+ INPUT_TOKENS = "gen_ai.usage.input_tokens"
210
+ OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
211
+ CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens"
212
+ CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens"
213
+ REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens"
214
+ # Tool attributes
215
+ TOOL_DEFINITIONS = "gen_ai.tool.definitions"
216
+ TOOL_CALL_ID = "gen_ai.tool.call.id"
217
+ TOOL_DESCRIPTION = "gen_ai.tool.description"
218
+ TOOL_NAME = "gen_ai.tool.name"
219
+ TOOL_TYPE = "gen_ai.tool.type"
220
+ TOOL_ARGUMENTS = "gen_ai.tool.call.arguments"
221
+ TOOL_RESULT = "gen_ai.tool.call.result"
222
+ # Agent attributes
223
+ AGENT_ID = "gen_ai.agent.id"
224
+ AGENT_NAME = "gen_ai.agent.name"
225
+ AGENT_DESCRIPTION = "gen_ai.agent.description"
226
+ CONVERSATION_ID = "gen_ai.conversation.id"
227
+ INPUT_MESSAGES = "gen_ai.input.messages"
228
+ OUTPUT_MESSAGES = "gen_ai.output.messages"
229
+ SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions"
230
+ SYSTEM = "gen_ai.system"
231
+ SERVICE_NAME = "service.name"
232
+ SERVICE_VERSION = "service.version"
233
+ # Client metric attributes
234
+ T_UNIT = "tokens"
235
+ T_TYPE = "gen_ai.token.type"
236
+ T_TYPE_INPUT = "input"
237
+ T_TYPE_OUTPUT = "output"
238
+ DURATION_UNIT = "s"
239
+ LLM_OPERATION_DURATION = "gen_ai.client.operation.duration"
240
+ LLM_TOKEN_USAGE = "gen_ai.client.token.usage"
241
+ # Activity events
242
+ EVENT_NAME = "event.name"
243
+ SYSTEM_MESSAGE = "gen_ai.system.message"
244
+ USER_MESSAGE = "gen_ai.user.message"
245
+ ASSISTANT_MESSAGE = "gen_ai.assistant.message"
246
+ TOOL_MESSAGE = "gen_ai.tool.message"
247
+ CHOICE = "gen_ai.choice"
248
+ # Operation names
249
+ CHAT_COMPLETION_OPERATION = "chat"
250
+ AGENT_INVOKE_OPERATION = "invoke_agent"
251
+ TOOL_EXECUTION_OPERATION = "execute_tool"
252
+ # MCP attributes
253
+ MCP_METHOD_NAME = "mcp.method.name"
254
+ MCP_PROTOCOL_VERSION = "mcp.protocol.version"
255
+ MCP_SESSION_ID = "mcp.session.id"
256
+ PROMPT_NAME = "gen_ai.prompt.name"
257
+ NETWORK_TRANSPORT = "network.transport"
258
+ NETWORK_PROTOCOL_NAME = "network.protocol.name"
259
+ # Measurement attributes. Step 5 deliberately migrates these exported
260
+ # metric names to the Chrys-owned ``chrys.function.*`` schema.
261
+ MEASUREMENT_FUNCTION_TAG_NAME = "chrys.function.name"
262
+ MEASUREMENT_FUNCTION_INVOCATION_DURATION = "chrys.function.invocation.duration"
263
+
264
+ def __repr__(self) -> str:
265
+ """Return the string representation of the enum member."""
266
+ return self.value
267
+
268
+ def __str__(self) -> str:
269
+ """Return the string representation of the enum member."""
270
+ return self.value
271
+
272
+
273
+ # Event and finish-reason names used by message telemetry.
274
+ ROLE_EVENT_MAP = {
275
+ "system": OtelAttr.SYSTEM_MESSAGE,
276
+ "user": OtelAttr.USER_MESSAGE,
277
+ "assistant": OtelAttr.ASSISTANT_MESSAGE,
278
+ "tool": OtelAttr.TOOL_MESSAGE,
279
+ }
280
+ FINISH_REASON_MAP = {
281
+ "stop": "stop",
282
+ "content_filter": "content_filter",
283
+ "tool_calls": "tool_call",
284
+ "length": "length",
285
+ }
286
+ USAGE_DETAIL_TO_OTEL_ATTR: Final[tuple[tuple[str, OtelAttr], ...]] = (
287
+ ("input_token_count", OtelAttr.INPUT_TOKENS),
288
+ ("output_token_count", OtelAttr.OUTPUT_TOKENS),
289
+ ("anthropic.cache_creation_input_tokens", OtelAttr.CACHE_CREATION_INPUT_TOKENS),
290
+ ("anthropic.cache_read_input_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
291
+ ("deepseek.prompt_cache_hit_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
292
+ ("openai.cached_input_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
293
+ ("prompt/cached_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
294
+ ("cache_creation_input_token_count", OtelAttr.CACHE_CREATION_INPUT_TOKENS),
295
+ ("cache_read_input_token_count", OtelAttr.CACHE_READ_INPUT_TOKENS),
296
+ ("reasoning_output_token_count", OtelAttr.REASONING_OUTPUT_TOKENS),
297
+ ("openai.reasoning_tokens", OtelAttr.REASONING_OUTPUT_TOKENS),
298
+ ("completion/reasoning_tokens", OtelAttr.REASONING_OUTPUT_TOKENS),
299
+ )
300
+
301
+
302
+ class MessageListTimestampFilter(logging.Filter):
303
+ """Bump per-message log timestamps to preserve history order.
304
+
305
+ Chat-history events are emitted within nanoseconds of each other and many
306
+ backends truncate timestamp resolution; incrementing by 1 microsecond per
307
+ message index preserves a restorable order.
308
+ """
309
+
310
+ INDEX_KEY: ClassVar[str] = "chat_message_index"
311
+
312
+ def filter(self, record: logging.LogRecord) -> bool:
313
+ """Increment the timestamp of INFO logs by 1 microsecond."""
314
+ if hasattr(record, self.INDEX_KEY):
315
+ idx = getattr(record, self.INDEX_KEY)
316
+ record.created += idx * 1e-6
317
+ return True
318
+
319
+
320
+ logger.addFilter(MessageListTimestampFilter())
321
+
322
+
323
+ def get_tracer(
324
+ instrumenting_module_name: str = _SCOPE_NAME,
325
+ instrumenting_library_version: str = _SCOPE_VERSION,
326
+ schema_url: str | None = None,
327
+ attributes: dict[str, Any] | None = None,
328
+ ) -> trace.Tracer:
329
+ """Get a tracer scoped to the installed Chrys version by default."""
330
+ return trace.get_tracer(
331
+ instrumenting_module_name=instrumenting_module_name,
332
+ instrumenting_library_version=instrumenting_library_version,
333
+ schema_url=schema_url,
334
+ attributes=attributes,
335
+ )
336
+
337
+
338
+ def get_meter(
339
+ name: str = _SCOPE_NAME,
340
+ version: str = _SCOPE_VERSION,
341
+ schema_url: str | None = None,
342
+ attributes: dict[str, Any] | None = None,
343
+ ) -> metrics.Meter:
344
+ """Get a Chrys-scoped meter, tolerating SDKs without meter attributes."""
345
+ try:
346
+ return metrics.get_meter(name=name, version=version, schema_url=schema_url, attributes=attributes)
347
+ except TypeError:
348
+ # Older OpenTelemetry releases do not support the attributes parameter.
349
+ return metrics.get_meter(name=name, version=version, schema_url=schema_url)
350
+
351
+
352
+ def _get_duration_histogram() -> metrics.Histogram:
353
+ """Create the model-operation duration histogram with shared duration buckets."""
354
+ return get_meter().create_histogram(
355
+ name=OtelAttr.LLM_OPERATION_DURATION,
356
+ unit=OtelAttr.DURATION_UNIT,
357
+ description="Captures the duration of operations of function-invoking chat clients",
358
+ explicit_bucket_boundaries_advisory=OPERATION_DURATION_BUCKET_BOUNDARIES,
359
+ )
360
+
361
+
362
+ def _get_token_usage_histogram() -> metrics.Histogram:
363
+ """Create the chat token-usage histogram with shared token buckets."""
364
+ return get_meter().create_histogram(
365
+ name=OtelAttr.LLM_TOKEN_USAGE,
366
+ unit=OtelAttr.T_UNIT,
367
+ description="Captures the token usage of chat clients",
368
+ explicit_bucket_boundaries_advisory=TOKEN_USAGE_BUCKET_BOUNDARIES,
369
+ )
370
+
371
+
372
+ def get_function_duration_histogram() -> metrics.Histogram:
373
+ """Create the per-tool duration histogram through the proxy meter.
374
+
375
+ The loop caches it on first use and records only while ``TELEMETRY_GATE``
376
+ is enabled.
377
+ """
378
+ return get_meter().create_histogram(
379
+ name=OtelAttr.MEASUREMENT_FUNCTION_INVOCATION_DURATION,
380
+ unit=OtelAttr.DURATION_UNIT,
381
+ description="Measures the duration of a function's execution",
382
+ explicit_bucket_boundaries_advisory=OPERATION_DURATION_BUCKET_BOUNDARIES,
383
+ )
384
+
385
+
386
+ # Runtime kwargs that are not JSON serializable stay out of sensitive
387
+ # TOOL_ARGUMENTS capture.
388
+ FUNCTION_SPAN_EXCLUDED_KWARGS: Final[frozenset[str]] = frozenset(
389
+ {
390
+ "chat_options",
391
+ "tools",
392
+ "tool_choice",
393
+ "session",
394
+ "conversation_id",
395
+ "options",
396
+ "response_format",
397
+ }
398
+ )
399
+
400
+
401
+ def get_function_span_attributes(function: FunctionTool, tool_call_id: str | None = None) -> dict[str, str]:
402
+ """Build tool identity and operation attributes for a function span."""
403
+ attributes: dict[str, str] = {
404
+ OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
405
+ OtelAttr.TOOL_NAME: function.name,
406
+ OtelAttr.TOOL_CALL_ID: tool_call_id or "unknown",
407
+ OtelAttr.TOOL_TYPE: "function",
408
+ }
409
+ if function.description:
410
+ attributes[OtelAttr.TOOL_DESCRIPTION] = function.description
411
+ return attributes
412
+
413
+
414
+ def get_function_span(attributes: dict[str, str]) -> contextlib.AbstractContextManager[trace.Span]:
415
+ """Start the current ``execute_tool {name}`` span; the caller records errors explicitly."""
416
+ return get_tracer().start_as_current_span(
417
+ name=f"{attributes[OtelAttr.OPERATION]} {attributes[OtelAttr.TOOL_NAME]}",
418
+ attributes=attributes,
419
+ set_status_on_exception=False,
420
+ end_on_exit=True,
421
+ record_exception=False,
422
+ )
423
+
424
+
425
+ @contextlib.contextmanager
426
+ def create_mcp_client_span(
427
+ method_name: str,
428
+ target: str | None = None,
429
+ attributes: dict[str, Any] | None = None,
430
+ ) -> Generator[trace.Span, Any, Any]:
431
+ """Create a Chrys-gated MCP client span."""
432
+ span_name = f"{method_name} {target}" if target else method_name
433
+ attrs: dict[str, Any] = {OtelAttr.MCP_METHOD_NAME: method_name}
434
+ if attributes:
435
+ attrs.update(attributes)
436
+ tracer = get_tracer() if TELEMETRY_GATE.enabled else trace.NoOpTracer()
437
+ span = tracer.start_span(span_name, kind=trace.SpanKind.CLIENT, attributes=attrs)
438
+ with trace.use_span(
439
+ span=span,
440
+ end_on_exit=True,
441
+ record_exception=True,
442
+ set_status_on_exception=True,
443
+ ) as current_span:
444
+ yield current_span
445
+
446
+
447
+ def set_mcp_span_error(span: trace.Span, error_type: str, description: str | None = None) -> None:
448
+ """Set MCP span error status and ``error.type``."""
449
+ span.set_attribute(OtelAttr.ERROR_TYPE, error_type)
450
+ span.set_status(trace.StatusCode.ERROR, description=description)
451
+
452
+
453
+ def _serialize_tool_definitions(tools: Any) -> str | None:
454
+ """Serialize canonical OTel tool definitions without raising into a run.
455
+
456
+ Normalize once, then independently convert, canonicalize,
457
+ narrow, and serialize each tool so one malformed declaration cannot drop
458
+ the valid definitions around it.
459
+ """
460
+ try:
461
+ normalized_tools = normalize_tools(tools)
462
+ except Exception:
463
+ logger.warning(
464
+ "Failed to normalize tool definitions for telemetry; skipping attribute.",
465
+ exc_info=True,
466
+ )
467
+ return None
468
+ if not normalized_tools:
469
+ return None
470
+
471
+ fragments: list[str] = []
472
+ for tool_item in normalized_tools:
473
+ try:
474
+ definition = _build_tool_otel_definition(tool_item)
475
+ if definition is None:
476
+ continue
477
+ fragments.append(json.dumps(definition, ensure_ascii=False))
478
+ except Exception:
479
+ logger.warning(
480
+ "Failed to build or serialize telemetry definition for tool %r (%s); skipping tool.",
481
+ _tool_name_for_log(tool_item),
482
+ type(tool_item).__name__,
483
+ exc_info=True,
484
+ )
485
+ continue
486
+ return f"[{','.join(fragments)}]" if fragments else None
487
+
488
+
489
+ def _tool_name_for_log(tool_item: Any) -> str:
490
+ """Return a best-effort tool name without risking the warning path."""
491
+ if isinstance(tool_item, FunctionTool):
492
+ return tool_item.name
493
+ if isinstance(tool_item, Mapping):
494
+ try:
495
+ nested = tool_item.get("function") if tool_item.get("type") == "function" else None
496
+ source = nested if isinstance(nested, Mapping) else tool_item
497
+ for key in ("name", "server_label", "type"):
498
+ value = source.get(key)
499
+ if isinstance(value, str) and value:
500
+ return value
501
+ except Exception:
502
+ pass
503
+ return type(tool_item).__name__
504
+
505
+
506
+ def _build_tool_otel_definition(tool_item: Any) -> dict[str, Any] | None:
507
+ """Convert one tool declaration and canonicalize its OTel field set."""
508
+ raw: Any
509
+ if isinstance(tool_item, FunctionTool):
510
+ raw = tool_item.to_json_schema_spec()
511
+ else:
512
+ model_dump = getattr(tool_item, "model_dump", None)
513
+ if callable(model_dump):
514
+ raw = model_dump(exclude_none=True)
515
+ else:
516
+ to_dict = getattr(tool_item, "to_dict", None)
517
+ if callable(to_dict):
518
+ raw = to_dict()
519
+ elif isinstance(tool_item, Mapping):
520
+ raw = tool_item
521
+ else:
522
+ logger.warning(
523
+ "Can't parse tool to OpenTelemetry tool definition: %s.",
524
+ type(tool_item).__name__,
525
+ )
526
+ return None
527
+
528
+ if not isinstance(raw, Mapping):
529
+ logger.warning(
530
+ "Can't parse tool to OpenTelemetry tool definition: %s conversion returned %s.",
531
+ type(tool_item).__name__,
532
+ type(raw).__name__,
533
+ )
534
+ return None
535
+ return _otel_definition_from_mapping(raw)
536
+
537
+
538
+ def _otel_definition_from_mapping(raw: Mapping[str, Any]) -> dict[str, Any] | None:
539
+ """Flatten provider tool shapes and retain only OTel-safe fields."""
540
+ nested_function = raw.get("function") if raw.get("type") == "function" else None
541
+ if isinstance(nested_function, Mapping):
542
+ name = nested_function.get("name")
543
+ if not isinstance(name, str) or not name:
544
+ logger.warning("Can't parse tool to OpenTelemetry tool definition: missing 'name'.")
545
+ return None
546
+ return _otel_tool_definition(
547
+ "function",
548
+ name,
549
+ description=nested_function.get("description"),
550
+ parameters=nested_function.get("parameters"),
551
+ )
552
+
553
+ type_value = raw.get("type")
554
+ if not isinstance(type_value, str) or not type_value:
555
+ logger.warning("Can't parse tool to OpenTelemetry tool definition: missing 'type'.")
556
+ return None
557
+
558
+ name_value = next(
559
+ (value for key in ("name", "server_label") if isinstance((value := raw.get(key)), str) and value),
560
+ type_value,
561
+ )
562
+ return _otel_tool_definition(
563
+ type_value,
564
+ name_value,
565
+ description=raw.get("description") or raw.get("server_description"),
566
+ parameters=raw.get("parameters") or raw.get("input_schema"),
567
+ )
568
+
569
+
570
+ def _otel_tool_definition(
571
+ type_value: str,
572
+ name_value: str,
573
+ *,
574
+ description: Any = None,
575
+ parameters: Any = None,
576
+ ) -> dict[str, Any]:
577
+ """Emit only ``type``, ``name``, and non-empty descriptive fields."""
578
+ definition: dict[str, Any] = {"type": type_value, "name": name_value}
579
+ if description:
580
+ definition["description"] = description
581
+ if parameters:
582
+ definition["parameters"] = parameters
583
+ return definition
584
+
585
+
586
+ def _get_data_bytes_as_str(content: Content) -> str | None:
587
+ """Return the base64 payload for data URI content.
588
+
589
+ Return ``None`` for other content; reject data URIs without base64 encoding
590
+ with ``ValueError``.
591
+ """
592
+ if content.type not in ("data", "uri"):
593
+ return None
594
+ uri = content.uri
595
+ if not uri:
596
+ return None
597
+ if not uri.startswith("data:"):
598
+ return None
599
+ if ";base64," not in uri:
600
+ raise ValueError("Data URI must use base64 encoding")
601
+ _, data = uri.split(";base64,", 1)
602
+ return data
603
+
604
+
605
+ @contextlib.contextmanager
606
+ def _activate_span(span: trace.Span) -> Generator[None]:
607
+ """Attach the span for one iterator pull and detach it in the same context."""
608
+ token = otel_context.attach(trace.set_span_in_context(span))
609
+ try:
610
+ yield
611
+ finally:
612
+ otel_context.detach(token)
613
+
614
+
615
+ @contextlib.contextmanager
616
+ def _get_span(
617
+ attributes: dict[str, Any],
618
+ span_name_attribute: str,
619
+ ) -> Generator[trace.Span, Any, Any]:
620
+ """Activate an operation span and end it on context exit."""
621
+ operation = attributes.get(OtelAttr.OPERATION, "operation")
622
+ span_name = attributes.get(span_name_attribute, "unknown")
623
+ span = get_tracer().start_span(f"{operation} {span_name}")
624
+ span.set_attributes(attributes)
625
+ with trace.use_span(
626
+ span=span,
627
+ end_on_exit=True,
628
+ record_exception=False,
629
+ set_status_on_exception=False,
630
+ ) as current_span:
631
+ yield current_span
632
+
633
+
634
+ def _start_streaming_span(attributes: dict[str, Any], span_name_attribute: str) -> trace.Span:
635
+ """Start a streaming span without attaching it to the current context.
636
+
637
+ The caller owns ending it (cleanup hooks) and activating it around each
638
+ pull (:func:`_activate_span` via ``with_pull_context_manager``) —
639
+ attaching at creation would die in cross-context cleanup.
640
+ """
641
+ operation = attributes.get(OtelAttr.OPERATION, "operation")
642
+ span_name = attributes.get(span_name_attribute, "unknown")
643
+ span = get_tracer().start_span(f"{operation} {span_name}")
644
+ span.set_attributes(attributes)
645
+ return span
646
+
647
+
648
+ def _get_instructions_from_options(options: Any) -> str | list[str] | None:
649
+ """Read string or string-list instructions from an options mapping."""
650
+ if options is None:
651
+ return None
652
+ if isinstance(options, Mapping):
653
+ instructions = cast("Mapping[str, Any]", options).get("instructions")
654
+ if isinstance(instructions, str):
655
+ return instructions
656
+ if isinstance(instructions, list) and all(isinstance(item, str) for item in instructions):
657
+ return instructions
658
+ return None
659
+ return None
660
+
661
+
662
+ # Request-to-span mapping. Each entry:
663
+ # source_keys -> (otel_attribute_key, transform_func, check_options_first, default_value).
664
+ # The ``tools`` transform performs guarded, per-tool canonicalization so span
665
+ # construction cannot expose provider-only fields or raise into a live request.
666
+ OTEL_ATTR_MAP: dict[str | tuple[str, ...], tuple[str, Callable[[Any], Any] | None, bool, Any]] = {
667
+ "choice_count": (OtelAttr.CHOICE_COUNT, None, False, 1),
668
+ "operation_name": (OtelAttr.OPERATION, None, False, None),
669
+ "system_name": (OtelAttr.SYSTEM, None, False, None),
670
+ "provider_name": (OtelAttr.PROVIDER_NAME, None, False, None),
671
+ "service_url": (OtelAttr.ADDRESS, None, False, None),
672
+ "conversation_id": (OtelAttr.CONVERSATION_ID, None, True, None),
673
+ "seed": (OtelAttr.SEED, None, True, None),
674
+ "frequency_penalty": (OtelAttr.FREQUENCY_PENALTY, None, True, None),
675
+ "max_tokens": (OtelAttr.REQUEST_MAX_TOKENS, None, True, None),
676
+ "stop": (OtelAttr.STOP_SEQUENCES, None, True, None),
677
+ "temperature": (OtelAttr.REQUEST_TEMPERATURE, None, True, None),
678
+ "top_p": (OtelAttr.REQUEST_TOP_P, None, True, None),
679
+ "presence_penalty": (OtelAttr.PRESENCE_PENALTY, None, True, None),
680
+ "top_k": (OtelAttr.TOP_K, None, True, None),
681
+ "encoding_formats": (
682
+ OtelAttr.ENCODING_FORMATS,
683
+ lambda v: json.dumps(v if isinstance(v, list) else [v]),
684
+ True,
685
+ None,
686
+ ),
687
+ "agent_id": (OtelAttr.AGENT_ID, None, False, None),
688
+ "agent_name": (OtelAttr.AGENT_NAME, None, False, None),
689
+ "agent_description": (OtelAttr.AGENT_DESCRIPTION, None, False, None),
690
+ "model": (OtelAttr.REQUEST_MODEL, None, True, None),
691
+ # Tools with validation - returns None if no valid tools
692
+ "tools": (
693
+ OtelAttr.TOOL_DEFINITIONS,
694
+ _serialize_tool_definitions,
695
+ True,
696
+ None,
697
+ ),
698
+ # Error type extraction
699
+ "error": (OtelAttr.ERROR_TYPE, lambda e: type(e).__name__, False, None),
700
+ # thread_id overrides conversation_id - processed after conversation_id due to dict ordering
701
+ "thread_id": (OtelAttr.CONVERSATION_ID, None, False, None),
702
+ }
703
+
704
+
705
+ def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
706
+ """Map request settings to telemetry attributes, omitting unset or rejected values."""
707
+ attributes: dict[str, Any] = {}
708
+ options = kwargs.get("all_options", kwargs.get("options"))
709
+ options_mapping = cast("Mapping[str, Any]", options) if isinstance(options, Mapping) else None
710
+
711
+ for source_keys, (otel_key, transform_func, check_options, default_value) in OTEL_ATTR_MAP.items():
712
+ keys = (source_keys,) if isinstance(source_keys, str) else source_keys
713
+
714
+ value = None
715
+ for key in keys:
716
+ if check_options and options_mapping is not None:
717
+ value = options_mapping.get(key)
718
+ if value is None:
719
+ value = kwargs.get(key)
720
+ if value is not None:
721
+ break
722
+
723
+ if value is None and default_value is not None:
724
+ value = default_value
725
+
726
+ if value is not None:
727
+ result = transform_func(value) if transform_func else value
728
+ # Allow transform_func to return None to skip attribute
729
+ if result is not None:
730
+ attributes[otel_key] = result
731
+
732
+ return attributes
733
+
734
+
735
+ def capture_exception(span: trace.Span, exception: BaseException, timestamp: int | None = None) -> None:
736
+ """Record the exception and mark the span as failed."""
737
+ span.set_attribute(OtelAttr.ERROR_TYPE, type(exception).__name__)
738
+ span.record_exception(exception=exception, timestamp=timestamp)
739
+ span.set_status(status=trace.StatusCode.ERROR, description=repr(exception))
740
+
741
+
742
+ def _capture_system_instructions(span: trace.Span, system_instructions: str | list[str] | None) -> None:
743
+ """Capture system instructions on a span."""
744
+ if not system_instructions:
745
+ return
746
+ otel_sys_instructions = [
747
+ {"type": "text", "content": instruction} for instruction in _normalize_instructions(system_instructions)
748
+ ]
749
+ span.set_attribute(OtelAttr.SYSTEM_INSTRUCTIONS, json.dumps(otel_sys_instructions, ensure_ascii=False))
750
+
751
+
752
+ def _capture_current_agent_system_instructions(
753
+ agent_span: trace.Span,
754
+ chat_span: trace.Span,
755
+ system_instructions: str | list[str] | None,
756
+ ) -> None:
757
+ """Capture final chat instructions on the current agent span when the chat span belongs to it."""
758
+ if not system_instructions or not agent_span.is_recording():
759
+ return
760
+
761
+ agent_attributes_obj = getattr(agent_span, "attributes", None)
762
+ if not isinstance(agent_attributes_obj, Mapping):
763
+ return
764
+ agent_attributes = cast("Mapping[str, Any]", agent_attributes_obj)
765
+ if agent_attributes.get(OtelAttr.OPERATION.value) != OtelAttr.AGENT_INVOKE_OPERATION:
766
+ return
767
+
768
+ if not _instructions_preserve_existing_agent_instructions(agent_attributes, system_instructions):
769
+ return
770
+
771
+ chat_parent = getattr(chat_span, "parent", None)
772
+ agent_context = agent_span.get_span_context()
773
+ if (
774
+ chat_parent is None
775
+ or chat_parent.span_id != agent_context.span_id
776
+ or chat_parent.trace_id != agent_context.trace_id
777
+ ):
778
+ return
779
+
780
+ _capture_system_instructions(agent_span, system_instructions)
781
+
782
+
783
+ def _normalize_instructions(system_instructions: str | list[str]) -> list[str]:
784
+ """Normalize system instructions to telemetry text items."""
785
+ return system_instructions if isinstance(system_instructions, list) else [system_instructions]
786
+
787
+
788
+ def _instructions_preserve_existing_agent_instructions(
789
+ agent_attributes: Mapping[str, Any],
790
+ system_instructions: str | list[str],
791
+ ) -> bool:
792
+ """Return True when chat instructions preserve the agent span's existing instructions."""
793
+ existing = agent_attributes.get(OtelAttr.SYSTEM_INSTRUCTIONS)
794
+ if not isinstance(existing, str):
795
+ return True
796
+
797
+ try:
798
+ existing_items_obj = json.loads(existing)
799
+ except json.JSONDecodeError:
800
+ return False
801
+
802
+ if not isinstance(existing_items_obj, list):
803
+ return False
804
+ existing_items = cast("list[object]", existing_items_obj)
805
+
806
+ existing_contents: list[str] = []
807
+ for item in existing_items:
808
+ if not isinstance(item, Mapping):
809
+ continue
810
+ content = cast("Mapping[str, Any]", item).get("content")
811
+ if isinstance(content, str):
812
+ existing_contents.append(content)
813
+
814
+ existing_text = "\n".join(existing_contents)
815
+ new_text = "\n".join(_normalize_instructions(system_instructions))
816
+ return new_text == existing_text or new_text.startswith(f"{existing_text}\n")
817
+
818
+
819
+ def _capture_messages(
820
+ span: trace.Span,
821
+ provider_name: str,
822
+ messages: AgentRunInputs,
823
+ system_instructions: str | list[str] | None = None,
824
+ output: bool = False,
825
+ finish_reason: FinishReasonLiteral | FinishReason | None = None,
826
+ ) -> None:
827
+ """Emit sensitive message events and capture their representation on the span."""
828
+ normalized_messages = normalize_messages(messages)
829
+ otel_messages: list[dict[str, Any]] = []
830
+ for index, message in enumerate(normalized_messages):
831
+ # Reuse the otel message representation for logging instead of calling to_dict()
832
+ # to avoid expensive Pydantic serialization overhead
833
+ otel_message = _to_otel_message(message)
834
+ logger.info(
835
+ otel_message,
836
+ extra={
837
+ OtelAttr.EVENT_NAME: OtelAttr.CHOICE if output else ROLE_EVENT_MAP.get(message.role),
838
+ OtelAttr.PROVIDER_NAME: provider_name,
839
+ MessageListTimestampFilter.INDEX_KEY: index,
840
+ },
841
+ )
842
+ otel_messages.append(otel_message)
843
+ # Total in both directions: an empty message list has no tail to annotate, and a
844
+ # provider-specific reason no map entry covers is emitted verbatim rather than
845
+ # raising past the caller's exception handler and mislabelling the span as failed.
846
+ if finish_reason and otel_messages:
847
+ otel_messages[-1]["finish_reason"] = FINISH_REASON_MAP.get(finish_reason, finish_reason)
848
+ span.set_attribute(
849
+ OtelAttr.OUTPUT_MESSAGES if output else OtelAttr.INPUT_MESSAGES, json.dumps(otel_messages, ensure_ascii=False)
850
+ )
851
+ _capture_system_instructions(span, system_instructions)
852
+
853
+
854
+ def _to_otel_message(message: Message) -> dict[str, Any]:
855
+ """Convert a message into its telemetry role and content parts."""
856
+ return {"role": message.role, "parts": [_to_otel_part(content) for content in message.contents]}
857
+
858
+
859
+ def _to_otel_part(content: Content) -> dict[str, Any] | None:
860
+ """Convert content to a telemetry part, retaining unknown content through serialization."""
861
+ match content.type:
862
+ case "text":
863
+ return {"type": "text", "content": content.text}
864
+ case "text_reasoning":
865
+ return {"type": "reasoning", "content": content.text}
866
+ case "uri":
867
+ return {
868
+ "type": "uri",
869
+ "uri": content.uri,
870
+ "mime_type": content.media_type,
871
+ "modality": content.media_type.split("/")[0] if content.media_type else None,
872
+ }
873
+ case "data":
874
+ return {
875
+ "type": "blob",
876
+ "content": _get_data_bytes_as_str(content),
877
+ "mime_type": content.media_type,
878
+ "modality": content.media_type.split("/")[0] if content.media_type else None,
879
+ }
880
+ case "function_call":
881
+ return {"type": "tool_call", "id": content.call_id, "name": content.name, "arguments": content.arguments}
882
+ case "function_result":
883
+ return {
884
+ "type": "tool_call_response",
885
+ "id": content.call_id,
886
+ "response": content.result if content.result is not None else "",
887
+ }
888
+ case _:
889
+ # GenericPart in otel output messages json spec.
890
+ # just required type, and arbitrary other fields.
891
+ return content.to_dict(exclude_none=True)
892
+ return None
893
+
894
+
895
+ def _mark_inner_response_telemetry_captured(response: ChatResponse | AgentResponse) -> None:
896
+ """Track response fields and accumulated usage already captured by the inner chat layer."""
897
+ captured_fields = INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.get()
898
+ if captured_fields is None:
899
+ return
900
+ if response.response_id:
901
+ captured_fields.add(INNER_RESPONSE_ID_CAPTURED_FIELD)
902
+ if response.usage_details:
903
+ captured_fields.add(INNER_USAGE_CAPTURED_FIELD)
904
+ accumulated = INNER_ACCUMULATED_USAGE.get()
905
+ if accumulated is not None:
906
+ INNER_ACCUMULATED_USAGE.set(add_usage_details(accumulated, response.usage_details))
907
+
908
+
909
+ def _apply_accumulated_usage(attributes: dict[str, Any], captured_fields: set[str]) -> None:
910
+ """Apply inner-call usage to the agent attributes once the inner layer has recorded it."""
911
+ if INNER_USAGE_CAPTURED_FIELD not in captured_fields:
912
+ return
913
+ accumulated = INNER_ACCUMULATED_USAGE.get()
914
+ if not accumulated:
915
+ return
916
+ _apply_usage_attributes(attributes, accumulated)
917
+
918
+
919
+ def _apply_usage_attributes(attributes: dict[str, Any], usage: Mapping[str, Any]) -> None:
920
+ """Apply known usage details as standard OTel GenAI attributes."""
921
+ for usage_key, otel_attr in USAGE_DETAIL_TO_OTEL_ATTR:
922
+ value = usage.get(usage_key)
923
+ if value is None or isinstance(value, bool) or not isinstance(value, int):
924
+ continue
925
+ attributes.setdefault(otel_attr, value)
926
+
927
+
928
+ def _get_response_attributes(
929
+ attributes: dict[str, Any],
930
+ response: ChatResponse | AgentResponse,
931
+ *,
932
+ capture_response_id: bool = True,
933
+ capture_usage: bool = True,
934
+ ) -> dict[str, Any]:
935
+ """Add response identity, finish reason, model and selected usage fields to span attributes."""
936
+ if capture_response_id and response.response_id:
937
+ attributes[OtelAttr.RESPONSE_ID] = response.response_id
938
+ finish_reason = response.finish_reason
939
+ if not finish_reason:
940
+ finish_reason = (
941
+ getattr(response.raw_representation, "finish_reason", None) if response.raw_representation else None
942
+ )
943
+ if isinstance(finish_reason, str) and finish_reason:
944
+ attributes[OtelAttr.FINISH_REASONS] = json.dumps([finish_reason])
945
+ if model := getattr(response, "model", None):
946
+ attributes[OtelAttr.RESPONSE_MODEL] = model
947
+ if capture_usage and (usage := response.usage_details):
948
+ _apply_usage_attributes(attributes, usage)
949
+ return attributes
950
+
951
+
952
+ # Shared dimensions for GenAI duration and usage metrics.
953
+ GEN_AI_METRIC_ATTRIBUTES = (
954
+ OtelAttr.OPERATION,
955
+ OtelAttr.PROVIDER_NAME,
956
+ OtelAttr.REQUEST_MODEL,
957
+ OtelAttr.RESPONSE_MODEL,
958
+ OtelAttr.ADDRESS,
959
+ OtelAttr.PORT,
960
+ )
961
+
962
+
963
+ def _capture_response(
964
+ span: trace.Span,
965
+ attributes: dict[str, Any],
966
+ operation_duration_histogram: metrics.Histogram | None = None,
967
+ token_usage_histogram: metrics.Histogram | None = None,
968
+ duration: float | None = None,
969
+ ) -> None:
970
+ """Write response attributes to the span and record duration and token metrics."""
971
+ span.set_attributes(attributes)
972
+ attrs: dict[str, Any] = {k: v for k, v in attributes.items() if k in GEN_AI_METRIC_ATTRIBUTES}
973
+ if token_usage_histogram and (input_tokens := attributes.get(OtelAttr.INPUT_TOKENS)) is not None:
974
+ token_usage_histogram.record(input_tokens, attributes={**attrs, OtelAttr.T_TYPE: OtelAttr.T_TYPE_INPUT})
975
+ if token_usage_histogram and (output_tokens := attributes.get(OtelAttr.OUTPUT_TOKENS)) is not None:
976
+ token_usage_histogram.record(output_tokens, {**attrs, OtelAttr.T_TYPE: OtelAttr.T_TYPE_OUTPUT})
977
+ if operation_duration_histogram and duration is not None:
978
+ if OtelAttr.ERROR_TYPE in attributes:
979
+ attrs[OtelAttr.ERROR_TYPE] = attributes[OtelAttr.ERROR_TYPE]
980
+ operation_duration_histogram.record(duration, attributes=attrs)
981
+
982
+
983
+ def _record_failed_duration(
984
+ histogram: metrics.Histogram | None,
985
+ attributes: Mapping[str, Any],
986
+ exception: BaseException,
987
+ duration: float | None,
988
+ ) -> None:
989
+ """Record a failed operation's duration under its ``error.type``, beside the successful ones."""
990
+ if histogram is None or duration is None:
991
+ return
992
+ attrs: dict[str, Any] = {k: v for k, v in attributes.items() if k in GEN_AI_METRIC_ATTRIBUTES}
993
+ attrs[OtelAttr.ERROR_TYPE] = type(exception).__name__
994
+ histogram.record(duration, attributes=attrs)
995
+
996
+
997
+ def _stream_error_of(stream: Any) -> Exception | None:
998
+ """Read a stream failure, defaulting to None when the stream exposes no error field."""
999
+ return getattr(stream, "_stream_error", None)
1000
+
1001
+
1002
+ def _stream_abandoned(stream: Any) -> bool:
1003
+ """Return whether the stream was closed before producing a final response.
1004
+
1005
+ An abandoned stream's partial updates must never be finalized — a
1006
+ ``get_final_response()`` here would run result hooks (``after_run``
1007
+ context providers included) on a response the run never produced.
1008
+ """
1009
+ return getattr(stream, "_abandoned", False)
1010
+
1011
+
1012
+ class _StreamingSpan:
1013
+ """Own span closure, elapsed time and stream hooks without retaining the stream.
1014
+
1015
+ With a *duration_histogram*, a failed stream records its duration there
1016
+ under its ``error.type``.
1017
+ """
1018
+
1019
+ def __init__(
1020
+ self,
1021
+ span: trace.Span,
1022
+ *,
1023
+ attributes: Mapping[str, Any],
1024
+ duration_histogram: metrics.Histogram | None = None,
1025
+ ) -> None:
1026
+ self._span = span
1027
+ self._attributes = attributes
1028
+ self._duration_histogram = duration_histogram
1029
+ self._closed = False
1030
+ self.duration: float | None = None
1031
+ self._start_time = perf_counter()
1032
+
1033
+ def close(self) -> None:
1034
+ if self._closed:
1035
+ return
1036
+ self._closed = True
1037
+ self._span.end()
1038
+
1039
+ def _record_duration(self) -> None:
1040
+ self.duration = perf_counter() - self._start_time
1041
+
1042
+ def _fail(self, exception: BaseException) -> None:
1043
+ capture_exception(span=self._span, exception=exception, timestamp=time_ns())
1044
+ _record_failed_duration(self._duration_histogram, self._attributes, exception, self.duration)
1045
+
1046
+ def should_finalize(self, stream: ResponseStream[Any, Any]) -> bool:
1047
+ stream_error = _stream_error_of(stream)
1048
+ if stream_error is not None:
1049
+ # Skip result hooks such as after_run context providers on error
1050
+ # paths. Capture the error on the span before returning.
1051
+ self._fail(stream_error)
1052
+ return False
1053
+ # Closed before completion (cancel/stall abandonment): partial
1054
+ # updates are not a response and must not be finalized.
1055
+ return not _stream_abandoned(stream)
1056
+
1057
+ async def final_response[FinalT](self, stream: ResponseStream[Any, FinalT]) -> FinalT:
1058
+ """Finalize *stream* from its cleanup hook; a failing finalizer or result hook fails it once.
1059
+
1060
+ The failure is recorded on the span and re-raised to the consumer.
1061
+ Swallowed, it would leave the stream unfinalized, and the stream
1062
+ finalizes again right after its cleanup hooks, re-running every result
1063
+ hook that already succeeded.
1064
+ """
1065
+ try:
1066
+ return await stream.get_final_response()
1067
+ except Exception as exception:
1068
+ self._fail(exception)
1069
+ raise
1070
+
1071
+ def wrap[UpdateT, FinalT](
1072
+ self,
1073
+ stream: ResponseStream[UpdateT, FinalT],
1074
+ finalize_stream: Callable[[], Awaitable[None]],
1075
+ ) -> ResponseStream[UpdateT, FinalT]:
1076
+ # Activate around each pull so inner chat, HTTP and tool spans inherit
1077
+ # this parent. Attach and detach stay in the same async context.
1078
+ wrapped_stream = (
1079
+ stream.with_cleanup_hook(self._record_duration)
1080
+ .with_cleanup_hook(finalize_stream)
1081
+ .with_pull_context_manager(lambda: _activate_span(self._span))
1082
+ )
1083
+ # Retain only this scaffold, never the stream or its finalizer closure,
1084
+ # so an unconsumed stream can be collected and its span closed once.
1085
+ weakref.finalize(wrapped_stream, self.close)
1086
+ return wrapped_stream
1087
+
1088
+
1089
+ class ChatTelemetryLayer(_ChatTelemetryBase):
1090
+ """Trace chat calls through ``super().get_response()`` under ``TELEMETRY_GATE``.
1091
+
1092
+ Telemetry must wrap ``get_response`` and must never define
1093
+ ``_inner_get_response``: the wire client below this layer implements
1094
+ that hook, and request reporting rides on it.
1095
+ """
1096
+
1097
+ def __init__(self, *args: Any, otel_provider_name: str | None = None, **kwargs: Any) -> None:
1098
+ """Initialize the client before constructing telemetry attributes and histograms."""
1099
+ super().__init__(*args, **kwargs)
1100
+ self.token_usage_histogram = _get_token_usage_histogram()
1101
+ self.duration_histogram = _get_duration_histogram()
1102
+ self.otel_provider_name = otel_provider_name or getattr(self, "OTEL_PROVIDER_NAME", "unknown")
1103
+
1104
+ @staticmethod
1105
+ def _backfill_request_model(span: trace.Span, attributes: dict[str, Any]) -> None:
1106
+ """Backfill the request model and span name from the served response model."""
1107
+ response_model = attributes.get(OtelAttr.RESPONSE_MODEL)
1108
+ if response_model and attributes.get(OtelAttr.REQUEST_MODEL, "unknown") == "unknown":
1109
+ attributes[OtelAttr.REQUEST_MODEL] = response_model
1110
+ operation = attributes.get(OtelAttr.OPERATION, "operation")
1111
+ span.update_name(f"{operation} {response_model}")
1112
+
1113
+ @overload
1114
+ def get_response(
1115
+ self,
1116
+ messages: Sequence[Message],
1117
+ *,
1118
+ stream: Literal[False] = False,
1119
+ options: Mapping[str, Any] | None = None,
1120
+ compaction_strategy: Any = None,
1121
+ tokenizer: Any = None,
1122
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1123
+ client_kwargs: Mapping[str, Any] | None = None,
1124
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
1125
+ ) -> Awaitable[ChatResponse[Any]]: ...
1126
+
1127
+ @overload
1128
+ def get_response(
1129
+ self,
1130
+ messages: Sequence[Message],
1131
+ *,
1132
+ stream: Literal[True],
1133
+ options: Mapping[str, Any] | None = None,
1134
+ compaction_strategy: Any = None,
1135
+ tokenizer: Any = None,
1136
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1137
+ client_kwargs: Mapping[str, Any] | None = None,
1138
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
1139
+ ) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
1140
+
1141
+ @overload
1142
+ def get_response(
1143
+ self,
1144
+ messages: Sequence[Message],
1145
+ *,
1146
+ stream: bool,
1147
+ options: Mapping[str, Any] | None = None,
1148
+ compaction_strategy: Any = None,
1149
+ tokenizer: Any = None,
1150
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1151
+ client_kwargs: Mapping[str, Any] | None = None,
1152
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
1153
+ ) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
1154
+
1155
+ def get_response( # ty: ignore[invalid-method-override] # Overloads refine the cooperative mixin protocol.
1156
+ self,
1157
+ messages: Sequence[Message],
1158
+ *,
1159
+ stream: bool = False,
1160
+ options: Mapping[str, Any] | None = None,
1161
+ compaction_strategy: Any = None,
1162
+ tokenizer: Any = None,
1163
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1164
+ client_kwargs: Mapping[str, Any] | None = None,
1165
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
1166
+ ) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
1167
+ """Trace chat responses with OpenTelemetry spans and metrics."""
1168
+ super_get_response = super().get_response # type: ignore[misc]
1169
+ merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
1170
+ prepared_observer_kwargs = (
1171
+ {"request_message_observer": request_message_observer}
1172
+ if request_message_observer is not None and isinstance(self, _PreparedRequestObserverClient)
1173
+ else {}
1174
+ )
1175
+
1176
+ if not TELEMETRY_GATE.enabled:
1177
+ return super_get_response( # type: ignore[no-any-return]
1178
+ messages=messages,
1179
+ stream=stream,
1180
+ options=options,
1181
+ compaction_strategy=compaction_strategy,
1182
+ tokenizer=tokenizer,
1183
+ function_invocation_kwargs=function_invocation_kwargs,
1184
+ client_kwargs=merged_client_kwargs,
1185
+ **prepared_observer_kwargs,
1186
+ )
1187
+
1188
+ opts: Mapping[str, Any] = options or {}
1189
+ provider_name = str(self.otel_provider_name)
1190
+ model = merged_client_kwargs.get("model") or opts.get("model") or getattr(self, "model", None) or "unknown"
1191
+ service_url_func = getattr(self, "service_url", None)
1192
+ service_url = str(service_url_func() if callable(service_url_func) else "unknown")
1193
+ attributes = _get_span_attributes(
1194
+ operation_name=OtelAttr.CHAT_COMPLETION_OPERATION,
1195
+ provider_name=provider_name,
1196
+ model=model,
1197
+ service_url=service_url,
1198
+ **merged_client_kwargs,
1199
+ )
1200
+
1201
+ if stream:
1202
+ agent_span = trace.get_current_span()
1203
+ span = _start_streaming_span(attributes, OtelAttr.REQUEST_MODEL)
1204
+
1205
+ if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
1206
+ system_instructions = _get_instructions_from_options(opts)
1207
+ _capture_current_agent_system_instructions(agent_span, span, system_instructions)
1208
+ _capture_messages(
1209
+ span=span,
1210
+ provider_name=provider_name,
1211
+ messages=messages,
1212
+ system_instructions=system_instructions,
1213
+ )
1214
+
1215
+ stream_span = _StreamingSpan(span, attributes=attributes, duration_histogram=self.duration_histogram)
1216
+
1217
+ try:
1218
+ with _activate_span(span):
1219
+ result_stream = cast(
1220
+ "ResponseStream[ChatResponseUpdate, ChatResponse[Any]]",
1221
+ super_get_response(
1222
+ messages=messages,
1223
+ stream=True,
1224
+ options=opts,
1225
+ compaction_strategy=compaction_strategy,
1226
+ tokenizer=tokenizer,
1227
+ function_invocation_kwargs=function_invocation_kwargs,
1228
+ client_kwargs=merged_client_kwargs,
1229
+ **prepared_observer_kwargs,
1230
+ ),
1231
+ )
1232
+ except Exception as exception:
1233
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
1234
+ stream_span.close()
1235
+ raise
1236
+
1237
+ async def _finalize_stream() -> None:
1238
+ try:
1239
+ if not stream_span.should_finalize(result_stream):
1240
+ return
1241
+ response: ChatResponse[Any] = await stream_span.final_response(result_stream)
1242
+ try:
1243
+ duration = stream_span.duration
1244
+ response_attributes = _get_response_attributes(attributes, response)
1245
+ self._backfill_request_model(span, response_attributes)
1246
+ _capture_response(
1247
+ span=span,
1248
+ attributes=response_attributes,
1249
+ token_usage_histogram=self.token_usage_histogram,
1250
+ operation_duration_histogram=self.duration_histogram,
1251
+ duration=duration,
1252
+ )
1253
+ _mark_inner_response_telemetry_captured(response)
1254
+ if (
1255
+ TELEMETRY_GATE.sensitive_enabled
1256
+ and isinstance(response, ChatResponse)
1257
+ and response.messages
1258
+ and span.is_recording()
1259
+ ):
1260
+ _capture_messages(
1261
+ span=span,
1262
+ provider_name=provider_name,
1263
+ messages=response.messages,
1264
+ finish_reason=response.finish_reason, # type: ignore[arg-type]
1265
+ output=True,
1266
+ )
1267
+ except Exception as exception:
1268
+ # Telemetry's own failure never fails the call.
1269
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
1270
+ finally:
1271
+ stream_span.close()
1272
+
1273
+ return stream_span.wrap(result_stream, _finalize_stream)
1274
+
1275
+ async def _get_response() -> ChatResponse:
1276
+ agent_span = trace.get_current_span()
1277
+ with _get_span(attributes=attributes, span_name_attribute=OtelAttr.REQUEST_MODEL) as span:
1278
+ if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
1279
+ system_instructions = _get_instructions_from_options(opts)
1280
+ _capture_current_agent_system_instructions(agent_span, span, system_instructions)
1281
+ _capture_messages(
1282
+ span=span,
1283
+ provider_name=provider_name,
1284
+ messages=messages,
1285
+ system_instructions=system_instructions,
1286
+ )
1287
+ start_time_stamp = perf_counter()
1288
+ try:
1289
+ response = cast(
1290
+ "ChatResponse[Any]",
1291
+ await super_get_response(
1292
+ messages=messages,
1293
+ stream=False,
1294
+ options=opts,
1295
+ compaction_strategy=compaction_strategy,
1296
+ tokenizer=tokenizer,
1297
+ function_invocation_kwargs=function_invocation_kwargs,
1298
+ client_kwargs=merged_client_kwargs,
1299
+ **prepared_observer_kwargs,
1300
+ ),
1301
+ )
1302
+ except Exception as exception:
1303
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
1304
+ _record_failed_duration(
1305
+ self.duration_histogram, attributes, exception, perf_counter() - start_time_stamp
1306
+ )
1307
+ raise
1308
+ duration = perf_counter() - start_time_stamp
1309
+ response_attributes = _get_response_attributes(attributes, response)
1310
+ self._backfill_request_model(span, response_attributes)
1311
+ _capture_response(
1312
+ span=span,
1313
+ attributes=response_attributes,
1314
+ token_usage_histogram=self.token_usage_histogram,
1315
+ operation_duration_histogram=self.duration_histogram,
1316
+ duration=duration,
1317
+ )
1318
+ _mark_inner_response_telemetry_captured(response)
1319
+ if TELEMETRY_GATE.sensitive_enabled and response.messages and span.is_recording():
1320
+ _capture_messages(
1321
+ span=span,
1322
+ provider_name=provider_name,
1323
+ messages=response.messages,
1324
+ finish_reason=response.finish_reason,
1325
+ output=True,
1326
+ )
1327
+ return response # type: ignore[return-value,no-any-return]
1328
+
1329
+ return _get_response()
1330
+
1331
+
1332
+ class AgentTelemetryLayer(_AgentTelemetryBase):
1333
+ """Trace agent runs under ``TELEMETRY_GATE``.
1334
+
1335
+ Non-streaming dedup ContextVars live inside the returned coroutine so it
1336
+ can be driven by any task. Streaming finalization closes the span before
1337
+ resetting those variables and tolerates cross-context reset failures.
1338
+ """
1339
+
1340
+ def __init__(
1341
+ self,
1342
+ *args: Any,
1343
+ otel_agent_provider_name: str | None = None,
1344
+ otel_provider_name: str | None = None,
1345
+ **kwargs: Any,
1346
+ ) -> None:
1347
+ """Read the provider name before initializing the core, then construct histograms."""
1348
+ self.otel_provider_name = (
1349
+ otel_agent_provider_name or otel_provider_name or getattr(self, "AGENT_PROVIDER_NAME", "unknown")
1350
+ )
1351
+ super().__init__(*args, **kwargs)
1352
+ self.token_usage_histogram = _get_token_usage_histogram()
1353
+ self.duration_histogram = _get_duration_histogram()
1354
+
1355
+ def _trace_agent_invocation(
1356
+ self,
1357
+ *,
1358
+ messages: AgentRunInputs | None,
1359
+ session: AgentSession | None,
1360
+ merged_options: Mapping[str, Any],
1361
+ client_kwargs: Mapping[str, Any] | None,
1362
+ stream: bool,
1363
+ execute: Callable[[], Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]],
1364
+ ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
1365
+ """Trace an agent invocation while delegating execution to ``execute``."""
1366
+ if not TELEMETRY_GATE.enabled:
1367
+ return execute()
1368
+
1369
+ provider_name = str(self.otel_provider_name)
1370
+ merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
1371
+ attributes = _get_span_attributes(
1372
+ operation_name=OtelAttr.AGENT_INVOKE_OPERATION,
1373
+ provider_name=provider_name,
1374
+ agent_id=getattr(self, "id", "unknown"),
1375
+ agent_name=getattr(self, "name", None) or getattr(self, "id", "unknown"),
1376
+ agent_description=getattr(self, "description", None),
1377
+ thread_id=session.service_session_id if session else None,
1378
+ all_options=dict(merged_options),
1379
+ **merged_client_kwargs,
1380
+ )
1381
+
1382
+ if stream:
1383
+ # Set dedup ContextVars at stream creation; the inner chat layer
1384
+ # reads them during consumer pulls. Normal callers create and
1385
+ # consume in the same task; finalization also tolerates resets
1386
+ # from another context after closing the span.
1387
+ inner_response_telemetry_captured_fields: set[str] = set()
1388
+ captured_fields_token = INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.set(
1389
+ inner_response_telemetry_captured_fields
1390
+ )
1391
+ accumulated_usage_token = INNER_ACCUMULATED_USAGE.set({})
1392
+
1393
+ span = _start_streaming_span(attributes, OtelAttr.AGENT_NAME)
1394
+
1395
+ if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
1396
+ _capture_messages(
1397
+ span=span,
1398
+ provider_name=provider_name,
1399
+ messages=messages,
1400
+ system_instructions=_get_instructions_from_options(dict(merged_options)),
1401
+ )
1402
+
1403
+ stream_span = _StreamingSpan(span, attributes=attributes)
1404
+
1405
+ def _reset_inner_vars() -> None:
1406
+ # The finalizer may run in a different context than the sync
1407
+ # set above (only when a caller hands the un-consumed stream
1408
+ # across tasks); a cross-context reset must not break span
1409
+ # finalization, so it degrades to a debug log. The vars are
1410
+ # task-scoped copies — a leaked set in a dead context is inert.
1411
+ try:
1412
+ INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.reset(captured_fields_token)
1413
+ INNER_ACCUMULATED_USAGE.reset(accumulated_usage_token)
1414
+ except ValueError:
1415
+ logger.debug("Skipped cross-context reset of inner telemetry ContextVars")
1416
+
1417
+ try:
1418
+ with _activate_span(span):
1419
+ run_result: object = execute()
1420
+ if _is_agent_response_stream(run_result):
1421
+ result_stream = run_result
1422
+ elif isinstance(run_result, Awaitable):
1423
+ result_stream = ResponseStream.from_awaitable(
1424
+ cast(
1425
+ "Awaitable[ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]",
1426
+ run_result,
1427
+ )
1428
+ )
1429
+ else:
1430
+ raise RuntimeError("Streaming telemetry requires a ResponseStream result.")
1431
+ except Exception as exception:
1432
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
1433
+ _reset_inner_vars()
1434
+ stream_span.close()
1435
+ raise
1436
+
1437
+ async def _finalize_stream() -> None:
1438
+ try:
1439
+ if not stream_span.should_finalize(result_stream):
1440
+ return
1441
+ response: AgentResponse[Any] = await stream_span.final_response(result_stream)
1442
+ try:
1443
+ duration = stream_span.duration
1444
+ response_attributes = _get_response_attributes(
1445
+ attributes,
1446
+ response,
1447
+ capture_response_id=INNER_RESPONSE_ID_CAPTURED_FIELD
1448
+ not in inner_response_telemetry_captured_fields,
1449
+ capture_usage=INNER_USAGE_CAPTURED_FIELD not in inner_response_telemetry_captured_fields,
1450
+ )
1451
+ _apply_accumulated_usage(response_attributes, inner_response_telemetry_captured_fields)
1452
+ _capture_response(span=span, attributes=response_attributes, duration=duration)
1453
+ if (
1454
+ TELEMETRY_GATE.sensitive_enabled
1455
+ and isinstance(response, AgentResponse)
1456
+ and response.messages
1457
+ and span.is_recording()
1458
+ ):
1459
+ _capture_messages(
1460
+ span=span,
1461
+ provider_name=provider_name,
1462
+ messages=response.messages,
1463
+ output=True,
1464
+ )
1465
+ except Exception as exception:
1466
+ # Telemetry's own failure never fails the run.
1467
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
1468
+ finally:
1469
+ # Close first: a cross-context reset must never leave the span open.
1470
+ stream_span.close()
1471
+ _reset_inner_vars()
1472
+
1473
+ return stream_span.wrap(result_stream, _finalize_stream)
1474
+
1475
+ async def _run() -> AgentResponse[Any]:
1476
+ # Set AND reset inside this coroutine so both run in the awaiting
1477
+ # task's context, regardless of which task created the coroutine —
1478
+ # the inner chat layer executes within ``await execute()`` below
1479
+ # and therefore sees the vars regardless.
1480
+ inner_response_telemetry_captured_fields: set[str] = set()
1481
+ captured_fields_token = INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.set(
1482
+ inner_response_telemetry_captured_fields
1483
+ )
1484
+ accumulated_usage_token = INNER_ACCUMULATED_USAGE.set({})
1485
+ try:
1486
+ with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span:
1487
+ if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
1488
+ _capture_messages(
1489
+ span=span,
1490
+ provider_name=provider_name,
1491
+ messages=messages,
1492
+ system_instructions=_get_instructions_from_options(dict(merged_options)),
1493
+ )
1494
+ start_time_stamp = perf_counter()
1495
+ try:
1496
+ response = await cast("Awaitable[AgentResponse[Any]]", execute())
1497
+ except Exception as exception:
1498
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
1499
+ raise
1500
+ duration = perf_counter() - start_time_stamp
1501
+ if response:
1502
+ response_attributes = _get_response_attributes(
1503
+ attributes,
1504
+ response,
1505
+ capture_response_id=INNER_RESPONSE_ID_CAPTURED_FIELD
1506
+ not in inner_response_telemetry_captured_fields,
1507
+ capture_usage=INNER_USAGE_CAPTURED_FIELD not in inner_response_telemetry_captured_fields,
1508
+ )
1509
+ _apply_accumulated_usage(response_attributes, inner_response_telemetry_captured_fields)
1510
+ _capture_response(span=span, attributes=response_attributes, duration=duration)
1511
+ if TELEMETRY_GATE.sensitive_enabled and response.messages and span.is_recording():
1512
+ _capture_messages(
1513
+ span=span,
1514
+ provider_name=provider_name,
1515
+ messages=response.messages,
1516
+ output=True,
1517
+ )
1518
+ return response
1519
+ finally:
1520
+ INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.reset(captured_fields_token)
1521
+ INNER_ACCUMULATED_USAGE.reset(accumulated_usage_token)
1522
+
1523
+ return _run()
1524
+
1525
+ @overload
1526
+ def run(
1527
+ self,
1528
+ messages: AgentRunInputs | None = None,
1529
+ *,
1530
+ stream: Literal[False] = False,
1531
+ session: AgentSession | None = None,
1532
+ middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
1533
+ tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
1534
+ options: Mapping[str, Any] | None = None,
1535
+ compaction_strategy: Any = None,
1536
+ tokenizer: Any = None,
1537
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1538
+ client_kwargs: Mapping[str, Any] | None = None,
1539
+ ) -> Awaitable[AgentResponse[Any]]: ...
1540
+
1541
+ @overload
1542
+ def run(
1543
+ self,
1544
+ messages: AgentRunInputs | None = None,
1545
+ *,
1546
+ stream: Literal[True],
1547
+ session: AgentSession | None = None,
1548
+ middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
1549
+ tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
1550
+ options: Mapping[str, Any] | None = None,
1551
+ compaction_strategy: Any = None,
1552
+ tokenizer: Any = None,
1553
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1554
+ client_kwargs: Mapping[str, Any] | None = None,
1555
+ ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
1556
+
1557
+ @overload
1558
+ def run(
1559
+ self,
1560
+ messages: AgentRunInputs | None = None,
1561
+ *,
1562
+ stream: bool,
1563
+ session: AgentSession | None = None,
1564
+ middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
1565
+ tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
1566
+ options: Mapping[str, Any] | None = None,
1567
+ compaction_strategy: Any = None,
1568
+ tokenizer: Any = None,
1569
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1570
+ client_kwargs: Mapping[str, Any] | None = None,
1571
+ ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
1572
+
1573
+ def run( # ty: ignore[invalid-method-override] # Overloads refine the cooperative mixin protocol.
1574
+ self,
1575
+ messages: AgentRunInputs | None = None,
1576
+ *,
1577
+ stream: bool = False,
1578
+ session: AgentSession | None = None,
1579
+ middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
1580
+ tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
1581
+ options: Mapping[str, Any] | None = None,
1582
+ compaction_strategy: Any = None,
1583
+ tokenizer: Any = None,
1584
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
1585
+ client_kwargs: Mapping[str, Any] | None = None,
1586
+ ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
1587
+ """Trace agent runs with OpenTelemetry spans and metrics."""
1588
+ super_run = cast(
1589
+ "Callable[..., Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]",
1590
+ super().run, # type: ignore[misc]
1591
+ )
1592
+ super_run_kwargs: dict[str, Any] = {
1593
+ "messages": messages,
1594
+ "stream": stream,
1595
+ "session": session,
1596
+ "tools": tools,
1597
+ "options": options,
1598
+ "compaction_strategy": compaction_strategy,
1599
+ "tokenizer": tokenizer,
1600
+ "function_invocation_kwargs": function_invocation_kwargs,
1601
+ "client_kwargs": client_kwargs,
1602
+ }
1603
+ if middleware is not None:
1604
+ super_run_kwargs["middleware"] = middleware
1605
+
1606
+ default_options = dict(getattr(self, "default_options", {}))
1607
+ merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
1608
+ merged_options: dict[str, Any] = merge_chat_options(
1609
+ default_options, dict(options) if options is not None else {}
1610
+ )
1611
+ return self._trace_agent_invocation(
1612
+ messages=messages,
1613
+ session=session,
1614
+ merged_options=merged_options,
1615
+ client_kwargs=merged_client_kwargs,
1616
+ stream=stream,
1617
+ execute=lambda: super_run(**super_run_kwargs),
1618
+ )