iCode-TUI 0.29.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1138) hide show
  1. chrys/__init__.py +16 -0
  2. chrys/app/__init__.py +3 -0
  3. chrys/app/acp/__init__.py +3 -0
  4. chrys/app/acp/bridge.py +664 -0
  5. chrys/app/acp/content.py +132 -0
  6. chrys/app/acp/doc/frontend-api.md +425 -0
  7. chrys/app/acp/history.py +585 -0
  8. chrys/app/acp/server.py +1796 -0
  9. chrys/app/acp/session_manager.py +1815 -0
  10. chrys/app/acp/tool_status.py +45 -0
  11. chrys/app/cli/__init__.py +3 -0
  12. chrys/app/cli/acp.py +178 -0
  13. chrys/app/cli/app.py +165 -0
  14. chrys/app/cli/approvals.py +101 -0
  15. chrys/app/cli/headless.py +223 -0
  16. chrys/app/cli/launch_cwd.py +42 -0
  17. chrys/app/cli/profiles.py +298 -0
  18. chrys/app/cli/progress.py +857 -0
  19. chrys/app/cli/run.py +425 -0
  20. chrys/app/cli/serve.py +1593 -0
  21. chrys/app/cli/tool_summary.py +225 -0
  22. chrys/app/cli/trajectory.py +201 -0
  23. chrys/app/cli/workflow.py +601 -0
  24. chrys/app/features/__init__.py +3 -0
  25. chrys/app/features/buddy/__init__.py +3 -0
  26. chrys/app/features/buddy/actions.py +68 -0
  27. chrys/app/features/buddy/animation.py +76 -0
  28. chrys/app/features/buddy/commands.py +196 -0
  29. chrys/app/features/buddy/docs/design.md +187 -0
  30. chrys/app/features/buddy/hatchery.py +80 -0
  31. chrys/app/features/buddy/lifecycle.py +41 -0
  32. chrys/app/features/buddy/model.py +265 -0
  33. chrys/app/features/buddy/pixel_renderer.py +113 -0
  34. chrys/app/features/buddy/pixel_sprites.py +279 -0
  35. chrys/app/features/buddy/portrait.py +76 -0
  36. chrys/app/features/buddy/progression.py +58 -0
  37. chrys/app/features/buddy/replies.py +191 -0
  38. chrys/app/features/buddy/sprites/alien.toml +70 -0
  39. chrys/app/features/buddy/sprites/axolotl.toml +71 -0
  40. chrys/app/features/buddy/sprites/bat.toml +69 -0
  41. chrys/app/features/buddy/sprites/bee.toml +69 -0
  42. chrys/app/features/buddy/sprites/cactus.toml +73 -0
  43. chrys/app/features/buddy/sprites/capybara.toml +70 -0
  44. chrys/app/features/buddy/sprites/cat.toml +69 -0
  45. chrys/app/features/buddy/sprites/crab.toml +70 -0
  46. chrys/app/features/buddy/sprites/dragon.toml +71 -0
  47. chrys/app/features/buddy/sprites/duck.toml +69 -0
  48. chrys/app/features/buddy/sprites/fox.toml +69 -0
  49. chrys/app/features/buddy/sprites/frog.toml +70 -0
  50. chrys/app/features/buddy/sprites/ghost.toml +70 -0
  51. chrys/app/features/buddy/sprites/goose.toml +69 -0
  52. chrys/app/features/buddy/sprites/jelly.toml +70 -0
  53. chrys/app/features/buddy/sprites/llama.toml +70 -0
  54. chrys/app/features/buddy/sprites/mushroom.toml +69 -0
  55. chrys/app/features/buddy/sprites/octopus.toml +70 -0
  56. chrys/app/features/buddy/sprites/owl.toml +70 -0
  57. chrys/app/features/buddy/sprites/panda.toml +71 -0
  58. chrys/app/features/buddy/sprites/penguin.toml +69 -0
  59. chrys/app/features/buddy/sprites/rabbit.toml +70 -0
  60. chrys/app/features/buddy/sprites/robot.toml +72 -0
  61. chrys/app/features/buddy/sprites/shark.toml +70 -0
  62. chrys/app/features/buddy/sprites/snail.toml +69 -0
  63. chrys/app/features/buddy/sprites/snake.toml +69 -0
  64. chrys/app/features/buddy/sprites/tabby.toml +69 -0
  65. chrys/app/features/buddy/sprites/turtle.toml +69 -0
  66. chrys/app/features/buddy/store.py +143 -0
  67. chrys/app/features/session_title/__init__.py +7 -0
  68. chrys/app/features/session_title/updater.py +582 -0
  69. chrys/app/installer.py +651 -0
  70. chrys/app/parsing.py +17 -0
  71. chrys/app/tui/__init__.py +3 -0
  72. chrys/app/tui/app.py +1287 -0
  73. chrys/app/tui/behaviors/__init__.py +3 -0
  74. chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
  75. chrys/app/tui/behaviors/insert_clipboard.py +73 -0
  76. chrys/app/tui/behaviors/right_click_copy.py +146 -0
  77. chrys/app/tui/binding_display.py +139 -0
  78. chrys/app/tui/buddy_messages.py +17 -0
  79. chrys/app/tui/buddy_reply.py +86 -0
  80. chrys/app/tui/chrys.tcss +549 -0
  81. chrys/app/tui/clipboard.py +109 -0
  82. chrys/app/tui/copy_messages.py +10 -0
  83. chrys/app/tui/i18n.py +230 -0
  84. chrys/app/tui/language.py +58 -0
  85. chrys/app/tui/notifications/__init__.py +12 -0
  86. chrys/app/tui/notifications/drivers.py +180 -0
  87. chrys/app/tui/notifications/service.py +126 -0
  88. chrys/app/tui/notifications/settings.py +141 -0
  89. chrys/app/tui/screens/__init__.py +3 -0
  90. chrys/app/tui/screens/agents/__init__.py +8 -0
  91. chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
  92. chrys/app/tui/screens/agents/config.py +2142 -0
  93. chrys/app/tui/screens/agents/config.tcss +169 -0
  94. chrys/app/tui/screens/agents/panels/__init__.py +3 -0
  95. chrys/app/tui/screens/agents/panels/acp.py +1176 -0
  96. chrys/app/tui/screens/agents/panels/basic.py +497 -0
  97. chrys/app/tui/screens/agents/panels/compaction.py +231 -0
  98. chrys/app/tui/screens/agents/panels/config_card.py +218 -0
  99. chrys/app/tui/screens/agents/panels/instructions.py +91 -0
  100. chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
  101. chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
  102. chrys/app/tui/screens/agents/panels/memory.py +684 -0
  103. chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
  104. chrys/app/tui/screens/agents/panels/skills.py +806 -0
  105. chrys/app/tui/screens/agents/panels/subagents.py +652 -0
  106. chrys/app/tui/screens/agents/panels/tools.py +205 -0
  107. chrys/app/tui/screens/agents/picker.py +103 -0
  108. chrys/app/tui/screens/agents/skill_paths.py +38 -0
  109. chrys/app/tui/screens/agents/validation_messages.py +452 -0
  110. chrys/app/tui/screens/dialogs/__init__.py +35 -0
  111. chrys/app/tui/screens/dialogs/agent_load.py +834 -0
  112. chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
  113. chrys/app/tui/screens/dialogs/app_mode.py +55 -0
  114. chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
  115. chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
  116. chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
  117. chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
  118. chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
  119. chrys/app/tui/screens/dialogs/approval/body.py +110 -0
  120. chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
  121. chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
  122. chrys/app/tui/screens/dialogs/ask_user.py +148 -0
  123. chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
  124. chrys/app/tui/screens/dialogs/base.py +84 -0
  125. chrys/app/tui/screens/dialogs/confirm.py +184 -0
  126. chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
  127. chrys/app/tui/screens/dialogs/connection_test.py +183 -0
  128. chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
  129. chrys/app/tui/screens/dialogs/diagram.py +102 -0
  130. chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
  131. chrys/app/tui/screens/dialogs/editor.py +378 -0
  132. chrys/app/tui/screens/dialogs/editor.tcss +132 -0
  133. chrys/app/tui/screens/dialogs/file_picker.py +582 -0
  134. chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
  135. chrys/app/tui/screens/dialogs/fork_session.py +219 -0
  136. chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
  137. chrys/app/tui/screens/dialogs/image_compression.py +102 -0
  138. chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
  139. chrys/app/tui/screens/dialogs/man_page.py +172 -0
  140. chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
  141. chrys/app/tui/screens/dialogs/new_folder.py +277 -0
  142. chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
  143. chrys/app/tui/screens/dialogs/notice.tcss +14 -0
  144. chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
  145. chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
  146. chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
  147. chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
  148. chrys/app/tui/screens/dialogs/session_title.py +110 -0
  149. chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
  150. chrys/app/tui/screens/dialogs/tool_view.py +207 -0
  151. chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
  152. chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
  153. chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
  154. chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
  155. chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
  156. chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
  157. chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
  158. chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
  159. chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
  160. chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
  161. chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
  162. chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
  163. chrys/app/tui/screens/diff/__init__.py +22 -0
  164. chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
  165. chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
  166. chrys/app/tui/screens/diff/screen.py +1532 -0
  167. chrys/app/tui/screens/diff/screen.tcss +104 -0
  168. chrys/app/tui/screens/guides/__init__.py +7 -0
  169. chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
  170. chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
  171. chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
  172. chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
  173. chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
  174. chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
  175. chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
  176. chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
  177. chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
  178. chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
  179. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
  180. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
  181. chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
  182. chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
  183. chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
  184. chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
  185. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
  186. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
  187. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
  188. chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
  189. chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
  190. chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
  191. chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
  192. chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
  193. chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
  194. chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
  195. chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
  196. chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
  197. chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
  198. chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
  199. chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
  200. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
  201. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
  202. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
  203. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
  204. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
  205. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
  206. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
  207. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
  208. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
  209. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
  210. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
  211. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
  212. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
  213. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
  214. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
  215. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
  216. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
  217. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
  218. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
  219. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
  220. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
  221. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
  222. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
  223. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
  224. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
  225. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
  226. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
  227. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
  228. chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
  229. chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
  230. chrys/app/tui/screens/guides/index.py +215 -0
  231. chrys/app/tui/screens/guides/reader.py +173 -0
  232. chrys/app/tui/screens/guides/screen.py +442 -0
  233. chrys/app/tui/screens/guides/screen.tcss +52 -0
  234. chrys/app/tui/screens/logs/__init__.py +7 -0
  235. chrys/app/tui/screens/logs/screen.py +504 -0
  236. chrys/app/tui/screens/logs/screen.tcss +45 -0
  237. chrys/app/tui/screens/main/__init__.py +7 -0
  238. chrys/app/tui/screens/main/action_availability.py +30 -0
  239. chrys/app/tui/screens/main/buddy_command.py +143 -0
  240. chrys/app/tui/screens/main/commands.py +1073 -0
  241. chrys/app/tui/screens/main/config_actions.py +464 -0
  242. chrys/app/tui/screens/main/copy_actions.py +117 -0
  243. chrys/app/tui/screens/main/dialog_controllers.py +826 -0
  244. chrys/app/tui/screens/main/dialog_gateway.py +166 -0
  245. chrys/app/tui/screens/main/diff_controller.py +478 -0
  246. chrys/app/tui/screens/main/engine_read_model.py +184 -0
  247. chrys/app/tui/screens/main/event_handlers.py +2014 -0
  248. chrys/app/tui/screens/main/input_flow.py +241 -0
  249. chrys/app/tui/screens/main/live_diff.py +244 -0
  250. chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
  251. chrys/app/tui/screens/main/model_indicator.py +217 -0
  252. chrys/app/tui/screens/main/navigation.py +343 -0
  253. chrys/app/tui/screens/main/ports.py +571 -0
  254. chrys/app/tui/screens/main/recent_dirs.py +123 -0
  255. chrys/app/tui/screens/main/rollback_controller.py +586 -0
  256. chrys/app/tui/screens/main/runtime_info.py +71 -0
  257. chrys/app/tui/screens/main/screen.py +2265 -0
  258. chrys/app/tui/screens/main/screen.tcss +21 -0
  259. chrys/app/tui/screens/main/session_handlers.py +1127 -0
  260. chrys/app/tui/screens/main/session_title.py +239 -0
  261. chrys/app/tui/screens/main/settings_coordinator.py +365 -0
  262. chrys/app/tui/screens/main/settings_persistence.py +184 -0
  263. chrys/app/tui/screens/main/shell_mode.py +112 -0
  264. chrys/app/tui/screens/main/state.py +289 -0
  265. chrys/app/tui/screens/main/submit_state.py +11 -0
  266. chrys/app/tui/screens/main/subscriptions.py +177 -0
  267. chrys/app/tui/screens/main/suggestions.py +982 -0
  268. chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
  269. chrys/app/tui/screens/main/view_adapter.py +1567 -0
  270. chrys/app/tui/screens/main/workflow_browser.py +747 -0
  271. chrys/app/tui/screens/main/workflow_content.py +248 -0
  272. chrys/app/tui/screens/main/workflow_controller.py +240 -0
  273. chrys/app/tui/screens/main/workflow_feedback.py +137 -0
  274. chrys/app/tui/screens/main/workflow_flow.py +49 -0
  275. chrys/app/tui/screens/main/workflow_run_control.py +456 -0
  276. chrys/app/tui/screens/main/workflow_session_view.py +316 -0
  277. chrys/app/tui/screens/main/workspace_actions.py +272 -0
  278. chrys/app/tui/screens/main/workspace_branch.py +194 -0
  279. chrys/app/tui/screens/menus/__init__.py +8 -0
  280. chrys/app/tui/screens/menus/languages.py +114 -0
  281. chrys/app/tui/screens/models/__init__.py +7 -0
  282. chrys/app/tui/screens/models/picker.py +147 -0
  283. chrys/app/tui/screens/models/screen.py +2043 -0
  284. chrys/app/tui/screens/models/screen.tcss +255 -0
  285. chrys/app/tui/screens/sessions/__init__.py +8 -0
  286. chrys/app/tui/screens/sessions/presenter.py +586 -0
  287. chrys/app/tui/screens/sessions/screen.py +779 -0
  288. chrys/app/tui/screens/sessions/screen.tcss +127 -0
  289. chrys/app/tui/screens/settings/__init__.py +29 -0
  290. chrys/app/tui/screens/settings/danger.py +26 -0
  291. chrys/app/tui/screens/settings/dialog.py +281 -0
  292. chrys/app/tui/screens/settings/layout.py +519 -0
  293. chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
  294. chrys/app/tui/screens/settings/panes/__init__.py +3 -0
  295. chrys/app/tui/screens/settings/panes/notifications.py +248 -0
  296. chrys/app/tui/screens/settings/panes/sessions.py +148 -0
  297. chrys/app/tui/screens/settings/ports.py +55 -0
  298. chrys/app/tui/screens/settings/provenance.py +102 -0
  299. chrys/app/tui/screens/settings/rows.py +546 -0
  300. chrys/app/tui/screens/settings/settings.tcss +406 -0
  301. chrys/app/tui/screens/themes/__init__.py +8 -0
  302. chrys/app/tui/screens/themes/dialogs.py +330 -0
  303. chrys/app/tui/screens/themes/editor.py +632 -0
  304. chrys/app/tui/screens/themes/editor.tcss +268 -0
  305. chrys/app/tui/screens/themes/messages.py +95 -0
  306. chrys/app/tui/screens/themes/palette.py +694 -0
  307. chrys/app/tui/screens/themes/panel.py +113 -0
  308. chrys/app/tui/screens/themes/picker.py +203 -0
  309. chrys/app/tui/screens/themes/save.py +191 -0
  310. chrys/app/tui/support/__init__.py +3 -0
  311. chrys/app/tui/support/file_manager.py +64 -0
  312. chrys/app/tui/support/gc_freeze.py +1114 -0
  313. chrys/app/tui/support/path_shortcuts.py +50 -0
  314. chrys/app/tui/support/prompt_history.py +298 -0
  315. chrys/app/tui/support/workspace_mru.py +476 -0
  316. chrys/app/tui/terminal/__init__.py +6 -0
  317. chrys/app/tui/terminal/_pty_backend.py +114 -0
  318. chrys/app/tui/terminal/emulator/__init__.py +24 -0
  319. chrys/app/tui/terminal/emulator/buffer.py +456 -0
  320. chrys/app/tui/terminal/emulator/charsets.py +113 -0
  321. chrys/app/tui/terminal/emulator/core.py +963 -0
  322. chrys/app/tui/terminal/emulator/input.py +342 -0
  323. chrys/app/tui/terminal/emulator/parser.py +227 -0
  324. chrys/app/tui/terminal/emulator/pen.py +158 -0
  325. chrys/app/tui/terminal/launcher.py +455 -0
  326. chrys/app/tui/terminal/leaked_reports.py +44 -0
  327. chrys/app/tui/terminal/panel.py +323 -0
  328. chrys/app/tui/terminal/pty_process.py +405 -0
  329. chrys/app/tui/terminal/rendering.py +151 -0
  330. chrys/app/tui/terminal/shell.py +273 -0
  331. chrys/app/tui/terminal/shell_integration.py +199 -0
  332. chrys/app/tui/terminal/title.py +224 -0
  333. chrys/app/tui/terminal/widget.py +654 -0
  334. chrys/app/tui/theme.py +422 -0
  335. chrys/app/tui/theme_loader.py +201 -0
  336. chrys/app/tui/theme_stylesheet.py +49 -0
  337. chrys/app/tui/themes/__init__.py +3 -0
  338. chrys/app/tui/themes/document.py +120 -0
  339. chrys/app/tui/themes/preview.py +170 -0
  340. chrys/app/tui/themes/store.py +176 -0
  341. chrys/app/tui/toasts.py +69 -0
  342. chrys/app/tui/util/__init__.py +3 -0
  343. chrys/app/tui/util/context_pressure.py +146 -0
  344. chrys/app/tui/util/debug_log.py +292 -0
  345. chrys/app/tui/util/diff_entries.py +64 -0
  346. chrys/app/tui/util/formatting.py +53 -0
  347. chrys/app/tui/util/git_branch.py +326 -0
  348. chrys/app/tui/util/invocation_progress.py +57 -0
  349. chrys/app/tui/util/logo.py +32 -0
  350. chrys/app/tui/util/message_gate.py +35 -0
  351. chrys/app/tui/util/mutation_sources.py +14 -0
  352. chrys/app/tui/util/picker_text.py +27 -0
  353. chrys/app/tui/util/removal.py +47 -0
  354. chrys/app/tui/util/rich_style.py +36 -0
  355. chrys/app/tui/util/source_text.py +12 -0
  356. chrys/app/tui/util/static_update.py +88 -0
  357. chrys/app/tui/util/todo_format.py +51 -0
  358. chrys/app/tui/util/visibility.py +214 -0
  359. chrys/app/tui/widgets/__init__.py +60 -0
  360. chrys/app/tui/widgets/ask_user_controls.py +652 -0
  361. chrys/app/tui/widgets/ask_user_markdown.py +17 -0
  362. chrys/app/tui/widgets/ask_user_prompt.py +606 -0
  363. chrys/app/tui/widgets/buttons.py +52 -0
  364. chrys/app/tui/widgets/chat/__init__.py +3 -0
  365. chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
  366. chrys/app/tui/widgets/chat/chrome.py +296 -0
  367. chrys/app/tui/widgets/chat/compaction_card.py +353 -0
  368. chrys/app/tui/widgets/chat/context_fold.py +77 -0
  369. chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
  370. chrys/app/tui/widgets/chat/image_preview.py +214 -0
  371. chrys/app/tui/widgets/chat/live_renderer.py +383 -0
  372. chrys/app/tui/widgets/chat/messages.py +1039 -0
  373. chrys/app/tui/widgets/chat/panel.py +1769 -0
  374. chrys/app/tui/widgets/chat/ports.py +245 -0
  375. chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
  376. chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
  377. chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
  378. chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
  379. chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
  380. chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
  381. chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
  382. chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
  383. chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
  384. chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
  385. chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
  386. chrys/app/tui/widgets/chat/renderers/search.py +348 -0
  387. chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
  388. chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
  389. chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
  390. chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
  391. chrys/app/tui/widgets/chat/renderers/web.py +69 -0
  392. chrys/app/tui/widgets/chat/replay.py +1605 -0
  393. chrys/app/tui/widgets/chat/replay_mount.py +322 -0
  394. chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
  395. chrys/app/tui/widgets/chat/selection_controller.py +435 -0
  396. chrys/app/tui/widgets/chat/session_json.py +508 -0
  397. chrys/app/tui/widgets/chat/toc_model.py +156 -0
  398. chrys/app/tui/widgets/chat/tool_call.py +2478 -0
  399. chrys/app/tui/widgets/chat/tool_registry.py +753 -0
  400. chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
  401. chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
  402. chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
  403. chrys/app/tui/widgets/checkbox.py +24 -0
  404. chrys/app/tui/widgets/chrome/__init__.py +3 -0
  405. chrys/app/tui/widgets/chrome/app_header.py +283 -0
  406. chrys/app/tui/widgets/chrome/commands.py +95 -0
  407. chrys/app/tui/widgets/chrome/file_index.py +68 -0
  408. chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
  409. chrys/app/tui/widgets/chrome/footer.py +284 -0
  410. chrys/app/tui/widgets/chrome/image_paste.py +351 -0
  411. chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
  412. chrys/app/tui/widgets/chrome/status_bar.py +911 -0
  413. chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
  414. chrys/app/tui/widgets/click_affordance.py +24 -0
  415. chrys/app/tui/widgets/color_picker/__init__.py +254 -0
  416. chrys/app/tui/widgets/color_picker/controls.py +171 -0
  417. chrys/app/tui/widgets/color_picker/model.py +89 -0
  418. chrys/app/tui/widgets/dialog_buttons.py +58 -0
  419. chrys/app/tui/widgets/dialog_scroll.py +24 -0
  420. chrys/app/tui/widgets/diff_view/__init__.py +20 -0
  421. chrys/app/tui/widgets/diff_view/cells.py +59 -0
  422. chrys/app/tui/widgets/diff_view/code.py +280 -0
  423. chrys/app/tui/widgets/diff_view/compute.py +286 -0
  424. chrys/app/tui/widgets/diff_view/gutter.py +161 -0
  425. chrys/app/tui/widgets/diff_view/inline.py +77 -0
  426. chrys/app/tui/widgets/diff_view/palette.py +87 -0
  427. chrys/app/tui/widgets/diff_view/protocols.py +24 -0
  428. chrys/app/tui/widgets/diff_view/rows.py +138 -0
  429. chrys/app/tui/widgets/diff_view/unified.py +267 -0
  430. chrys/app/tui/widgets/diff_view/widget.py +209 -0
  431. chrys/app/tui/widgets/editor/__init__.py +79 -0
  432. chrys/app/tui/widgets/editor/coordinates.py +30 -0
  433. chrys/app/tui/widgets/editor/highlighter.py +139 -0
  434. chrys/app/tui/widgets/editor/keymaps.py +357 -0
  435. chrys/app/tui/widgets/editor/types.py +216 -0
  436. chrys/app/tui/widgets/editor/vim.py +1195 -0
  437. chrys/app/tui/widgets/editor/widget.py +401 -0
  438. chrys/app/tui/widgets/file_preview.py +58 -0
  439. chrys/app/tui/widgets/hatch.py +138 -0
  440. chrys/app/tui/widgets/input.py +66 -0
  441. chrys/app/tui/widgets/loading.py +125 -0
  442. chrys/app/tui/widgets/markdown/__init__.py +25 -0
  443. chrys/app/tui/widgets/markdown/_utils.py +21 -0
  444. chrys/app/tui/widgets/markdown/anchors.py +71 -0
  445. chrys/app/tui/widgets/markdown/blocks.py +115 -0
  446. chrys/app/tui/widgets/markdown/copyable.py +163 -0
  447. chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
  448. chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
  449. chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
  450. chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
  451. chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
  452. chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
  453. chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
  454. chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
  455. chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
  456. chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
  457. chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
  458. chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
  459. chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
  460. chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
  461. chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
  462. chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
  463. chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
  464. chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
  465. chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
  466. chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
  467. chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
  468. chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
  469. chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
  470. chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
  471. chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
  472. chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
  473. chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
  474. chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
  475. chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
  476. chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
  477. chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
  478. chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
  479. chrys/app/tui/widgets/markdown/links.py +47 -0
  480. chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
  481. chrys/app/tui/widgets/markdown/math/layout.py +739 -0
  482. chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
  483. chrys/app/tui/widgets/markdown/math/parser.py +325 -0
  484. chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
  485. chrys/app/tui/widgets/markdown/navigator.py +80 -0
  486. chrys/app/tui/widgets/markdown/parser.py +1057 -0
  487. chrys/app/tui/widgets/markdown/stream.py +73 -0
  488. chrys/app/tui/widgets/markdown/toc.py +98 -0
  489. chrys/app/tui/widgets/markdown/viewer.py +146 -0
  490. chrys/app/tui/widgets/markdown/widget.py +2046 -0
  491. chrys/app/tui/widgets/marquee.py +101 -0
  492. chrys/app/tui/widgets/option_menu.py +82 -0
  493. chrys/app/tui/widgets/page_navigator.py +118 -0
  494. chrys/app/tui/widgets/select.py +196 -0
  495. chrys/app/tui/widgets/selection.py +59 -0
  496. chrys/app/tui/widgets/sidebar/__init__.py +3 -0
  497. chrys/app/tui/widgets/sidebar/buddy.py +367 -0
  498. chrys/app/tui/widgets/sidebar/context.py +482 -0
  499. chrys/app/tui/widgets/sidebar/debug.py +195 -0
  500. chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
  501. chrys/app/tui/widgets/sidebar/panel.py +198 -0
  502. chrys/app/tui/widgets/sidebar/tasks.py +198 -0
  503. chrys/app/tui/widgets/sidebar/toc.py +166 -0
  504. chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
  505. chrys/app/tui/widgets/syntax_theme.py +85 -0
  506. chrys/app/tui/widgets/text_area.py +258 -0
  507. chrys/app/tui/widgets/trajectory/__init__.py +8 -0
  508. chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
  509. chrys/app/tui/widgets/trajectory/insights.py +1266 -0
  510. chrys/app/tui/widgets/trajectory/overview.py +1101 -0
  511. chrys/app/tui/widgets/trajectory/panel.py +983 -0
  512. chrys/app/tui/widgets/trajectory/presentation.py +311 -0
  513. chrys/app/tui/widgets/trajectory/session_info.py +124 -0
  514. chrys/app/tui/widgets/trajectory/text_view.py +124 -0
  515. chrys/app/tui/widgets/trajectory/timeline.py +380 -0
  516. chrys/app/tui/widgets/welcome.py +126 -0
  517. chrys/app/tui/widgets/workflow/__init__.py +5 -0
  518. chrys/app/tui/widgets/workflow/edges.py +89 -0
  519. chrys/app/tui/widgets/workflow/graph.py +869 -0
  520. chrys/app/tui/widgets/workflow/info.py +310 -0
  521. chrys/app/tui/widgets/workflow/node_view.py +27 -0
  522. chrys/app/tui/widgets/workflow/output.py +225 -0
  523. chrys/app/tui/widgets/workflow/panel.py +658 -0
  524. chrys/app/tui/widgets/workflow/projector.py +324 -0
  525. chrys/app/tui/widgets/workflow/records.py +69 -0
  526. chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
  527. chrys/app/tui/widgets/workflow/selection.py +151 -0
  528. chrys/app/tui/widgets/workflow/source.py +65 -0
  529. chrys/app/tui/widgets/workflow/text.py +257 -0
  530. chrys/app/tui/widgets/workflow/values.py +317 -0
  531. chrys/foundation/__init__.py +0 -0
  532. chrys/foundation/branding.py +15 -0
  533. chrys/foundation/config/__init__.py +7 -0
  534. chrys/foundation/config/coercion.py +374 -0
  535. chrys/foundation/config/context.py +34 -0
  536. chrys/foundation/config/env_file.py +141 -0
  537. chrys/foundation/config/env_layers.py +370 -0
  538. chrys/foundation/config/migrations.py +521 -0
  539. chrys/foundation/config/notification_events.py +22 -0
  540. chrys/foundation/config/process_settings.py +332 -0
  541. chrys/foundation/config/runtime_pointer.py +133 -0
  542. chrys/foundation/config/settings.py +1952 -0
  543. chrys/foundation/config/settings_store.py +996 -0
  544. chrys/foundation/config/spec.py +396 -0
  545. chrys/foundation/config/user_settings.py +140 -0
  546. chrys/foundation/config/warnings.py +262 -0
  547. chrys/foundation/config/web_values.py +135 -0
  548. chrys/foundation/config/yaml_store.py +487 -0
  549. chrys/foundation/errors/__init__.py +68 -0
  550. chrys/foundation/errors/_legacy.py +117 -0
  551. chrys/foundation/errors/_text.py +13 -0
  552. chrys/foundation/errors/_walk.py +85 -0
  553. chrys/foundation/errors/classify.py +278 -0
  554. chrys/foundation/errors/display.py +274 -0
  555. chrys/foundation/errors/formatting.py +283 -0
  556. chrys/foundation/errors/kinds.py +67 -0
  557. chrys/foundation/errors/network.py +497 -0
  558. chrys/foundation/errors/provider.py +445 -0
  559. chrys/foundation/errors/route.py +85 -0
  560. chrys/foundation/events/__init__.py +8 -0
  561. chrys/foundation/events/bus.py +143 -0
  562. chrys/foundation/events/types.py +1629 -0
  563. chrys/foundation/events/workflow.py +38 -0
  564. chrys/foundation/hosted_tools.py +142 -0
  565. chrys/foundation/i18n/__init__.py +32 -0
  566. chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
  567. chrys/foundation/i18n/catalogs.py +42 -0
  568. chrys/foundation/i18n/formatting.py +202 -0
  569. chrys/foundation/i18n/locale.py +92 -0
  570. chrys/foundation/i18n/localizer.py +187 -0
  571. chrys/foundation/i18n/messages.py +268 -0
  572. chrys/foundation/io/__init__.py +3 -0
  573. chrys/foundation/io/result_content.py +237 -0
  574. chrys/foundation/models/__init__.py +3 -0
  575. chrys/foundation/models/approval_reuse.py +23 -0
  576. chrys/foundation/models/ask_user.py +468 -0
  577. chrys/foundation/models/execution.py +18 -0
  578. chrys/foundation/models/history_markers.py +115 -0
  579. chrys/foundation/models/invocations.py +63 -0
  580. chrys/foundation/models/mutation_scope.py +28 -0
  581. chrys/foundation/models/session_env.py +87 -0
  582. chrys/foundation/models/session_surface.py +29 -0
  583. chrys/foundation/models/todos.py +74 -0
  584. chrys/foundation/models/turns.py +125 -0
  585. chrys/foundation/models/workflow_session.py +89 -0
  586. chrys/foundation/models/workspace.py +59 -0
  587. chrys/foundation/net/__init__.py +3 -0
  588. chrys/foundation/net/address_class.py +57 -0
  589. chrys/foundation/net/route_probe.py +94 -0
  590. chrys/foundation/net/url.py +107 -0
  591. chrys/foundation/observability/__init__.py +3 -0
  592. chrys/foundation/observability/exporters.py +174 -0
  593. chrys/foundation/observability/gate.py +31 -0
  594. chrys/foundation/observability/setup.py +497 -0
  595. chrys/foundation/observability/sink.py +228 -0
  596. chrys/foundation/patches/__init__.py +19 -0
  597. chrys/foundation/patches/mdurl_cache.py +59 -0
  598. chrys/foundation/patches/patcher.py +287 -0
  599. chrys/foundation/patches/staged_members.py +205 -0
  600. chrys/foundation/patches/textual_block_border.py +105 -0
  601. chrys/foundation/patches/textual_button_ansi.py +65 -0
  602. chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
  603. chrys/foundation/patches/textual_callback_cache.py +248 -0
  604. chrys/foundation/patches/textual_callback_dispatch.py +109 -0
  605. chrys/foundation/patches/textual_compositor_cjk.py +566 -0
  606. chrys/foundation/patches/textual_dispatch_cache.py +150 -0
  607. chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
  608. chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
  609. chrys/foundation/patches/textual_lru_acyclic.py +241 -0
  610. chrys/foundation/patches/textual_message_pump.py +136 -0
  611. chrys/foundation/patches/textual_node_diet.py +875 -0
  612. chrys/foundation/patches/textual_one_shot_timer.py +48 -0
  613. chrys/foundation/patches/textual_option_list.py +90 -0
  614. chrys/foundation/patches/textual_precompose.py +89 -0
  615. chrys/foundation/patches/textual_pruned_tabs.py +69 -0
  616. chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
  617. chrys/foundation/patches/textual_removed_node_caches.py +86 -0
  618. chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
  619. chrys/foundation/patches/textual_selection_extract.py +48 -0
  620. chrys/foundation/patches/textual_strip_cycles.py +119 -0
  621. chrys/foundation/patches/textual_tab_selection.py +1611 -0
  622. chrys/foundation/patches/textual_timer_skip.py +70 -0
  623. chrys/foundation/patches/textual_utf8_decoder.py +106 -0
  624. chrys/foundation/patches/textual_win_sleep.py +167 -0
  625. chrys/foundation/patches/watchdog_fsevents.py +70 -0
  626. chrys/foundation/patches/watchdog_windows.py +52 -0
  627. chrys/foundation/platform/__init__.py +556 -0
  628. chrys/foundation/platform/c_api.py +32 -0
  629. chrys/foundation/platform/child_reap.py +107 -0
  630. chrys/foundation/platform/command_line.py +95 -0
  631. chrys/foundation/platform/files.py +1470 -0
  632. chrys/foundation/platform/output_capture.py +113 -0
  633. chrys/foundation/platform/paths.py +193 -0
  634. chrys/foundation/platform/process.py +1468 -0
  635. chrys/foundation/platform/pty_output.py +19 -0
  636. chrys/foundation/platform/runtime_paths.py +281 -0
  637. chrys/foundation/platform/win_console.py +129 -0
  638. chrys/foundation/reasoning_origin.py +67 -0
  639. chrys/foundation/recovery.py +16 -0
  640. chrys/foundation/retry.py +278 -0
  641. chrys/foundation/text/__init__.py +3 -0
  642. chrys/foundation/text/encoding.py +603 -0
  643. chrys/foundation/text/images.py +304 -0
  644. chrys/foundation/text/lines.py +24 -0
  645. chrys/foundation/text/mentions.py +90 -0
  646. chrys/foundation/text/model_json.py +25 -0
  647. chrys/foundation/text/reading_order.py +20 -0
  648. chrys/foundation/text/tokenizer.py +25 -0
  649. chrys/foundation/text/tool_output.py +287 -0
  650. chrys/foundation/text/yaml_io.py +62 -0
  651. chrys/foundation/tool_call_context.py +202 -0
  652. chrys/foundation/tool_execution_stamp.py +184 -0
  653. chrys/foundation/tool_invocation_order.py +48 -0
  654. chrys/foundation/tool_kinds.py +102 -0
  655. chrys/foundation/tool_result_metadata.py +287 -0
  656. chrys/foundation/trajectory/__init__.py +10 -0
  657. chrys/foundation/trajectory/context.py +481 -0
  658. chrys/foundation/trajectory/envelope.py +571 -0
  659. chrys/foundation/trajectory/event_types.py +294 -0
  660. chrys/foundation/trajectory/finalizers.py +46 -0
  661. chrys/foundation/trajectory/fingerprint.py +62 -0
  662. chrys/foundation/trajectory/ids.py +155 -0
  663. chrys/foundation/trajectory/keys.py +99 -0
  664. chrys/foundation/trajectory/lease.py +173 -0
  665. chrys/foundation/trajectory/metadata.py +72 -0
  666. chrys/foundation/trajectory/reader.py +271 -0
  667. chrys/foundation/trajectory/recovery.py +225 -0
  668. chrys/foundation/trajectory/revisions.py +152 -0
  669. chrys/foundation/trajectory/segments.py +225 -0
  670. chrys/foundation/trajectory/tools.py +117 -0
  671. chrys/foundation/trajectory/usage.py +151 -0
  672. chrys/foundation/trajectory/writer.py +972 -0
  673. chrys/foundation/trajectory_timing.py +97 -0
  674. chrys/foundation/util/__init__.py +3 -0
  675. chrys/foundation/util/async_tasks.py +29 -0
  676. chrys/foundation/util/chrys_headers.py +23 -0
  677. chrys/foundation/util/env_templates.py +104 -0
  678. chrys/foundation/util/filenames.py +96 -0
  679. chrys/foundation/util/header_charset.py +138 -0
  680. chrys/foundation/util/httpx_helpers.py +12 -0
  681. chrys/foundation/util/lock.py +236 -0
  682. chrys/foundation/util/once_close.py +46 -0
  683. chrys/foundation/util/session_ids.py +29 -0
  684. chrys/foundation/util/shell_tokens.py +88 -0
  685. chrys/foundation/util/sub_agent_context.py +38 -0
  686. chrys/foundation/util/time.py +35 -0
  687. chrys/foundation/util/unicode_scalars.py +36 -0
  688. chrys/foundation/vendor/__init__.py +43 -0
  689. chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
  690. chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
  691. chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
  692. chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
  693. chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
  694. chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
  695. chrys/kernel/__init__.py +256 -0
  696. chrys/kernel/_content.py +1866 -0
  697. chrys/kernel/_loop_recorder.py +631 -0
  698. chrys/kernel/_null_overlay.py +1026 -0
  699. chrys/kernel/_result_ceiling.py +216 -0
  700. chrys/kernel/_serialization.py +431 -0
  701. chrys/kernel/_tool_arg_errors.py +1629 -0
  702. chrys/kernel/_tool_execution.py +1172 -0
  703. chrys/kernel/_tool_expansion.py +26 -0
  704. chrys/kernel/_types.py +2598 -0
  705. chrys/kernel/agent.py +994 -0
  706. chrys/kernel/client.py +1084 -0
  707. chrys/kernel/compaction.py +1315 -0
  708. chrys/kernel/exceptions.py +211 -0
  709. chrys/kernel/exchanges.py +683 -0
  710. chrys/kernel/identity.py +95 -0
  711. chrys/kernel/images.py +36 -0
  712. chrys/kernel/instrumentation.py +1618 -0
  713. chrys/kernel/loop.py +2308 -0
  714. chrys/kernel/middleware.py +616 -0
  715. chrys/kernel/sessions.py +604 -0
  716. chrys/kernel/tools.py +1061 -0
  717. chrys/kernel/types.py +126 -0
  718. chrys/orchestration/__init__.py +3 -0
  719. chrys/orchestration/engine/__init__.py +3 -0
  720. chrys/orchestration/engine/assembly.py +236 -0
  721. chrys/orchestration/engine/build/__init__.py +3 -0
  722. chrys/orchestration/engine/build/builder.py +1061 -0
  723. chrys/orchestration/engine/build/construction.py +408 -0
  724. chrys/orchestration/engine/build/loaded.py +139 -0
  725. chrys/orchestration/engine/engine.py +789 -0
  726. chrys/orchestration/engine/execution.py +771 -0
  727. chrys/orchestration/engine/loader.py +865 -0
  728. chrys/orchestration/engine/rollback.py +918 -0
  729. chrys/orchestration/engine/run/__init__.py +3 -0
  730. chrys/orchestration/engine/run/active_injection.py +419 -0
  731. chrys/orchestration/engine/run/attachments.py +519 -0
  732. chrys/orchestration/engine/run/bindings.py +1095 -0
  733. chrys/orchestration/engine/run/coordinator.py +1062 -0
  734. chrys/orchestration/engine/run/finalizer.py +417 -0
  735. chrys/orchestration/engine/run/input_refs.py +60 -0
  736. chrys/orchestration/engine/run/prompt_content.py +457 -0
  737. chrys/orchestration/engine/run/resume.py +299 -0
  738. chrys/orchestration/engine/run/retry.py +948 -0
  739. chrys/orchestration/engine/run/runner.py +625 -0
  740. chrys/orchestration/engine/run/runtime_skills.py +146 -0
  741. chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
  742. chrys/orchestration/engine/run/turn_hooks.py +258 -0
  743. chrys/orchestration/engine/run/turn_state.py +149 -0
  744. chrys/orchestration/engine/run/working_dir.py +50 -0
  745. chrys/orchestration/engine/session_lifecycle.py +2013 -0
  746. chrys/orchestration/engine/state/__init__.py +3 -0
  747. chrys/orchestration/engine/state/active_session.py +183 -0
  748. chrys/orchestration/engine/state/controls.py +639 -0
  749. chrys/orchestration/engine/state/current_agent.py +26 -0
  750. chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
  751. chrys/orchestration/engine/state/machine.py +193 -0
  752. chrys/orchestration/engine/state/session_writer.py +434 -0
  753. chrys/orchestration/engine/trajectory.py +714 -0
  754. chrys/orchestration/engine/usage.py +118 -0
  755. chrys/orchestration/invoker/__init__.py +3 -0
  756. chrys/orchestration/invoker/acp.py +727 -0
  757. chrys/orchestration/invoker/acp_protocol.py +1115 -0
  758. chrys/orchestration/invoker/acp_spec.py +79 -0
  759. chrys/orchestration/invoker/attempts.py +994 -0
  760. chrys/orchestration/invoker/child_compaction.py +131 -0
  761. chrys/orchestration/invoker/child_history.py +99 -0
  762. chrys/orchestration/invoker/contracts.py +253 -0
  763. chrys/orchestration/invoker/evidence.py +94 -0
  764. chrys/orchestration/invoker/kernel.py +343 -0
  765. chrys/orchestration/invoker/origin.py +79 -0
  766. chrys/orchestration/invoker/resources.py +343 -0
  767. chrys/orchestration/invoker/runtime.py +299 -0
  768. chrys/orchestration/invoker/usage.py +80 -0
  769. chrys/orchestration/session_hooks.py +117 -0
  770. chrys/orchestration/session_host.py +1023 -0
  771. chrys/orchestration/session_resources.py +95 -0
  772. chrys/orchestration/session_usage.py +219 -0
  773. chrys/orchestration/startup.py +255 -0
  774. chrys/orchestration/sub_agents/__init__.py +3 -0
  775. chrys/orchestration/sub_agents/acp_policy.py +269 -0
  776. chrys/orchestration/sub_agents/kernel_policy.py +848 -0
  777. chrys/orchestration/sub_agents/registration.py +44 -0
  778. chrys/orchestration/sub_agents/shell.py +337 -0
  779. chrys/orchestration/sub_agents/tools.py +2228 -0
  780. chrys/orchestration/workflows/__init__.py +3 -0
  781. chrys/orchestration/workflows/agent_archive.py +156 -0
  782. chrys/orchestration/workflows/agent_node.py +894 -0
  783. chrys/orchestration/workflows/agent_node_build.py +687 -0
  784. chrys/orchestration/workflows/catalog.py +332 -0
  785. chrys/orchestration/workflows/coordinator.py +771 -0
  786. chrys/orchestration/workflows/hooks.py +176 -0
  787. chrys/orchestration/workflows/preview.py +322 -0
  788. chrys/orchestration/workflows/rollback.py +151 -0
  789. chrys/orchestration/workflows/runner.py +986 -0
  790. chrys/orchestration/workflows/session.py +390 -0
  791. chrys/orchestration/workflows/settings.py +97 -0
  792. chrys/orchestration/workflows/validation.py +674 -0
  793. chrys/orchestration/workflows/worker_client.py +1002 -0
  794. chrys/service/__init__.py +3 -0
  795. chrys/service/acp_client/__init__.py +65 -0
  796. chrys/service/acp_client/_transport.py +922 -0
  797. chrys/service/acp_client/client.py +1311 -0
  798. chrys/service/acp_client/errors.py +224 -0
  799. chrys/service/acp_client/protocol.py +220 -0
  800. chrys/service/acp_client/spawn.py +239 -0
  801. chrys/service/acp_client/spec.py +100 -0
  802. chrys/service/agent_middleware/__init__.py +60 -0
  803. chrys/service/agent_middleware/_metadata_keys.py +52 -0
  804. chrys/service/agent_middleware/control/__init__.py +3 -0
  805. chrys/service/agent_middleware/control/approval.py +769 -0
  806. chrys/service/agent_middleware/control/approval_hooks.py +62 -0
  807. chrys/service/agent_middleware/control/ask_user.py +207 -0
  808. chrys/service/agent_middleware/control/interrupt.py +50 -0
  809. chrys/service/agent_middleware/control/sleep.py +118 -0
  810. chrys/service/agent_middleware/control/todo.py +117 -0
  811. chrys/service/agent_middleware/events/__init__.py +3 -0
  812. chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
  813. chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
  814. chrys/service/agent_middleware/events/intermediate_text.py +128 -0
  815. chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
  816. chrys/service/agent_middleware/events/result_persistence.py +98 -0
  817. chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
  818. chrys/service/agent_middleware/events/tool_error_text.py +22 -0
  819. chrys/service/agent_middleware/events/tool_events.py +779 -0
  820. chrys/service/agent_middleware/injection.py +510 -0
  821. chrys/service/agent_middleware/reminders/__init__.py +91 -0
  822. chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
  823. chrys/service/agent_middleware/reminders/base.py +25 -0
  824. chrys/service/agent_middleware/reminders/context_usage.py +74 -0
  825. chrys/service/agent_middleware/reminders/file_change.py +63 -0
  826. chrys/service/agent_middleware/reminders/mcp.py +39 -0
  827. chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
  828. chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
  829. chrys/service/agent_middleware/reminders/skills.py +44 -0
  830. chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
  831. chrys/service/agent_middleware/reminders/todo.py +35 -0
  832. chrys/service/agent_middleware/reminders/turn_line.py +48 -0
  833. chrys/service/agent_middleware/response_validation.py +1380 -0
  834. chrys/service/agent_middleware/system_reminder.py +1107 -0
  835. chrys/service/agent_middleware/validators.py +396 -0
  836. chrys/service/analytics/__init__.py +124 -0
  837. chrys/service/analytics/_actions.py +896 -0
  838. chrys/service/analytics/_context_evidence.py +357 -0
  839. chrys/service/analytics/_critical_path.py +179 -0
  840. chrys/service/analytics/_facts.py +919 -0
  841. chrys/service/analytics/_insights.py +769 -0
  842. chrys/service/analytics/_metric_ops.py +83 -0
  843. chrys/service/analytics/_session_projection.py +460 -0
  844. chrys/service/analytics/_timeline.py +434 -0
  845. chrys/service/analytics/_turn_graph.py +1237 -0
  846. chrys/service/analytics/_turns.py +1377 -0
  847. chrys/service/analytics/_workflows.py +171 -0
  848. chrys/service/analytics/aggregation.py +1474 -0
  849. chrys/service/analytics/classification.py +128 -0
  850. chrys/service/analytics/export.py +803 -0
  851. chrys/service/analytics/findings.py +282 -0
  852. chrys/service/analytics/math.py +79 -0
  853. chrys/service/analytics/model.py +886 -0
  854. chrys/service/analytics/reader.py +321 -0
  855. chrys/service/approval/__init__.py +3 -0
  856. chrys/service/approval/arbitration.py +102 -0
  857. chrys/service/approval/command_identity.py +109 -0
  858. chrys/service/approval/correlation.py +57 -0
  859. chrys/service/approval/grant_store.py +108 -0
  860. chrys/service/approval/judge.py +589 -0
  861. chrys/service/approval/level.py +15 -0
  862. chrys/service/approval/policy.py +209 -0
  863. chrys/service/approval/reuse.py +223 -0
  864. chrys/service/approval/reuse_binding.py +133 -0
  865. chrys/service/approval/safety_classifier.py +536 -0
  866. chrys/service/approval/turn_context.py +49 -0
  867. chrys/service/context/__init__.py +3 -0
  868. chrys/service/context/compaction/__init__.py +38 -0
  869. chrys/service/context/compaction/breaker.py +156 -0
  870. chrys/service/context/compaction/budgets.py +93 -0
  871. chrys/service/context/compaction/calibration.py +77 -0
  872. chrys/service/context/compaction/compression.py +690 -0
  873. chrys/service/context/compaction/current_turn_drop.py +458 -0
  874. chrys/service/context/compaction/events.py +201 -0
  875. chrys/service/context/compaction/exclusions.py +261 -0
  876. chrys/service/context/compaction/groups.py +361 -0
  877. chrys/service/context/compaction/last_words.py +1996 -0
  878. chrys/service/context/compaction/last_words_state.py +752 -0
  879. chrys/service/context/compaction/scoped.py +472 -0
  880. chrys/service/context/compaction/spill.py +1947 -0
  881. chrys/service/context/compaction/strategy.py +1116 -0
  882. chrys/service/context/compaction/summaries.py +319 -0
  883. chrys/service/context/compaction/turns.py +375 -0
  884. chrys/service/context/manager.py +174 -0
  885. chrys/service/context/memory_loader.py +792 -0
  886. chrys/service/context/middleware/__init__.py +3 -0
  887. chrys/service/context/middleware/usage.py +332 -0
  888. chrys/service/context/providers/__init__.py +3 -0
  889. chrys/service/context/providers/context_management.py +432 -0
  890. chrys/service/context/providers/history.py +647 -0
  891. chrys/service/context/providers/memory.py +50 -0
  892. chrys/service/context/tool_content.py +22 -0
  893. chrys/service/hooks/__init__.py +37 -0
  894. chrys/service/hooks/detached_worker.py +118 -0
  895. chrys/service/hooks/docs/README.md +14 -0
  896. chrys/service/hooks/docs/authoring.md +684 -0
  897. chrys/service/hooks/docs/configuration.md +499 -0
  898. chrys/service/hooks/docs/design.md +359 -0
  899. chrys/service/hooks/docs/project-hooks.md +348 -0
  900. chrys/service/hooks/events.py +157 -0
  901. chrys/service/hooks/loader.py +665 -0
  902. chrys/service/hooks/manager.py +807 -0
  903. chrys/service/hooks/matcher.py +111 -0
  904. chrys/service/hooks/outbox.py +294 -0
  905. chrys/service/hooks/runner.py +572 -0
  906. chrys/service/hooks/schema.py +407 -0
  907. chrys/service/llm/__init__.py +7 -0
  908. chrys/service/llm/_structured_outputs.py +192 -0
  909. chrys/service/llm/anthropic_messages/__init__.py +9 -0
  910. chrys/service/llm/anthropic_messages/client.py +254 -0
  911. chrys/service/llm/anthropic_messages/decode.py +331 -0
  912. chrys/service/llm/anthropic_messages/history.py +279 -0
  913. chrys/service/llm/anthropic_messages/request.py +323 -0
  914. chrys/service/llm/anthropic_messages/server_tools.py +319 -0
  915. chrys/service/llm/anthropic_messages/stream.py +297 -0
  916. chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
  917. chrys/service/llm/chat_completions/__init__.py +9 -0
  918. chrys/service/llm/chat_completions/client.py +369 -0
  919. chrys/service/llm/chat_completions/decode.py +338 -0
  920. chrys/service/llm/chat_completions/history.py +657 -0
  921. chrys/service/llm/chat_completions/reasoning.py +244 -0
  922. chrys/service/llm/chat_completions/request.py +192 -0
  923. chrys/service/llm/chat_completions/stream.py +546 -0
  924. chrys/service/llm/chat_completions/validation.py +308 -0
  925. chrys/service/llm/clients.py +613 -0
  926. chrys/service/llm/images.py +65 -0
  927. chrys/service/llm/mock.py +413 -0
  928. chrys/service/llm/observer.py +446 -0
  929. chrys/service/llm/one_shot.py +106 -0
  930. chrys/service/llm/openai_exceptions.py +93 -0
  931. chrys/service/llm/openai_responses/__init__.py +10 -0
  932. chrys/service/llm/openai_responses/client.py +323 -0
  933. chrys/service/llm/openai_responses/decode.py +432 -0
  934. chrys/service/llm/openai_responses/history.py +519 -0
  935. chrys/service/llm/openai_responses/hosted.py +625 -0
  936. chrys/service/llm/openai_responses/replay.py +629 -0
  937. chrys/service/llm/openai_responses/request.py +342 -0
  938. chrys/service/llm/openai_responses/stream.py +847 -0
  939. chrys/service/llm/openai_timestamps.py +57 -0
  940. chrys/service/llm/providers.py +107 -0
  941. chrys/service/llm/proxy_route.py +54 -0
  942. chrys/service/llm/raw_http_log.py +309 -0
  943. chrys/service/llm/route_facts.py +63 -0
  944. chrys/service/llm/route_sessions.py +49 -0
  945. chrys/service/llm/sdk_anthropic.py +18 -0
  946. chrys/service/llm/sdk_headers.py +47 -0
  947. chrys/service/llm/sdk_openai.py +18 -0
  948. chrys/service/llm/sdk_retry.py +59 -0
  949. chrys/service/llm/wire_client.py +246 -0
  950. chrys/service/mcp/__init__.py +3 -0
  951. chrys/service/mcp/_connection.py +235 -0
  952. chrys/service/mcp/_http_transport.py +350 -0
  953. chrys/service/mcp/_progressive.py +459 -0
  954. chrys/service/mcp/_stdio_transport.py +614 -0
  955. chrys/service/mcp/_tool_mixins.py +96 -0
  956. chrys/service/mcp/adapter.py +1031 -0
  957. chrys/service/mcp/cache.py +476 -0
  958. chrys/service/mcp/content.py +32 -0
  959. chrys/service/mcp/errors.py +169 -0
  960. chrys/service/mcp/owned.py +1330 -0
  961. chrys/service/mcp/result_limits.py +168 -0
  962. chrys/service/mcp/thinking_warning.py +95 -0
  963. chrys/service/mcp/validation.py +74 -0
  964. chrys/service/mutations/__init__.py +84 -0
  965. chrys/service/mutations/coordination.py +894 -0
  966. chrys/service/mutations/detector.py +458 -0
  967. chrys/service/mutations/git_calibrator.py +397 -0
  968. chrys/service/mutations/git_state.py +647 -0
  969. chrys/service/mutations/pipeline.py +779 -0
  970. chrys/service/mutations/scanner.py +466 -0
  971. chrys/service/mutations/snapshot_files.py +233 -0
  972. chrys/service/mutations/store.py +625 -0
  973. chrys/service/mutations/tool_names.py +15 -0
  974. chrys/service/mutations/trace.py +246 -0
  975. chrys/service/mutations/tracker.py +1289 -0
  976. chrys/service/mutations/types.py +559 -0
  977. chrys/service/mutations/workspace_changes.py +1583 -0
  978. chrys/service/profiles/__init__.py +2 -0
  979. chrys/service/profiles/agents/__init__.py +3 -0
  980. chrys/service/profiles/agents/builtins/Code.yaml +199 -0
  981. chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
  982. chrys/service/profiles/agents/builtins/General.yaml +114 -0
  983. chrys/service/profiles/agents/builtins/QA.yaml +128 -0
  984. chrys/service/profiles/agents/loader.py +808 -0
  985. chrys/service/profiles/agents/registry.py +727 -0
  986. chrys/service/profiles/agents/schema.py +371 -0
  987. chrys/service/profiles/agents/serializer.py +196 -0
  988. chrys/service/profiles/coercion.py +22 -0
  989. chrys/service/profiles/models/__init__.py +3 -0
  990. chrys/service/profiles/models/env_bridge.py +157 -0
  991. chrys/service/profiles/models/loader.py +282 -0
  992. chrys/service/profiles/models/options.py +438 -0
  993. chrys/service/profiles/models/registry.py +104 -0
  994. chrys/service/profiles/models/resolver.py +286 -0
  995. chrys/service/profiles/models/schema.py +91 -0
  996. chrys/service/profiles/models/serializer.py +76 -0
  997. chrys/service/session/__init__.py +3 -0
  998. chrys/service/session/checkpoint.py +156 -0
  999. chrys/service/session/history.py +1616 -0
  1000. chrys/service/session/message_metadata.py +151 -0
  1001. chrys/service/session/persistence.py +435 -0
  1002. chrys/service/session/runtime_metadata.py +210 -0
  1003. chrys/service/session/sub_agent_logs.py +1069 -0
  1004. chrys/service/session/sub_agent_transcript.py +654 -0
  1005. chrys/service/skills/__init__.py +3 -0
  1006. chrys/service/skills/adapter.py +408 -0
  1007. chrys/service/skills/constants.py +8 -0
  1008. chrys/service/skills/loader.py +575 -0
  1009. chrys/service/skills/model.py +130 -0
  1010. chrys/service/skills/provider.py +841 -0
  1011. chrys/service/skills/runner.py +390 -0
  1012. chrys/service/state/__init__.py +3 -0
  1013. chrys/service/state/_fork.py +710 -0
  1014. chrys/service/state/_session_files.py +175 -0
  1015. chrys/service/state/_session_meta.py +552 -0
  1016. chrys/service/state/locks.py +154 -0
  1017. chrys/service/state/serializers.py +156 -0
  1018. chrys/service/state/session_catalog.py +361 -0
  1019. chrys/service/state/session_listing.py +84 -0
  1020. chrys/service/state/session_migration.py +446 -0
  1021. chrys/service/state/session_mru.py +315 -0
  1022. chrys/service/state/store.py +2132 -0
  1023. chrys/service/state/workflow.py +158 -0
  1024. chrys/service/text_blocks.py +47 -0
  1025. chrys/service/todos/__init__.py +3 -0
  1026. chrys/service/todos/reminder.py +31 -0
  1027. chrys/service/todos/tracker.py +58 -0
  1028. chrys/service/tools/__init__.py +3 -0
  1029. chrys/service/tools/approval_targets.py +92 -0
  1030. chrys/service/tools/builtins/__init__.py +3 -0
  1031. chrys/service/tools/builtins/ask_user.py +326 -0
  1032. chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
  1033. chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
  1034. chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
  1035. chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
  1036. chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
  1037. chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
  1038. chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
  1039. chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
  1040. chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
  1041. chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
  1042. chrys/service/tools/builtins/doc_converter/registry.py +55 -0
  1043. chrys/service/tools/builtins/filesystem.py +1071 -0
  1044. chrys/service/tools/builtins/search.py +1265 -0
  1045. chrys/service/tools/builtins/shell.py +967 -0
  1046. chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
  1047. chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
  1048. chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
  1049. chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
  1050. chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
  1051. chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
  1052. chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
  1053. chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
  1054. chrys/service/tools/builtins/shell_filter.py +24 -0
  1055. chrys/service/tools/builtins/sleep.py +91 -0
  1056. chrys/service/tools/builtins/todo.py +64 -0
  1057. chrys/service/tools/builtins/web/__init__.py +6 -0
  1058. chrys/service/tools/builtins/web/build.py +268 -0
  1059. chrys/service/tools/builtins/web/config.py +338 -0
  1060. chrys/service/tools/builtins/web/credentials.py +37 -0
  1061. chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
  1062. chrys/service/tools/builtins/web/fetch/cache.py +68 -0
  1063. chrys/service/tools/builtins/web/fetch/convert.py +56 -0
  1064. chrys/service/tools/builtins/web/fetch/tool.py +223 -0
  1065. chrys/service/tools/builtins/web/hosted.py +61 -0
  1066. chrys/service/tools/builtins/web/http.py +330 -0
  1067. chrys/service/tools/builtins/web/progress.py +18 -0
  1068. chrys/service/tools/builtins/web/search/__init__.py +3 -0
  1069. chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
  1070. chrys/service/tools/builtins/web/search/html.py +152 -0
  1071. chrys/service/tools/builtins/web/search/json_api.py +227 -0
  1072. chrys/service/tools/builtins/web/search/providers.py +77 -0
  1073. chrys/service/tools/builtins/web/search/tool.py +218 -0
  1074. chrys/service/tools/builtins/web/search/types.py +28 -0
  1075. chrys/service/tools/builtins/web/tls_identity.py +66 -0
  1076. chrys/service/tools/kinds.py +115 -0
  1077. chrys/service/tools/names.py +47 -0
  1078. chrys/service/tools/registry.py +242 -0
  1079. chrys/service/tools/result_metadata.py +103 -0
  1080. chrys/service/tools/session_artifacts.py +196 -0
  1081. chrys/service/tools/spill.py +218 -0
  1082. chrys/service/tools/workspace_paths.py +81 -0
  1083. chrys/service/trajectory/__init__.py +11 -0
  1084. chrys/service/trajectory/approvals.py +201 -0
  1085. chrys/service/trajectory/compaction.py +420 -0
  1086. chrys/service/trajectory/fork.py +75 -0
  1087. chrys/service/trajectory/hooks.py +217 -0
  1088. chrys/service/trajectory/items.py +41 -0
  1089. chrys/service/trajectory/preparation.py +374 -0
  1090. chrys/service/trajectory/retries.py +95 -0
  1091. chrys/service/trajectory/revisions.py +129 -0
  1092. chrys/service/trajectory/segmented.py +163 -0
  1093. chrys/service/trajectory/session.py +762 -0
  1094. chrys/service/trajectory/state.py +83 -0
  1095. chrys/service/trajectory/sub_agents.py +169 -0
  1096. chrys/service/trajectory/tombstone.py +262 -0
  1097. chrys/service/trajectory/tools.py +323 -0
  1098. chrys/service/trajectory/validation.py +130 -0
  1099. chrys/service/trajectory/waits.py +161 -0
  1100. chrys/service/trajectory/workflow.py +148 -0
  1101. chrys/service/vision.py +128 -0
  1102. chrys/service/workflows/__init__.py +3 -0
  1103. chrys/service/workflows/admission.py +151 -0
  1104. chrys/service/workflows/artifacts.py +109 -0
  1105. chrys/service/workflows/asks.py +130 -0
  1106. chrys/service/workflows/builtins/__init__.py +12 -0
  1107. chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
  1108. chrys/service/workflows/builtins/demo-workflow.py +756 -0
  1109. chrys/service/workflows/discovery.py +554 -0
  1110. chrys/service/workflows/environment.py +284 -0
  1111. chrys/service/workflows/graph.py +430 -0
  1112. chrys/service/workflows/history.py +82 -0
  1113. chrys/service/workflows/interpreter.py +107 -0
  1114. chrys/service/workflows/journal.py +334 -0
  1115. chrys/service/workflows/layout.py +56 -0
  1116. chrys/service/workflows/ledger.py +149 -0
  1117. chrys/service/workflows/model_selection.py +18 -0
  1118. chrys/service/workflows/orphans.py +209 -0
  1119. chrys/service/workflows/outcomes.py +46 -0
  1120. chrys/service/workflows/protocol.py +210 -0
  1121. chrys/service/workflows/records.py +142 -0
  1122. chrys/service/workflows/scheduler.py +770 -0
  1123. chrys/service/workflows/sdk/__init__.py +51 -0
  1124. chrys/service/workflows/sdk/_ask.py +167 -0
  1125. chrys/service/workflows/sdk/_builder.py +781 -0
  1126. chrys/service/workflows/sdk/_values.py +92 -0
  1127. chrys/service/workflows/sdk_artifact.py +74 -0
  1128. chrys/service/workflows/store.py +494 -0
  1129. chrys/service/workflows/transcript.py +102 -0
  1130. chrys/service/workflows/values.py +73 -0
  1131. chrys/service/workflows/worker_host.py +1820 -0
  1132. chrys/workflows.py +38 -0
  1133. icode_tui-0.29.1.dist-info/METADATA +104 -0
  1134. icode_tui-0.29.1.dist-info/RECORD +1138 -0
  1135. icode_tui-0.29.1.dist-info/WHEEL +4 -0
  1136. icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
  1137. icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
  1138. icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
