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,1172 @@
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
+ """Tool-call execution for the model/tool loop.
6
+
7
+ A landed batch runs concurrently: each call is looked up, validated against
8
+ its schema, then run through the function middleware pipeline. Approval
9
+ middleware controls tool admission; ``MiddlewareTermination`` stops the loop.
10
+ A tool that raises answers the model with a fixed ``Error: Function failed.``
11
+ unless it raised ``ModelVisibleToolError``, whose message the model reads;
12
+ argument-validation errors include safe schema guidance without echoing the
13
+ full argument payload. Every failed result also records the exception tree in
14
+ its ``exception`` field, which never goes on the wire.
15
+
16
+ Tool execution owns the invocation logs and per-tool spans. Raw arguments and
17
+ results are logged only when ``TELEMETRY_GATE.sensitive_data`` is set; spans
18
+ require ``TELEMETRY_GATE.enabled``. Direct ``FunctionTool.invoke`` calls
19
+ remain silent. Tool containers are normalized again before each batch, since
20
+ direct callers and progressive exposure can add tools after agent preparation.
21
+
22
+ This module also defines how a tool result is shaped
23
+ (``_result_additional_properties``, ``_tool_trajectory_timing``,
24
+ ``_failure_record_text``). The recovery journal builds its interrupted results
25
+ with the same rules, so a change here must hold for those results too. Results
26
+ reach the journal only through ``ToolResultCommit``.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import asyncio
32
+ import contextlib
33
+ import datetime
34
+ import decimal
35
+ import json
36
+ import logging
37
+ from collections import Counter
38
+ from collections.abc import Mapping, Sequence
39
+ from copy import copy, deepcopy
40
+ from time import perf_counter, time_ns
41
+ from typing import TYPE_CHECKING, Any, Protocol, TypeGuard
42
+
43
+ from pydantic import BaseModel, ValidationError
44
+
45
+ from chrys.foundation.observability.gate import TELEMETRY_GATE
46
+ from chrys.foundation.tool_call_context import (
47
+ TOOL_CALL_CONTEXT_METADATA_KEY,
48
+ get_tool_context,
49
+ merge_tool_call_context_property,
50
+ )
51
+ from chrys.foundation.tool_execution_stamp import EXECUTION_STAMP_KEY, execution_stamp_from_metadata
52
+ from chrys.foundation.tool_invocation_order import TOOL_INVOCATION_ORDER_KEY, read_tool_invocation_order
53
+ from chrys.foundation.tool_kinds import TOOL_CALL_KIND_METADATA_KEY, get_tool_kind
54
+ from chrys.foundation.tool_result_metadata import (
55
+ TOOL_ERROR_KIND_METADATA_KEY,
56
+ TOOL_ERROR_MESSAGE_METADATA_KEY,
57
+ TOOL_FAILED_METADATA_KEY,
58
+ TOOL_RESULT_METADATA_KEY,
59
+ )
60
+ from chrys.foundation.trajectory.context import current_trajectory
61
+ from chrys.foundation.trajectory.event_types import ToolOutcome
62
+ from chrys.foundation.trajectory.ids import new_analytics_id
63
+ from chrys.foundation.trajectory.metadata import (
64
+ ANALYTICS_ITEM_ID_KEY,
65
+ OPERATION_ID_KEY,
66
+ TOOL_RESULT_CARRIER_ITEM_ID_METADATA_KEY,
67
+ TOOL_RESULT_ITEM_ID_METADATA_KEY,
68
+ read_analytics_item_id,
69
+ read_operation_id,
70
+ )
71
+ from chrys.foundation.trajectory.tools import tool_operation_finished_draft, tool_operation_started_draft
72
+ from chrys.foundation.trajectory.writer import EmitResult
73
+ from chrys.foundation.trajectory_timing import (
74
+ TRAJECTORY_TIMING_KEY,
75
+ build_instant_trajectory_timing,
76
+ trajectory_timing_from_metadata,
77
+ )
78
+ from chrys.foundation.util.sub_agent_context import (
79
+ SUB_AGENT_RESULT_COMMIT_CALLBACK_KEY,
80
+ sub_agent_parent_result_metadata,
81
+ )
82
+
83
+ from ._content import Content
84
+ from ._result_ceiling import apply_result_ceiling
85
+ from ._tool_arg_errors import (
86
+ _accepts_arbitrary_argument_names,
87
+ _argument_validation_message,
88
+ _rejects_unexpected_arguments,
89
+ _unexpected_argument_names,
90
+ )
91
+ from .exceptions import ChrysException, tool_error_result_text
92
+ from .instrumentation import (
93
+ FUNCTION_SPAN_EXCLUDED_KWARGS,
94
+ OtelAttr,
95
+ capture_exception,
96
+ get_function_duration_histogram,
97
+ get_function_span,
98
+ get_function_span_attributes,
99
+ )
100
+ from .middleware import FunctionMiddlewarePipeline, MiddlewareTermination
101
+ from .sessions import AgentSession
102
+ from .tools import (
103
+ FunctionTool,
104
+ SyncToolCancelledAfterCompletion,
105
+ _is_skip_parsing_sentinel,
106
+ _validate_arguments_against_schema,
107
+ normalize_tools,
108
+ )
109
+
110
+ if TYPE_CHECKING:
111
+ from collections.abc import Callable, Container
112
+
113
+ from .middleware import FunctionInvocationContext
114
+
115
+ # Log lines keep the loop's logger name: log configuration and tests filter on it.
116
+ logger = logging.getLogger("chrys.kernel.loop")
117
+
118
+
119
+ def _is_actionable_function_call(content: Content) -> bool:
120
+ """Return whether a function call must be executed by the local tool loop."""
121
+ return content.type == "function_call" and not content.informational_only
122
+
123
+
124
+ class ToolResultCommit(Protocol):
125
+ """Journal handle for one call's result slot.
126
+
127
+ Defined by its user: the recovery journal's slot handle satisfies it
128
+ structurally, so tool execution never imports the journal.
129
+ ``commit_raw`` runs as soon as the tool returns, before function
130
+ middleware unwinds; ``commit_final`` records the terminal result;
131
+ ``commit_interrupted`` records a call cancelled before it settled. A
132
+ sub-agent tool calls ``commit_interrupted`` again for the same call, through
133
+ ``SUB_AGENT_RESULT_COMMIT_CALLBACK_KEY``, to add result metadata that
134
+ arrives late; the slot must merge it into the interrupted result it
135
+ already holds, even after the exchange was sealed.
136
+ """
137
+
138
+ def commit_raw(self, result: Content) -> None: ...
139
+
140
+ def commit_final(self, result: Content) -> None: ...
141
+
142
+ def commit_interrupted(
143
+ self,
144
+ function_call: Content,
145
+ invocation_context: FunctionInvocationContext | None,
146
+ metadata: Mapping[str, Any] | None = None,
147
+ ) -> None: ...
148
+
149
+
150
+ def _describe_arguments(arguments: Mapping[str, Any] | BaseModel, schema_names: Container[str]) -> str:
151
+ """Render tool arguments for the DEBUG log line.
152
+
153
+ Raw argument text is gated on ``TELEMETRY_GATE.sensitive_data``. With the
154
+ gate off, only names listed in the tool schema's ``properties`` are
155
+ disclosed: the model controls the actual keys (schemas rarely pin
156
+ ``additionalProperties: false``, so unexpected keys survive validation)
157
+ and could smuggle sensitive text through a key name — such keys are
158
+ reported as an ``+N unrecognized`` count only.
159
+ """
160
+ if _is_argument_mapping(arguments):
161
+ argument_mapping: Mapping[str, Any] = arguments
162
+ else:
163
+ # The middleware contract admits ``BaseModel`` rewrites of
164
+ # ``context.arguments``; pydantic models iterate as (key, value)
165
+ # pairs, so ``dict()`` recovers the mapping form. ``tool.invoke``
166
+ # normalizes the same way internally — a describe-only crash here
167
+ # would otherwise be converted into a tool error result.
168
+ argument_mapping = dict(arguments)
169
+ if TELEMETRY_GATE.sensitive_data:
170
+ return str(dict(argument_mapping))
171
+ if not argument_mapping:
172
+ return "0 key(s)"
173
+ known = [key for key in argument_mapping if key in schema_names]
174
+ parts = (
175
+ known
176
+ if len(known) == len(argument_mapping)
177
+ else [*known, f"+{len(argument_mapping) - len(known)} unrecognized"]
178
+ )
179
+ return f"{len(argument_mapping)} key(s) ({', '.join(parts)})"
180
+
181
+
182
+ def _is_argument_mapping(value: Mapping[str, Any] | BaseModel) -> TypeGuard[Mapping[str, Any]]:
183
+ """Narrow middleware arguments after the runtime mapping check."""
184
+ return isinstance(value, Mapping)
185
+
186
+
187
+ def _describe_result(result: Any, *, skip_parsing: bool) -> str:
188
+ """Render a tool result for DEBUG logging under the sensitive-data gate.
189
+
190
+ Skip-parsing tools return arbitrary values, including non-Content lists:
191
+ render those with ``str`` when sensitive data is enabled, otherwise use
192
+ the type name. Parsed results are ``list[Content]`` and can be summarized
193
+ by item type or, when permitted, joined as text.
194
+ """
195
+ if skip_parsing or not isinstance(result, list):
196
+ return str(result) if TELEMETRY_GATE.sensitive_data else type(result).__name__
197
+ if TELEMETRY_GATE.sensitive_data:
198
+ text = "\n".join(item.text or "" for item in result if item.type == "text")
199
+ return text or str(result)
200
+ if not result:
201
+ return "None"
202
+ return f"{len(result)} item(s) ({', '.join(item.type for item in result)})"
203
+
204
+
205
+ _FUNCTION_DURATION_HISTOGRAM: Any = None
206
+
207
+
208
+ def _function_duration_histogram() -> Any:
209
+ """Cache the function-duration histogram on its first gated use.
210
+
211
+ Lazy construction lets provider setup finish before obtaining the meter.
212
+ """
213
+ global _FUNCTION_DURATION_HISTOGRAM
214
+ if _FUNCTION_DURATION_HISTOGRAM is None:
215
+ _FUNCTION_DURATION_HISTOGRAM = get_function_duration_histogram()
216
+ return _FUNCTION_DURATION_HISTOGRAM
217
+
218
+
219
+ async def _invoke_with_function_span(
220
+ tool: FunctionTool,
221
+ context: Any,
222
+ call_id: str | None,
223
+ *,
224
+ arguments: BaseModel | Mapping[str, Any],
225
+ arguments_in_callable_keyspace: bool = False,
226
+ tool_result_ceiling_tokens: int | None = None,
227
+ ) -> Any:
228
+ """Run ``tool.invoke`` inside an ``execute_tool {name}`` span.
229
+
230
+ Capture arguments and results only under ``TELEMETRY_GATE.sensitive_enabled``;
231
+ record exceptions and duration on failure as well as success. The caller
232
+ owns logging. Skip-parsing results use ``str(result)``; parsed Content
233
+ lists contribute their text, selected by the same ``SKIP_PARSING`` sentinel
234
+ that ``invoke`` uses.
235
+
236
+ Captured arguments are the middleware-final values. Validation happens
237
+ inside ``invoke``, so the span does not capture its normalized argument dict.
238
+ """
239
+ attributes = get_function_span_attributes(tool, tool_call_id=call_id)
240
+ # The middleware contract admits ``BaseModel`` rewrites of
241
+ # ``context.arguments`` (same shape ``_describe_arguments`` / ``invoke``
242
+ # handle); normalize it to a dict before filtering so sensitive capture
243
+ # doesn't silently record ``"None"`` for a model-shaped argument set.
244
+ if isinstance(arguments, BaseModel):
245
+ argument_items: dict[str, Any] = arguments.model_dump(exclude_none=True)
246
+ elif isinstance(arguments, Mapping):
247
+ argument_items = dict(arguments)
248
+ else:
249
+ argument_items = {}
250
+ serializable_kwargs = {k: v for k, v in argument_items.items() if k not in FUNCTION_SPAN_EXCLUDED_KWARGS}
251
+ if TELEMETRY_GATE.sensitive_enabled:
252
+ attributes[OtelAttr.TOOL_ARGUMENTS] = (
253
+ json.dumps(serializable_kwargs, default=str, ensure_ascii=False) if serializable_kwargs else "None"
254
+ )
255
+ with get_function_span(attributes=attributes) as span:
256
+ attributes[OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME] = tool.name
257
+ start_time_stamp = perf_counter()
258
+ end_time_stamp: float | None = None
259
+ try:
260
+ result = await tool.invoke(
261
+ arguments=arguments,
262
+ context=context,
263
+ tool_call_id=call_id,
264
+ _arguments_in_callable_keyspace=arguments_in_callable_keyspace,
265
+ )
266
+ result = apply_result_ceiling(result, tool_result_ceiling_tokens)
267
+ end_time_stamp = perf_counter()
268
+ except Exception as exception:
269
+ end_time_stamp = perf_counter()
270
+ attributes[OtelAttr.ERROR_TYPE] = type(exception).__name__
271
+ capture_exception(span=span, exception=exception, timestamp=time_ns())
272
+ raise
273
+ else:
274
+ if TELEMETRY_GATE.sensitive_enabled:
275
+ if _is_skip_parsing_sentinel(tool.result_parser):
276
+ result_str = str(result)
277
+ else:
278
+ result_str = "\n".join(c.text or "" for c in result if c.type == "text") or str(result)
279
+ span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
280
+ return result
281
+ finally:
282
+ duration = (end_time_stamp or perf_counter()) - start_time_stamp
283
+ span.set_attribute(OtelAttr.MEASUREMENT_FUNCTION_INVOCATION_DURATION, duration)
284
+ _function_duration_histogram().record(duration, attributes=attributes)
285
+
286
+
287
+ def _arguments_unparseable(arguments: Any) -> bool:
288
+ """True when raw tool-call arguments are an empty or invalid JSON string.
289
+
290
+ This is the fingerprint of a truncated / incomplete argument payload — the
291
+ model was cut off before or during JSON emission, so ``Content.parse_arguments``
292
+ either returns ``{}`` for an empty string or falls back to wrapping the raw
293
+ blob under ``{"raw": ...}``. It deliberately does NOT flag arguments that
294
+ parsed cleanly (a dict, or a pre-parsed mapping) but failed schema validation
295
+ — those are real argument errors, not a token-limit cutoff.
296
+ """
297
+ if not isinstance(arguments, str):
298
+ return False
299
+ if not arguments:
300
+ return True
301
+ try:
302
+ json.loads(arguments)
303
+ except json.JSONDecodeError:
304
+ return True
305
+ return False
306
+
307
+
308
+ def _arguments_not_object(arguments: Any) -> bool:
309
+ """True when raw tool-call arguments are present but are not a JSON object.
310
+
311
+ ``Content.parse_arguments`` wraps such a payload as ``{"raw": ...}``, and a
312
+ tool whose schema accepts extra keys would then run on arguments the model
313
+ never gave it (MCP adapters drop the undeclared ``raw`` and call with none).
314
+ ``None`` and ``""`` mean "no arguments"; whitespace, undecodable text and
315
+ any JSON value other than an object do not, nor does a parsed non-mapping
316
+ value, however falsy (``[]``, ``0``, ``False``).
317
+ """
318
+ if arguments is None:
319
+ return False
320
+ if isinstance(arguments, str):
321
+ if not arguments:
322
+ return False
323
+ try:
324
+ loaded = json.loads(arguments)
325
+ except json.JSONDecodeError:
326
+ return True
327
+ return not isinstance(loaded, dict)
328
+ return not isinstance(arguments, Mapping)
329
+
330
+
331
+ def _middleware_arguments_equal(left: Any, right: Any) -> bool:
332
+ """Type-strict structural equality for the trusted middleware snapshot."""
333
+ if type(left) is not type(right):
334
+ return False
335
+ if type(left) is dict:
336
+ if len(left) != len(right):
337
+ return False
338
+ unmatched = list(right.items())
339
+ for left_key, left_value in left.items():
340
+ for index, (right_key, right_value) in enumerate(unmatched):
341
+ if _middleware_arguments_equal(left_key, right_key):
342
+ if not _middleware_arguments_equal(left_value, right_value):
343
+ return False
344
+ unmatched.pop(index)
345
+ break
346
+ else:
347
+ return False
348
+ return True
349
+ if type(left) in {list, tuple}:
350
+ return len(left) == len(right) and all(
351
+ _middleware_arguments_equal(left_item, right_item)
352
+ for left_item, right_item in zip(left, right, strict=True)
353
+ )
354
+ if type(left) in {set, frozenset}:
355
+ if len(left) != len(right):
356
+ return False
357
+ unmatched = list(right)
358
+ for left_item in left:
359
+ for index, right_item in enumerate(unmatched):
360
+ if _middleware_arguments_equal(left_item, right_item):
361
+ unmatched.pop(index)
362
+ break
363
+ else:
364
+ return False
365
+ return True
366
+ if type(left) in {float, complex, decimal.Decimal}:
367
+ return bool(left == right and repr(left) == repr(right))
368
+ if type(left) in {datetime.datetime, datetime.time}:
369
+ return bool(
370
+ left == right
371
+ and left.utcoffset() == right.utcoffset()
372
+ and left.tzname() == right.tzname()
373
+ and left.fold == right.fold
374
+ )
375
+ return bool(left == right)
376
+
377
+
378
+ def _arguments_look_like_truncated_mapping(arguments: Any, schema: Mapping[str, Any]) -> bool:
379
+ """True for parsed Anthropic tool args that look cut off, not just invalid."""
380
+ if not isinstance(arguments, Mapping):
381
+ return False
382
+ if not arguments:
383
+ return True
384
+
385
+ required = schema.get("required")
386
+ properties = schema.get("properties")
387
+ if not isinstance(required, Sequence) or isinstance(required, (str, bytes)):
388
+ return False
389
+ if not isinstance(properties, Mapping):
390
+ return False
391
+
392
+ argument_keys = {key for key in arguments if isinstance(key, str)}
393
+ if len(argument_keys) != len(arguments):
394
+ return False
395
+ if not argument_keys.issubset(properties.keys()):
396
+ return False
397
+ return any(isinstance(key, str) and key not in arguments for key in required)
398
+
399
+
400
+ def _stamp_call_provenance(function_call: Content, tool: FunctionTool) -> None:
401
+ """Stamp tool kind + static context onto the call content, first-write-wins.
402
+
403
+ Runs right after tool lookup, BEFORE argument validation, so calls that
404
+ fail pre-pipeline validation — which return without entering the
405
+ middleware pipeline — still persist their provenance. Static context
406
+ only: the per-call builder needs validated args and belongs to the
407
+ record pipeline.
408
+ """
409
+ props = function_call.additional_properties
410
+ kind = get_tool_kind(tool)
411
+ if kind and TOOL_CALL_KIND_METADATA_KEY not in props:
412
+ props[TOOL_CALL_KIND_METADATA_KEY] = kind
413
+ static = get_tool_context(tool)
414
+ if static and TOOL_CALL_CONTEXT_METADATA_KEY not in props:
415
+ # Already sanitized at set_tool_context() — single sanitization
416
+ # authority; the stamp only copies, never re-sanitizes.
417
+ props[TOOL_CALL_CONTEXT_METADATA_KEY] = dict(static)
418
+
419
+
420
+ # The failure record is saved with the session and exported to telemetry: a long
421
+ # cause chain, a large exception group or a message that embeds a response body
422
+ # must not bloat either. Each message is clipped before the record is joined, the
423
+ # exception cap also bounds the record's recursion, and the total cap is the backstop.
424
+ _FAILURE_RECORD_MAX_EXCEPTIONS = 16
425
+ _FAILURE_RECORD_MAX_MESSAGE_CHARS = 1000
426
+ _FAILURE_RECORD_MAX_CHARS = 4000
427
+
428
+
429
+ def _clip(text: str, limit: int) -> str:
430
+ return text if len(text) <= limit else f"{text[: limit - 1]}…"
431
+
432
+
433
+ def _exception_message(exc: BaseException) -> str:
434
+ """Return *exc*'s own message, clipped for the failure record.
435
+
436
+ ``str()`` of a ChrysException built with an ``inner_exception`` is the repr
437
+ of its args tuple, and a KeyError's is quoted; both hold the message itself
438
+ as their first argument. A ``__str__`` that raises must not stop the call
439
+ from becoming a failed result, so it records what a traceback would.
440
+ """
441
+ if isinstance(exc, ChrysException | KeyError) and exc.args and isinstance(exc.args[0], str):
442
+ message = exc.args[0]
443
+ else:
444
+ try:
445
+ message = str(exc)
446
+ except Exception:
447
+ message = "<exception str() failed>"
448
+ return _clip(message.strip(), _FAILURE_RECORD_MAX_MESSAGE_CHARS)
449
+
450
+
451
+ def _exception_causes(exc: BaseException) -> list[BaseException]:
452
+ """Return what *exc* was explicitly raised from and, when it differs, the ``inner_exception`` a ChrysException wraps."""
453
+ causes = [] if exc.__cause__ is None else [exc.__cause__]
454
+ if isinstance(exc, ChrysException) and len(exc.args) > 1:
455
+ inner = exc.args[1]
456
+ if isinstance(inner, BaseException) and inner is not exc.__cause__:
457
+ causes.append(inner)
458
+ return causes
459
+
460
+
461
+ def _failure_record_text(exc: BaseException) -> str:
462
+ """Return the ``exception`` a failed result records: ``Type: message`` for *exc* and everything it came from.
463
+
464
+ The record is a tree, ``Type: message [member; …] (caused by cause; …)``:
465
+ an exception group lists its members in brackets (its ``str()`` only
466
+ counts them), and the causes are ``raise … from`` plus a ChrysException's
467
+ ``inner_exception`` when that is a different exception. It follows only
468
+ these explicit links, never the implicit ``__context__`` of whatever was
469
+ being handled when the tool raised — unlike ``foundation.errors``' linear
470
+ chain walk, which reads that context to classify retryable errors.
471
+
472
+ The record is for people reading a saved session, never for the model. It
473
+ is never empty: readers take a non-empty record to mean the call failed.
474
+ """
475
+ seen: set[int] = set()
476
+
477
+ def describe(current: BaseException) -> str:
478
+ seen.add(id(current))
479
+ if isinstance(current, BaseExceptionGroup):
480
+ message = _clip(current.message.strip(), _FAILURE_RECORD_MAX_MESSAGE_CHARS)
481
+ members = describe_each(current.exceptions)
482
+ else:
483
+ message = _exception_message(current)
484
+ members = ""
485
+ text = f"{type(current).__name__}: {message}" if message else type(current).__name__
486
+ if members:
487
+ text = f"{text} [{members}]"
488
+ causes = describe_each(_exception_causes(current))
489
+ return f"{text} (caused by {causes})" if causes else text
490
+
491
+ def describe_each(exceptions: Sequence[BaseException]) -> str:
492
+ parts: list[str] = []
493
+ for member in exceptions:
494
+ if id(member) in seen:
495
+ continue
496
+ if len(seen) >= _FAILURE_RECORD_MAX_EXCEPTIONS:
497
+ parts.append("…")
498
+ break
499
+ parts.append(describe(member))
500
+ return "; ".join(parts)
501
+
502
+ return _clip(describe(exc), _FAILURE_RECORD_MAX_CHARS)
503
+
504
+
505
+ def _result_additional_properties(
506
+ function_call: Content,
507
+ invocation_context: FunctionInvocationContext | None = None,
508
+ ) -> dict[str, Any]:
509
+ """Return call properties for a function result, minus call provenance.
510
+
511
+ Every result-construction path propagates the call's properties; this
512
+ filters call-only provenance (kind, context, invocation ordinal) and any
513
+ stale call-side execution stamp or result metadata. Without the provenance
514
+ filter, the nested context dict would also be shared by reference between
515
+ call and result (the Content constructor's copy is shallow). A fresh stamp
516
+ and fresh result metadata come only from this invocation's context — this
517
+ fold is the single attachment point for ``_chrys_tool_result_metadata``,
518
+ at result construction, when the result's identity is unambiguous.
519
+ """
520
+ result = {
521
+ key: value
522
+ for key, value in function_call.additional_properties.items()
523
+ if key
524
+ not in (
525
+ TOOL_CALL_KIND_METADATA_KEY,
526
+ TOOL_CALL_CONTEXT_METADATA_KEY,
527
+ TOOL_INVOCATION_ORDER_KEY,
528
+ EXECUTION_STAMP_KEY,
529
+ TOOL_RESULT_METADATA_KEY,
530
+ TRAJECTORY_TIMING_KEY,
531
+ ANALYTICS_ITEM_ID_KEY,
532
+ )
533
+ }
534
+ timing = _tool_trajectory_timing(function_call, invocation_context)
535
+ result[TRAJECTORY_TIMING_KEY] = timing
536
+ # The result is its own item (the call's operation id is inherited above
537
+ # so call and result share one tool operation). A pre-minted id on the
538
+ # invocation context keeps the raw-commit checkpoint and the terminal
539
+ # result naming the same item.
540
+ result[ANALYTICS_ITEM_ID_KEY] = _result_item_id(invocation_context)
541
+ if invocation_context is not None:
542
+ stamp = execution_stamp_from_metadata(invocation_context.metadata)
543
+ if stamp is not None:
544
+ result[EXECUTION_STAMP_KEY] = stamp
545
+ carried = invocation_context.metadata.get(TOOL_RESULT_METADATA_KEY)
546
+ if isinstance(carried, Mapping) and carried:
547
+ result[TOOL_RESULT_METADATA_KEY] = dict(carried)
548
+ return result
549
+
550
+
551
+ def _result_item_id(invocation_context: FunctionInvocationContext | None) -> str:
552
+ if invocation_context is not None:
553
+ pre_minted = invocation_context.metadata.get(TOOL_RESULT_ITEM_ID_METADATA_KEY)
554
+ if isinstance(pre_minted, str) and pre_minted:
555
+ return pre_minted
556
+ return new_analytics_id()
557
+
558
+
559
+ def _stamped_tool_context(props: Mapping[str, Any]) -> Mapping[str, Any] | None:
560
+ """The static provenance segment stamped on a call, when there is one.
561
+
562
+ A call that never enters the pipeline has no builder output — only what
563
+ landing copied off the tool — so an MCP call still names its server.
564
+ """
565
+ context = props.get(TOOL_CALL_CONTEXT_METADATA_KEY)
566
+ return context if isinstance(context, Mapping) and context else None
567
+
568
+
569
+ async def _record_unexecuted_tool_operation(
570
+ function_call: Content,
571
+ *,
572
+ outcome: str,
573
+ result_carrier_item_id: str | None = None,
574
+ result: Content | None = None,
575
+ queued: bool = False,
576
+ ) -> None:
577
+ """Emit the ``tool.operation`` pair for a call that never entered the pipeline.
578
+
579
+ Unknown-tool and invalid-argument calls still own a tool operation id
580
+ (stamped by response dispatch collection) and a result item, so the
581
+ trajectory closes them with their terminal outcome instead of leaving
582
+ dangling operations. A filtered call owns the operation but never gets a
583
+ result, and the pair closes without naming one.
584
+ """
585
+ context = current_trajectory()
586
+ if context is None:
587
+ return
588
+ operation_id = read_operation_id(function_call.additional_properties)
589
+ if operation_id is None:
590
+ return
591
+ props = function_call.additional_properties
592
+ kind = props.get(TOOL_CALL_KIND_METADATA_KEY)
593
+ try:
594
+ started = tool_operation_started_draft(
595
+ context,
596
+ operation_id=operation_id,
597
+ tool_name=function_call.name or "",
598
+ tool_kind=kind if isinstance(kind, str) else "",
599
+ invocation_order=read_tool_invocation_order(props),
600
+ batch_index=None,
601
+ arguments=function_call.arguments,
602
+ call_item_id=read_analytics_item_id(props),
603
+ tool_context=_stamped_tool_context(props),
604
+ )
605
+ finished = tool_operation_finished_draft(
606
+ context,
607
+ operation_id=operation_id,
608
+ outcome=outcome,
609
+ duration_ms=0,
610
+ result_item_id=read_analytics_item_id(result.additional_properties) if result is not None else None,
611
+ result_carrier_item_id=result_carrier_item_id if result is not None else None,
612
+ error_kind=outcome,
613
+ )
614
+ except Exception:
615
+ logger.debug("Trajectory tool operation emit failed", exc_info=True)
616
+ return
617
+ if queued:
618
+ # The caller is unwinding from a cancellation: waiting for the write
619
+ # acknowledgement here would hold a Stop for as long as the writer
620
+ # takes to answer. Both lines take their sequence now and are
621
+ # acknowledged in the background — and a start the sink refuses
622
+ # outright still leaves nothing to close.
623
+ try:
624
+ if context.sink.emit_soon(started) is None:
625
+ context.sink.emit_soon(finished)
626
+ except Exception:
627
+ logger.debug("Trajectory tool operation emit failed", exc_info=True)
628
+ return
629
+ try:
630
+ opened = await context.sink.emit(started)
631
+ except Exception:
632
+ logger.debug("Trajectory tool operation emit failed", exc_info=True)
633
+ return
634
+ except BaseException:
635
+ # Cancelled while the opening line was in flight: the write is
636
+ # committed regardless, so the terminal has to follow it. Only this
637
+ # wait needs the rescue — the one below can first be cancelled once
638
+ # its own line is committed too.
639
+ with contextlib.suppress(Exception):
640
+ context.sink.emit_soon(finished)
641
+ raise
642
+ if opened is not EmitResult.WRITTEN:
643
+ # An unknown tool's name is whatever the model asked for, and one past
644
+ # the line budget makes the whole opening event unwritable: its slot
645
+ # becomes a gap. A terminal behind that gap closes an operation the
646
+ # log never opened, which is not a shape readers handle; an operation
647
+ # that opens and never closes is one they already do.
648
+ return
649
+ try:
650
+ await context.sink.emit(finished)
651
+ except Exception:
652
+ logger.debug("Trajectory tool operation emit failed", exc_info=True)
653
+
654
+
655
+ def _tool_trajectory_timing(
656
+ function_call: Content,
657
+ invocation_context: FunctionInvocationContext | None,
658
+ ) -> dict[str, Any]:
659
+ """Resolve one tool span and mirror it onto its persisted call content."""
660
+ timing = trajectory_timing_from_metadata(invocation_context.metadata) if invocation_context is not None else None
661
+ if timing is None:
662
+ timing = trajectory_timing_from_metadata(function_call.additional_properties)
663
+ if timing is None:
664
+ timing = build_instant_trajectory_timing()
665
+ function_call.additional_properties[TRAJECTORY_TIMING_KEY] = dict(timing)
666
+ return timing
667
+
668
+
669
+ async def _invoke_function_call(
670
+ function_call: Content,
671
+ *,
672
+ result_commit: ToolResultCommit | None,
673
+ tool_map: dict[str, FunctionTool],
674
+ custom_args: dict[str, Any],
675
+ invocation_session: AgentSession | None,
676
+ pipeline: FunctionMiddlewarePipeline,
677
+ live_tools: list[Any] | None,
678
+ response_truncated: bool = False,
679
+ function_call_may_be_truncated: bool = False,
680
+ same_tool_calls_in_batch: int = 1,
681
+ tool_result_ceiling_tokens: int | None = None,
682
+ result_carrier_item_id: str,
683
+ ) -> Content:
684
+ """Invoke one model-requested function call through the middleware pipeline.
685
+
686
+ A raised exception answers the model with ``tool_error_result_text``: a
687
+ fixed line, or a ``ModelVisibleToolError``'s own message. Argument-validation
688
+ failures include safe schema guidance so the model can repair its next call.
689
+ Each failed result records ``_failure_record_text`` as its ``exception``.
690
+ """
691
+ from .middleware import FunctionInvocationContext
692
+
693
+ tool = tool_map.get(function_call.name) # type: ignore[arg-type]
694
+ if tool is None:
695
+ message = f'Requested function "{function_call.name}" not found.'
696
+ exc = KeyError(f'Function "{function_call.name}" not found.')
697
+ additional = _result_additional_properties(function_call)
698
+ additional.update(
699
+ {
700
+ TOOL_FAILED_METADATA_KEY: True,
701
+ TOOL_ERROR_KIND_METADATA_KEY: "tool_not_found",
702
+ TOOL_ERROR_MESSAGE_METADATA_KEY: message,
703
+ }
704
+ )
705
+ result = Content.from_function_result(
706
+ call_id=function_call.call_id, # type: ignore[arg-type]
707
+ result=f"Error: {message}",
708
+ exception=_failure_record_text(exc),
709
+ additional_properties=additional,
710
+ )
711
+ await _record_unexecuted_tool_operation(
712
+ function_call,
713
+ outcome=ToolOutcome.UNKNOWN_TOOL,
714
+ result_carrier_item_id=result_carrier_item_id,
715
+ result=result,
716
+ )
717
+ return result
718
+
719
+ _stamp_call_provenance(function_call, tool)
720
+
721
+ # Judged on the raw payload: parsing would wrap a non-object as {"raw": ...}
722
+ # and turn a falsy parsed value into {}.
723
+ arguments_not_object = _arguments_not_object(function_call.arguments)
724
+ parsed_args: dict[str, Any] = {} if arguments_not_object else dict(function_call.parse_arguments() or {})
725
+
726
+ # Filter out internal kwargs before passing to tools; conversation_id is an
727
+ # internal tracking id that must not be forwarded.
728
+ runtime_kwargs: dict[str, Any] = {
729
+ key: value
730
+ for key, value in custom_args.items()
731
+ if key not in {"_function_middleware_pipeline", "middleware", "conversation_id"}
732
+ }
733
+ if invocation_session is not None:
734
+ runtime_kwargs["session"] = invocation_session
735
+
736
+ # Pre-pipeline validation: bad arguments return an error
737
+ # result without entering the middleware pipeline (no approval dialog for
738
+ # a call that could never run).
739
+ argument_schema = tool.parameters()
740
+ typed_model_dump = not tool._schema_supplied and tool.input_model is not None
741
+ validation_input_model = tool.input_model if typed_model_dump else None
742
+ reject_unexpected = _rejects_unexpected_arguments(
743
+ argument_schema, typed_model_dump=typed_model_dump
744
+ ) and not _accepts_arbitrary_argument_names(validation_input_model)
745
+ try:
746
+ if arguments_not_object:
747
+ raise TypeError(f"Arguments for '{tool.name}' are not a JSON object.")
748
+ unexpected_arguments = _unexpected_argument_names(
749
+ parsed_args,
750
+ argument_schema,
751
+ reject_unexpected=reject_unexpected,
752
+ input_model=validation_input_model,
753
+ )
754
+ if unexpected_arguments:
755
+ raise TypeError(f"Unexpected argument(s) for '{tool.name}'.")
756
+ if typed_model_dump:
757
+ try:
758
+ validation_args = deepcopy(parsed_args)
759
+ except Exception:
760
+ validation_args = parsed_args
761
+ validated = tool.input_model.model_validate(validation_args)
762
+ transformed_args = tool._dump_arguments(validated)
763
+ _validate_arguments_against_schema(
764
+ arguments=transformed_args,
765
+ schema=tool._serialization_schema,
766
+ tool_name=tool.name,
767
+ tuples_as_arrays=True,
768
+ values_prevalidated=True,
769
+ )
770
+ pipeline_args = transformed_args
771
+ else:
772
+ _validate_arguments_against_schema(arguments=parsed_args, schema=argument_schema, tool_name=tool.name)
773
+ pipeline_args = parsed_args
774
+ except (TypeError, ValidationError) as exc:
775
+ # ``response_truncated`` (finish_reason == "length") is a response-wide
776
+ # signal, so it alone doesn't prove THIS call's arguments were cut off —
777
+ # a fully-parsed but schema-invalid call, or one bad call among several,
778
+ # can ride along in a truncated response. Only report truncation when
779
+ # this call's own argument payload is actually incomplete/unparseable
780
+ # (raw JSON string), or when the final content block is a parsed mapping
781
+ # that looks incomplete (Anthropic blocking path). Otherwise it's a real
782
+ # argument error the model should fix by correcting the arguments, not by
783
+ # raising max_tokens.
784
+ arguments_unparseable = _arguments_unparseable(function_call.arguments)
785
+ arguments_cut_off = arguments_unparseable or (
786
+ function_call_may_be_truncated
787
+ and _arguments_look_like_truncated_mapping(function_call.arguments, argument_schema)
788
+ )
789
+ if response_truncated and arguments_cut_off:
790
+ message = (
791
+ "The tool call was cut off at the model's output token limit "
792
+ "(max_tokens) before its arguments were complete, so they could not "
793
+ "be parsed. Raise max_tokens for this model, or split the work into "
794
+ "smaller calls (e.g. write the file in parts)."
795
+ )
796
+ error_kind = "argument_truncated"
797
+ else:
798
+ message = _argument_validation_message(
799
+ tool_name=tool.name,
800
+ arguments=parsed_args,
801
+ arguments_unparseable=arguments_unparseable or arguments_not_object,
802
+ schema=argument_schema,
803
+ exception=exc,
804
+ reject_unexpected=reject_unexpected,
805
+ input_model=validation_input_model,
806
+ )
807
+ error_kind = "argument_parsing"
808
+ additional = _result_additional_properties(function_call)
809
+ additional.update(
810
+ {
811
+ TOOL_FAILED_METADATA_KEY: True,
812
+ TOOL_ERROR_KIND_METADATA_KEY: error_kind,
813
+ TOOL_ERROR_MESSAGE_METADATA_KEY: message,
814
+ }
815
+ )
816
+ result = Content.from_function_result(
817
+ call_id=function_call.call_id, # type: ignore[arg-type]
818
+ result=f"Error: {message}",
819
+ exception=_failure_record_text(exc),
820
+ additional_properties=additional,
821
+ )
822
+ await _record_unexecuted_tool_operation(
823
+ function_call,
824
+ outcome=ToolOutcome.INVALID_ARGUMENTS,
825
+ result_carrier_item_id=result_carrier_item_id,
826
+ result=result,
827
+ )
828
+ return result
829
+
830
+ args = dict(pipeline_args)
831
+ try:
832
+ pipeline_args_snapshot = deepcopy(args)
833
+ except Exception:
834
+ pipeline_args_snapshot = None
835
+
836
+ call_id = function_call.call_id
837
+ if call_id is None:
838
+ # Landing stamped this call an operation id and the batch already
839
+ # counted it as dispatched, so the terminal is owed here — the same
840
+ # debt the unknown-tool and invalid-argument exits above settle.
841
+ await _record_unexecuted_tool_operation(function_call, outcome=ToolOutcome.FILTERED)
842
+ raise KeyError(f'Function "{function_call.name}" is missing call_id.')
843
+
844
+ context = FunctionInvocationContext(
845
+ function=tool,
846
+ arguments=args,
847
+ session=invocation_session,
848
+ kwargs=runtime_kwargs.copy(),
849
+ tools=live_tools,
850
+ )
851
+ context.same_tool_calls_in_batch = same_tool_calls_in_batch
852
+ # Always pass call_id to middleware.
853
+ context.metadata["call_id"] = call_id
854
+ # Seed the kernel-stamped invocation ordinal for middleware readers
855
+ # (approval, event persistence). The call content is the single source;
856
+ # the middleware never renumbers a seeded context.
857
+ ordinal = read_tool_invocation_order(function_call.additional_properties)
858
+ if ordinal is not None:
859
+ context.metadata[TOOL_INVOCATION_ORDER_KEY] = ordinal
860
+ # Trajectory identities for middleware readers: the call's tool operation
861
+ # id (landing stamped it on the content) and the item id every result
862
+ # construction path below will give this call's result.
863
+ operation_id = read_operation_id(function_call.additional_properties)
864
+ if operation_id is not None:
865
+ context.metadata[OPERATION_ID_KEY] = operation_id
866
+ # The call's own item id travels the same way, so a call that runs names
867
+ # its request item exactly like one that never reaches the pipeline.
868
+ call_item_id = read_analytics_item_id(function_call.additional_properties)
869
+ if call_item_id is not None:
870
+ context.metadata[ANALYTICS_ITEM_ID_KEY] = call_item_id
871
+ context.metadata[TOOL_RESULT_ITEM_ID_METADATA_KEY] = new_analytics_id()
872
+ context.metadata[TOOL_RESULT_CARRIER_ITEM_ID_METADATA_KEY] = result_carrier_item_id
873
+ if result_commit is not None:
874
+
875
+ def _commit_sub_agent_interruption(metadata: Mapping[str, Any]) -> None:
876
+ result_commit.commit_interrupted(function_call, context, metadata)
877
+
878
+ context.metadata[SUB_AGENT_RESULT_COMMIT_CALLBACK_KEY] = _commit_sub_agent_interruption
879
+
880
+ async def final_function_handler(context_obj: Any) -> Any:
881
+ # Middleware sees normalized callable-keyspace values. An untouched
882
+ # context goes back through invoke in the original validation keyspace;
883
+ # a trusted mapping rewrite bypasses validation-keyspace conversion.
884
+ logger.info("Function name: %s", tool.name)
885
+ if logger.isEnabledFor(logging.DEBUG):
886
+ schema_names = tool.parameters().get("properties") or {}
887
+ logger.debug("Function arguments: %s", _describe_arguments(context_obj.arguments, schema_names))
888
+ # No failure log line here: the outer except below already reports
889
+ # failures (tool name + exception + "returning an error result") — a
890
+ # second line would be duplication (invoke itself is chrys-owned and
891
+ # telemetry/logging-free; the span records the exception).
892
+ start = perf_counter()
893
+ try:
894
+ arguments_unchanged = (
895
+ pipeline_args_snapshot is not None
896
+ and context_obj.arguments is args
897
+ and _middleware_arguments_equal(context_obj.arguments, pipeline_args_snapshot)
898
+ )
899
+ except Exception:
900
+ arguments_unchanged = False
901
+ invocation_arguments = parsed_args if arguments_unchanged else context_obj.arguments
902
+ arguments_in_callable_keyspace = (
903
+ typed_model_dump and not arguments_unchanged and isinstance(invocation_arguments, Mapping)
904
+ )
905
+
906
+ def _commit_raw_result(value: Any) -> None:
907
+ if result_commit is None:
908
+ return
909
+ # This callback runs before FunctionMiddleware ``finally`` blocks
910
+ # publish their measured span. The instant timing makes the raw
911
+ # crash checkpoint complete; terminal construction overwrites it
912
+ # from ``context.metadata`` after middleware unwinds.
913
+ additional = _result_additional_properties(function_call, context)
914
+ parent_metadata = sub_agent_parent_result_metadata.get()
915
+ if parent_metadata is not None:
916
+ carried: dict[str, Any] = {}
917
+ if parent_metadata.sub_agent_invocation_id:
918
+ carried["sub_agent_invocation_id"] = parent_metadata.sub_agent_invocation_id
919
+ if parent_metadata.sub_agent_log_file:
920
+ carried["sub_agent_log_file"] = parent_metadata.sub_agent_log_file
921
+ if parent_metadata.sub_agent_audit_complete:
922
+ carried["sub_agent_audit_complete"] = True
923
+ if carried:
924
+ additional[TOOL_RESULT_METADATA_KEY] = carried
925
+ result_commit.commit_raw(
926
+ Content.from_function_result(
927
+ call_id=call_id,
928
+ result=value,
929
+ additional_properties=additional,
930
+ )
931
+ )
932
+
933
+ try:
934
+ if TELEMETRY_GATE.enabled:
935
+ result = await _invoke_with_function_span(
936
+ tool,
937
+ context_obj,
938
+ call_id,
939
+ arguments=invocation_arguments,
940
+ arguments_in_callable_keyspace=arguments_in_callable_keyspace,
941
+ tool_result_ceiling_tokens=tool_result_ceiling_tokens,
942
+ )
943
+ else:
944
+ result = await tool.invoke(
945
+ arguments=invocation_arguments,
946
+ context=context_obj,
947
+ tool_call_id=call_id,
948
+ _arguments_in_callable_keyspace=arguments_in_callable_keyspace,
949
+ )
950
+ result = apply_result_ceiling(result, tool_result_ceiling_tokens)
951
+ except SyncToolCancelledAfterCompletion as exc:
952
+ # The worker finished before cancellation landed: journal the
953
+ # completed value as this slot's raw result so the interrupt
954
+ # handler preserves it instead of recording a fabricated
955
+ # interruption for work that actually ran.
956
+ exc.completed_result = apply_result_ceiling(exc.completed_result, tool_result_ceiling_tokens)
957
+ _commit_raw_result(exc.completed_result)
958
+ raise
959
+ _commit_raw_result(result)
960
+ logger.info("Function %s succeeded in %.3fs.", tool.name, perf_counter() - start)
961
+ if logger.isEnabledFor(logging.DEBUG):
962
+ logger.debug(
963
+ "Function result: %s",
964
+ _describe_result(result, skip_parsing=_is_skip_parsing_sentinel(tool.result_parser)),
965
+ )
966
+ return result
967
+
968
+ try:
969
+ function_result = await pipeline.execute(
970
+ context=context,
971
+ final_handler=final_function_handler,
972
+ )
973
+ return Content.from_function_result(
974
+ call_id=call_id,
975
+ result=function_result,
976
+ additional_properties=_result_additional_properties(function_call, context),
977
+ )
978
+ except asyncio.CancelledError:
979
+ # Function middleware ``finally`` blocks have completed at this
980
+ # boundary, so their measured timing and result metadata are now on
981
+ # the invocation context. Commit with that context before the outer
982
+ # batch-level cancellation fallback loses it.
983
+ if result_commit is not None:
984
+ result_commit.commit_interrupted(function_call, context)
985
+ raise
986
+ except MiddlewareTermination as term_exc:
987
+ # Re-raise to signal loop termination, capturing any middleware-set
988
+ # result first.
989
+ if context.result is not None:
990
+ term_exc.result = Content.from_function_result(
991
+ call_id=call_id,
992
+ result=context.result,
993
+ additional_properties=_result_additional_properties(function_call, context),
994
+ )
995
+ elif isinstance(term_exc.result, Content):
996
+ # Middleware may cache and reuse a prebuilt result across concurrent
997
+ # calls or later runs. Own a wrapper per invocation before binding
998
+ # call-scoped identity and metadata; its payload may contain
999
+ # external objects that are deliberately unsafe to deep-copy.
1000
+ owned_result = copy(term_exc.result)
1001
+ owned_result.call_id = call_id
1002
+ prebuilt_properties = term_exc.result.additional_properties
1003
+ # This call's properties go on top of whatever the prebuilt result
1004
+ # carried, the same fold every other result path uses: a result
1005
+ # cached across calls arrives wearing another invocation's identity,
1006
+ # and the tool operation and pre-minted item id have to be this
1007
+ # one's or the trajectory's terminal names an item nothing saved.
1008
+ properties = {**prebuilt_properties, **_result_additional_properties(function_call, context)}
1009
+ owned = prebuilt_properties.get(TOOL_RESULT_METADATA_KEY)
1010
+ if isinstance(owned, Mapping) and owned:
1011
+ # A middleware that built its own result Content owns whatever
1012
+ # metadata it chose to attach — fold first-write-wins only.
1013
+ properties[TOOL_RESULT_METADATA_KEY] = dict(owned)
1014
+ owned_result.additional_properties = properties
1015
+ term_exc.result = owned_result
1016
+ else:
1017
+ term_exc.result = Content.from_function_result(
1018
+ call_id=call_id,
1019
+ result=term_exc.result,
1020
+ additional_properties=_result_additional_properties(function_call, context),
1021
+ )
1022
+ raise
1023
+ except Exception as exc:
1024
+ logger.warning(
1025
+ "Function '%s' raised an exception; returning an error result to the model. Exception: %r",
1026
+ tool.name,
1027
+ exc,
1028
+ )
1029
+ return Content.from_function_result(
1030
+ call_id=function_call.call_id, # type: ignore[arg-type]
1031
+ result=tool_error_result_text(exc),
1032
+ # Never None: it marks the result failed for the consecutive-error
1033
+ # count and the providers' error flag, whatever the model reads.
1034
+ exception=_failure_record_text(exc),
1035
+ additional_properties=_result_additional_properties(function_call, context),
1036
+ )
1037
+ finally:
1038
+ # Backfill per-call provenance subkeys (builder context resolved from
1039
+ # the final middleware args) onto the persisted call content. Runs on
1040
+ # every pipeline exit; on cancellation the middleware wrote no
1041
+ # carriage, so there is nothing to merge.
1042
+ carried_context = context.metadata.get(TOOL_CALL_CONTEXT_METADATA_KEY)
1043
+ if isinstance(carried_context, Mapping):
1044
+ merge_tool_call_context_property(function_call.additional_properties, carried_context)
1045
+
1046
+
1047
+ async def _execute_function_calls(
1048
+ *,
1049
+ function_calls: Sequence[Content],
1050
+ result_commits: Sequence[ToolResultCommit] | None,
1051
+ tool_options: dict[str, Any],
1052
+ custom_args: dict[str, Any],
1053
+ invocation_session: AgentSession | None,
1054
+ pipeline: FunctionMiddlewarePipeline,
1055
+ result_carrier_item_id: str,
1056
+ response_truncated: bool = False,
1057
+ truncated_final_function_call_ids: set[int] | None = None,
1058
+ tool_result_ceiling_tokens: int | None = None,
1059
+ dispatch_observer: Callable[[Sequence[Content]], None] | None = None,
1060
+ ) -> tuple[list[Content], bool, bool]:
1061
+ """Execute a batch concurrently and collect results, termination and error flags."""
1062
+ raw_tools = tool_options.get("tools")
1063
+ if not raw_tools:
1064
+ return [], False, False
1065
+
1066
+ # Normalize per batch: direct callers and progressive exposure can add
1067
+ # tools after agent preparation. Every executed tool must use FunctionTool.invoke.
1068
+ tools = normalize_tools(raw_tools)
1069
+ tool_options["tools"] = tools
1070
+ if not tools:
1071
+ return [], False, False
1072
+ # Rebuild from the live list so progressive exposure takes effect next batch.
1073
+ # Tool admission has already enforced unique names.
1074
+ tool_map: dict[str, FunctionTool] = {t.name: t for t in tools if isinstance(t, FunctionTool)}
1075
+ live_tools: list[Any] | None = tools
1076
+
1077
+ # Per-name call counts for this batch: middleware with singleton semantics
1078
+ # (e.g. whole-list-replacement tools) reads the stamped count to reject
1079
+ # same-batch duplicates deterministically instead of racing the gather.
1080
+ same_name_counts = Counter(fc.name for fc in function_calls)
1081
+
1082
+ commits: Sequence[ToolResultCommit | None]
1083
+ if result_commits is None:
1084
+ commits = (None,) * len(function_calls)
1085
+ else:
1086
+ if len(result_commits) != len(function_calls):
1087
+ raise ValueError("Tool-result commit bindings do not match the extracted call batch.")
1088
+ commits = result_commits
1089
+
1090
+ async def invoke_with_termination_handling(
1091
+ function_call: Content,
1092
+ result_commit: ToolResultCommit | None,
1093
+ ) -> tuple[Content, bool]:
1094
+ """Catch MiddlewareTermination, returning ``(result, should_terminate)``."""
1095
+ # The handover is per call and happens on this task's own first step,
1096
+ # not once for the batch: a task cancelled before it ever runs never
1097
+ # executes a line of this body, so its operation has to stay in the
1098
+ # loop's ledger for the reconciliation pass to close.
1099
+ if dispatch_observer is not None:
1100
+ dispatch_observer((function_call,))
1101
+ try:
1102
+ result = await _invoke_function_call(
1103
+ function_call,
1104
+ result_commit=result_commit,
1105
+ tool_map=tool_map,
1106
+ custom_args=custom_args,
1107
+ invocation_session=invocation_session,
1108
+ pipeline=pipeline,
1109
+ live_tools=live_tools,
1110
+ response_truncated=response_truncated,
1111
+ function_call_may_be_truncated=(
1112
+ response_truncated
1113
+ and truncated_final_function_call_ids is not None
1114
+ and id(function_call) in truncated_final_function_call_ids
1115
+ ),
1116
+ same_tool_calls_in_batch=same_name_counts[function_call.name],
1117
+ tool_result_ceiling_tokens=tool_result_ceiling_tokens,
1118
+ result_carrier_item_id=result_carrier_item_id,
1119
+ )
1120
+ result = apply_result_ceiling(result, tool_result_ceiling_tokens)
1121
+ if result_commit is not None:
1122
+ result_commit.commit_final(result)
1123
+ return (result, False)
1124
+ except MiddlewareTermination as exc:
1125
+ # exc.result may already be a Content (set by _invoke_function_call)
1126
+ # or a raw value.
1127
+ if isinstance(exc.result, Content):
1128
+ bounded_result = apply_result_ceiling(exc.result, tool_result_ceiling_tokens)
1129
+ if result_commit is not None:
1130
+ result_commit.commit_final(bounded_result)
1131
+ return (bounded_result, True)
1132
+ result_content = Content.from_function_result(
1133
+ call_id=function_call.call_id, # type: ignore[arg-type]
1134
+ result=exc.result,
1135
+ additional_properties=_result_additional_properties(function_call),
1136
+ )
1137
+ result_content = apply_result_ceiling(result_content, tool_result_ceiling_tokens)
1138
+ if result_commit is not None:
1139
+ result_commit.commit_final(result_content)
1140
+ return (result_content, True)
1141
+ except asyncio.CancelledError:
1142
+ if result_commit is not None:
1143
+ result_commit.commit_interrupted(function_call, None)
1144
+ raise
1145
+
1146
+ tasks = [
1147
+ asyncio.create_task(invoke_with_termination_handling(function_call, result_commit))
1148
+ for function_call, result_commit in zip(function_calls, commits, strict=True)
1149
+ ]
1150
+ try:
1151
+ settled = await asyncio.gather(*tasks, return_exceptions=True)
1152
+ except asyncio.CancelledError:
1153
+ for task in tasks:
1154
+ if not task.done():
1155
+ task.cancel()
1156
+ settling = asyncio.gather(*tasks, return_exceptions=True)
1157
+ while not settling.done():
1158
+ try:
1159
+ await asyncio.shield(settling)
1160
+ except asyncio.CancelledError:
1161
+ continue
1162
+ raise
1163
+
1164
+ for outcome in settled:
1165
+ if isinstance(outcome, BaseException):
1166
+ raise outcome
1167
+ execution_results = [outcome for outcome in settled if not isinstance(outcome, BaseException)]
1168
+
1169
+ contents: list[Content] = [result[0] for result in execution_results]
1170
+ should_terminate = any(result[1] for result in execution_results)
1171
+ had_errors = any(fcr.exception is not None for fcr in contents if fcr.type == "function_result")
1172
+ return contents, should_terminate, had_errors