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
chrys/kernel/client.py ADDED
@@ -0,0 +1,1084 @@
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
+ """Chrys-owned chat client base.
6
+
7
+ The ``BaseChatClient`` subset Chrys consumes:
8
+ option validation, response-stream construction, compaction preparation, and
9
+ the public ``get_response`` dispatcher. The ``as_agent`` and
10
+ embedding surfaces are intentionally not ported.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import json
17
+ import logging
18
+ import math
19
+ from abc import ABC, abstractmethod
20
+ from collections.abc import AsyncIterable, Awaitable, Callable, Collection, Iterator, Mapping, Sequence
21
+ from contextlib import contextmanager
22
+ from contextvars import ContextVar
23
+ from copy import copy
24
+ from dataclasses import dataclass
25
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol, TypeGuard, TypeIs, cast, overload
26
+
27
+ from chrys.foundation.errors import ProviderResponseError
28
+ from chrys.foundation.trajectory.context import TRAJECTORY_EXCHANGE_KWARG, ExchangeTrace, side_call_scope
29
+ from chrys.foundation.trajectory.envelope import ActorRole
30
+
31
+ from ._serialization import SerializationMixin, make_json_safe
32
+ from ._types import ChatResponse, ChatResponseUpdate, Message, ResponseStream, validate_chat_options
33
+
34
+ if TYPE_CHECKING:
35
+ from .compaction import CompactionCallContext, TokenizerProtocol
36
+
37
+
38
+ _log = logging.getLogger(__name__)
39
+
40
+ # A streamed wire pull's stall watchdog also times the work that runs before
41
+ # the provider's first byte: compaction and its LAST_WORDS side call. That
42
+ # work reports liveness through this callback, which the watchdog installs in
43
+ # the context it times, so the watchdog times idle gaps instead of the whole
44
+ # pull. A timer nested inside (a side call's own read timeout) chains to it.
45
+ # Outside a watchdog it is None.
46
+ _WIRE_PROGRESS: ContextVar[Callable[[], None] | None] = ContextVar("chrys_wire_progress", default=None)
47
+
48
+
49
+ def report_wire_progress() -> None:
50
+ """Restart the idle timers of the enclosing stall watchdogs, if any are running."""
51
+ callback = _WIRE_PROGRESS.get()
52
+ if callback is not None:
53
+ callback()
54
+
55
+
56
+ @contextmanager
57
+ def wire_progress_scope(on_progress: Callable[[], None]) -> Iterator[None]:
58
+ """Report progress from the block, and every task it spawns, to *on_progress*.
59
+
60
+ A report also reaches the scopes this one is nested in: an inner timer
61
+ never hides progress from the watchdog waiting on its work. Spawned tasks
62
+ copy the context, so they can report after the block exits: every
63
+ callback must tolerate a report once its timer is gone.
64
+ """
65
+ enclosing = _WIRE_PROGRESS.get()
66
+
67
+ def _report() -> None:
68
+ on_progress()
69
+ if enclosing is not None:
70
+ enclosing()
71
+
72
+ token = _WIRE_PROGRESS.set(_report)
73
+ try:
74
+ yield
75
+ finally:
76
+ _WIRE_PROGRESS.reset(token)
77
+
78
+
79
+ def start_with_wire_progress(awaitable: Awaitable[Any], on_progress: Callable[[], None]) -> asyncio.Future[Any]:
80
+ """Schedule *awaitable* as a task whose context reports progress to *on_progress*.
81
+
82
+ The task copies the context at creation, so the callback reaches
83
+ everything the task runs and nothing the caller runs afterwards.
84
+ """
85
+ with wire_progress_scope(on_progress):
86
+ return asyncio.ensure_future(awaitable)
87
+
88
+
89
+ def _report_side_call_update(_update: ChatResponseUpdate) -> None:
90
+ report_wire_progress()
91
+
92
+
93
+ # Output-cap spellings in provider conflict-priority order. The canonical
94
+ # ``max_tokens`` alias wins when more than one is supplied.
95
+ OUTPUT_CAP_OPTION_ALIASES: tuple[str, ...] = ("max_tokens", "max_output_tokens", "max_completion_tokens")
96
+
97
+ # Option keys that must never reach a LAST_WORDS side call.
98
+ #
99
+ # Continuation markers would chain the throwaway request into service-side
100
+ # conversation state: the OpenAI Responses serializer treats every one of
101
+ # conversation_id / previous_response_id / conversation as a service-side
102
+ # storage marker, the raw provider keys pass through its run options
103
+ # untouched, and a continuation_token short-circuits _inner_get_response
104
+ # into retrieving an existing background response — the side call's messages
105
+ # would be ignored and a live response returned as the note. The side call
106
+ # sends a self-contained scoped slice, so it needs no continuation state to be
107
+ # useful.
108
+ _SIDE_CALL_CONTINUATION_KEYS = ("conversation_id", "previous_response_id", "conversation", "continuation_token")
109
+ CONVERSATION_HANDLE_KEYS = ("conversation_id", "previous_response_id", "conversation")
110
+ _FORCED_STATELESS_OPTION_KEYS = (*CONVERSATION_HANDLE_KEYS, "continuation_token", "background")
111
+ # Output-shaping keys would force the note into schema JSON — non-empty, so
112
+ # it would pass the have_note guard. Beyond the first-class response_format,
113
+ # provider-native passthroughs survive serialization: OpenAI Responses honors
114
+ # a raw ``text`` (carrying ``format``) / ``text_format``, and the Anthropic
115
+ # serializer forwards a raw ``output_format`` as a request kwarg.
116
+ _SIDE_CALL_OUTPUT_SHAPING_KEYS = ("response_format", "text", "text_format", "output_format", "output_config")
117
+ # Execution-mode keys: an inherited Responses ``background=True`` would
118
+ # launch a never-polled background response per attempt (its in_progress
119
+ # status parses to empty text, so the retry loop would keep spawning
120
+ # orphans). The side call must run as a plain foreground request.
121
+ _SIDE_CALL_EXECUTION_MODE_KEYS = ("background",)
122
+ # Keys scrubbed from a forwarded ``extra_body`` mapping: provider SDKs merge
123
+ # ``extra_body`` into the request body AFTER the named parameters, so a
124
+ # nested spelling of any of these would silently override the side call's
125
+ # top-level sanitization (``store=False``, ``tool_choice="none"``, the
126
+ # output cap) on the wire.
127
+ _SIDE_CALL_EXTRA_BODY_STRIP_KEYS = frozenset(
128
+ {
129
+ *_SIDE_CALL_CONTINUATION_KEYS,
130
+ *_SIDE_CALL_OUTPUT_SHAPING_KEYS,
131
+ *_SIDE_CALL_EXECUTION_MODE_KEYS,
132
+ *OUTPUT_CAP_OPTION_ALIASES,
133
+ "store",
134
+ "tool_choice",
135
+ # Input-bearing keys: a nested spelling would replace the scoped
136
+ # LAST_WORDS slice or its instruction — or re-enable tools — once
137
+ # the SDK merges ``extra_body`` over the prepared body. The profile
138
+ # loader strips these from user config, but programmatic options and
139
+ # client kwargs never pass through it.
140
+ "input",
141
+ "messages",
142
+ "prompt",
143
+ "tools",
144
+ "system",
145
+ "instructions",
146
+ "stream",
147
+ }
148
+ )
149
+
150
+
151
+ @dataclass(frozen=True, slots=True)
152
+ class StorageModeResolution:
153
+ """First-request storage mode plus handle-free request copies."""
154
+
155
+ service_side: bool
156
+ handles_present: bool
157
+ options_without_handles: dict[str, Any]
158
+ client_kwargs_without_handles: dict[str, Any]
159
+
160
+
161
+ def _copy_without_conversation_handles(
162
+ payload: Mapping[str, Any] | None,
163
+ *,
164
+ force_stateless: bool = False,
165
+ ) -> tuple[dict[str, Any], bool]:
166
+ copied = dict(payload or {})
167
+ stripped_keys = _FORCED_STATELESS_OPTION_KEYS if force_stateless else CONVERSATION_HANDLE_KEYS
168
+ handles_present = any(key in copied for key in stripped_keys)
169
+ for key in stripped_keys:
170
+ copied.pop(key, None)
171
+ extra_body = copied.get("extra_body")
172
+ if isinstance(extra_body, Mapping):
173
+ clean_extra_body = dict(extra_body)
174
+ handles_present = handles_present or any(key in clean_extra_body for key in stripped_keys)
175
+ for key in stripped_keys:
176
+ clean_extra_body.pop(key, None)
177
+ copied["extra_body"] = clean_extra_body
178
+ return copied, handles_present
179
+
180
+
181
+ def conversation_handle_value(value: Any) -> str | None:
182
+ """Normalize one conversation-handle spelling to its string id.
183
+
184
+ ``conversation`` admits a mapping form carrying the id under ``"id"``;
185
+ the other spellings carry the id directly. Anything else returns None —
186
+ an unrecognized shape is foreign state this layer must not interpret.
187
+ """
188
+ if isinstance(value, Mapping):
189
+ value = value.get("id")
190
+ if isinstance(value, str) and value:
191
+ return value
192
+ return None
193
+
194
+
195
+ def continuation_token_response_id(value: Any) -> str | None:
196
+ """Normalize a continuation token to the response id it would retrieve.
197
+
198
+ Provider tokens are opaque TypedDicts; the Responses client's token
199
+ carries the background response's id under ``"response_id"`` and
200
+ short-circuits the request into retrieving that response outright while
201
+ the request messages are ignored. Tokens of any other shape return
202
+ None — they cannot be interpreted at this layer.
203
+ """
204
+ if isinstance(value, Mapping):
205
+ response_id = value.get("response_id")
206
+ if isinstance(response_id, str) and response_id:
207
+ return response_id
208
+ return None
209
+
210
+
211
+ def collect_conversation_handles(payload: Mapping[str, Any] | None) -> list[str]:
212
+ """Collect every normalized conversation handle riding a request mapping.
213
+
214
+ Scans each ``CONVERSATION_HANDLE_KEYS`` spelling at the top level and
215
+ nested inside ``extra_body``: provider SDKs merge ``extra_body`` into the
216
+ request body after the named parameters, so a nested spelling reaches the
217
+ wire as continuation state all the same. A top-level
218
+ ``continuation_token`` counts too — its response id is continuation
219
+ state exactly like ``previous_response_id`` (nested extra_body copies
220
+ never reach the retrieve short-circuit, so they don't participate).
221
+ """
222
+ handles: list[str] = []
223
+ if not payload:
224
+ return handles
225
+ extra_body = payload.get("extra_body")
226
+ for source in (payload, extra_body if isinstance(extra_body, Mapping) else None):
227
+ if source is None:
228
+ continue
229
+ for key in CONVERSATION_HANDLE_KEYS:
230
+ handle = conversation_handle_value(source.get(key))
231
+ if handle is not None:
232
+ handles.append(handle)
233
+ token_response_id = continuation_token_response_id(payload.get("continuation_token"))
234
+ if token_response_id is not None:
235
+ handles.append(token_response_id)
236
+ return handles
237
+
238
+
239
+ def strip_invalidated_conversation_handles(
240
+ payload: Mapping[str, Any] | None,
241
+ invalidated: Collection[str],
242
+ ) -> dict[str, Any]:
243
+ """Copy a request mapping without the handles a session has invalidated.
244
+
245
+ The provider-facing reflection view: history providers decide whether
246
+ the service owns a run's history by looking at the request's
247
+ continuation handles, so a handle the wire choke point strips must not
248
+ appear live to them. Removal-only and copy-on-write across the same
249
+ spellings :func:`collect_conversation_handles` scans; live handles pass
250
+ through untouched.
251
+ """
252
+ copied = dict(payload or {})
253
+ if not invalidated:
254
+ return copied
255
+ for key in CONVERSATION_HANDLE_KEYS:
256
+ handle = conversation_handle_value(copied.get(key))
257
+ if handle is not None and handle in invalidated:
258
+ del copied[key]
259
+ token_response_id = continuation_token_response_id(copied.get("continuation_token"))
260
+ if token_response_id is not None and token_response_id in invalidated:
261
+ del copied["continuation_token"]
262
+ extra_body = copied.get("extra_body")
263
+ if isinstance(extra_body, Mapping):
264
+ poisoned_keys = [
265
+ key
266
+ for key in CONVERSATION_HANDLE_KEYS
267
+ if (nested := conversation_handle_value(extra_body.get(key))) is not None and nested in invalidated
268
+ ]
269
+ if poisoned_keys:
270
+ clean_extra_body = dict(extra_body)
271
+ for key in poisoned_keys:
272
+ del clean_extra_body[key]
273
+ copied["extra_body"] = clean_extra_body
274
+ return copied
275
+
276
+
277
+ def resolve_storage_mode_and_handles(
278
+ options: Mapping[str, Any] | None,
279
+ *,
280
+ stores_by_default: bool,
281
+ client_kwargs: Mapping[str, Any] | None = None,
282
+ force_stateless: bool = False,
283
+ ) -> StorageModeResolution:
284
+ """Resolve effective storage and copy both request mappings without handles.
285
+
286
+ The wire truth lives in ``options``: every current client derives
287
+ ``store`` from options alone, with a provider ``extra_body`` value
288
+ overriding the named parameter (a present ``None`` reaches the wire as
289
+ JSON ``null``, selecting the provider's storage default). Client
290
+ kwargs never reach the request,
291
+ so a kwargs-side ``store`` may only *escalate* the judgment toward
292
+ service-side — misclassifying a stored request as local would license
293
+ in-place re-creates that duplicate hosted work, while the reverse merely
294
+ retries more conservatively. Conversation handles never decide storage
295
+ mode; the first request's effective ``store`` value does.
296
+
297
+ ``force_stateless`` is a client capability veto: it keeps the request in
298
+ local-history/retry mode regardless of every ``store`` spelling and
299
+ removes conversation, continuation, and background state from the
300
+ reflected copies. The provider-facing options remain the caller's own
301
+ view so a raw client can reject unsupported execution modes explicitly.
302
+ """
303
+ effective_options = options or {}
304
+ effective_kwargs = client_kwargs or {}
305
+ option_extra = effective_options.get("extra_body")
306
+ kwargs_extra = effective_kwargs.get("extra_body")
307
+ if isinstance(option_extra, Mapping) and "store" in option_extra:
308
+ # A *present* extra_body key overrides the named parameter even when
309
+ # its value is None: the merged request body then carries
310
+ # ``"store": null`` and the provider applies its storage default —
311
+ # so fall to ``stores_by_default`` rather than the named value.
312
+ explicit_store = option_extra["store"]
313
+ else:
314
+ explicit_store = effective_options.get("store")
315
+ kwargs_escalation = bool(
316
+ (kwargs_extra.get("store") if isinstance(kwargs_extra, Mapping) else None) or effective_kwargs.get("store")
317
+ )
318
+ clean_options, option_handles = _copy_without_conversation_handles(
319
+ effective_options,
320
+ force_stateless=force_stateless,
321
+ )
322
+ clean_kwargs, kwargs_handles = _copy_without_conversation_handles(
323
+ effective_kwargs,
324
+ force_stateless=force_stateless,
325
+ )
326
+ return StorageModeResolution(
327
+ service_side=False
328
+ if force_stateless
329
+ else (bool(explicit_store) if explicit_store is not None else stores_by_default) or kwargs_escalation,
330
+ handles_present=option_handles or kwargs_handles,
331
+ options_without_handles=clean_options,
332
+ client_kwargs_without_handles=clean_kwargs,
333
+ )
334
+
335
+
336
+ def _scrub_side_call_extra_body(payload: dict[str, Any]) -> None:
337
+ """Replace a Mapping ``extra_body`` with a copy minus wire-overriding keys."""
338
+ extra_body = payload.get("extra_body")
339
+ if isinstance(extra_body, Mapping):
340
+ payload["extra_body"] = {
341
+ key: value for key, value in extra_body.items() if key not in _SIDE_CALL_EXTRA_BODY_STRIP_KEYS
342
+ }
343
+
344
+
345
+ def _tool_choice_forces_tool_use(tool_choice: Any) -> bool:
346
+ """Return whether a standard or provider-native choice forbids prose-only output."""
347
+ if isinstance(tool_choice, str):
348
+ return tool_choice.lower() in {"required", "any"}
349
+ if not isinstance(tool_choice, Mapping):
350
+ tool_choice = make_json_safe(tool_choice)
351
+ if not isinstance(tool_choice, Mapping):
352
+ return False
353
+
354
+ mode = tool_choice.get("mode")
355
+ if isinstance(mode, str):
356
+ return mode.lower() in {"required", "any"}
357
+ choice_type = tool_choice.get("type")
358
+ if isinstance(choice_type, str) and choice_type.lower() in {"required", "any", "function", "tool", "custom"}:
359
+ return True
360
+ return bool(tool_choice.get("required_function_name"))
361
+
362
+
363
+ def _estimate_tool_definition_tokens(options: Mapping[str, Any], tokenizer: TokenizerProtocol | None) -> int:
364
+ """Estimate the live call's tool definitions without touching provider objects."""
365
+ tools = options.get("tools")
366
+ if tools is None or tokenizer is None:
367
+ return 0
368
+ serialized = json.dumps(make_json_safe(tools), ensure_ascii=False, sort_keys=True, separators=(",", ":"))
369
+ return max(0, tokenizer.count_tokens(serialized))
370
+
371
+
372
+ def _estimate_value_tokens(value: Any, tokenizer: TokenizerProtocol) -> int:
373
+ if isinstance(value, type):
374
+ # A class-valued option (e.g. a Pydantic ``response_format``) wires
375
+ # as its JSON schema; ``make_json_safe`` would instead walk the class
376
+ # ``__dict__`` (core schema, validators, …) and inflate a one-field
377
+ # model to ~100k serialized characters.
378
+ schema_method = getattr(value, "model_json_schema", None)
379
+ try:
380
+ value = schema_method() if callable(schema_method) else str(value)
381
+ except Exception:
382
+ value = str(value)
383
+ text = (
384
+ value
385
+ if isinstance(value, str)
386
+ else json.dumps(
387
+ make_json_safe(value),
388
+ ensure_ascii=False,
389
+ sort_keys=True,
390
+ separators=(",", ":"),
391
+ )
392
+ )
393
+ return max(0, tokenizer.count_tokens(text))
394
+
395
+
396
+ # Instruction-bearing plus every provider-native output-shaping spelling —
397
+ # the same set the side call scrubs, because the same keys carry wire payload.
398
+ _REQUEST_OVERHEAD_OPTION_KEYS = ("instructions", "system", *_SIDE_CALL_OUTPUT_SHAPING_KEYS)
399
+
400
+
401
+ def _estimate_fixed_request_overhead_tokens(
402
+ options: Mapping[str, Any],
403
+ client_kwargs: Mapping[str, Any],
404
+ tokenizer: TokenizerProtocol | None,
405
+ *,
406
+ tool_definition_tokens: int | None = None,
407
+ ) -> int:
408
+ """Estimate fixed request content injected outside the message list."""
409
+ if tokenizer is None:
410
+ return 0
411
+ total = (
412
+ _estimate_tool_definition_tokens(options, tokenizer)
413
+ if tool_definition_tokens is None
414
+ else tool_definition_tokens
415
+ )
416
+ # ``extra_body`` merges into the request body after the named parameters,
417
+ # so nested spellings of these keys ship on the wire too.
418
+ extra_bodies = [
419
+ body for body in (client_kwargs.get("extra_body"), options.get("extra_body")) if isinstance(body, Mapping)
420
+ ]
421
+ for key in _REQUEST_OVERHEAD_OPTION_KEYS:
422
+ # Providers disagree on options-vs-kwargs precedence for these keys
423
+ # (Anthropic ultimately wires ``options["instructions"]`` over a
424
+ # kwargs-derived ``system``), so admission counts the larger
425
+ # candidate: overestimating only trims output room, undercounting
426
+ # could overflow the context.
427
+ candidates = [client_kwargs.get(key), options.get(key), *(body.get(key) for body in extra_bodies)]
428
+ estimates = [_estimate_value_tokens(value, tokenizer) for value in candidates if value is not None]
429
+ if estimates:
430
+ total += max(estimates)
431
+ return total
432
+
433
+
434
+ def _thinking_budget_tokens(options: Mapping[str, Any], client_kwargs: Mapping[str, Any]) -> int:
435
+ thinking = client_kwargs["thinking"] if "thinking" in client_kwargs else options.get("thinking")
436
+ if not isinstance(thinking, Mapping) or thinking.get("type") != "enabled":
437
+ return 0
438
+ budget = thinking.get("budget_tokens")
439
+ if isinstance(budget, int) and not isinstance(budget, bool) and budget > 0:
440
+ return budget
441
+ return 0
442
+
443
+
444
+ # Headroom for estimate drift since the last calibration: the calibrated
445
+ # input estimate can trail the provider's count by a few tokens, and
446
+ # input + output past the window is a hard 400.
447
+ _ADMISSION_SAFETY_MARGIN_MIN_TOKENS = 256
448
+ _ADMISSION_SAFETY_MARGIN_PCT = 1
449
+ # The margin never squeezes a cap below this, and never lifts one past the room.
450
+ _ADMISSION_MIN_USEFUL_OUTPUT_TOKENS = 1_024
451
+
452
+
453
+ def _admission_safety_margin(max_context_tokens: int) -> int:
454
+ return max(_ADMISSION_SAFETY_MARGIN_MIN_TOKENS, math.ceil(max_context_tokens * _ADMISSION_SAFETY_MARGIN_PCT / 100))
455
+
456
+
457
+ def _clamp_output_cap_for_context(
458
+ options: Mapping[str, Any],
459
+ *,
460
+ strategy: Any,
461
+ request_overhead_tokens: int,
462
+ client_kwargs: Mapping[str, Any],
463
+ provider_min_output_cap_tokens: int = 1,
464
+ ) -> dict[str, Any]:
465
+ """Clamp present output caps to the calibrated room left in the context.
466
+
467
+ The admitted cap keeps a safety margin inside the room while the room is
468
+ wide; when it is narrow, the margin yields down to a minimum useful output
469
+ but the cap still never exceeds the room (only a provider's legal minimum
470
+ can).
471
+ """
472
+ from .compaction import CompactionAdmissionState
473
+
474
+ copied = dict(options)
475
+ if not isinstance(strategy, CompactionAdmissionState):
476
+ return copied
477
+ present_aliases = [alias for alias in OUTPUT_CAP_OPTION_ALIASES if alias in copied]
478
+ if not present_aliases:
479
+ _log.debug("Context admission skipped output-cap clamp because no cap alias is present")
480
+ return copied
481
+
482
+ overhead = max(strategy.system_overhead_tokens, request_overhead_tokens)
483
+ estimated_input = math.ceil((strategy.last_included_tokens + overhead) * strategy.calibration_ratio)
484
+ room = strategy.max_context_tokens - estimated_input
485
+ margin = _admission_safety_margin(strategy.max_context_tokens)
486
+ guarded_room = min(room, max(room - margin, _ADMISSION_MIN_USEFUL_OUTPUT_TOKENS))
487
+ thinking_budget = _thinking_budget_tokens(copied, client_kwargs)
488
+ min_legal = max(provider_min_output_cap_tokens, thinking_budget + 1 if thinking_budget else 1)
489
+ admitted_cap = max(guarded_room, min_legal)
490
+ clamped: list[tuple[str, int, int]] = []
491
+ for alias in present_aliases:
492
+ value = copied[alias]
493
+ if isinstance(value, int) and not isinstance(value, bool) and value > admitted_cap:
494
+ copied[alias] = admitted_cap
495
+ clamped.append((alias, value, admitted_cap))
496
+ if clamped:
497
+ _log.warning(
498
+ "Clamped model output cap for context admission: estimated_input=%d, max_context_tokens=%d, "
499
+ "room=%d, margin=%d, min_legal=%d, caps=%s",
500
+ estimated_input,
501
+ strategy.max_context_tokens,
502
+ room,
503
+ margin,
504
+ min_legal,
505
+ ", ".join(f"{key}:{old}->{new}" for key, old, new in clamped),
506
+ )
507
+ return copied
508
+
509
+
510
+ class _PreparedRequestObserverClient:
511
+ """Marker for clients that consume a post-prepare request observer.
512
+
513
+ ``ChatMiddlewareLayer`` keeps its own final-handler observation for every
514
+ inner client. Marked clients additionally receive the observer so request
515
+ preparation below that layer (notably compaction summary insertion) can
516
+ generate fresh wrapper views and register their contents immediately before
517
+ the provider call. The marker prevents the chrys-only callback from
518
+ leaking into arbitrary custom-client kwargs.
519
+ """
520
+
521
+
522
+ def _is_chat_response_stream(
523
+ value: object,
524
+ ) -> TypeIs[ResponseStream[ChatResponseUpdate, ChatResponse[Any]]]:
525
+ """Narrow the response-stream branch of the client result contract."""
526
+ return isinstance(value, ResponseStream)
527
+
528
+
529
+ def _is_message_list(messages: Sequence[Message]) -> TypeGuard[list[Message]]:
530
+ """Narrow a mutable caller-owned message sequence without copying it."""
531
+ return isinstance(messages, list)
532
+
533
+
534
+ def _wire_message_view(message: Message) -> Message:
535
+ """Per-call wrapper view of an outgoing conversation message.
536
+
537
+ Fresh wrapper + fresh contents list, so a client that mutates a received
538
+ message in place (appending contents to a history message) can rewrite
539
+ neither landed history nor caller session-state objects — the structural
540
+ corruption vectors (duplicate ordinals, duplicate objects, corrupted
541
+ stored history). ``additional_properties`` stays THE wrapper's dict on
542
+ purpose: message-level metadata is the stack's sanctioned write-through
543
+ channel (compaction exclusion flags land through it), and metadata writes
544
+ cannot violate the transcript's structural invariants. Content objects
545
+ stay shared like everywhere else.
546
+
547
+ The single view builder for both outgoing directions: the tool loop's
548
+ wire views of conversation messages and the final provider-request views
549
+ built below.
550
+ """
551
+ view = copy(message)
552
+ view.contents = list(message.contents)
553
+ return view
554
+
555
+
556
+ def _prepare_provider_request_messages(
557
+ messages: Sequence[Message],
558
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
559
+ ) -> list[Message]:
560
+ """Build final provider views and observe the exact outgoing contents."""
561
+ wire_messages = [_wire_message_view(message) for message in messages]
562
+ if request_message_observer is not None:
563
+ request_message_observer(wire_messages)
564
+ return wire_messages
565
+
566
+
567
+ class _ClientLastWordsCompleter:
568
+ """``LastWordsCompleter`` bound to one ``get_response`` invocation.
569
+
570
+ Scoped LAST_WORDS side call: sends the strategy-chosen slice plus one
571
+ appended user instruction through the same client, stream mode,
572
+ options, tool definitions, and client kwargs as the live call.
573
+ ``tool_choice`` is forced to ``"none"`` whenever tools ride along:
574
+ instruction-only suppression proved insufficient (models still attempted
575
+ typed tool calls), and the prefix-cache argument for inheriting the live
576
+ value died with the physically scoped slice — the side call's message
577
+ prefix already diverges from the live call, so the options delta forfeits
578
+ nothing. The no-tools instruction text (composed by the strategy layer)
579
+ stays because ``tool_choice`` cannot stop tool-call markup emitted as
580
+ plain text, with the response guard below as the hard backstop. Goes through
581
+ ``_inner_get_response`` — not ``get_response`` — so the side call never
582
+ re-enters compaction. Returns only the note text; the side-call response
583
+ object never escapes.
584
+ """
585
+
586
+ def __init__(
587
+ self,
588
+ client: BaseChatClient,
589
+ *,
590
+ stream: bool,
591
+ options: Mapping[str, Any],
592
+ client_kwargs: Mapping[str, Any],
593
+ kwargs_cap_ceiling: int | None = None,
594
+ ) -> None:
595
+ self._client = client
596
+ self._stream = stream
597
+ self._options = options
598
+ self._client_kwargs = client_kwargs
599
+ self._kwargs_cap_ceiling = kwargs_cap_ceiling
600
+
601
+ async def complete_last_words(
602
+ self,
603
+ base_messages: Sequence[Message],
604
+ instruction: str,
605
+ *,
606
+ max_output_tokens: int,
607
+ on_usage: Callable[[Mapping[str, Any]], None] | None = None,
608
+ ) -> str:
609
+ from .compaction import (
610
+ LastWordsToolCallError,
611
+ internal_side_call_scope,
612
+ messages_contain_tool_calls,
613
+ raise_if_context_window_filled,
614
+ )
615
+
616
+ # Shallow copy with top-level-key overrides only: nested values (the
617
+ # tools list in particular) stay shared by reference and are never
618
+ # mutated; the live call's options dict is never touched.
619
+ side_options: dict[str, Any] = dict(self._options)
620
+ side_options["max_tokens"] = (
621
+ min(max_output_tokens, self._kwargs_cap_ceiling)
622
+ if self._kwargs_cap_ceiling is not None
623
+ else max_output_tokens
624
+ )
625
+ # Hard-disable tool use for the summarization side call — see the
626
+ # class docstring. Only when tools are actually present: "none"
627
+ # without tools is meaningless and providers differ on rejecting the
628
+ # combination, so a tool-less side call keeps the live value (or its
629
+ # absence) untouched.
630
+ if side_options.get("tools"):
631
+ side_options["tool_choice"] = "none"
632
+ # The throwaway summarization response must never become a chainable
633
+ # node in service-side storage (OpenAI Responses store mode). Only
634
+ # providers that store by default (or were asked to) get the key —
635
+ # other providers would reject an unknown request parameter.
636
+ if "store" in side_options or type(self._client).STORES_BY_DEFAULT:
637
+ side_options["store"] = False
638
+ for key in (*_SIDE_CALL_CONTINUATION_KEYS, *_SIDE_CALL_OUTPUT_SHAPING_KEYS, *_SIDE_CALL_EXECUTION_MODE_KEYS):
639
+ side_options.pop(key, None)
640
+ _scrub_side_call_extra_body(side_options)
641
+
642
+ side_messages = _prepare_provider_request_messages([*base_messages, Message("user", [instruction])])
643
+ # The scope keeps response instrumentation layered onto
644
+ # ``_inner_get_response`` (intermediate-text publication in
645
+ # particular) silent: even if the model ignores the no-tools
646
+ # instruction and the guard below rejects the response, nothing
647
+ # about it may be published or persisted. It covers the stream
648
+ # drain too — stream hooks fire during ``get_final_response()``.
649
+ forwarded_kwargs = {
650
+ key: value
651
+ for key, value in self._client_kwargs.items()
652
+ if key
653
+ not in (
654
+ *_SIDE_CALL_CONTINUATION_KEYS,
655
+ *_SIDE_CALL_OUTPUT_SHAPING_KEYS,
656
+ *_SIDE_CALL_EXECUTION_MODE_KEYS,
657
+ "store",
658
+ )
659
+ }
660
+ _scrub_side_call_extra_body(forwarded_kwargs)
661
+ # A streamed note arrives chunk by chunk: every chunk (and each
662
+ # attempt's dispatch) restarts the live call's stall watchdog, which
663
+ # still cancels a side call that goes idle.
664
+ report_wire_progress()
665
+ try:
666
+ with internal_side_call_scope(), side_call_scope(ActorRole.COMPLETER):
667
+ result = self._client._inner_get_response(
668
+ messages=side_messages,
669
+ stream=self._stream,
670
+ options=side_options,
671
+ **forwarded_kwargs,
672
+ )
673
+ response: ChatResponse[Any]
674
+ if _is_chat_response_stream(result):
675
+ response = await result.with_transform_hook(_report_side_call_update).get_final_response()
676
+ else:
677
+ awaited = await result
678
+ response = (
679
+ await awaited.with_transform_hook(_report_side_call_update).get_final_response()
680
+ if isinstance(awaited, ResponseStream)
681
+ else awaited
682
+ )
683
+ except ProviderResponseError as err:
684
+ # A response the adapter failed consumed provider tokens too.
685
+ if on_usage is not None and err.usage_details:
686
+ on_usage(err.usage_details)
687
+ raise
688
+ report_wire_progress()
689
+ # Report spend before any acceptance decision: a response the guard
690
+ # rejects below still consumed real provider tokens.
691
+ if on_usage is not None and response.usage_details:
692
+ on_usage(response.usage_details)
693
+ raise_if_context_window_filled(response)
694
+ if messages_contain_tool_calls(response.messages):
695
+ raise LastWordsToolCallError(
696
+ "last-words side call returned tool-call content despite the no-tools instruction"
697
+ )
698
+ # Verbatim, not ``.text``: outer whitespace (leading indentation in
699
+ # particular) is meaningful to the structured-note validator.
700
+ return response.raw_text
701
+
702
+
703
+ class SupportsAclose(Protocol):
704
+ """A chat client stack's one close entry; each layer forwards it inward."""
705
+
706
+ async def aclose(self) -> None: ...
707
+
708
+
709
+ class BaseChatClient(SerializationMixin, _PreparedRequestObserverClient, ABC):
710
+ """Abstract base class for loop-free chat clients."""
711
+
712
+ OTEL_PROVIDER_NAME: ClassVar[str] = "unknown"
713
+ DEFAULT_EXCLUDE: ClassVar[set[str]] = {
714
+ "additional_properties",
715
+ "compaction_strategy",
716
+ "tokenizer",
717
+ }
718
+ STORES_BY_DEFAULT: ClassVar[bool] = False
719
+ # Provider dialects that cannot persist or resume any request state set
720
+ # this independently from the ordinary storage default.
721
+ FORCES_STATELESS: ClassVar[bool] = False
722
+ MIN_OUTPUT_CAP_TOKENS: ClassVar[int] = 1
723
+
724
+ def __init__(
725
+ self,
726
+ *,
727
+ compaction_strategy: Any = None,
728
+ tokenizer: TokenizerProtocol | None = None,
729
+ additional_properties: dict[str, Any] | None = None,
730
+ ) -> None:
731
+ self.additional_properties = additional_properties or {}
732
+ self.compaction_strategy = compaction_strategy
733
+ self.tokenizer = tokenizer
734
+ super().__init__()
735
+
736
+ async def aclose(self) -> None:
737
+ """Release provider resources; clients that own none keep this no-op."""
738
+
739
+ def to_dict(self, *, exclude: set[str] | None = None, exclude_none: bool = True) -> dict[str, Any]:
740
+ """Serialize the client, lifting additional properties to the root."""
741
+ result = super().to_dict(exclude=exclude, exclude_none=exclude_none)
742
+ if self.additional_properties:
743
+ result.update(self.additional_properties)
744
+ return result
745
+
746
+ async def _validate_options(self, options: Mapping[str, Any]) -> dict[str, Any]:
747
+ """Validate and normalize chat options."""
748
+ return await validate_chat_options(dict(options))
749
+
750
+ def _finalize_response_updates(
751
+ self,
752
+ updates: Sequence[ChatResponseUpdate],
753
+ *,
754
+ response_format: Any | None = None,
755
+ ) -> ChatResponse[Any]:
756
+ """Finalize streamed updates into one chat response."""
757
+ return ChatResponse.from_updates(updates, output_format_type=response_format)
758
+
759
+ def _build_response_stream(
760
+ self,
761
+ stream: AsyncIterable[ChatResponseUpdate] | Awaitable[AsyncIterable[ChatResponseUpdate]],
762
+ *,
763
+ response_format: Any | None = None,
764
+ ) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
765
+ """Create a response stream with the standard finalizer."""
766
+ return ResponseStream(
767
+ stream,
768
+ finalizer=lambda updates: self._finalize_response_updates(updates, response_format=response_format),
769
+ )
770
+
771
+ async def _prepare_messages_for_model_call(
772
+ self,
773
+ messages: Sequence[Message],
774
+ *,
775
+ compaction_strategy: Any = None,
776
+ tokenizer: TokenizerProtocol | None = None,
777
+ context: CompactionCallContext | None = None,
778
+ ) -> list[Message]:
779
+ """Apply annotation/compaction before a provider model call."""
780
+ prepared_messages = list(messages)
781
+ if compaction_strategy is None:
782
+ if tokenizer is None:
783
+ return prepared_messages
784
+ from .compaction import annotate_message_groups
785
+
786
+ annotate_message_groups(prepared_messages, tokenizer=tokenizer)
787
+ return prepared_messages
788
+
789
+ from .compaction import apply_compaction
790
+
791
+ if _is_message_list(messages):
792
+ working_messages: list[Message] = messages
793
+ else:
794
+ working_messages = prepared_messages
795
+ return await apply_compaction(
796
+ working_messages,
797
+ strategy=compaction_strategy,
798
+ tokenizer=tokenizer,
799
+ context=context,
800
+ )
801
+
802
+ def _resolve_compaction_overrides(
803
+ self,
804
+ *,
805
+ compaction_strategy: Any = None,
806
+ tokenizer: TokenizerProtocol | None = None,
807
+ ) -> dict[str, Any]:
808
+ current_compaction_strategy = self.compaction_strategy
809
+ current_tokenizer = self.tokenizer
810
+ ret: dict[str, Any] = {}
811
+ if current_compaction_strategy is not None or compaction_strategy is not None:
812
+ ret["compaction_strategy"] = (
813
+ current_compaction_strategy if compaction_strategy is None else compaction_strategy
814
+ )
815
+ if current_tokenizer is not None or tokenizer is not None:
816
+ ret["tokenizer"] = current_tokenizer if tokenizer is None else tokenizer
817
+ return ret
818
+
819
+ def _build_compaction_call_context(
820
+ self,
821
+ *,
822
+ stream: bool,
823
+ options: Mapping[str, Any] | None,
824
+ client_kwargs: Mapping[str, Any],
825
+ tokenizer: TokenizerProtocol | None = None,
826
+ kwargs_cap_ceiling: int | None = None,
827
+ ) -> CompactionCallContext:
828
+ """Build the per-call context offered to the compaction strategy.
829
+
830
+ Single construction point for the LAST_WORDS completer — every stack
831
+ that routes compaction through ``_prepare_messages_for_model_call``
832
+ (``get_response`` here, which ``MockChatClient`` also calls beneath its
833
+ tool loop) must build the context through this method so gating stays
834
+ uniform.
835
+
836
+ Side-call behavior under OpenAI Responses server-side storage still
837
+ needs live smoke validation. Until then, store-mode profiles get no
838
+ completer and Phase 4 keeps the validated reconstruction fallback.
839
+ """
840
+ from .compaction import CompactionCallContext
841
+
842
+ effective_options = options or {}
843
+ storage = resolve_storage_mode_and_handles(
844
+ effective_options,
845
+ stores_by_default=type(self).STORES_BY_DEFAULT,
846
+ client_kwargs=client_kwargs,
847
+ force_stateless=type(self).FORCES_STATELESS,
848
+ )
849
+ forced_tool_choice = _tool_choice_forces_tool_use(effective_options.get("tool_choice"))
850
+ completer = (
851
+ None
852
+ if storage.service_side or forced_tool_choice
853
+ else _ClientLastWordsCompleter(
854
+ self,
855
+ stream=stream,
856
+ options=effective_options,
857
+ client_kwargs=client_kwargs,
858
+ kwargs_cap_ceiling=kwargs_cap_ceiling,
859
+ )
860
+ )
861
+ effective_tokenizer = tokenizer or self.tokenizer
862
+ tool_definition_tokens = _estimate_tool_definition_tokens(effective_options, effective_tokenizer)
863
+ exchange_trace = client_kwargs.get(TRAJECTORY_EXCHANGE_KWARG)
864
+ return CompactionCallContext(
865
+ last_words_completer=completer,
866
+ tool_definition_tokens=tool_definition_tokens,
867
+ request_overhead_tokens=_estimate_fixed_request_overhead_tokens(
868
+ effective_options,
869
+ client_kwargs,
870
+ effective_tokenizer,
871
+ tool_definition_tokens=tool_definition_tokens,
872
+ ),
873
+ next_exchange_operation_id=(
874
+ exchange_trace.operation_id if isinstance(exchange_trace, ExchangeTrace) else None
875
+ ),
876
+ )
877
+
878
+ def _normalize_wire_inputs(
879
+ self,
880
+ options: Mapping[str, Any] | None,
881
+ client_kwargs: Mapping[str, Any],
882
+ ) -> tuple[Any, dict[str, Any], int | None]:
883
+ """Snapshot admission inputs and fold kwargs output caps into options."""
884
+ if options is None:
885
+ options_snapshot: Any = {}
886
+ elif isinstance(options, Mapping):
887
+ options_snapshot = dict(options)
888
+ tools = options_snapshot.get("tools")
889
+ if isinstance(tools, list | tuple):
890
+ options_snapshot["tools"] = list(tools)
891
+ elif isinstance(tools, Mapping):
892
+ options_snapshot["tools"] = dict(tools)
893
+ thinking = options_snapshot.get("thinking")
894
+ if isinstance(thinking, Mapping):
895
+ options_snapshot["thinking"] = dict(thinking)
896
+ else:
897
+ return options, dict(client_kwargs), None
898
+
899
+ sanitized_client_kwargs = dict(client_kwargs)
900
+ kwargs_caps: dict[str, Any] = {}
901
+ for alias in OUTPUT_CAP_OPTION_ALIASES:
902
+ if alias in sanitized_client_kwargs:
903
+ kwargs_caps[alias] = sanitized_client_kwargs.pop(alias)
904
+ if kwargs_caps:
905
+ # A kwargs cap must keep overriding an options cap even when the
906
+ # spellings differ: provider serializers resolve alias conflicts
907
+ # toward canonical ``max_tokens``, so leaving the options spelling
908
+ # in place would invert the historical kwargs-wins precedence.
909
+ for alias in OUTPUT_CAP_OPTION_ALIASES:
910
+ options_snapshot.pop(alias, None)
911
+ options_snapshot.update(kwargs_caps)
912
+ kwargs_cap_ceiling = next(
913
+ (
914
+ value
915
+ for alias in OUTPUT_CAP_OPTION_ALIASES
916
+ if isinstance((value := kwargs_caps.get(alias)), int) and not isinstance(value, bool) and value > 0
917
+ ),
918
+ None,
919
+ )
920
+ return options_snapshot, sanitized_client_kwargs, kwargs_cap_ceiling
921
+
922
+ async def _prepare_wire_call(
923
+ self,
924
+ messages: Sequence[Message],
925
+ *,
926
+ call_context: CompactionCallContext,
927
+ options_snapshot: Mapping[str, Any],
928
+ sanitized_client_kwargs: Mapping[str, Any],
929
+ compaction_overrides: Mapping[str, Any],
930
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
931
+ ) -> tuple[list[Message], dict[str, Any]]:
932
+ """Apply compaction and context admission for one wire attempt."""
933
+ prepared = await self._prepare_messages_for_model_call(
934
+ messages,
935
+ context=call_context,
936
+ **compaction_overrides,
937
+ )
938
+ wire_messages = _prepare_provider_request_messages(prepared, request_message_observer)
939
+ wire_options = _clamp_output_cap_for_context(
940
+ options_snapshot,
941
+ strategy=compaction_overrides.get("compaction_strategy"),
942
+ request_overhead_tokens=call_context.request_overhead_tokens,
943
+ client_kwargs=sanitized_client_kwargs,
944
+ provider_min_output_cap_tokens=type(self).MIN_OUTPUT_CAP_TOKENS,
945
+ )
946
+ # The provider's time to first byte starts now, not when compaction did.
947
+ report_wire_progress()
948
+ return wire_messages, wire_options
949
+
950
+ @abstractmethod
951
+ def _inner_get_response(
952
+ self,
953
+ *,
954
+ messages: Sequence[Message],
955
+ stream: bool,
956
+ options: Mapping[str, Any],
957
+ **kwargs: Any,
958
+ ) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
959
+ """Send prepared messages to the model service."""
960
+
961
+ @overload
962
+ def get_response(
963
+ self,
964
+ messages: Sequence[Message],
965
+ *,
966
+ stream: Literal[False] = ...,
967
+ options: Mapping[str, Any] | None = None,
968
+ compaction_strategy: Any = None,
969
+ tokenizer: TokenizerProtocol | None = None,
970
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
971
+ client_kwargs: Mapping[str, Any] | None = None,
972
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
973
+ ) -> Awaitable[ChatResponse[Any]]: ...
974
+
975
+ @overload
976
+ def get_response(
977
+ self,
978
+ messages: Sequence[Message],
979
+ *,
980
+ stream: Literal[True],
981
+ options: Mapping[str, Any] | None = None,
982
+ compaction_strategy: Any = None,
983
+ tokenizer: TokenizerProtocol | None = None,
984
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
985
+ client_kwargs: Mapping[str, Any] | None = None,
986
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
987
+ ) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
988
+
989
+ def get_response(
990
+ self,
991
+ messages: Sequence[Message],
992
+ *,
993
+ stream: bool = False,
994
+ options: Mapping[str, Any] | None = None,
995
+ compaction_strategy: Any = None,
996
+ tokenizer: TokenizerProtocol | None = None,
997
+ function_invocation_kwargs: Mapping[str, Any] | None = None,
998
+ client_kwargs: Mapping[str, Any] | None = None,
999
+ request_message_observer: Callable[[Sequence[Message]], None] | None = None,
1000
+ ) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
1001
+ """Get a non-streaming or streaming chat response."""
1002
+ del function_invocation_kwargs
1003
+ compaction_overrides = self._resolve_compaction_overrides(
1004
+ compaction_strategy=compaction_strategy,
1005
+ tokenizer=tokenizer,
1006
+ )
1007
+ merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
1008
+ options_snapshot, sanitized_client_kwargs, kwargs_cap_ceiling = self._normalize_wire_inputs(
1009
+ options,
1010
+ merged_client_kwargs,
1011
+ )
1012
+
1013
+ # Non-Mapping options bypass compaction too: they cannot be admitted
1014
+ # or clamped, and ``options or {}`` in the context build would
1015
+ # silently swallow falsey invalid values instead of letting the
1016
+ # provider reject them like every other path does.
1017
+ if not compaction_overrides or not isinstance(options_snapshot, Mapping):
1018
+ wire_messages = _prepare_provider_request_messages(messages, request_message_observer)
1019
+ return self._inner_get_response(
1020
+ messages=wire_messages,
1021
+ stream=stream,
1022
+ options=cast("Mapping[str, Any]", options_snapshot),
1023
+ **sanitized_client_kwargs,
1024
+ )
1025
+
1026
+ call_context = self._build_compaction_call_context(
1027
+ stream=stream,
1028
+ options=cast("Mapping[str, Any]", options_snapshot),
1029
+ client_kwargs=sanitized_client_kwargs,
1030
+ tokenizer=compaction_overrides.get("tokenizer"),
1031
+ kwargs_cap_ceiling=kwargs_cap_ceiling,
1032
+ )
1033
+
1034
+ if stream:
1035
+
1036
+ async def _get_stream() -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
1037
+ prepared_messages, wire_options = await self._prepare_wire_call(
1038
+ messages,
1039
+ call_context=call_context,
1040
+ options_snapshot=cast("Mapping[str, Any]", options_snapshot),
1041
+ sanitized_client_kwargs=sanitized_client_kwargs,
1042
+ compaction_overrides=compaction_overrides,
1043
+ request_message_observer=request_message_observer,
1044
+ )
1045
+ stream_response = self._inner_get_response(
1046
+ messages=prepared_messages,
1047
+ stream=True,
1048
+ options=wire_options,
1049
+ **sanitized_client_kwargs,
1050
+ )
1051
+ if _is_chat_response_stream(stream_response):
1052
+ return stream_response
1053
+ awaited_stream_response = await stream_response
1054
+ if _is_chat_response_stream(awaited_stream_response):
1055
+ return awaited_stream_response
1056
+ raise ValueError("Streaming responses must return a ResponseStream.")
1057
+
1058
+ return ResponseStream.from_awaitable(_get_stream())
1059
+
1060
+ async def _get_response() -> ChatResponse[Any]:
1061
+ prepared_messages, wire_options = await self._prepare_wire_call(
1062
+ messages,
1063
+ call_context=call_context,
1064
+ options_snapshot=cast("Mapping[str, Any]", options_snapshot),
1065
+ sanitized_client_kwargs=sanitized_client_kwargs,
1066
+ compaction_overrides=compaction_overrides,
1067
+ request_message_observer=request_message_observer,
1068
+ )
1069
+ response = cast(
1070
+ "Awaitable[ChatResponse[Any]]",
1071
+ self._inner_get_response(
1072
+ messages=prepared_messages,
1073
+ stream=False,
1074
+ options=wire_options,
1075
+ **sanitized_client_kwargs,
1076
+ ),
1077
+ )
1078
+ return await response
1079
+
1080
+ return _get_response()
1081
+
1082
+ def service_url(self) -> str:
1083
+ """Return the provider service URL when a subclass exposes one."""
1084
+ return "Unknown"