@@ -0,0 +1,1026 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Static policy for restoring caller-explicit null tool arguments.
4
+
5
+ Pydantic's ``model_dump(exclude_none=True)`` removes explicit ``None`` values
6
+ at typed field/member positions. Caller-explicit nulls must reach tool
7
+ callables, while omitted default-null fields must stay absent.
8
+
9
+ This module classifies each input model once and assigns one of three tiers:
10
+
11
+ ``FULL``
12
+ Every reachable annotation is structurally plain. The whitelist contains
13
+ BaseModel, pydantic/stdlib dataclasses, TypedDict, primitive/enum/literal
14
+ leaves, built-in collections plus Sequence/Mapping/MutableMapping,
15
+ unions, Annotated validation metadata, Any, and real NewType objects.
16
+ Serializer metadata, serializer decorators, alias generators, TypedDict or
17
+ dataclass aliases/default metadata, and unknown annotation nodes reject the
18
+ graph. Invoke-time restoration then follows runtime containers only and
19
+ descends into model/dataclass instances whose classes the policy approved.
20
+
21
+ ``TOP_LEVEL``
22
+ The graph was not fully plain, but the root model has no model serializer.
23
+ Only statically keyed, serializer-free, non-excluded root fields can regain
24
+ an explicit null. Nested output is left exactly as Pydantic produced it.
25
+
26
+ ``OFF``
27
+ Even the root output is not structurally owned by its fields, normally
28
+ because a model serializer controls it. Restoration is disabled.
29
+
30
+ Classification may execute first-party build-time behavior while Pydantic
31
+ resolves annotations and compiled field metadata. Invoke time executes no
32
+ serializer or alias-generator code beyond the single ordinary Pydantic dump.
33
+ Schema-supplied tools bypass this module. Middleware and tool-author runtime
34
+ objects are trusted: the loop may use ``deepcopy`` and ordinary equality, and
35
+ a middleware rewrite that changes only dict insertion order may be classified
36
+ as untouched.
37
+
38
+ The accepted trade-off is reduced restoration coverage for rejected graphs:
39
+ nested explicit nulls stay absent, with one build-time debug record emitted by
40
+ the FunctionTool integration. The invariant retained here is structural: the
41
+ walk never rewrites or deletes Pydantic output. It inserts ``None`` only at a
42
+ slot whose class/key/container shape the static policy approved; a runtime
43
+ shape mismatch or unapproved class is an untouched leaf.
44
+
45
+ Declared slot closures also guard runtime model/dataclass descent. A validator
46
+ substitution to a class outside that slot is left untouched, and a slot that
47
+ co-declares a subclass pair degrades at classification. The remaining bounded
48
+ residual is substitution between unrelated co-declared classes; Pydantic's
49
+ declared-schema dump already emits mismatched output before this walk runs.
50
+ A compiled default value that itself mimics a typed core-schema node may
51
+ conservatively degrade classification; this loses restoration only.
52
+ Nested nulls beneath a defaulted dataclass field are not restored because
53
+ dataclasses expose no caller-provenance set for those materialized values.
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ import dataclasses
59
+ import datetime
60
+ import decimal
61
+ import enum
62
+ import types
63
+ import typing
64
+ import uuid
65
+ from collections.abc import Mapping, MutableMapping, Sequence
66
+ from dataclasses import dataclass
67
+ from types import MappingProxyType
68
+ from typing import Annotated, Any, Literal, get_args, get_origin
69
+
70
+ import annotated_types
71
+ import typing_extensions
72
+ from pydantic import AfterValidator, BaseModel, BeforeValidator, PlainSerializer, WrapSerializer, WrapValidator
73
+ from pydantic.fields import FieldInfo
74
+ from typing_extensions import is_typeddict as _te_is_typeddict
75
+
76
+
77
+ class OverlayTier(enum.Enum):
78
+ """Strength of the statically approved restore."""
79
+
80
+ FULL = "full"
81
+ TOP_LEVEL = "top_level"
82
+ OFF = "off"
83
+
84
+
85
+ @dataclass(frozen=True, slots=True)
86
+ class OverlayPolicy:
87
+ """Immutable output of the one-time annotation-graph classification."""
88
+
89
+ tier: OverlayTier
90
+ root_model: type[BaseModel]
91
+ approved_classes: frozenset[type]
92
+ dump_keys: Mapping[type, Mapping[str, str]]
93
+ never_restore: Mapping[type, frozenset[str]]
94
+ dataclass_null_restorable: Mapping[type, frozenset[str]]
95
+ dataclass_required_fields: Mapping[type, frozenset[str]]
96
+ computed_keys: Mapping[type, frozenset[str]]
97
+ slot_classes: Mapping[tuple[type, str], frozenset[type]]
98
+ top_level_restorable: frozenset[str]
99
+ reason: str | None = None
100
+
101
+
102
+ _PLAIN_LEAVES = frozenset(
103
+ {
104
+ str,
105
+ int,
106
+ float,
107
+ bool,
108
+ bytes,
109
+ complex,
110
+ type(None),
111
+ datetime.datetime,
112
+ datetime.date,
113
+ datetime.time,
114
+ datetime.timedelta,
115
+ decimal.Decimal,
116
+ uuid.UUID,
117
+ }
118
+ )
119
+ _CONTAINER_ORIGINS = frozenset({list, tuple, set, frozenset, dict, Sequence, Mapping, MutableMapping})
120
+ _TRANSPARENT_WRAPPERS = frozenset(
121
+ {
122
+ typing.Required,
123
+ typing.NotRequired,
124
+ typing.ReadOnly,
125
+ typing_extensions.Required,
126
+ typing_extensions.NotRequired,
127
+ typing_extensions.ReadOnly,
128
+ }
129
+ )
130
+ _SAFE_ANNOTATED_METADATA = (
131
+ FieldInfo,
132
+ BeforeValidator,
133
+ WrapValidator,
134
+ AfterValidator,
135
+ annotated_types.BaseMetadata,
136
+ annotated_types.GroupedMetadata,
137
+ str,
138
+ )
139
+
140
+
141
+ def _is_static_member(value: Any, members: frozenset[Any]) -> bool:
142
+ """Test a static whitelist without requiring the annotation to be hashable."""
143
+ try:
144
+ return value in members
145
+ except TypeError:
146
+ return False
147
+
148
+
149
+ def _is_typed_dict(cls: Any) -> bool:
150
+ return isinstance(cls, type) and (typing.is_typeddict(cls) or _te_is_typeddict(cls))
151
+
152
+
153
+ def _typed_dict_config(td_cls: type, seen: set[int] | None = None) -> Mapping[str, Any] | None:
154
+ """Resolve TypedDict config through its flattened declaration bases."""
155
+ seen = set() if seen is None else seen
156
+ if id(td_cls) in seen:
157
+ return None
158
+ seen.add(id(td_cls))
159
+ config = td_cls.__dict__.get("__pydantic_config__")
160
+ if isinstance(config, Mapping):
161
+ return config
162
+ for declared_base in td_cls.__dict__.get("__orig_bases__", ()):
163
+ base = get_origin(declared_base) or declared_base
164
+ if _is_typed_dict(base):
165
+ inherited = _typed_dict_config(base, seen)
166
+ if inherited is not None:
167
+ return inherited
168
+ return None
169
+
170
+
171
+ def _real_new_type(annotation: Any) -> bool:
172
+ """Whether *annotation* is a real stdlib/backport NewType object."""
173
+ return isinstance(annotation, typing.NewType)
174
+
175
+
176
+ def _has_custom_core_schema(cls: type, framework_base: type | None = None) -> bool:
177
+ """Whether a class below its framework base controls core-schema output."""
178
+ for base in cls.__mro__:
179
+ if base is framework_base:
180
+ return False
181
+ if "__get_pydantic_core_schema__" in base.__dict__:
182
+ return True
183
+ return False
184
+
185
+
186
+ def _metadata_contains_serializer(metadata: list[Any], seen: set[int] | None = None) -> bool:
187
+ """Whether metadata contains serializer ownership, including FieldInfo carriers."""
188
+ seen = set() if seen is None else seen
189
+ for item in metadata:
190
+ if isinstance(item, (PlainSerializer, WrapSerializer)):
191
+ return True
192
+ if isinstance(item, FieldInfo) and id(item) not in seen:
193
+ seen.add(id(item))
194
+ if _metadata_contains_serializer(item.metadata, seen):
195
+ return True
196
+ return False
197
+
198
+
199
+ def _annotation_field_info(annotation: Any) -> FieldInfo | None:
200
+ """Merged FieldInfo through Annotated and TypedDict wrapper layers."""
201
+ layers: list[list[FieldInfo]] = []
202
+ seen: set[int] = set()
203
+ while id(annotation) not in seen:
204
+ seen.add(id(annotation))
205
+ if _real_new_type(annotation):
206
+ annotation = annotation.__supertype__
207
+ continue
208
+ origin = get_origin(annotation)
209
+ if origin is Annotated:
210
+ annotation, *metadata = get_args(annotation)
211
+ layers.append([item for item in metadata if isinstance(item, FieldInfo)])
212
+ continue
213
+ if _is_static_member(origin, _TRANSPARENT_WRAPPERS):
214
+ annotation = get_args(annotation)[0]
215
+ continue
216
+ break
217
+ infos = [item for layer in reversed(layers) for item in layer]
218
+ # The overlay mirrors pydantic-internal field merging; revisit when pydantic removes it.
219
+ return FieldInfo.merge_field_infos(*infos) if infos else None # ty: ignore[deprecated]
220
+
221
+
222
+ def _field_dump_key(model_cls: type[BaseModel], field_name: str, field_info: FieldInfo) -> str:
223
+ """Field key emitted by the module's ordinary model dump call."""
224
+ if not bool(model_cls.model_config.get("serialize_by_alias", False)):
225
+ return field_name
226
+ if field_info.serialization_alias is not None:
227
+ return field_info.serialization_alias
228
+ if field_info.alias is not None:
229
+ return field_info.alias
230
+ return field_name
231
+
232
+
233
+ def _dataclass_mro_attribute(dataclass_cls: type, name: str) -> tuple[Any, int] | None:
234
+ for index, base in enumerate(dataclass_cls.__mro__):
235
+ if name in base.__dict__:
236
+ return (base.__dict__[name], index)
237
+ return None
238
+
239
+
240
+ def _dataclass_decorator_state(dataclass_cls: type) -> tuple[bool, dict[str, Any]]:
241
+ """Serializer presence and computed-field metadata across the MRO."""
242
+ computed_fields: dict[str, Any] = {}
243
+ for base in dataclass_cls.__mro__:
244
+ decorators = base.__dict__.get("__pydantic_decorators__")
245
+ if decorators is not None and (decorators.model_serializers or decorators.field_serializers):
246
+ return (True, {})
247
+ if decorators is not None:
248
+ for name, decorator in decorators.computed_fields.items():
249
+ computed_fields.setdefault(name, decorator.info)
250
+ for name, attribute in base.__dict__.items():
251
+ info = getattr(attribute, "decorator_info", None)
252
+ if info is None:
253
+ continue
254
+ info_name = type(info).__name__
255
+ if info_name in {"ModelSerializerDecoratorInfo", "FieldSerializerDecoratorInfo"}:
256
+ return (True, {})
257
+ if info_name == "ComputedFieldInfo":
258
+ computed_fields.setdefault(name, info)
259
+ return (False, computed_fields)
260
+
261
+
262
+ def _dataclass_field_info(
263
+ dataclass_cls: type, dataclass_field: dataclasses.Field[Any], annotation: Any
264
+ ) -> FieldInfo | None:
265
+ """Effective Pydantic field metadata for a dataclass field."""
266
+ compiled = _dataclass_mro_attribute(dataclass_cls, "__pydantic_fields__")
267
+ if compiled is not None:
268
+ pydantic_fields, owner_index = compiled
269
+ owner_fields = dataclass_cls.__mro__[owner_index].__dict__.get("__dataclass_fields__")
270
+ if (
271
+ isinstance(pydantic_fields, dict)
272
+ and isinstance(owner_fields, dict)
273
+ and owner_fields.get(dataclass_field.name) is dataclass_field
274
+ ):
275
+ field_info = pydantic_fields.get(dataclass_field.name)
276
+ if isinstance(field_info, FieldInfo):
277
+ return field_info
278
+ default = dataclass_field.default
279
+ attribute = default if isinstance(default, FieldInfo) else dataclass_field
280
+ return FieldInfo.from_annotated_attribute(annotation, attribute)
281
+
282
+
283
+ class _PolicyBuilder:
284
+ def __init__(self) -> None:
285
+ self.approved_classes: set[type] = set()
286
+ self.dump_keys: dict[type, dict[str, str]] = {}
287
+ self.never_restore: dict[type, frozenset[str]] = {}
288
+ self.dataclass_null_restorable: dict[type, frozenset[str]] = {}
289
+ self.dataclass_required_fields: dict[type, frozenset[str]] = {}
290
+ self.computed_keys: dict[type, frozenset[str]] = {}
291
+ self.slot_classes: dict[tuple[type, str], frozenset[type]] = {}
292
+ self._visiting: set[type] = set()
293
+ self.reason: str | None = None
294
+
295
+ def reject(self, reason: str) -> bool:
296
+ if self.reason is None:
297
+ self.reason = reason
298
+ return False
299
+
300
+ def metadata_is_plain(self, metadata: list[Any]) -> bool:
301
+ """Validate one metadata sequence, including nested FieldInfo carriers."""
302
+ if _metadata_contains_serializer(metadata):
303
+ return self.reject("Annotated serializer metadata")
304
+ for item in metadata:
305
+ if isinstance(item, FieldInfo):
306
+ if not self.metadata_is_plain(item.metadata):
307
+ return False
308
+ continue
309
+ if not isinstance(item, _SAFE_ANNOTATED_METADATA):
310
+ return self.reject(f"unsupported Annotated metadata {type(item).__name__}")
311
+ return True
312
+
313
+ def slot_model_classes(self, annotation: Any, seen: set[int] | None = None) -> frozenset[type]:
314
+ """Approved model/dataclass classes reachable before a nested class boundary."""
315
+ seen = set() if seen is None else seen
316
+ if id(annotation) in seen:
317
+ return frozenset()
318
+ seen.add(id(annotation))
319
+ if _real_new_type(annotation):
320
+ return self.slot_model_classes(annotation.__supertype__, seen)
321
+ origin = get_origin(annotation)
322
+ if origin is Annotated or _is_static_member(origin, _TRANSPARENT_WRAPPERS):
323
+ return self.slot_model_classes(get_args(annotation)[0], seen)
324
+ if origin in {dict, Mapping, MutableMapping}:
325
+ arguments = get_args(annotation)
326
+ return self.slot_model_classes(arguments[1], seen) if len(arguments) == 2 else frozenset()
327
+ if origin in (typing.Union, types.UnionType) or _is_static_member(origin, _CONTAINER_ORIGINS):
328
+ classes: set[type] = set()
329
+ for item in get_args(annotation):
330
+ if item is not Ellipsis:
331
+ classes.update(self.slot_model_classes(item, seen.copy()))
332
+ return frozenset(classes)
333
+ if _is_typed_dict(annotation):
334
+ try:
335
+ hints = typing.get_type_hints(annotation, include_extras=True)
336
+ except Exception:
337
+ return frozenset()
338
+ classes = set()
339
+ for member_annotation in hints.values():
340
+ classes.update(self.slot_model_classes(member_annotation, seen.copy()))
341
+ extra_items = annotation.__dict__.get("__extra_items__", typing_extensions.NoExtraItems)
342
+ no_extra_items = typing.__dict__.get("NoExtraItems", typing_extensions.NoExtraItems)
343
+ if extra_items is not typing_extensions.NoExtraItems and extra_items is not no_extra_items:
344
+ classes.update(self.slot_model_classes(extra_items, seen.copy()))
345
+ return frozenset(classes)
346
+ if isinstance(annotation, type) and (issubclass(annotation, BaseModel) or dataclasses.is_dataclass(annotation)):
347
+ return frozenset({annotation})
348
+ return frozenset()
349
+
350
+ def record_slot_classes(self, owner: type, name: str, annotation: Any) -> bool:
351
+ """Record one slot unless its declared closure contains a subclass pair."""
352
+ classes = self.slot_model_classes(annotation)
353
+ ordered = sorted(classes, key=lambda cls: (cls.__module__, cls.__qualname__))
354
+ for index, left in enumerate(ordered):
355
+ for right in ordered[index + 1 :]:
356
+ if issubclass(left, right) or issubclass(right, left):
357
+ return self.reject(f"co-declared subclass pair {left.__name__}/{right.__name__} in one slot")
358
+ self.slot_classes[(owner, name)] = classes
359
+ return True
360
+
361
+ def annotation_is_plain(self, annotation: Any) -> bool:
362
+ if annotation is Any or _is_static_member(annotation, _PLAIN_LEAVES):
363
+ return True
364
+ if _real_new_type(annotation):
365
+ return self.annotation_is_plain(annotation.__supertype__)
366
+ origin = get_origin(annotation)
367
+ if origin is Annotated:
368
+ base, *metadata = get_args(annotation)
369
+ return self.metadata_is_plain(metadata) and self.annotation_is_plain(base)
370
+ if _is_static_member(origin, _TRANSPARENT_WRAPPERS):
371
+ return self.annotation_is_plain(get_args(annotation)[0])
372
+ if origin in (typing.Union, types.UnionType):
373
+ return all(self.annotation_is_plain(item) for item in get_args(annotation))
374
+ if origin is Literal:
375
+ return True
376
+ if _is_static_member(origin, _CONTAINER_ORIGINS):
377
+ return all(item is Ellipsis or self.annotation_is_plain(item) for item in get_args(annotation))
378
+ if _is_static_member(annotation, _CONTAINER_ORIGINS):
379
+ return True
380
+ if _is_typed_dict(annotation):
381
+ return self.typed_dict_is_plain(annotation)
382
+ if isinstance(annotation, type) and issubclass(annotation, BaseModel):
383
+ return self.model_is_plain(annotation)
384
+ if isinstance(annotation, type) and dataclasses.is_dataclass(annotation):
385
+ return self.dataclass_is_plain(annotation)
386
+ if isinstance(annotation, type) and issubclass(annotation, enum.Enum):
387
+ if _has_custom_core_schema(annotation, enum.Enum):
388
+ return self.reject(f"custom core schema on {annotation.__name__}")
389
+ return True
390
+ return self.reject(f"unsupported annotation {annotation!r}")
391
+
392
+ def model_is_plain(self, model_cls: type[BaseModel]) -> bool:
393
+ if model_cls in self.approved_classes or model_cls in self._visiting:
394
+ return True
395
+ self._visiting.add(model_cls)
396
+ if _has_custom_core_schema(model_cls, BaseModel):
397
+ self._visiting.discard(model_cls)
398
+ return self.reject(f"custom core schema on {model_cls.__name__}")
399
+ decorators = model_cls.__pydantic_decorators__
400
+ if decorators.model_serializers or decorators.field_serializers:
401
+ self._visiting.discard(model_cls)
402
+ return self.reject(f"serializer decorator on {model_cls.__name__}")
403
+ if model_cls.model_config.get("alias_generator") is not None:
404
+ self._visiting.discard(model_cls)
405
+ return self.reject(f"alias generator on {model_cls.__name__}")
406
+ try:
407
+ hints = typing.get_type_hints(model_cls, include_extras=True)
408
+ except Exception:
409
+ self._visiting.discard(model_cls)
410
+ return self.reject(f"unresolved annotations on {model_cls.__name__}")
411
+
412
+ keys: dict[str, str] = {}
413
+ never: set[str] = set()
414
+ claims: dict[str, int] = {}
415
+ for field_name, field_info in model_cls.model_fields.items():
416
+ if not self.metadata_is_plain(field_info.metadata):
417
+ self._visiting.discard(model_cls)
418
+ return False
419
+ key = _field_dump_key(model_cls, field_name, field_info)
420
+ keys[field_name] = key
421
+ if field_info.exclude is True:
422
+ never.add(field_name)
423
+ continue
424
+ claims[key] = claims.get(key, 0) + 1
425
+ annotation = (
426
+ field_info.annotation
427
+ if model_cls.__pydantic_root_model__ and field_name == "root"
428
+ else hints.get(field_name, field_info.annotation)
429
+ )
430
+ if not self.annotation_is_plain(annotation):
431
+ self._visiting.discard(model_cls)
432
+ return False
433
+ if not self.record_slot_classes(model_cls, field_name, annotation):
434
+ self._visiting.discard(model_cls)
435
+ return False
436
+ if field_info.exclude_if is not None:
437
+ never.add(field_name)
438
+
439
+ by_alias = bool(model_cls.model_config.get("serialize_by_alias", False))
440
+ computed: set[str] = set()
441
+ for field_name, field_info in model_cls.model_computed_fields.items():
442
+ key = field_info.alias if by_alias and field_info.alias is not None else field_name
443
+ computed.add(key)
444
+ claims[key] = claims.get(key, 0) + 1
445
+ if any(count > 1 for count in claims.values()):
446
+ self._visiting.discard(model_cls)
447
+ return self.reject(f"colliding dump keys on {model_cls.__name__}")
448
+
449
+ self._visiting.discard(model_cls)
450
+ self.approved_classes.add(model_cls)
451
+ self.dump_keys[model_cls] = keys
452
+ self.never_restore[model_cls] = frozenset(never)
453
+ self.computed_keys[model_cls] = frozenset(computed)
454
+ return True
455
+
456
+ def typed_dict_is_plain(self, td_cls: type) -> bool:
457
+ if td_cls in self.approved_classes or td_cls in self._visiting:
458
+ return True
459
+ self._visiting.add(td_cls)
460
+ config = _typed_dict_config(td_cls)
461
+ if isinstance(config, Mapping) and config.get("alias_generator") is not None:
462
+ self._visiting.discard(td_cls)
463
+ return self.reject(f"alias generator on {td_cls.__name__}")
464
+ try:
465
+ hints = typing.get_type_hints(td_cls, include_extras=True)
466
+ except Exception:
467
+ self._visiting.discard(td_cls)
468
+ return self.reject(f"unresolved annotations on {td_cls.__name__}")
469
+ keys: dict[str, str] = {}
470
+ for member, annotation in hints.items():
471
+ field_info = _annotation_field_info(annotation)
472
+ if field_info is not None:
473
+ if not self.metadata_is_plain(field_info.metadata):
474
+ self._visiting.discard(td_cls)
475
+ return False
476
+ if (
477
+ field_info.alias is not None
478
+ or field_info.serialization_alias is not None
479
+ or field_info.validation_alias is not None
480
+ ):
481
+ self._visiting.discard(td_cls)
482
+ return self.reject(f"alias metadata on {td_cls.__name__}.{member}")
483
+ if field_info.exclude is True or field_info.exclude_if is not None:
484
+ self._visiting.discard(td_cls)
485
+ return self.reject(f"exclusion metadata on {td_cls.__name__}.{member}")
486
+ if not field_info.is_required():
487
+ self._visiting.discard(td_cls)
488
+ return self.reject(f"default metadata on {td_cls.__name__}.{member}")
489
+ if not self.annotation_is_plain(annotation):
490
+ self._visiting.discard(td_cls)
491
+ return False
492
+ keys[member] = member
493
+ if not self.record_slot_classes(td_cls, member, annotation):
494
+ self._visiting.discard(td_cls)
495
+ return False
496
+ extra_items = td_cls.__dict__.get("__extra_items__", typing_extensions.NoExtraItems)
497
+ no_extra_items = typing.__dict__.get("NoExtraItems", typing_extensions.NoExtraItems)
498
+ if (
499
+ extra_items is not typing_extensions.NoExtraItems
500
+ and extra_items is not no_extra_items
501
+ and not self.annotation_is_plain(extra_items)
502
+ ):
503
+ self._visiting.discard(td_cls)
504
+ return False
505
+ self._visiting.discard(td_cls)
506
+ self.approved_classes.add(td_cls)
507
+ self.dump_keys[td_cls] = keys
508
+ self.never_restore[td_cls] = frozenset()
509
+ self.computed_keys[td_cls] = frozenset()
510
+ return True
511
+
512
+ def dataclass_is_plain(self, dataclass_cls: type) -> bool:
513
+ if dataclass_cls in self.approved_classes or dataclass_cls in self._visiting:
514
+ return True
515
+ self._visiting.add(dataclass_cls)
516
+ if _has_custom_core_schema(dataclass_cls):
517
+ self._visiting.discard(dataclass_cls)
518
+ return self.reject(f"custom core schema on {dataclass_cls.__name__}")
519
+ has_serializers, computed_fields = _dataclass_decorator_state(dataclass_cls)
520
+ if has_serializers:
521
+ self._visiting.discard(dataclass_cls)
522
+ return self.reject(f"serializer decorator on {dataclass_cls.__name__}")
523
+ if any(info.alias is not None for info in computed_fields.values()):
524
+ self._visiting.discard(dataclass_cls)
525
+ return self.reject(f"computed-field alias on {dataclass_cls.__name__}")
526
+ compiled_config = _dataclass_mro_attribute(dataclass_cls, "__pydantic_config__")
527
+ if compiled_config is not None and compiled_config[0].get("alias_generator") is not None:
528
+ self._visiting.discard(dataclass_cls)
529
+ return self.reject(f"alias generator on {dataclass_cls.__name__}")
530
+ try:
531
+ hints = typing.get_type_hints(dataclass_cls, include_extras=True)
532
+ except Exception:
533
+ self._visiting.discard(dataclass_cls)
534
+ return self.reject(f"unresolved annotations on {dataclass_cls.__name__}")
535
+
536
+ keys: dict[str, str] = {}
537
+ never: set[str] = set()
538
+ null_restorable: set[str] = set()
539
+ required_fields: set[str] = set()
540
+ # Callers admit only classes already proven by dataclasses.is_dataclass().
541
+ for dataclass_field in dataclasses.fields(dataclass_cls): # ty: ignore[invalid-argument-type]
542
+ name = dataclass_field.name
543
+ annotation = hints.get(name, dataclass_field.type)
544
+ field_info = _dataclass_field_info(dataclass_cls, dataclass_field, annotation)
545
+ if not self.metadata_is_plain(field_info.metadata):
546
+ self._visiting.discard(dataclass_cls)
547
+ return False
548
+ if (
549
+ field_info.alias is not None
550
+ or field_info.serialization_alias is not None
551
+ or field_info.validation_alias is not None
552
+ ):
553
+ self._visiting.discard(dataclass_cls)
554
+ return self.reject(f"alias metadata on {dataclass_cls.__name__}.{name}")
555
+ keys[name] = name
556
+ if not dataclass_field.init or field_info.exclude is True or field_info.exclude_if is not None:
557
+ never.add(name)
558
+ continue
559
+ if name in computed_fields:
560
+ self._visiting.discard(dataclass_cls)
561
+ return self.reject(f"computed-field collision on {dataclass_cls.__name__}.{name}")
562
+ if not self.annotation_is_plain(annotation):
563
+ self._visiting.discard(dataclass_cls)
564
+ return False
565
+ if not self.record_slot_classes(dataclass_cls, name, annotation):
566
+ self._visiting.discard(dataclass_cls)
567
+ return False
568
+ if field_info.is_required():
569
+ required_fields.add(name)
570
+ null_restorable.add(name)
571
+ elif field_info.default_factory is None and field_info.default is not None:
572
+ null_restorable.add(name)
573
+
574
+ self._visiting.discard(dataclass_cls)
575
+ self.approved_classes.add(dataclass_cls)
576
+ self.dump_keys[dataclass_cls] = keys
577
+ self.never_restore[dataclass_cls] = frozenset(never)
578
+ self.dataclass_null_restorable[dataclass_cls] = frozenset(null_restorable)
579
+ self.dataclass_required_fields[dataclass_cls] = frozenset(required_fields)
580
+ self.computed_keys[dataclass_cls] = frozenset()
581
+ return True
582
+
583
+
584
+ def _root_output_reason(model_cls: type[BaseModel]) -> str | None:
585
+ if model_cls.__pydantic_decorators__.model_serializers:
586
+ return f"model serializer on {model_cls.__name__}"
587
+ if _has_custom_core_schema(model_cls, BaseModel):
588
+ return f"custom core schema on {model_cls.__name__}"
589
+ if model_cls.model_dump is not BaseModel.model_dump:
590
+ return f"model_dump override on {model_cls.__name__}"
591
+ return None
592
+
593
+
594
+ def _node_has_untrusted_serialization(node: Mapping[str, Any]) -> bool:
595
+ """Whether a compiled node installs non-framework serialization."""
596
+ if not isinstance(node.get("type"), str):
597
+ return False
598
+ serialization = node.get("serialization")
599
+ if not isinstance(serialization, Mapping):
600
+ return False
601
+ if "function" not in str(serialization.get("type", "")):
602
+ return True
603
+ function = serialization.get("function")
604
+ module = getattr(function, "__module__", None)
605
+ return not isinstance(module, str) or not module.startswith("pydantic.")
606
+
607
+
608
+ def _compiled_definition_index(schema: Any, seen: set[int] | None = None) -> dict[str, Mapping[str, Any]]:
609
+ """Index named schemas from every compiled definitions table."""
610
+ if not isinstance(schema, (dict, list, tuple)):
611
+ return {}
612
+ seen = set() if seen is None else seen
613
+ if id(schema) in seen:
614
+ return {}
615
+ seen.add(id(schema))
616
+ indexed: dict[str, Mapping[str, Any]] = {}
617
+ if isinstance(schema, dict):
618
+ definitions = schema.get("definitions")
619
+ if isinstance(definitions, (list, tuple)):
620
+ for definition in definitions:
621
+ if isinstance(definition, Mapping) and isinstance(definition.get("ref"), str):
622
+ indexed.setdefault(definition["ref"], definition)
623
+ for value in schema.values():
624
+ indexed.update(_compiled_definition_index(value, seen))
625
+ else:
626
+ for value in schema:
627
+ indexed.update(_compiled_definition_index(value, seen))
628
+ return indexed
629
+
630
+
631
+ def _schema_has_untrusted_serialization(
632
+ schema: Any,
633
+ seen: set[int] | None = None,
634
+ definitions: Mapping[str, Mapping[str, Any]] | None = None,
635
+ ) -> bool:
636
+ """Scan a compiled core-schema graph for non-framework serialization."""
637
+ if not isinstance(schema, (dict, list, tuple)):
638
+ return False
639
+ seen = set() if seen is None else seen
640
+ if id(schema) in seen:
641
+ return False
642
+ seen.add(id(schema))
643
+ if isinstance(schema, dict):
644
+ if _node_has_untrusted_serialization(schema):
645
+ return True
646
+ if schema.get("type") == "definition-ref" and definitions is not None:
647
+ schema_ref = schema.get("schema_ref")
648
+ if not isinstance(schema_ref, str):
649
+ return True
650
+ target = definitions.get(schema_ref)
651
+ if target is None:
652
+ return True
653
+ return _schema_has_untrusted_serialization(target, seen, definitions)
654
+ return any(_schema_has_untrusted_serialization(value, seen, definitions) for value in schema.values())
655
+ return any(_schema_has_untrusted_serialization(value, seen, definitions) for value in schema)
656
+
657
+
658
+ _ROOT_SCHEMA_WRAPPERS = frozenset(
659
+ {
660
+ "definitions",
661
+ "function-before",
662
+ "function-after",
663
+ "function-wrap",
664
+ "function-plain",
665
+ "default",
666
+ "nullable",
667
+ }
668
+ )
669
+
670
+
671
+ def _root_schema_has_untrusted_serialization(schema: Any, seen: set[int] | None = None) -> bool:
672
+ """Check non-framework serialization only along the root wrapper spine."""
673
+ if not isinstance(schema, dict):
674
+ return False
675
+ seen = set() if seen is None else seen
676
+ if id(schema) in seen:
677
+ return False
678
+ seen.add(id(schema))
679
+ if _node_has_untrusted_serialization(schema):
680
+ return True
681
+ schema_type = schema.get("type")
682
+ if (schema_type == "model" and not bool(schema.get("root_model", False))) or schema_type in _ROOT_SCHEMA_WRAPPERS:
683
+ return _root_schema_has_untrusted_serialization(schema.get("schema"), seen)
684
+ if schema_type == "lax-or-strict":
685
+ return _root_schema_has_untrusted_serialization(
686
+ schema.get("lax_schema"), seen
687
+ ) or _root_schema_has_untrusted_serialization(schema.get("strict_schema"), seen)
688
+ if schema_type == "json-or-python":
689
+ return _root_schema_has_untrusted_serialization(
690
+ schema.get("json_schema"), seen
691
+ ) or _root_schema_has_untrusted_serialization(schema.get("python_schema"), seen)
692
+ return False
693
+
694
+
695
+ def _root_model_fields(schema: Any, seen: set[int] | None = None) -> Mapping[str, Any] | None:
696
+ """Locate the root model-fields mapping along the compiled wrapper spine."""
697
+ if not isinstance(schema, dict):
698
+ return None
699
+ seen = set() if seen is None else seen
700
+ if id(schema) in seen:
701
+ return None
702
+ seen.add(id(schema))
703
+ schema_type = schema.get("type")
704
+ if schema_type == "model-fields":
705
+ fields = schema.get("fields")
706
+ return fields if isinstance(fields, Mapping) else None
707
+ if schema_type == "model" or schema_type in _ROOT_SCHEMA_WRAPPERS:
708
+ return _root_model_fields(schema.get("schema"), seen)
709
+ if schema_type == "lax-or-strict":
710
+ lax_fields = _root_model_fields(schema.get("lax_schema"), seen.copy())
711
+ strict_fields = _root_model_fields(schema.get("strict_schema"), seen.copy())
712
+ if lax_fields is None:
713
+ return strict_fields
714
+ if strict_fields is None or lax_fields is strict_fields:
715
+ return lax_fields
716
+ return None
717
+ if schema_type == "json-or-python":
718
+ json_fields = _root_model_fields(schema.get("json_schema"), seen.copy())
719
+ python_fields = _root_model_fields(schema.get("python_schema"), seen.copy())
720
+ if json_fields is None:
721
+ return python_fields
722
+ if python_fields is None or json_fields is python_fields:
723
+ return json_fields
724
+ return None
725
+ return None
726
+
727
+
728
+ def _off_policy(model: type[BaseModel], reason: str) -> OverlayPolicy:
729
+ return OverlayPolicy(
730
+ tier=OverlayTier.OFF,
731
+ root_model=model,
732
+ approved_classes=frozenset(),
733
+ dump_keys=MappingProxyType({}),
734
+ never_restore=MappingProxyType({}),
735
+ dataclass_null_restorable=MappingProxyType({}),
736
+ dataclass_required_fields=MappingProxyType({}),
737
+ computed_keys=MappingProxyType({}),
738
+ slot_classes=MappingProxyType({}),
739
+ top_level_restorable=frozenset(),
740
+ reason=reason,
741
+ )
742
+
743
+
744
+ def _root_top_level_fields(
745
+ model_cls: type[BaseModel],
746
+ core_schema: Any,
747
+ ) -> tuple[frozenset[str], dict[str, str]]:
748
+ """Serializer-free root fields whose own null slot is statically keyed."""
749
+ decorators = model_cls.__pydantic_decorators__
750
+ serialized_fields: set[str] = set()
751
+ all_fields_serialized = False
752
+ for decorator in decorators.field_serializers.values():
753
+ if "*" in decorator.info.fields:
754
+ all_fields_serialized = True
755
+ serialized_fields.update(decorator.info.fields)
756
+ try:
757
+ hints = typing.get_type_hints(model_cls, include_extras=True)
758
+ except Exception:
759
+ hints = {}
760
+
761
+ keys = {name: _field_dump_key(model_cls, name, info) for name, info in model_cls.model_fields.items()}
762
+ computed_keys = {
763
+ info.alias if bool(model_cls.model_config.get("serialize_by_alias", False)) and info.alias is not None else name
764
+ for name, info in model_cls.model_computed_fields.items()
765
+ }
766
+ counts: dict[str, int] = {}
767
+ for name, key in keys.items():
768
+ if model_cls.model_fields[name].exclude is not True:
769
+ counts[key] = counts.get(key, 0) + 1
770
+ for key in computed_keys:
771
+ counts[key] = counts.get(key, 0) + 1
772
+
773
+ compiled_fields = _root_model_fields(core_schema)
774
+ if core_schema is not None and compiled_fields is None:
775
+ return (frozenset(), keys)
776
+ definitions = _compiled_definition_index(core_schema)
777
+ restorable: set[str] = set()
778
+ for name, info in model_cls.model_fields.items():
779
+ if (
780
+ all_fields_serialized
781
+ or name in serialized_fields
782
+ or info.exclude is True
783
+ or info.exclude_if is not None
784
+ or counts.get(keys[name], 0) != 1
785
+ ):
786
+ continue
787
+ annotation = hints.get(name, info.annotation)
788
+ if _annotation_contains_serializer(annotation):
789
+ continue
790
+ if compiled_fields is not None:
791
+ compiled_field = compiled_fields.get(name)
792
+ if not isinstance(compiled_field, Mapping) or _schema_has_untrusted_serialization(
793
+ compiled_field,
794
+ definitions=definitions,
795
+ ):
796
+ continue
797
+ restorable.add(name)
798
+ return (frozenset(restorable), keys)
799
+
800
+
801
+ def _annotation_contains_serializer(annotation: Any, seen: set[int] | None = None) -> bool:
802
+ seen = set() if seen is None else seen
803
+ if id(annotation) in seen:
804
+ return False
805
+ seen.add(id(annotation))
806
+ if _real_new_type(annotation):
807
+ return _annotation_contains_serializer(annotation.__supertype__, seen)
808
+ if isinstance(annotation, (typing.TypeAliasType, typing_extensions.TypeAliasType)):
809
+ return True
810
+ if get_origin(annotation) is Annotated:
811
+ base, *metadata = get_args(annotation)
812
+ return _metadata_contains_serializer(metadata) or _annotation_contains_serializer(base, seen)
813
+ return any(_annotation_contains_serializer(item, seen) for item in get_args(annotation))
814
+
815
+
816
+ def classify(model: type[BaseModel]) -> OverlayPolicy:
817
+ """Classify one tool input model against the finite structural whitelist."""
818
+ if not isinstance(model, type) or not issubclass(model, BaseModel):
819
+ raise TypeError("model must be a BaseModel subclass")
820
+ root_reason = _root_output_reason(model)
821
+ if root_reason is not None:
822
+ return _off_policy(model, root_reason)
823
+
824
+ core_schema = getattr(model, "__pydantic_core_schema__", None)
825
+ if _root_schema_has_untrusted_serialization(core_schema):
826
+ return _off_policy(model, f"root serialization in compiled schema for {model.__name__}")
827
+ compiled_reason = (
828
+ "compiled core schema unavailable"
829
+ if core_schema is None
830
+ else "serialization in compiled schema"
831
+ if _schema_has_untrusted_serialization(core_schema)
832
+ else None
833
+ )
834
+
835
+ builder = _PolicyBuilder()
836
+ graph_is_plain = builder.model_is_plain(model)
837
+ if graph_is_plain and compiled_reason is None:
838
+ tier = OverlayTier.FULL
839
+ restorable = frozenset()
840
+ else:
841
+ tier = OverlayTier.TOP_LEVEL
842
+ restorable, root_keys = _root_top_level_fields(model, core_schema)
843
+ builder.dump_keys[model] = root_keys
844
+ builder.never_restore[model] = frozenset(set(model.model_fields) - set(restorable))
845
+
846
+ return OverlayPolicy(
847
+ tier=tier,
848
+ root_model=model,
849
+ approved_classes=frozenset(builder.approved_classes),
850
+ dump_keys=MappingProxyType({cls: MappingProxyType(dict(keys)) for cls, keys in builder.dump_keys.items()}),
851
+ never_restore=MappingProxyType(dict(builder.never_restore)),
852
+ dataclass_null_restorable=MappingProxyType(dict(builder.dataclass_null_restorable)),
853
+ dataclass_required_fields=MappingProxyType(dict(builder.dataclass_required_fields)),
854
+ computed_keys=MappingProxyType(dict(builder.computed_keys)),
855
+ slot_classes=MappingProxyType(dict(builder.slot_classes)),
856
+ top_level_restorable=restorable,
857
+ reason=builder.reason or compiled_reason,
858
+ )
859
+
860
+
861
+ def _classification_fallback(model: type[BaseModel], reason: str) -> OverlayPolicy:
862
+ """Build the weakest usable policy after an unexpected classifier failure."""
863
+ try:
864
+ root_reason = _root_output_reason(model)
865
+ if root_reason is not None:
866
+ return _off_policy(model, root_reason)
867
+ core_schema = getattr(model, "__pydantic_core_schema__", None)
868
+ if _root_schema_has_untrusted_serialization(core_schema):
869
+ return _off_policy(model, reason)
870
+ restorable, keys = _root_top_level_fields(model, core_schema)
871
+ except Exception:
872
+ return _off_policy(model, reason)
873
+ return OverlayPolicy(
874
+ tier=OverlayTier.TOP_LEVEL,
875
+ root_model=model,
876
+ approved_classes=frozenset(),
877
+ dump_keys=MappingProxyType({model: MappingProxyType(keys)}),
878
+ never_restore=MappingProxyType({model: frozenset(set(model.model_fields) - set(restorable))}),
879
+ dataclass_null_restorable=MappingProxyType({}),
880
+ dataclass_required_fields=MappingProxyType({}),
881
+ computed_keys=MappingProxyType({}),
882
+ slot_classes=MappingProxyType({}),
883
+ top_level_restorable=restorable,
884
+ reason=reason,
885
+ )
886
+
887
+
888
+ def _model_dynamic_collisions(value: BaseModel, policy: OverlayPolicy) -> set[str]:
889
+ extras = value.model_extra or {}
890
+ if not extras:
891
+ return set()
892
+ cls = type(value)
893
+ claimed = {
894
+ key for name, key in policy.dump_keys.get(cls, {}).items() if cls.model_fields[name].exclude is not True
895
+ } | set(policy.computed_keys.get(cls, frozenset()))
896
+ return claimed.intersection(extras)
897
+
898
+
899
+ def _restore_full(
900
+ value: Any,
901
+ dumped: Any,
902
+ policy: OverlayPolicy,
903
+ allowed_classes: frozenset[type],
904
+ ) -> Any:
905
+ if isinstance(value, BaseModel):
906
+ cls = type(value)
907
+ if cls not in policy.approved_classes or cls not in allowed_classes:
908
+ return dumped
909
+ if cls.__pydantic_root_model__:
910
+ root = value.__dict__.get("root")
911
+ root_classes = policy.slot_classes.get((cls, "root"), frozenset())
912
+ return _restore_full(root, dumped, policy, root_classes)
913
+ if not isinstance(dumped, dict):
914
+ return dumped
915
+ keys = policy.dump_keys[cls]
916
+ never = policy.never_restore.get(cls, frozenset())
917
+ extras = value.model_extra or {}
918
+ collisions = _model_dynamic_collisions(value, policy)
919
+ declared_fields_set = value.model_fields_set.intersection(cls.model_fields)
920
+ for name in declared_fields_set:
921
+ if name in never or name not in keys:
922
+ continue
923
+ field_value = value.__dict__.get(name)
924
+ key = keys[name]
925
+ if key in collisions:
926
+ continue
927
+ if field_value is None:
928
+ if key not in dumped:
929
+ dumped[key] = None
930
+ elif key in dumped:
931
+ slot_classes = policy.slot_classes.get((cls, name), frozenset())
932
+ dumped[key] = _restore_full(field_value, dumped[key], policy, slot_classes)
933
+ for key, extra_value in extras.items():
934
+ if key in collisions:
935
+ continue
936
+ if extra_value is None:
937
+ if key not in dumped:
938
+ dumped[key] = None
939
+ elif key in dumped:
940
+ dumped[key] = _restore_full(extra_value, dumped[key], policy, frozenset())
941
+ return dumped
942
+
943
+ if dataclasses.is_dataclass(value) and not isinstance(value, type):
944
+ cls = type(value)
945
+ if cls not in policy.approved_classes or cls not in allowed_classes or not isinstance(dumped, dict):
946
+ return dumped
947
+ keys = policy.dump_keys[cls]
948
+ never = policy.never_restore.get(cls, frozenset())
949
+ null_restorable = policy.dataclass_null_restorable.get(cls, frozenset())
950
+ required_fields = policy.dataclass_required_fields.get(cls, frozenset())
951
+ for field in dataclasses.fields(cls):
952
+ name = field.name
953
+ if name in never or name not in keys:
954
+ continue
955
+ try:
956
+ field_value = object.__getattribute__(value, name)
957
+ except AttributeError:
958
+ continue
959
+ key = keys[name]
960
+ if field_value is None:
961
+ if name in null_restorable and key not in dumped:
962
+ dumped[key] = None
963
+ elif name in required_fields and key in dumped:
964
+ slot_classes = policy.slot_classes.get((cls, name), frozenset())
965
+ dumped[key] = _restore_full(field_value, dumped[key], policy, slot_classes)
966
+ return dumped
967
+
968
+ if isinstance(value, dict):
969
+ if not isinstance(dumped, dict):
970
+ return dumped
971
+ for key, item in value.items():
972
+ if item is None:
973
+ if key not in dumped:
974
+ dumped[key] = None
975
+ elif key in dumped:
976
+ dumped[key] = _restore_full(item, dumped[key], policy, allowed_classes)
977
+ return dumped
978
+
979
+ if isinstance(value, (list, tuple)):
980
+ expected_type = list if isinstance(value, list) else tuple
981
+ if type(dumped) is not expected_type:
982
+ return dumped
983
+ none_count = sum(item is None for item in value)
984
+ if len(dumped) == len(value):
985
+ restored = list(dumped)
986
+ for index, item in enumerate(value):
987
+ if item is not None:
988
+ restored[index] = _restore_full(item, restored[index], policy, allowed_classes)
989
+ elif len(dumped) == len(value) - none_count:
990
+ restored = []
991
+ dumped_items = iter(dumped)
992
+ for item in value:
993
+ if item is None:
994
+ restored.append(None)
995
+ else:
996
+ restored.append(_restore_full(item, next(dumped_items), policy, allowed_classes))
997
+ else:
998
+ return dumped
999
+ return restored if expected_type is list else tuple(restored)
1000
+
1001
+ if isinstance(value, set) and isinstance(dumped, set):
1002
+ if None in value:
1003
+ dumped.add(None)
1004
+ return dumped
1005
+ if isinstance(value, frozenset) and isinstance(dumped, frozenset):
1006
+ return dumped | {None} if None in value else dumped
1007
+ return dumped
1008
+
1009
+
1010
+ def restore_explicit_nulls(validated: BaseModel, dumped: dict[str, Any], policy: OverlayPolicy) -> dict[str, Any]:
1011
+ """Insert statically approved explicit nulls without rewriting dump data."""
1012
+ if policy.tier is OverlayTier.OFF or type(validated) is not policy.root_model:
1013
+ return dumped
1014
+ if policy.tier is OverlayTier.TOP_LEVEL:
1015
+ keys = policy.dump_keys.get(policy.root_model, {})
1016
+ for name in validated.model_fields_set.intersection(policy.top_level_restorable):
1017
+ value = validated.__dict__.get(name)
1018
+ key = keys.get(name)
1019
+ if value is None and key is not None and key not in dumped:
1020
+ dumped[key] = None
1021
+ return dumped
1022
+ restored = _restore_full(validated, dumped, policy, frozenset({policy.root_model}))
1023
+ return restored if isinstance(restored, dict) else dumped
1024
+
1025
+
1026
+ __all__ = ["OverlayPolicy", "OverlayTier", "classify", "restore_explicit_nulls"]