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
chrys/kernel/tools.py
ADDED
|
@@ -0,0 +1,1061 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
3
|
+
# Contains code adapted from Microsoft Agent Framework (MIT License; see NOTICE).
|
|
4
|
+
|
|
5
|
+
"""Chrys-owned FunctionTool and tool-normalization surface.
|
|
6
|
+
|
|
7
|
+
This module provides ``FunctionTool`` construction, schema generation,
|
|
8
|
+
serialization and invocation,
|
|
9
|
+
``SKIP_PARSING``, ``tool()``, and ``normalize_tools()``. Tool-invocation
|
|
10
|
+
telemetry lives in ``_tool_execution.py``; ``FunctionTool.invoke``
|
|
11
|
+
intentionally stays free of observability and decorative logging.
|
|
12
|
+
Explicit-null restoration is classified and applied by the finite static
|
|
13
|
+
policy in ``_null_overlay.py``.
|
|
14
|
+
|
|
15
|
+
HARD RULE: kernel modules may import only the stdlib, intra-package modules,
|
|
16
|
+
allowed third-party packages, and downward ``chrys.foundation.*`` modules.
|
|
17
|
+
Sibling or upward ``chrys.*`` imports remain forbidden.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import contextvars
|
|
24
|
+
import copy
|
|
25
|
+
import inspect
|
|
26
|
+
import logging
|
|
27
|
+
import types
|
|
28
|
+
import typing
|
|
29
|
+
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
|
|
30
|
+
from contextlib import suppress
|
|
31
|
+
from functools import reduce, wraps
|
|
32
|
+
from operator import or_
|
|
33
|
+
from typing import (
|
|
34
|
+
Annotated,
|
|
35
|
+
Any,
|
|
36
|
+
ClassVar,
|
|
37
|
+
Final,
|
|
38
|
+
Literal,
|
|
39
|
+
TypedDict,
|
|
40
|
+
TypeGuard,
|
|
41
|
+
get_args,
|
|
42
|
+
get_origin,
|
|
43
|
+
overload,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
from pydantic import (
|
|
47
|
+
BaseModel,
|
|
48
|
+
BeforeValidator,
|
|
49
|
+
Field,
|
|
50
|
+
ValidationError,
|
|
51
|
+
create_model,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
from chrys.foundation.text.model_json import model_json
|
|
55
|
+
|
|
56
|
+
from ._content import Content
|
|
57
|
+
from ._null_overlay import (
|
|
58
|
+
OverlayPolicy,
|
|
59
|
+
OverlayTier,
|
|
60
|
+
_classification_fallback,
|
|
61
|
+
classify,
|
|
62
|
+
restore_explicit_nulls,
|
|
63
|
+
)
|
|
64
|
+
from ._serialization import SerializationMixin
|
|
65
|
+
from .exceptions import ModelVisibleToolError, ToolException
|
|
66
|
+
from .middleware import FunctionInvocationContext
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"SKIP_PARSING",
|
|
70
|
+
"FunctionInvocationConfiguration",
|
|
71
|
+
"FunctionTool",
|
|
72
|
+
"SyncToolCancelledAfterCompletion",
|
|
73
|
+
"ToolException",
|
|
74
|
+
"ToolTypes",
|
|
75
|
+
"normalize_tools",
|
|
76
|
+
"tool",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
logger = logging.getLogger(__name__)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SyncToolCancelledAfterCompletion(asyncio.CancelledError):
|
|
83
|
+
"""Cancellation that landed after a threaded sync tool already returned.
|
|
84
|
+
|
|
85
|
+
The worker thread finished — side effects are done and the return value
|
|
86
|
+
exists — but the invoking task was cancelled before its ``await`` could
|
|
87
|
+
resume and deliver it. ``completed_result`` carries that value so the
|
|
88
|
+
tool loop can journal it before cancellation proceeds; plain
|
|
89
|
+
``except asyncio.CancelledError`` handlers still treat this as a normal
|
|
90
|
+
cancellation.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, completed_result: Any) -> None:
|
|
94
|
+
super().__init__()
|
|
95
|
+
self.completed_result = completed_result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class _SkipParsingSentinel:
|
|
99
|
+
"""Sentinel meaning ``FunctionTool.invoke`` should return raw values."""
|
|
100
|
+
|
|
101
|
+
_instance: ClassVar[_SkipParsingSentinel | None] = None
|
|
102
|
+
|
|
103
|
+
def __new__(cls) -> _SkipParsingSentinel:
|
|
104
|
+
if cls._instance is None:
|
|
105
|
+
cls._instance = super().__new__(cls)
|
|
106
|
+
return cls._instance
|
|
107
|
+
|
|
108
|
+
def __repr__(self) -> str:
|
|
109
|
+
return "SKIP_PARSING"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
SKIP_PARSING: Final[_SkipParsingSentinel] = _SkipParsingSentinel()
|
|
113
|
+
|
|
114
|
+
type ResultParser = Callable[[Any], str | list[Content]]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _is_result_parser(value: object) -> TypeGuard[ResultParser]:
|
|
118
|
+
"""Narrow a configured parser after the runtime callable check."""
|
|
119
|
+
return callable(value)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _is_skip_parsing_sentinel(value: object) -> bool:
|
|
123
|
+
"""Accept SKIP_PARSING and foreign sentinels with the same class name and representation."""
|
|
124
|
+
return value is SKIP_PARSING or (type(value).__name__ == "_SkipParsingSentinel" and repr(value) == "SKIP_PARSING")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _annotation_includes_function_invocation_context(annotation: Any) -> bool:
|
|
128
|
+
"""Check whether an annotation resolves to FunctionInvocationContext."""
|
|
129
|
+
candidates = get_args(annotation) or (annotation,)
|
|
130
|
+
return any(
|
|
131
|
+
candidate is FunctionInvocationContext
|
|
132
|
+
or candidate == "FunctionInvocationContext"
|
|
133
|
+
or (
|
|
134
|
+
getattr(candidate, "__name__", None) == "FunctionInvocationContext"
|
|
135
|
+
and str(getattr(candidate, "__module__", "")).endswith("middleware")
|
|
136
|
+
)
|
|
137
|
+
for candidate in candidates
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _parse_annotation(annotation: Any) -> Any:
|
|
142
|
+
"""Convert string annotation metadata into Pydantic field descriptions."""
|
|
143
|
+
origin = get_origin(annotation)
|
|
144
|
+
if origin is not None:
|
|
145
|
+
if origin is Literal:
|
|
146
|
+
return annotation
|
|
147
|
+
|
|
148
|
+
args = get_args(annotation)
|
|
149
|
+
if len(args) > 1 and isinstance(args[1], str):
|
|
150
|
+
args_list = list(args)
|
|
151
|
+
if len(args_list) == 2:
|
|
152
|
+
return Annotated.__getitem__((args_list[0], Field(description=args_list[1])))
|
|
153
|
+
return Annotated.__getitem__((args_list[0], Field(description=args_list[1]), tuple(args_list[2:])))
|
|
154
|
+
return annotation
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _reject_bool_as_int(value: Any) -> Any:
|
|
158
|
+
"""Reject bool before Pydantic applies its otherwise useful int coercions."""
|
|
159
|
+
if isinstance(value, bool):
|
|
160
|
+
raise ValueError("boolean values are not valid integers")
|
|
161
|
+
return value
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _make_bool_int_literal_guard(values: tuple[Any, ...]) -> Callable[[Any], Any]:
|
|
165
|
+
"""Require exact type+value literal matches for raw bool/int inputs.
|
|
166
|
+
|
|
167
|
+
Pydantic matches ``Literal`` members by equality, and ``True == 1`` /
|
|
168
|
+
``False == 0`` in Python — so ``Literal[0, True]`` would otherwise accept
|
|
169
|
+
``False`` (as ``0``) and ``1`` (as ``True``). Non-bool/int inputs pass
|
|
170
|
+
through so Pydantic keeps its own lax handling for them.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def _guard(value: Any) -> Any:
|
|
174
|
+
if type(value) in (bool, int) and not any(type(member) is type(value) and member == value for member in values):
|
|
175
|
+
raise ValueError("literal values require an exact type and value match")
|
|
176
|
+
return value
|
|
177
|
+
|
|
178
|
+
return _guard
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _prevent_bool_int_coercion(annotation: Any) -> Any:
|
|
182
|
+
"""Recursively guard int annotations while preserving other Pydantic coercions."""
|
|
183
|
+
if annotation is int:
|
|
184
|
+
return Annotated[int, BeforeValidator(_reject_bool_as_int)]
|
|
185
|
+
|
|
186
|
+
origin = get_origin(annotation)
|
|
187
|
+
if origin is Literal:
|
|
188
|
+
values = get_args(annotation)
|
|
189
|
+
if any(type(value) in (bool, int) for value in values):
|
|
190
|
+
return Annotated[annotation, BeforeValidator(_make_bool_int_literal_guard(values))]
|
|
191
|
+
return annotation
|
|
192
|
+
if origin is None:
|
|
193
|
+
return annotation
|
|
194
|
+
|
|
195
|
+
args = get_args(annotation)
|
|
196
|
+
if origin is Annotated:
|
|
197
|
+
base, *metadata = args
|
|
198
|
+
transformed = _prevent_bool_int_coercion(base)
|
|
199
|
+
if get_origin(transformed) is Annotated:
|
|
200
|
+
transformed_base, *transformed_metadata = get_args(transformed)
|
|
201
|
+
return Annotated[transformed_base, *metadata, *transformed_metadata]
|
|
202
|
+
return Annotated[transformed, *metadata]
|
|
203
|
+
|
|
204
|
+
transformed_args = tuple(_prevent_bool_int_coercion(arg) for arg in args)
|
|
205
|
+
if transformed_args == args:
|
|
206
|
+
return annotation
|
|
207
|
+
if origin is types.UnionType:
|
|
208
|
+
return reduce(or_, transformed_args)
|
|
209
|
+
return origin[transformed_args]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _matches_json_schema_type(value: Any, schema_type: str, *, tuples_as_arrays: bool = False) -> bool:
|
|
213
|
+
"""Check a value against a JSON Schema primitive type."""
|
|
214
|
+
match schema_type:
|
|
215
|
+
case "string":
|
|
216
|
+
return isinstance(value, str)
|
|
217
|
+
case "integer":
|
|
218
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
219
|
+
case "number":
|
|
220
|
+
return (isinstance(value, int | float)) and not isinstance(value, bool)
|
|
221
|
+
case "boolean":
|
|
222
|
+
return isinstance(value, bool)
|
|
223
|
+
case "array":
|
|
224
|
+
# Python-mode ``model_dump`` preserves tuple-typed fields, so the
|
|
225
|
+
# validated paths opt in via ``tuples_as_arrays``. Schema-supplied
|
|
226
|
+
# raw arguments keep the strict list-only contract.
|
|
227
|
+
if tuples_as_arrays and isinstance(value, tuple):
|
|
228
|
+
return True
|
|
229
|
+
return isinstance(value, list)
|
|
230
|
+
case "object":
|
|
231
|
+
return isinstance(value, dict)
|
|
232
|
+
case "null":
|
|
233
|
+
return value is None
|
|
234
|
+
case _:
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _validate_arguments_against_schema(
|
|
239
|
+
*,
|
|
240
|
+
arguments: Mapping[str, Any],
|
|
241
|
+
schema: Mapping[str, Any],
|
|
242
|
+
tool_name: str,
|
|
243
|
+
tuples_as_arrays: bool = False,
|
|
244
|
+
values_prevalidated: bool = False,
|
|
245
|
+
) -> dict[str, Any]:
|
|
246
|
+
"""Apply lightweight schema checks at the callable argument boundary."""
|
|
247
|
+
parsed_arguments = dict(arguments)
|
|
248
|
+
|
|
249
|
+
required_fields = [field for field in schema.get("required", []) if isinstance(field, str)]
|
|
250
|
+
missing_fields = [field for field in required_fields if field not in parsed_arguments]
|
|
251
|
+
if missing_fields:
|
|
252
|
+
raise TypeError(f"Missing required argument(s) for '{tool_name}': {', '.join(sorted(missing_fields))}")
|
|
253
|
+
|
|
254
|
+
if values_prevalidated:
|
|
255
|
+
return parsed_arguments
|
|
256
|
+
|
|
257
|
+
properties: Mapping[str, Any] = schema.get("properties", {})
|
|
258
|
+
if schema.get("additionalProperties") is False:
|
|
259
|
+
unexpected_fields = sorted(field for field in parsed_arguments if field not in properties)
|
|
260
|
+
if unexpected_fields:
|
|
261
|
+
raise TypeError(f"Unexpected argument(s) for '{tool_name}': {', '.join(unexpected_fields)}")
|
|
262
|
+
|
|
263
|
+
for field_name, field_value in parsed_arguments.items():
|
|
264
|
+
if not isinstance(properties.get(field_name), dict):
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
enum_values = properties.get(field_name, {}).get("enum")
|
|
268
|
+
if isinstance(enum_values, list) and enum_values and field_value not in enum_values:
|
|
269
|
+
raise TypeError(
|
|
270
|
+
f"Invalid value for '{field_name}' in '{tool_name}': {field_value!r} is not in {enum_values!r}"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
schema_type = properties.get(field_name, {}).get("type")
|
|
274
|
+
if isinstance(schema_type, str):
|
|
275
|
+
if not _matches_json_schema_type(field_value, schema_type, tuples_as_arrays=tuples_as_arrays):
|
|
276
|
+
raise TypeError(
|
|
277
|
+
f"Invalid type for '{field_name}' in '{tool_name}': "
|
|
278
|
+
f"expected {schema_type}, got {type(field_value).__name__}"
|
|
279
|
+
)
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
if isinstance(schema_type, list):
|
|
283
|
+
allowed_types: list[str] = [item for item in schema_type if isinstance(item, str)]
|
|
284
|
+
if allowed_types and not any(
|
|
285
|
+
_matches_json_schema_type(field_value, item, tuples_as_arrays=tuples_as_arrays)
|
|
286
|
+
for item in allowed_types
|
|
287
|
+
):
|
|
288
|
+
raise TypeError(
|
|
289
|
+
f"Invalid type for '{field_name}' in '{tool_name}': expected one of "
|
|
290
|
+
f"{allowed_types}, got {type(field_value).__name__}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return parsed_arguments
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _coerce_content(value: Any) -> Content | None:
|
|
297
|
+
"""Convert compatible foreign content through its serialized shape into Chrys Content."""
|
|
298
|
+
if isinstance(value, Content):
|
|
299
|
+
return value
|
|
300
|
+
to_dict = getattr(value, "to_dict", None)
|
|
301
|
+
if not callable(to_dict) or not hasattr(value, "type"):
|
|
302
|
+
return None
|
|
303
|
+
try:
|
|
304
|
+
payload = to_dict(exclude_none=False)
|
|
305
|
+
except TypeError:
|
|
306
|
+
payload = to_dict()
|
|
307
|
+
if not isinstance(payload, Mapping) or not isinstance(payload.get("type"), str):
|
|
308
|
+
return None
|
|
309
|
+
try:
|
|
310
|
+
converted = Content.from_dict(payload)
|
|
311
|
+
except Exception:
|
|
312
|
+
return None
|
|
313
|
+
converted.raw_representation = getattr(value, "raw_representation", None)
|
|
314
|
+
return converted
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class FunctionTool(SerializationMixin):
|
|
318
|
+
"""A callable tool with argument validation, result parsing and serializable schema."""
|
|
319
|
+
|
|
320
|
+
# Inherited class identifier: serialized payloads always carry the
|
|
321
|
+
# instance ``type`` field ("function_tool"), so subclasses must validate
|
|
322
|
+
# against that same identifier on from_dict — the snake_case class-name
|
|
323
|
+
# fallback would reject every payload a subclass itself serialized.
|
|
324
|
+
TYPE: ClassVar[str] = "function_tool"
|
|
325
|
+
INJECTABLE: ClassVar[set[str]] = {"func"}
|
|
326
|
+
DEFAULT_EXCLUDE: ClassVar[set[str]] = {
|
|
327
|
+
"additional_properties",
|
|
328
|
+
"input_model",
|
|
329
|
+
"_invocation_duration_histogram",
|
|
330
|
+
"_cached_parameters",
|
|
331
|
+
"_input_schema",
|
|
332
|
+
"_serialization_schema",
|
|
333
|
+
"_serialization_schema_cached",
|
|
334
|
+
"_null_overlay_policy",
|
|
335
|
+
"_null_overlay_policy_cached",
|
|
336
|
+
"_schema_supplied",
|
|
337
|
+
"_invoke_sync_on_event_loop",
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def __init__(
|
|
341
|
+
self,
|
|
342
|
+
*,
|
|
343
|
+
name: str,
|
|
344
|
+
description: str = "",
|
|
345
|
+
kind: str | None = None,
|
|
346
|
+
max_invocations: int | None = None,
|
|
347
|
+
max_invocation_exceptions: int | None = None,
|
|
348
|
+
additional_properties: dict[str, Any] | None = None,
|
|
349
|
+
func: Callable[..., Any] | None = None,
|
|
350
|
+
input_model: type[BaseModel] | Mapping[str, Any] | None = None,
|
|
351
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | object | None = None,
|
|
352
|
+
**kwargs: Any,
|
|
353
|
+
) -> None:
|
|
354
|
+
self.name = name
|
|
355
|
+
self.description = description
|
|
356
|
+
self.kind = kind
|
|
357
|
+
self.additional_properties = additional_properties
|
|
358
|
+
self._invoke_sync_on_event_loop = False
|
|
359
|
+
for key, value in kwargs.items():
|
|
360
|
+
setattr(self, key, value)
|
|
361
|
+
|
|
362
|
+
self.func = func
|
|
363
|
+
self._instance = None
|
|
364
|
+
self._context_parameter_name: str | None = None
|
|
365
|
+
self._input_model_explicitly_provided = input_model is not None
|
|
366
|
+
if self.func:
|
|
367
|
+
self._discover_injected_parameters()
|
|
368
|
+
|
|
369
|
+
self._input_schema_cached: dict[str, Any] | None = None
|
|
370
|
+
self._serialization_schema_cached: dict[str, Any] | None = None
|
|
371
|
+
self._null_overlay_policy_cached: OverlayPolicy | None = None
|
|
372
|
+
if isinstance(input_model, Mapping):
|
|
373
|
+
self._schema_supplied = True
|
|
374
|
+
self._input_schema_cached = dict(input_model)
|
|
375
|
+
self.input_model: type[BaseModel] | None = None
|
|
376
|
+
else:
|
|
377
|
+
self._schema_supplied = False
|
|
378
|
+
self.input_model = self._resolve_input_model(input_model)
|
|
379
|
+
self._cached_parameters: dict[str, Any] | None = None
|
|
380
|
+
if max_invocations is not None and max_invocations < 1:
|
|
381
|
+
raise ValueError("max_invocations must be at least 1 or None.")
|
|
382
|
+
if max_invocation_exceptions is not None and max_invocation_exceptions < 1:
|
|
383
|
+
raise ValueError("max_invocation_exceptions must be at least 1 or None.")
|
|
384
|
+
self.max_invocations = max_invocations
|
|
385
|
+
self.invocation_count = 0
|
|
386
|
+
self.max_invocation_exceptions = max_invocation_exceptions
|
|
387
|
+
self.invocation_exception_count = 0
|
|
388
|
+
self._invocation_duration_histogram = None
|
|
389
|
+
self.type: Literal["function_tool"] = "function_tool"
|
|
390
|
+
self.result_parser = SKIP_PARSING if _is_skip_parsing_sentinel(result_parser) else result_parser
|
|
391
|
+
|
|
392
|
+
def _discover_injected_parameters(self) -> None:
|
|
393
|
+
"""Inspect the wrapped function for runtime injection parameters."""
|
|
394
|
+
func = self.func.func if isinstance(self.func, FunctionTool) else self.func
|
|
395
|
+
if func is None:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
signature = inspect.signature(func)
|
|
399
|
+
try:
|
|
400
|
+
type_hints = typing.get_type_hints(func)
|
|
401
|
+
except Exception:
|
|
402
|
+
type_hints = {name: param.annotation for name, param in signature.parameters.items()}
|
|
403
|
+
|
|
404
|
+
for name, param in signature.parameters.items():
|
|
405
|
+
if name in {"self", "cls"}:
|
|
406
|
+
continue
|
|
407
|
+
annotation = type_hints.get(name, param.annotation)
|
|
408
|
+
if self._is_context_parameter(name, annotation):
|
|
409
|
+
if self._context_parameter_name is not None:
|
|
410
|
+
raise ValueError(f"Function '{self.name}' defines multiple FunctionInvocationContext parameters.")
|
|
411
|
+
self._context_parameter_name = name
|
|
412
|
+
|
|
413
|
+
def _is_context_parameter(self, name: str, annotation: Any) -> bool:
|
|
414
|
+
"""Check whether a callable parameter should receive FunctionInvocationContext injection."""
|
|
415
|
+
if _annotation_includes_function_invocation_context(annotation):
|
|
416
|
+
return True
|
|
417
|
+
return self._input_model_explicitly_provided and name == "ctx" and annotation is inspect.Parameter.empty
|
|
418
|
+
|
|
419
|
+
def __str__(self) -> str:
|
|
420
|
+
"""Return a string representation of the tool."""
|
|
421
|
+
if self.description:
|
|
422
|
+
return f"{self.__class__.__name__}(name={self.name}, description={self.description})"
|
|
423
|
+
return f"{self.__class__.__name__}(name={self.name})"
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def bound_instance(self) -> object | None:
|
|
427
|
+
"""Return the owner of a descriptor-bound tool, or None for an unbound tool."""
|
|
428
|
+
return self._instance
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def declaration_only(self) -> bool:
|
|
432
|
+
"""Indicate whether the function is declaration only."""
|
|
433
|
+
declaration_flag = self.__dict__.get("_declaration_only", False)
|
|
434
|
+
if isinstance(declaration_flag, bool) and declaration_flag:
|
|
435
|
+
return True
|
|
436
|
+
return self.func is None
|
|
437
|
+
|
|
438
|
+
def __get__(self, obj: Any, objtype: type | None = None) -> FunctionTool:
|
|
439
|
+
"""Bind descriptor tools to their owning instance."""
|
|
440
|
+
if obj is None:
|
|
441
|
+
return self
|
|
442
|
+
|
|
443
|
+
if self.func is not None:
|
|
444
|
+
sig = inspect.signature(self.func)
|
|
445
|
+
params = list(sig.parameters.keys())
|
|
446
|
+
if params and params[0] in {"self", "cls"}:
|
|
447
|
+
bound_func = copy.copy(self)
|
|
448
|
+
bound_func._instance = obj
|
|
449
|
+
return bound_func
|
|
450
|
+
|
|
451
|
+
return self
|
|
452
|
+
|
|
453
|
+
def _resolve_input_model(self, input_model: type[BaseModel] | None) -> type[BaseModel]:
|
|
454
|
+
"""Resolve the input model for the function."""
|
|
455
|
+
if input_model is not None:
|
|
456
|
+
if inspect.isclass(input_model) and issubclass(input_model, BaseModel):
|
|
457
|
+
return input_model
|
|
458
|
+
raise TypeError("input_model must be a Pydantic BaseModel subclass or a JSON schema dict.")
|
|
459
|
+
|
|
460
|
+
if self.func is None:
|
|
461
|
+
return create_model(f"{self.name}_input")
|
|
462
|
+
|
|
463
|
+
func = self.func.func if isinstance(self.func, FunctionTool) else self.func
|
|
464
|
+
if func is None:
|
|
465
|
+
return create_model(f"{self.name}_input")
|
|
466
|
+
sig = inspect.signature(func)
|
|
467
|
+
try:
|
|
468
|
+
type_hints = typing.get_type_hints(func, include_extras=True)
|
|
469
|
+
except Exception:
|
|
470
|
+
type_hints = {}
|
|
471
|
+
fields: dict[str, Any] = {
|
|
472
|
+
pname: (
|
|
473
|
+
_prevent_bool_int_coercion(_parse_annotation(type_hints.get(pname, param.annotation)))
|
|
474
|
+
if type_hints.get(pname, param.annotation) is not inspect.Parameter.empty
|
|
475
|
+
else str,
|
|
476
|
+
param.default if param.default is not inspect.Parameter.empty else ...,
|
|
477
|
+
)
|
|
478
|
+
for pname, param in sig.parameters.items()
|
|
479
|
+
if pname not in {"self", "cls"}
|
|
480
|
+
and pname != self._context_parameter_name
|
|
481
|
+
and param.kind not in {inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD}
|
|
482
|
+
}
|
|
483
|
+
return create_model(f"{self.name}_input", **fields)
|
|
484
|
+
|
|
485
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
486
|
+
"""Call the wrapped function with the provided arguments."""
|
|
487
|
+
if self.declaration_only:
|
|
488
|
+
raise ToolException(f"Function '{self.name}' is declaration only and cannot be invoked.")
|
|
489
|
+
if self.max_invocations is not None and self.invocation_count >= self.max_invocations:
|
|
490
|
+
raise ToolException(
|
|
491
|
+
f"Function '{self.name}' has reached its maximum invocation limit, you can no longer use this tool."
|
|
492
|
+
)
|
|
493
|
+
if (
|
|
494
|
+
self.max_invocation_exceptions is not None
|
|
495
|
+
and self.invocation_exception_count >= self.max_invocation_exceptions
|
|
496
|
+
):
|
|
497
|
+
raise ToolException(
|
|
498
|
+
f"Function '{self.name}' has reached its maximum exception limit, "
|
|
499
|
+
f"you tried to use this tool too many times and it kept failing."
|
|
500
|
+
)
|
|
501
|
+
self.invocation_count += 1
|
|
502
|
+
try:
|
|
503
|
+
func = self.func
|
|
504
|
+
if func is None:
|
|
505
|
+
raise ToolException(f"Function '{self.name}' has no implementation.")
|
|
506
|
+
call_args = (self._instance, *args) if self._instance is not None else args
|
|
507
|
+
result = func(*call_args, **kwargs)
|
|
508
|
+
except Exception:
|
|
509
|
+
self.invocation_exception_count += 1
|
|
510
|
+
raise
|
|
511
|
+
if inspect.isawaitable(result):
|
|
512
|
+
return self._count_awaited_exception(result)
|
|
513
|
+
return result
|
|
514
|
+
|
|
515
|
+
async def _count_awaited_exception(self, awaitable: Awaitable[Any]) -> Any:
|
|
516
|
+
"""Await an async tool's result; its failure surfaces only here, so count it here."""
|
|
517
|
+
try:
|
|
518
|
+
return await awaitable
|
|
519
|
+
except Exception:
|
|
520
|
+
self.invocation_exception_count += 1
|
|
521
|
+
raise
|
|
522
|
+
|
|
523
|
+
async def _invoke_function(self, call_kwargs: Mapping[str, Any]) -> Any:
|
|
524
|
+
"""Run sync tools off the event loop during async invocation."""
|
|
525
|
+
func = self.func.func if isinstance(self.func, FunctionTool) else self.func
|
|
526
|
+
if inspect.iscoroutinefunction(func) or self._invoke_sync_on_event_loop:
|
|
527
|
+
res = self.__call__(**call_kwargs)
|
|
528
|
+
return await res if inspect.isawaitable(res) else res
|
|
529
|
+
|
|
530
|
+
# ``to_thread`` equivalent with a completion record the cancellation
|
|
531
|
+
# path can inspect: the worker may finish — side effects done, value
|
|
532
|
+
# produced — while this task is still suspended, and a cancellation
|
|
533
|
+
# arriving in that window would otherwise drop the completed result.
|
|
534
|
+
var_context = contextvars.copy_context()
|
|
535
|
+
completed: list[Any] = []
|
|
536
|
+
|
|
537
|
+
def _call_and_record() -> Any:
|
|
538
|
+
value = var_context.run(self.__call__, **call_kwargs)
|
|
539
|
+
completed.append(value)
|
|
540
|
+
return value
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
res = await asyncio.get_running_loop().run_in_executor(None, _call_and_record)
|
|
544
|
+
except asyncio.CancelledError:
|
|
545
|
+
if completed and not inspect.isawaitable(completed[0]):
|
|
546
|
+
raise SyncToolCancelledAfterCompletion(completed[0]) from None
|
|
547
|
+
raise
|
|
548
|
+
return await res if inspect.isawaitable(res) else res
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
def _null_overlay_policy(self) -> OverlayPolicy:
|
|
552
|
+
"""Get the input model's one-time static null-restore policy."""
|
|
553
|
+
if self._null_overlay_policy_cached is None:
|
|
554
|
+
if self.input_model is None:
|
|
555
|
+
raise RuntimeError("Schema-supplied tools do not have a null-overlay policy.")
|
|
556
|
+
classification_failed = False
|
|
557
|
+
try:
|
|
558
|
+
policy = classify(self.input_model)
|
|
559
|
+
except Exception as exc:
|
|
560
|
+
classification_failed = True
|
|
561
|
+
reason = f"classification failed with {type(exc).__name__}"
|
|
562
|
+
policy = _classification_fallback(self.input_model, reason)
|
|
563
|
+
logger.debug(
|
|
564
|
+
"Tool %s null-overlay classification failed; using %s restoration: %s",
|
|
565
|
+
self.name,
|
|
566
|
+
policy.tier.value,
|
|
567
|
+
reason,
|
|
568
|
+
exc_info=True,
|
|
569
|
+
)
|
|
570
|
+
if not classification_failed and policy.tier is not OverlayTier.FULL:
|
|
571
|
+
logger.debug(
|
|
572
|
+
"Tool %s uses %s explicit-null restoration: %s",
|
|
573
|
+
self.name,
|
|
574
|
+
policy.tier.value,
|
|
575
|
+
policy.reason or "nested annotation graph is outside the static whitelist",
|
|
576
|
+
)
|
|
577
|
+
self._null_overlay_policy_cached = policy
|
|
578
|
+
return self._null_overlay_policy_cached
|
|
579
|
+
|
|
580
|
+
def _dump_arguments(self, validated: BaseModel) -> dict[str, Any]:
|
|
581
|
+
"""Dump once, then apply the cached static explicit-null policy."""
|
|
582
|
+
if self.input_model is None:
|
|
583
|
+
raise RuntimeError("Schema-supplied tools do not use typed argument dumps.")
|
|
584
|
+
if type(validated) is self.input_model:
|
|
585
|
+
dumped = validated.model_dump(exclude_none=True)
|
|
586
|
+
else:
|
|
587
|
+
dumped = self.input_model.__pydantic_serializer__.to_python(validated, exclude_none=True)
|
|
588
|
+
if not isinstance(dumped, dict):
|
|
589
|
+
return dumped
|
|
590
|
+
return restore_explicit_nulls(validated, dumped, self._null_overlay_policy)
|
|
591
|
+
|
|
592
|
+
@overload
|
|
593
|
+
async def invoke(
|
|
594
|
+
self,
|
|
595
|
+
*,
|
|
596
|
+
arguments: BaseModel | Mapping[str, Any] | None = None,
|
|
597
|
+
context: FunctionInvocationContext | None = None,
|
|
598
|
+
tool_call_id: str | None = None,
|
|
599
|
+
skip_parsing: Literal[True],
|
|
600
|
+
**kwargs: Any,
|
|
601
|
+
) -> Any: ...
|
|
602
|
+
|
|
603
|
+
@overload
|
|
604
|
+
async def invoke(
|
|
605
|
+
self,
|
|
606
|
+
*,
|
|
607
|
+
arguments: BaseModel | Mapping[str, Any] | None = None,
|
|
608
|
+
context: FunctionInvocationContext | None = None,
|
|
609
|
+
tool_call_id: str | None = None,
|
|
610
|
+
skip_parsing: Literal[False] = False,
|
|
611
|
+
**kwargs: Any,
|
|
612
|
+
) -> list[Content]: ...
|
|
613
|
+
|
|
614
|
+
async def invoke(
|
|
615
|
+
self,
|
|
616
|
+
*,
|
|
617
|
+
arguments: BaseModel | Mapping[str, Any] | None = None,
|
|
618
|
+
context: FunctionInvocationContext | None = None,
|
|
619
|
+
tool_call_id: str | None = None,
|
|
620
|
+
skip_parsing: bool = False,
|
|
621
|
+
**kwargs: Any,
|
|
622
|
+
) -> list[Content] | Any:
|
|
623
|
+
"""Run the wrapped function and parse the result to Chrys Content."""
|
|
624
|
+
if self.declaration_only:
|
|
625
|
+
raise ToolException(f"Function '{self.name}' is declaration only and cannot be invoked.")
|
|
626
|
+
|
|
627
|
+
configured_parser = self.result_parser
|
|
628
|
+
skip_parsing = skip_parsing or _is_skip_parsing_sentinel(configured_parser)
|
|
629
|
+
custom_parser = configured_parser if _is_result_parser(configured_parser) else None
|
|
630
|
+
arguments_in_callable_keyspace = bool(kwargs.pop("_arguments_in_callable_keyspace", False))
|
|
631
|
+
|
|
632
|
+
parameter_names = set(self.parameters().get("properties", {}).keys())
|
|
633
|
+
direct_argument_kwargs = (
|
|
634
|
+
{key: value for key, value in kwargs.items() if key in parameter_names} if arguments is None else {}
|
|
635
|
+
)
|
|
636
|
+
runtime_kwargs = dict(context.kwargs) if context is not None else {}
|
|
637
|
+
unexpected_kwargs = {key: value for key, value in kwargs.items() if key not in direct_argument_kwargs}
|
|
638
|
+
if unexpected_kwargs:
|
|
639
|
+
unexpected_names = ", ".join(sorted(unexpected_kwargs))
|
|
640
|
+
raise TypeError(
|
|
641
|
+
f"Unexpected keyword argument(s) for tool '{self.name}': {unexpected_names}. "
|
|
642
|
+
"Pass runtime data via FunctionInvocationContext instead."
|
|
643
|
+
)
|
|
644
|
+
if arguments is None and direct_argument_kwargs:
|
|
645
|
+
arguments = direct_argument_kwargs
|
|
646
|
+
if arguments is None and context is not None:
|
|
647
|
+
arguments = context.arguments
|
|
648
|
+
|
|
649
|
+
if arguments is None:
|
|
650
|
+
validated_arguments: dict[str, Any] = {}
|
|
651
|
+
else:
|
|
652
|
+
# Tuple-typed fields only survive into the arguments via a
|
|
653
|
+
# python-mode ``model_dump``; raw schema-supplied arguments keep
|
|
654
|
+
# the strict list-only array contract.
|
|
655
|
+
typed_model_dump = False
|
|
656
|
+
try:
|
|
657
|
+
if arguments_in_callable_keyspace:
|
|
658
|
+
if self.input_model is None or self._schema_supplied or not isinstance(arguments, Mapping):
|
|
659
|
+
raise TypeError("Callable-keyspace arguments require a typed-model mapping.")
|
|
660
|
+
parsed_arguments = dict(arguments)
|
|
661
|
+
typed_model_dump = True
|
|
662
|
+
elif isinstance(arguments, Mapping):
|
|
663
|
+
parsed_arguments = dict(arguments)
|
|
664
|
+
if self.input_model is not None and not self._schema_supplied:
|
|
665
|
+
try:
|
|
666
|
+
validation_arguments = copy.deepcopy(parsed_arguments)
|
|
667
|
+
except Exception:
|
|
668
|
+
validation_arguments = parsed_arguments
|
|
669
|
+
validated = self.input_model.model_validate(validation_arguments)
|
|
670
|
+
parsed_arguments = self._dump_arguments(validated)
|
|
671
|
+
typed_model_dump = True
|
|
672
|
+
elif isinstance(arguments, BaseModel):
|
|
673
|
+
if (
|
|
674
|
+
self.input_model is not None
|
|
675
|
+
and not self._schema_supplied
|
|
676
|
+
and not isinstance(arguments, self.input_model)
|
|
677
|
+
):
|
|
678
|
+
raise TypeError(f"Expected {self.input_model.__name__}, got {type(arguments).__name__}")
|
|
679
|
+
if self.input_model is not None and not self._schema_supplied:
|
|
680
|
+
parsed_arguments = self._dump_arguments(arguments)
|
|
681
|
+
else:
|
|
682
|
+
# Schema-supplied/no-model tools have no declared model
|
|
683
|
+
# policy; preserve their runtime-model behavior.
|
|
684
|
+
parsed_arguments = arguments.model_dump(exclude_none=True)
|
|
685
|
+
typed_model_dump = not self._schema_supplied
|
|
686
|
+
else:
|
|
687
|
+
raise TypeError(
|
|
688
|
+
f"Expected mapping-like arguments for tool '{self.name}', got {type(arguments).__name__}"
|
|
689
|
+
)
|
|
690
|
+
except ValidationError as exc:
|
|
691
|
+
raise TypeError(f"Invalid arguments for '{self.name}': {exc}") from exc
|
|
692
|
+
|
|
693
|
+
validated_arguments = _validate_arguments_against_schema(
|
|
694
|
+
arguments=parsed_arguments,
|
|
695
|
+
schema=self._serialization_schema
|
|
696
|
+
if typed_model_dump and not self._schema_supplied
|
|
697
|
+
else self.parameters(),
|
|
698
|
+
tool_name=self.name,
|
|
699
|
+
tuples_as_arrays=typed_model_dump,
|
|
700
|
+
values_prevalidated=typed_model_dump and not self._schema_supplied,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
effective_context = context
|
|
704
|
+
if effective_context is None and self._context_parameter_name is not None:
|
|
705
|
+
effective_context = FunctionInvocationContext(
|
|
706
|
+
function=self,
|
|
707
|
+
arguments=validated_arguments,
|
|
708
|
+
kwargs=runtime_kwargs,
|
|
709
|
+
)
|
|
710
|
+
if effective_context is not None:
|
|
711
|
+
effective_context.function = self
|
|
712
|
+
effective_context.arguments = validated_arguments
|
|
713
|
+
effective_context.kwargs = dict(runtime_kwargs)
|
|
714
|
+
|
|
715
|
+
call_kwargs = dict(validated_arguments)
|
|
716
|
+
if self._context_parameter_name is not None and effective_context is not None:
|
|
717
|
+
call_kwargs[self._context_parameter_name] = effective_context
|
|
718
|
+
|
|
719
|
+
try:
|
|
720
|
+
result = await self._invoke_function(call_kwargs)
|
|
721
|
+
except SyncToolCancelledAfterCompletion as exc:
|
|
722
|
+
# Parse the drained value in place (parsers are synchronous) so
|
|
723
|
+
# the journaled result has the same shape a normal return would
|
|
724
|
+
# have produced.
|
|
725
|
+
if not skip_parsing:
|
|
726
|
+
try:
|
|
727
|
+
exc.completed_result = self._parse_invocation_result(exc.completed_result, custom_parser)
|
|
728
|
+
except ModelVisibleToolError as parse_error:
|
|
729
|
+
exc.completed_result = [Content.from_text(parse_error.result_text or "")]
|
|
730
|
+
raise
|
|
731
|
+
if skip_parsing:
|
|
732
|
+
return result
|
|
733
|
+
return self._parse_invocation_result(result, custom_parser)
|
|
734
|
+
|
|
735
|
+
def _parse_invocation_result(self, result: Any, custom_parser: ResultParser | None) -> Any:
|
|
736
|
+
"""Convert a completed call's value to Content.
|
|
737
|
+
|
|
738
|
+
A failing custom parser fails the call without showing the raw value,
|
|
739
|
+
which can be large or internal; the default parser falls back to ``str()``.
|
|
740
|
+
"""
|
|
741
|
+
if custom_parser is None:
|
|
742
|
+
try:
|
|
743
|
+
parsed: Any = FunctionTool.parse_result(result)
|
|
744
|
+
except Exception:
|
|
745
|
+
logger.warning("Function %s: result parser failed, falling back to str().", self.name)
|
|
746
|
+
parsed = [Content.from_text(str(result))]
|
|
747
|
+
return FunctionTool._normalize_parser_output(parsed)
|
|
748
|
+
try:
|
|
749
|
+
parsed = custom_parser(result)
|
|
750
|
+
except Exception as exc:
|
|
751
|
+
raise ModelVisibleToolError(
|
|
752
|
+
f"Function '{self.name}' completed, but its result parser failed.", inner_exception=exc
|
|
753
|
+
) from exc
|
|
754
|
+
return FunctionTool._normalize_parser_output(parsed)
|
|
755
|
+
|
|
756
|
+
@property
|
|
757
|
+
def _input_schema(self) -> dict[str, Any]:
|
|
758
|
+
"""Get the input schema, generating it lazily if needed."""
|
|
759
|
+
if self._input_schema_cached is None:
|
|
760
|
+
if self.input_model is not None:
|
|
761
|
+
with suppress(Exception):
|
|
762
|
+
self.input_model.model_rebuild(force=False, raise_errors=False)
|
|
763
|
+
self._input_schema_cached = self.input_model.model_json_schema()
|
|
764
|
+
else:
|
|
765
|
+
self._input_schema_cached = {}
|
|
766
|
+
return self._input_schema_cached
|
|
767
|
+
|
|
768
|
+
@property
|
|
769
|
+
def _serialization_schema(self) -> dict[str, Any]:
|
|
770
|
+
"""Get the typed model's callable-facing serialization schema."""
|
|
771
|
+
if self._serialization_schema_cached is None:
|
|
772
|
+
if self.input_model is not None:
|
|
773
|
+
with suppress(Exception):
|
|
774
|
+
self.input_model.model_rebuild(force=False, raise_errors=False)
|
|
775
|
+
by_alias = bool(self.input_model.model_config.get("serialize_by_alias", False))
|
|
776
|
+
try:
|
|
777
|
+
schema = self.input_model.model_json_schema(mode="serialization", by_alias=by_alias)
|
|
778
|
+
except Exception:
|
|
779
|
+
schema = {}
|
|
780
|
+
else:
|
|
781
|
+
policy = self._null_overlay_policy
|
|
782
|
+
if policy.tier is OverlayTier.FULL:
|
|
783
|
+
restorable = set(self.input_model.model_fields) - set(
|
|
784
|
+
policy.never_restore.get(self.input_model, frozenset())
|
|
785
|
+
)
|
|
786
|
+
elif policy.tier is OverlayTier.TOP_LEVEL:
|
|
787
|
+
restorable = set(policy.top_level_restorable)
|
|
788
|
+
else:
|
|
789
|
+
restorable = set()
|
|
790
|
+
policy_keys = policy.dump_keys.get(self.input_model, {})
|
|
791
|
+
required: set[str] = set()
|
|
792
|
+
for field_name, field_info in self.input_model.model_fields.items():
|
|
793
|
+
if by_alias:
|
|
794
|
+
dump_key = (
|
|
795
|
+
field_info.serialization_alias
|
|
796
|
+
if field_info.serialization_alias is not None
|
|
797
|
+
else field_info.alias
|
|
798
|
+
if field_info.alias is not None
|
|
799
|
+
else field_name
|
|
800
|
+
)
|
|
801
|
+
else:
|
|
802
|
+
dump_key = field_name
|
|
803
|
+
dump_key = policy_keys.get(field_name, dump_key)
|
|
804
|
+
if field_info.is_required() and field_name in restorable:
|
|
805
|
+
required.add(dump_key)
|
|
806
|
+
schema["required"] = sorted(required)
|
|
807
|
+
self._serialization_schema_cached = schema
|
|
808
|
+
else:
|
|
809
|
+
self._serialization_schema_cached = {}
|
|
810
|
+
return self._serialization_schema_cached
|
|
811
|
+
|
|
812
|
+
def parameters(self) -> dict[str, Any]:
|
|
813
|
+
"""Create and cache the JSON schema for the function parameters."""
|
|
814
|
+
if self._cached_parameters is None:
|
|
815
|
+
self._cached_parameters = self._input_schema
|
|
816
|
+
return self._cached_parameters
|
|
817
|
+
|
|
818
|
+
@staticmethod
|
|
819
|
+
def _make_dumpable(value: Any) -> Any:
|
|
820
|
+
"""Recursively convert a value to a JSON-dumpable form."""
|
|
821
|
+
if isinstance(value, list):
|
|
822
|
+
return [FunctionTool._make_dumpable(item) for item in value]
|
|
823
|
+
if isinstance(value, dict):
|
|
824
|
+
return {key: FunctionTool._make_dumpable(item) for key, item in value.items()}
|
|
825
|
+
if content := _coerce_content(value):
|
|
826
|
+
return content.to_dict(exclude={"raw_representation", "additional_properties"})
|
|
827
|
+
if isinstance(value, BaseModel):
|
|
828
|
+
return value.model_dump()
|
|
829
|
+
to_dict = getattr(value, "to_dict", None)
|
|
830
|
+
if callable(to_dict):
|
|
831
|
+
return to_dict()
|
|
832
|
+
text = getattr(value, "text", None)
|
|
833
|
+
if isinstance(text, str):
|
|
834
|
+
return text
|
|
835
|
+
return value
|
|
836
|
+
|
|
837
|
+
@staticmethod
|
|
838
|
+
def parse_result(result: Any) -> list[Content]:
|
|
839
|
+
"""Convert a raw function return value to Chrys ``Content`` items."""
|
|
840
|
+
if result is None:
|
|
841
|
+
return [Content.from_text("")]
|
|
842
|
+
if isinstance(result, str):
|
|
843
|
+
return [Content.from_text(result)]
|
|
844
|
+
if content := _coerce_content(result):
|
|
845
|
+
return [content]
|
|
846
|
+
if isinstance(result, list) and any(_coerce_content(item) is not None for item in result):
|
|
847
|
+
parsed_items: list[Content] = []
|
|
848
|
+
for item in result:
|
|
849
|
+
if content := _coerce_content(item):
|
|
850
|
+
parsed_items.append(content)
|
|
851
|
+
else:
|
|
852
|
+
dumpable = FunctionTool._make_dumpable(item)
|
|
853
|
+
text = dumpable if isinstance(dumpable, str) else model_json(dumpable, default=str)
|
|
854
|
+
parsed_items.append(Content.from_text(text))
|
|
855
|
+
return parsed_items
|
|
856
|
+
dumpable = FunctionTool._make_dumpable(result)
|
|
857
|
+
if isinstance(dumpable, str):
|
|
858
|
+
return [Content.from_text(dumpable)]
|
|
859
|
+
return [Content.from_text(model_json(dumpable, default=str))]
|
|
860
|
+
|
|
861
|
+
@staticmethod
|
|
862
|
+
def _normalize_parser_output(parsed: Any) -> Any:
|
|
863
|
+
"""Normalize custom parser output into Chrys Content while retaining existing Content lists."""
|
|
864
|
+
if isinstance(parsed, str):
|
|
865
|
+
return [Content.from_text(parsed)]
|
|
866
|
+
if content := _coerce_content(parsed):
|
|
867
|
+
return [content]
|
|
868
|
+
if not isinstance(parsed, list) or not parsed:
|
|
869
|
+
return parsed
|
|
870
|
+
if all(isinstance(item, Content) for item in parsed):
|
|
871
|
+
return parsed
|
|
872
|
+
if not any(_coerce_content(item) is not None for item in parsed):
|
|
873
|
+
return parsed
|
|
874
|
+
|
|
875
|
+
normalized: list[Content] = []
|
|
876
|
+
for item in parsed:
|
|
877
|
+
if content := _coerce_content(item):
|
|
878
|
+
normalized.append(content)
|
|
879
|
+
else:
|
|
880
|
+
dumpable = FunctionTool._make_dumpable(item)
|
|
881
|
+
text = dumpable if isinstance(dumpable, str) else model_json(dumpable, default=str)
|
|
882
|
+
normalized.append(Content.from_text(text))
|
|
883
|
+
return normalized
|
|
884
|
+
|
|
885
|
+
def to_json_schema_spec(self) -> dict[str, Any]:
|
|
886
|
+
"""Convert this tool to the JSON Schema function specification format."""
|
|
887
|
+
return {
|
|
888
|
+
"type": "function",
|
|
889
|
+
"function": {
|
|
890
|
+
"name": self.name,
|
|
891
|
+
"description": self.description,
|
|
892
|
+
"parameters": self.parameters(),
|
|
893
|
+
},
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
def to_dict(self, *, exclude: set[str] | None = None, exclude_none: bool = True) -> dict[str, Any]:
|
|
897
|
+
"""Serialize the tool with input_model represented as its parameter schema."""
|
|
898
|
+
as_dict = super().to_dict(exclude=exclude, exclude_none=exclude_none)
|
|
899
|
+
if (exclude and "input_model" in exclude) or not self.input_model:
|
|
900
|
+
return as_dict
|
|
901
|
+
as_dict["input_model"] = self.parameters()
|
|
902
|
+
return as_dict
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
type ToolTypes = FunctionTool | Mapping[str, Any] | object
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
class FunctionInvocationConfiguration(TypedDict, total=False):
|
|
909
|
+
"""Configuration for function invocation in chat clients."""
|
|
910
|
+
|
|
911
|
+
enabled: bool
|
|
912
|
+
max_iterations: int
|
|
913
|
+
max_function_calls: int | None
|
|
914
|
+
max_consecutive_errors_per_request: int
|
|
915
|
+
terminate_on_unknown_calls: bool
|
|
916
|
+
additional_tools: Sequence[FunctionTool]
|
|
917
|
+
include_detailed_errors: bool
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
@overload
|
|
921
|
+
def tool(
|
|
922
|
+
func: Callable[..., Any],
|
|
923
|
+
*,
|
|
924
|
+
name: str | None = None,
|
|
925
|
+
description: str | None = None,
|
|
926
|
+
schema: type[BaseModel] | Mapping[str, Any] | None = None,
|
|
927
|
+
kind: str | None = None,
|
|
928
|
+
max_invocations: int | None = None,
|
|
929
|
+
max_invocation_exceptions: int | None = None,
|
|
930
|
+
additional_properties: dict[str, Any] | None = None,
|
|
931
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | object | None = None,
|
|
932
|
+
) -> FunctionTool: ...
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
@overload
|
|
936
|
+
def tool(
|
|
937
|
+
func: None = None,
|
|
938
|
+
*,
|
|
939
|
+
name: str | None = None,
|
|
940
|
+
description: str | None = None,
|
|
941
|
+
schema: type[BaseModel] | Mapping[str, Any] | None = None,
|
|
942
|
+
kind: str | None = None,
|
|
943
|
+
max_invocations: int | None = None,
|
|
944
|
+
max_invocation_exceptions: int | None = None,
|
|
945
|
+
additional_properties: dict[str, Any] | None = None,
|
|
946
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | object | None = None,
|
|
947
|
+
) -> Callable[[Callable[..., Any]], FunctionTool]: ...
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def tool(
|
|
951
|
+
func: Callable[..., Any] | None = None,
|
|
952
|
+
*,
|
|
953
|
+
name: str | None = None,
|
|
954
|
+
description: str | None = None,
|
|
955
|
+
schema: type[BaseModel] | Mapping[str, Any] | None = None,
|
|
956
|
+
kind: str | None = None,
|
|
957
|
+
max_invocations: int | None = None,
|
|
958
|
+
max_invocation_exceptions: int | None = None,
|
|
959
|
+
additional_properties: dict[str, Any] | None = None,
|
|
960
|
+
result_parser: Callable[[Any], str | list[Content]] | _SkipParsingSentinel | object | None = None,
|
|
961
|
+
) -> FunctionTool | Callable[[Callable[..., Any]], FunctionTool]:
|
|
962
|
+
"""Decorate a function into a Chrys ``FunctionTool``."""
|
|
963
|
+
|
|
964
|
+
def decorator(func: Callable[..., Any]) -> FunctionTool:
|
|
965
|
+
@wraps(func)
|
|
966
|
+
def wrapper(f: Callable[..., Any]) -> FunctionTool:
|
|
967
|
+
tool_name: str = name or getattr(f, "__name__", "unknown_function")
|
|
968
|
+
tool_desc: str = description or (f.__doc__ or "")
|
|
969
|
+
return FunctionTool(
|
|
970
|
+
name=tool_name,
|
|
971
|
+
description=tool_desc,
|
|
972
|
+
kind=kind,
|
|
973
|
+
max_invocations=max_invocations,
|
|
974
|
+
max_invocation_exceptions=max_invocation_exceptions,
|
|
975
|
+
additional_properties=additional_properties or {},
|
|
976
|
+
func=f,
|
|
977
|
+
input_model=schema,
|
|
978
|
+
result_parser=result_parser,
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
return wrapper(func)
|
|
982
|
+
|
|
983
|
+
return decorator(func) if func else decorator
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def normalize_tools(
|
|
987
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
|
988
|
+
) -> list[ToolTypes]:
|
|
989
|
+
"""Normalize tool inputs while preserving non-callable tool objects."""
|
|
990
|
+
if not tools:
|
|
991
|
+
return []
|
|
992
|
+
|
|
993
|
+
if isinstance(tools, (str, bytes, bytearray, Mapping)) or not isinstance(tools, Sequence):
|
|
994
|
+
tools = [tools]
|
|
995
|
+
|
|
996
|
+
normalized: list[ToolTypes] = []
|
|
997
|
+
for tool_item in tools:
|
|
998
|
+
if isinstance(tool_item, FunctionTool):
|
|
999
|
+
normalized.append(tool_item)
|
|
1000
|
+
continue
|
|
1001
|
+
if isinstance(tool_item, dict):
|
|
1002
|
+
normalized.append(tool_item)
|
|
1003
|
+
continue
|
|
1004
|
+
if callable(tool_item):
|
|
1005
|
+
normalized.append(tool(tool_item))
|
|
1006
|
+
continue
|
|
1007
|
+
collection_tools = getattr(tool_item, "tools", None)
|
|
1008
|
+
if isinstance(collection_tools, Iterable) and not isinstance(
|
|
1009
|
+
collection_tools, (str, bytes, bytearray, Mapping)
|
|
1010
|
+
):
|
|
1011
|
+
normalized.extend(normalize_tools(list(collection_tools)))
|
|
1012
|
+
continue
|
|
1013
|
+
if isinstance(tool_item, Iterable) and not isinstance(tool_item, (str, bytes, bytearray, Mapping, BaseModel)):
|
|
1014
|
+
normalized.extend(normalize_tools(list(tool_item)))
|
|
1015
|
+
continue
|
|
1016
|
+
normalized.append(tool_item)
|
|
1017
|
+
return normalized
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def declared_tool_name(tool_item: ToolTypes) -> str | None:
|
|
1021
|
+
"""Return the declared name of an owned function tool or function-tool mapping."""
|
|
1022
|
+
if isinstance(tool_item, FunctionTool):
|
|
1023
|
+
return tool_item.name
|
|
1024
|
+
if isinstance(tool_item, Mapping):
|
|
1025
|
+
function = tool_item.get("function")
|
|
1026
|
+
if not isinstance(function, Mapping):
|
|
1027
|
+
return None
|
|
1028
|
+
name = function.get("name")
|
|
1029
|
+
return name if isinstance(name, str) else None
|
|
1030
|
+
return None
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def _append_unique_tools(existing_tools: list[ToolTypes], new_tools: Sequence[ToolTypes]) -> list[ToolTypes]:
|
|
1034
|
+
"""Append named tools with atomic duplicate-name validation.
|
|
1035
|
+
|
|
1036
|
+
The caller supplies a throwaway ``existing_tools`` copy when it needs
|
|
1037
|
+
all-or-nothing mutation of a live list. Re-adding the exact same object is
|
|
1038
|
+
a no-op; a different object with the same declared name is rejected.
|
|
1039
|
+
Nameless provider tool objects remain append-only.
|
|
1040
|
+
"""
|
|
1041
|
+
seen_by_name: dict[str, ToolTypes] = {}
|
|
1042
|
+
for tool_item in existing_tools:
|
|
1043
|
+
if tool_name := declared_tool_name(tool_item):
|
|
1044
|
+
seen_by_name[tool_name] = tool_item
|
|
1045
|
+
|
|
1046
|
+
for tool_item in new_tools:
|
|
1047
|
+
tool_name = declared_tool_name(tool_item)
|
|
1048
|
+
if tool_name is None:
|
|
1049
|
+
existing_tools.append(tool_item)
|
|
1050
|
+
continue
|
|
1051
|
+
|
|
1052
|
+
existing_tool = seen_by_name.get(tool_name)
|
|
1053
|
+
if existing_tool is None:
|
|
1054
|
+
seen_by_name[tool_name] = tool_item
|
|
1055
|
+
existing_tools.append(tool_item)
|
|
1056
|
+
continue
|
|
1057
|
+
if existing_tool is tool_item:
|
|
1058
|
+
continue
|
|
1059
|
+
raise ValueError(f"Duplicate tool name '{tool_name}'. Tool names must be unique.")
|
|
1060
|
+
|
|
1061
|
+
return existing_tools
|