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.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- 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"]
|