iCode-TUI 0.29.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
|
@@ -0,0 +1,1172 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
3
|
+
# Contains code adapted from Microsoft Agent Framework (MIT License; see NOTICE).
|
|
4
|
+
|
|
5
|
+
"""Tool-call execution for the model/tool loop.
|
|
6
|
+
|
|
7
|
+
A landed batch runs concurrently: each call is looked up, validated against
|
|
8
|
+
its schema, then run through the function middleware pipeline. Approval
|
|
9
|
+
middleware controls tool admission; ``MiddlewareTermination`` stops the loop.
|
|
10
|
+
A tool that raises answers the model with a fixed ``Error: Function failed.``
|
|
11
|
+
unless it raised ``ModelVisibleToolError``, whose message the model reads;
|
|
12
|
+
argument-validation errors include safe schema guidance without echoing the
|
|
13
|
+
full argument payload. Every failed result also records the exception tree in
|
|
14
|
+
its ``exception`` field, which never goes on the wire.
|
|
15
|
+
|
|
16
|
+
Tool execution owns the invocation logs and per-tool spans. Raw arguments and
|
|
17
|
+
results are logged only when ``TELEMETRY_GATE.sensitive_data`` is set; spans
|
|
18
|
+
require ``TELEMETRY_GATE.enabled``. Direct ``FunctionTool.invoke`` calls
|
|
19
|
+
remain silent. Tool containers are normalized again before each batch, since
|
|
20
|
+
direct callers and progressive exposure can add tools after agent preparation.
|
|
21
|
+
|
|
22
|
+
This module also defines how a tool result is shaped
|
|
23
|
+
(``_result_additional_properties``, ``_tool_trajectory_timing``,
|
|
24
|
+
``_failure_record_text``). The recovery journal builds its interrupted results
|
|
25
|
+
with the same rules, so a change here must hold for those results too. Results
|
|
26
|
+
reach the journal only through ``ToolResultCommit``.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
import contextlib
|
|
33
|
+
import datetime
|
|
34
|
+
import decimal
|
|
35
|
+
import json
|
|
36
|
+
import logging
|
|
37
|
+
from collections import Counter
|
|
38
|
+
from collections.abc import Mapping, Sequence
|
|
39
|
+
from copy import copy, deepcopy
|
|
40
|
+
from time import perf_counter, time_ns
|
|
41
|
+
from typing import TYPE_CHECKING, Any, Protocol, TypeGuard
|
|
42
|
+
|
|
43
|
+
from pydantic import BaseModel, ValidationError
|
|
44
|
+
|
|
45
|
+
from chrys.foundation.observability.gate import TELEMETRY_GATE
|
|
46
|
+
from chrys.foundation.tool_call_context import (
|
|
47
|
+
TOOL_CALL_CONTEXT_METADATA_KEY,
|
|
48
|
+
get_tool_context,
|
|
49
|
+
merge_tool_call_context_property,
|
|
50
|
+
)
|
|
51
|
+
from chrys.foundation.tool_execution_stamp import EXECUTION_STAMP_KEY, execution_stamp_from_metadata
|
|
52
|
+
from chrys.foundation.tool_invocation_order import TOOL_INVOCATION_ORDER_KEY, read_tool_invocation_order
|
|
53
|
+
from chrys.foundation.tool_kinds import TOOL_CALL_KIND_METADATA_KEY, get_tool_kind
|
|
54
|
+
from chrys.foundation.tool_result_metadata import (
|
|
55
|
+
TOOL_ERROR_KIND_METADATA_KEY,
|
|
56
|
+
TOOL_ERROR_MESSAGE_METADATA_KEY,
|
|
57
|
+
TOOL_FAILED_METADATA_KEY,
|
|
58
|
+
TOOL_RESULT_METADATA_KEY,
|
|
59
|
+
)
|
|
60
|
+
from chrys.foundation.trajectory.context import current_trajectory
|
|
61
|
+
from chrys.foundation.trajectory.event_types import ToolOutcome
|
|
62
|
+
from chrys.foundation.trajectory.ids import new_analytics_id
|
|
63
|
+
from chrys.foundation.trajectory.metadata import (
|
|
64
|
+
ANALYTICS_ITEM_ID_KEY,
|
|
65
|
+
OPERATION_ID_KEY,
|
|
66
|
+
TOOL_RESULT_CARRIER_ITEM_ID_METADATA_KEY,
|
|
67
|
+
TOOL_RESULT_ITEM_ID_METADATA_KEY,
|
|
68
|
+
read_analytics_item_id,
|
|
69
|
+
read_operation_id,
|
|
70
|
+
)
|
|
71
|
+
from chrys.foundation.trajectory.tools import tool_operation_finished_draft, tool_operation_started_draft
|
|
72
|
+
from chrys.foundation.trajectory.writer import EmitResult
|
|
73
|
+
from chrys.foundation.trajectory_timing import (
|
|
74
|
+
TRAJECTORY_TIMING_KEY,
|
|
75
|
+
build_instant_trajectory_timing,
|
|
76
|
+
trajectory_timing_from_metadata,
|
|
77
|
+
)
|
|
78
|
+
from chrys.foundation.util.sub_agent_context import (
|
|
79
|
+
SUB_AGENT_RESULT_COMMIT_CALLBACK_KEY,
|
|
80
|
+
sub_agent_parent_result_metadata,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
from ._content import Content
|
|
84
|
+
from ._result_ceiling import apply_result_ceiling
|
|
85
|
+
from ._tool_arg_errors import (
|
|
86
|
+
_accepts_arbitrary_argument_names,
|
|
87
|
+
_argument_validation_message,
|
|
88
|
+
_rejects_unexpected_arguments,
|
|
89
|
+
_unexpected_argument_names,
|
|
90
|
+
)
|
|
91
|
+
from .exceptions import ChrysException, tool_error_result_text
|
|
92
|
+
from .instrumentation import (
|
|
93
|
+
FUNCTION_SPAN_EXCLUDED_KWARGS,
|
|
94
|
+
OtelAttr,
|
|
95
|
+
capture_exception,
|
|
96
|
+
get_function_duration_histogram,
|
|
97
|
+
get_function_span,
|
|
98
|
+
get_function_span_attributes,
|
|
99
|
+
)
|
|
100
|
+
from .middleware import FunctionMiddlewarePipeline, MiddlewareTermination
|
|
101
|
+
from .sessions import AgentSession
|
|
102
|
+
from .tools import (
|
|
103
|
+
FunctionTool,
|
|
104
|
+
SyncToolCancelledAfterCompletion,
|
|
105
|
+
_is_skip_parsing_sentinel,
|
|
106
|
+
_validate_arguments_against_schema,
|
|
107
|
+
normalize_tools,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if TYPE_CHECKING:
|
|
111
|
+
from collections.abc import Callable, Container
|
|
112
|
+
|
|
113
|
+
from .middleware import FunctionInvocationContext
|
|
114
|
+
|
|
115
|
+
# Log lines keep the loop's logger name: log configuration and tests filter on it.
|
|
116
|
+
logger = logging.getLogger("chrys.kernel.loop")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _is_actionable_function_call(content: Content) -> bool:
|
|
120
|
+
"""Return whether a function call must be executed by the local tool loop."""
|
|
121
|
+
return content.type == "function_call" and not content.informational_only
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ToolResultCommit(Protocol):
|
|
125
|
+
"""Journal handle for one call's result slot.
|
|
126
|
+
|
|
127
|
+
Defined by its user: the recovery journal's slot handle satisfies it
|
|
128
|
+
structurally, so tool execution never imports the journal.
|
|
129
|
+
``commit_raw`` runs as soon as the tool returns, before function
|
|
130
|
+
middleware unwinds; ``commit_final`` records the terminal result;
|
|
131
|
+
``commit_interrupted`` records a call cancelled before it settled. A
|
|
132
|
+
sub-agent tool calls ``commit_interrupted`` again for the same call, through
|
|
133
|
+
``SUB_AGENT_RESULT_COMMIT_CALLBACK_KEY``, to add result metadata that
|
|
134
|
+
arrives late; the slot must merge it into the interrupted result it
|
|
135
|
+
already holds, even after the exchange was sealed.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def commit_raw(self, result: Content) -> None: ...
|
|
139
|
+
|
|
140
|
+
def commit_final(self, result: Content) -> None: ...
|
|
141
|
+
|
|
142
|
+
def commit_interrupted(
|
|
143
|
+
self,
|
|
144
|
+
function_call: Content,
|
|
145
|
+
invocation_context: FunctionInvocationContext | None,
|
|
146
|
+
metadata: Mapping[str, Any] | None = None,
|
|
147
|
+
) -> None: ...
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _describe_arguments(arguments: Mapping[str, Any] | BaseModel, schema_names: Container[str]) -> str:
|
|
151
|
+
"""Render tool arguments for the DEBUG log line.
|
|
152
|
+
|
|
153
|
+
Raw argument text is gated on ``TELEMETRY_GATE.sensitive_data``. With the
|
|
154
|
+
gate off, only names listed in the tool schema's ``properties`` are
|
|
155
|
+
disclosed: the model controls the actual keys (schemas rarely pin
|
|
156
|
+
``additionalProperties: false``, so unexpected keys survive validation)
|
|
157
|
+
and could smuggle sensitive text through a key name — such keys are
|
|
158
|
+
reported as an ``+N unrecognized`` count only.
|
|
159
|
+
"""
|
|
160
|
+
if _is_argument_mapping(arguments):
|
|
161
|
+
argument_mapping: Mapping[str, Any] = arguments
|
|
162
|
+
else:
|
|
163
|
+
# The middleware contract admits ``BaseModel`` rewrites of
|
|
164
|
+
# ``context.arguments``; pydantic models iterate as (key, value)
|
|
165
|
+
# pairs, so ``dict()`` recovers the mapping form. ``tool.invoke``
|
|
166
|
+
# normalizes the same way internally — a describe-only crash here
|
|
167
|
+
# would otherwise be converted into a tool error result.
|
|
168
|
+
argument_mapping = dict(arguments)
|
|
169
|
+
if TELEMETRY_GATE.sensitive_data:
|
|
170
|
+
return str(dict(argument_mapping))
|
|
171
|
+
if not argument_mapping:
|
|
172
|
+
return "0 key(s)"
|
|
173
|
+
known = [key for key in argument_mapping if key in schema_names]
|
|
174
|
+
parts = (
|
|
175
|
+
known
|
|
176
|
+
if len(known) == len(argument_mapping)
|
|
177
|
+
else [*known, f"+{len(argument_mapping) - len(known)} unrecognized"]
|
|
178
|
+
)
|
|
179
|
+
return f"{len(argument_mapping)} key(s) ({', '.join(parts)})"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _is_argument_mapping(value: Mapping[str, Any] | BaseModel) -> TypeGuard[Mapping[str, Any]]:
|
|
183
|
+
"""Narrow middleware arguments after the runtime mapping check."""
|
|
184
|
+
return isinstance(value, Mapping)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _describe_result(result: Any, *, skip_parsing: bool) -> str:
|
|
188
|
+
"""Render a tool result for DEBUG logging under the sensitive-data gate.
|
|
189
|
+
|
|
190
|
+
Skip-parsing tools return arbitrary values, including non-Content lists:
|
|
191
|
+
render those with ``str`` when sensitive data is enabled, otherwise use
|
|
192
|
+
the type name. Parsed results are ``list[Content]`` and can be summarized
|
|
193
|
+
by item type or, when permitted, joined as text.
|
|
194
|
+
"""
|
|
195
|
+
if skip_parsing or not isinstance(result, list):
|
|
196
|
+
return str(result) if TELEMETRY_GATE.sensitive_data else type(result).__name__
|
|
197
|
+
if TELEMETRY_GATE.sensitive_data:
|
|
198
|
+
text = "\n".join(item.text or "" for item in result if item.type == "text")
|
|
199
|
+
return text or str(result)
|
|
200
|
+
if not result:
|
|
201
|
+
return "None"
|
|
202
|
+
return f"{len(result)} item(s) ({', '.join(item.type for item in result)})"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
_FUNCTION_DURATION_HISTOGRAM: Any = None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _function_duration_histogram() -> Any:
|
|
209
|
+
"""Cache the function-duration histogram on its first gated use.
|
|
210
|
+
|
|
211
|
+
Lazy construction lets provider setup finish before obtaining the meter.
|
|
212
|
+
"""
|
|
213
|
+
global _FUNCTION_DURATION_HISTOGRAM
|
|
214
|
+
if _FUNCTION_DURATION_HISTOGRAM is None:
|
|
215
|
+
_FUNCTION_DURATION_HISTOGRAM = get_function_duration_histogram()
|
|
216
|
+
return _FUNCTION_DURATION_HISTOGRAM
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
async def _invoke_with_function_span(
|
|
220
|
+
tool: FunctionTool,
|
|
221
|
+
context: Any,
|
|
222
|
+
call_id: str | None,
|
|
223
|
+
*,
|
|
224
|
+
arguments: BaseModel | Mapping[str, Any],
|
|
225
|
+
arguments_in_callable_keyspace: bool = False,
|
|
226
|
+
tool_result_ceiling_tokens: int | None = None,
|
|
227
|
+
) -> Any:
|
|
228
|
+
"""Run ``tool.invoke`` inside an ``execute_tool {name}`` span.
|
|
229
|
+
|
|
230
|
+
Capture arguments and results only under ``TELEMETRY_GATE.sensitive_enabled``;
|
|
231
|
+
record exceptions and duration on failure as well as success. The caller
|
|
232
|
+
owns logging. Skip-parsing results use ``str(result)``; parsed Content
|
|
233
|
+
lists contribute their text, selected by the same ``SKIP_PARSING`` sentinel
|
|
234
|
+
that ``invoke`` uses.
|
|
235
|
+
|
|
236
|
+
Captured arguments are the middleware-final values. Validation happens
|
|
237
|
+
inside ``invoke``, so the span does not capture its normalized argument dict.
|
|
238
|
+
"""
|
|
239
|
+
attributes = get_function_span_attributes(tool, tool_call_id=call_id)
|
|
240
|
+
# The middleware contract admits ``BaseModel`` rewrites of
|
|
241
|
+
# ``context.arguments`` (same shape ``_describe_arguments`` / ``invoke``
|
|
242
|
+
# handle); normalize it to a dict before filtering so sensitive capture
|
|
243
|
+
# doesn't silently record ``"None"`` for a model-shaped argument set.
|
|
244
|
+
if isinstance(arguments, BaseModel):
|
|
245
|
+
argument_items: dict[str, Any] = arguments.model_dump(exclude_none=True)
|
|
246
|
+
elif isinstance(arguments, Mapping):
|
|
247
|
+
argument_items = dict(arguments)
|
|
248
|
+
else:
|
|
249
|
+
argument_items = {}
|
|
250
|
+
serializable_kwargs = {k: v for k, v in argument_items.items() if k not in FUNCTION_SPAN_EXCLUDED_KWARGS}
|
|
251
|
+
if TELEMETRY_GATE.sensitive_enabled:
|
|
252
|
+
attributes[OtelAttr.TOOL_ARGUMENTS] = (
|
|
253
|
+
json.dumps(serializable_kwargs, default=str, ensure_ascii=False) if serializable_kwargs else "None"
|
|
254
|
+
)
|
|
255
|
+
with get_function_span(attributes=attributes) as span:
|
|
256
|
+
attributes[OtelAttr.MEASUREMENT_FUNCTION_TAG_NAME] = tool.name
|
|
257
|
+
start_time_stamp = perf_counter()
|
|
258
|
+
end_time_stamp: float | None = None
|
|
259
|
+
try:
|
|
260
|
+
result = await tool.invoke(
|
|
261
|
+
arguments=arguments,
|
|
262
|
+
context=context,
|
|
263
|
+
tool_call_id=call_id,
|
|
264
|
+
_arguments_in_callable_keyspace=arguments_in_callable_keyspace,
|
|
265
|
+
)
|
|
266
|
+
result = apply_result_ceiling(result, tool_result_ceiling_tokens)
|
|
267
|
+
end_time_stamp = perf_counter()
|
|
268
|
+
except Exception as exception:
|
|
269
|
+
end_time_stamp = perf_counter()
|
|
270
|
+
attributes[OtelAttr.ERROR_TYPE] = type(exception).__name__
|
|
271
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
272
|
+
raise
|
|
273
|
+
else:
|
|
274
|
+
if TELEMETRY_GATE.sensitive_enabled:
|
|
275
|
+
if _is_skip_parsing_sentinel(tool.result_parser):
|
|
276
|
+
result_str = str(result)
|
|
277
|
+
else:
|
|
278
|
+
result_str = "\n".join(c.text or "" for c in result if c.type == "text") or str(result)
|
|
279
|
+
span.set_attribute(OtelAttr.TOOL_RESULT, result_str)
|
|
280
|
+
return result
|
|
281
|
+
finally:
|
|
282
|
+
duration = (end_time_stamp or perf_counter()) - start_time_stamp
|
|
283
|
+
span.set_attribute(OtelAttr.MEASUREMENT_FUNCTION_INVOCATION_DURATION, duration)
|
|
284
|
+
_function_duration_histogram().record(duration, attributes=attributes)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _arguments_unparseable(arguments: Any) -> bool:
|
|
288
|
+
"""True when raw tool-call arguments are an empty or invalid JSON string.
|
|
289
|
+
|
|
290
|
+
This is the fingerprint of a truncated / incomplete argument payload — the
|
|
291
|
+
model was cut off before or during JSON emission, so ``Content.parse_arguments``
|
|
292
|
+
either returns ``{}`` for an empty string or falls back to wrapping the raw
|
|
293
|
+
blob under ``{"raw": ...}``. It deliberately does NOT flag arguments that
|
|
294
|
+
parsed cleanly (a dict, or a pre-parsed mapping) but failed schema validation
|
|
295
|
+
— those are real argument errors, not a token-limit cutoff.
|
|
296
|
+
"""
|
|
297
|
+
if not isinstance(arguments, str):
|
|
298
|
+
return False
|
|
299
|
+
if not arguments:
|
|
300
|
+
return True
|
|
301
|
+
try:
|
|
302
|
+
json.loads(arguments)
|
|
303
|
+
except json.JSONDecodeError:
|
|
304
|
+
return True
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _arguments_not_object(arguments: Any) -> bool:
|
|
309
|
+
"""True when raw tool-call arguments are present but are not a JSON object.
|
|
310
|
+
|
|
311
|
+
``Content.parse_arguments`` wraps such a payload as ``{"raw": ...}``, and a
|
|
312
|
+
tool whose schema accepts extra keys would then run on arguments the model
|
|
313
|
+
never gave it (MCP adapters drop the undeclared ``raw`` and call with none).
|
|
314
|
+
``None`` and ``""`` mean "no arguments"; whitespace, undecodable text and
|
|
315
|
+
any JSON value other than an object do not, nor does a parsed non-mapping
|
|
316
|
+
value, however falsy (``[]``, ``0``, ``False``).
|
|
317
|
+
"""
|
|
318
|
+
if arguments is None:
|
|
319
|
+
return False
|
|
320
|
+
if isinstance(arguments, str):
|
|
321
|
+
if not arguments:
|
|
322
|
+
return False
|
|
323
|
+
try:
|
|
324
|
+
loaded = json.loads(arguments)
|
|
325
|
+
except json.JSONDecodeError:
|
|
326
|
+
return True
|
|
327
|
+
return not isinstance(loaded, dict)
|
|
328
|
+
return not isinstance(arguments, Mapping)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _middleware_arguments_equal(left: Any, right: Any) -> bool:
|
|
332
|
+
"""Type-strict structural equality for the trusted middleware snapshot."""
|
|
333
|
+
if type(left) is not type(right):
|
|
334
|
+
return False
|
|
335
|
+
if type(left) is dict:
|
|
336
|
+
if len(left) != len(right):
|
|
337
|
+
return False
|
|
338
|
+
unmatched = list(right.items())
|
|
339
|
+
for left_key, left_value in left.items():
|
|
340
|
+
for index, (right_key, right_value) in enumerate(unmatched):
|
|
341
|
+
if _middleware_arguments_equal(left_key, right_key):
|
|
342
|
+
if not _middleware_arguments_equal(left_value, right_value):
|
|
343
|
+
return False
|
|
344
|
+
unmatched.pop(index)
|
|
345
|
+
break
|
|
346
|
+
else:
|
|
347
|
+
return False
|
|
348
|
+
return True
|
|
349
|
+
if type(left) in {list, tuple}:
|
|
350
|
+
return len(left) == len(right) and all(
|
|
351
|
+
_middleware_arguments_equal(left_item, right_item)
|
|
352
|
+
for left_item, right_item in zip(left, right, strict=True)
|
|
353
|
+
)
|
|
354
|
+
if type(left) in {set, frozenset}:
|
|
355
|
+
if len(left) != len(right):
|
|
356
|
+
return False
|
|
357
|
+
unmatched = list(right)
|
|
358
|
+
for left_item in left:
|
|
359
|
+
for index, right_item in enumerate(unmatched):
|
|
360
|
+
if _middleware_arguments_equal(left_item, right_item):
|
|
361
|
+
unmatched.pop(index)
|
|
362
|
+
break
|
|
363
|
+
else:
|
|
364
|
+
return False
|
|
365
|
+
return True
|
|
366
|
+
if type(left) in {float, complex, decimal.Decimal}:
|
|
367
|
+
return bool(left == right and repr(left) == repr(right))
|
|
368
|
+
if type(left) in {datetime.datetime, datetime.time}:
|
|
369
|
+
return bool(
|
|
370
|
+
left == right
|
|
371
|
+
and left.utcoffset() == right.utcoffset()
|
|
372
|
+
and left.tzname() == right.tzname()
|
|
373
|
+
and left.fold == right.fold
|
|
374
|
+
)
|
|
375
|
+
return bool(left == right)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _arguments_look_like_truncated_mapping(arguments: Any, schema: Mapping[str, Any]) -> bool:
|
|
379
|
+
"""True for parsed Anthropic tool args that look cut off, not just invalid."""
|
|
380
|
+
if not isinstance(arguments, Mapping):
|
|
381
|
+
return False
|
|
382
|
+
if not arguments:
|
|
383
|
+
return True
|
|
384
|
+
|
|
385
|
+
required = schema.get("required")
|
|
386
|
+
properties = schema.get("properties")
|
|
387
|
+
if not isinstance(required, Sequence) or isinstance(required, (str, bytes)):
|
|
388
|
+
return False
|
|
389
|
+
if not isinstance(properties, Mapping):
|
|
390
|
+
return False
|
|
391
|
+
|
|
392
|
+
argument_keys = {key for key in arguments if isinstance(key, str)}
|
|
393
|
+
if len(argument_keys) != len(arguments):
|
|
394
|
+
return False
|
|
395
|
+
if not argument_keys.issubset(properties.keys()):
|
|
396
|
+
return False
|
|
397
|
+
return any(isinstance(key, str) and key not in arguments for key in required)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _stamp_call_provenance(function_call: Content, tool: FunctionTool) -> None:
|
|
401
|
+
"""Stamp tool kind + static context onto the call content, first-write-wins.
|
|
402
|
+
|
|
403
|
+
Runs right after tool lookup, BEFORE argument validation, so calls that
|
|
404
|
+
fail pre-pipeline validation — which return without entering the
|
|
405
|
+
middleware pipeline — still persist their provenance. Static context
|
|
406
|
+
only: the per-call builder needs validated args and belongs to the
|
|
407
|
+
record pipeline.
|
|
408
|
+
"""
|
|
409
|
+
props = function_call.additional_properties
|
|
410
|
+
kind = get_tool_kind(tool)
|
|
411
|
+
if kind and TOOL_CALL_KIND_METADATA_KEY not in props:
|
|
412
|
+
props[TOOL_CALL_KIND_METADATA_KEY] = kind
|
|
413
|
+
static = get_tool_context(tool)
|
|
414
|
+
if static and TOOL_CALL_CONTEXT_METADATA_KEY not in props:
|
|
415
|
+
# Already sanitized at set_tool_context() — single sanitization
|
|
416
|
+
# authority; the stamp only copies, never re-sanitizes.
|
|
417
|
+
props[TOOL_CALL_CONTEXT_METADATA_KEY] = dict(static)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# The failure record is saved with the session and exported to telemetry: a long
|
|
421
|
+
# cause chain, a large exception group or a message that embeds a response body
|
|
422
|
+
# must not bloat either. Each message is clipped before the record is joined, the
|
|
423
|
+
# exception cap also bounds the record's recursion, and the total cap is the backstop.
|
|
424
|
+
_FAILURE_RECORD_MAX_EXCEPTIONS = 16
|
|
425
|
+
_FAILURE_RECORD_MAX_MESSAGE_CHARS = 1000
|
|
426
|
+
_FAILURE_RECORD_MAX_CHARS = 4000
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _clip(text: str, limit: int) -> str:
|
|
430
|
+
return text if len(text) <= limit else f"{text[: limit - 1]}…"
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _exception_message(exc: BaseException) -> str:
|
|
434
|
+
"""Return *exc*'s own message, clipped for the failure record.
|
|
435
|
+
|
|
436
|
+
``str()`` of a ChrysException built with an ``inner_exception`` is the repr
|
|
437
|
+
of its args tuple, and a KeyError's is quoted; both hold the message itself
|
|
438
|
+
as their first argument. A ``__str__`` that raises must not stop the call
|
|
439
|
+
from becoming a failed result, so it records what a traceback would.
|
|
440
|
+
"""
|
|
441
|
+
if isinstance(exc, ChrysException | KeyError) and exc.args and isinstance(exc.args[0], str):
|
|
442
|
+
message = exc.args[0]
|
|
443
|
+
else:
|
|
444
|
+
try:
|
|
445
|
+
message = str(exc)
|
|
446
|
+
except Exception:
|
|
447
|
+
message = "<exception str() failed>"
|
|
448
|
+
return _clip(message.strip(), _FAILURE_RECORD_MAX_MESSAGE_CHARS)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _exception_causes(exc: BaseException) -> list[BaseException]:
|
|
452
|
+
"""Return what *exc* was explicitly raised from and, when it differs, the ``inner_exception`` a ChrysException wraps."""
|
|
453
|
+
causes = [] if exc.__cause__ is None else [exc.__cause__]
|
|
454
|
+
if isinstance(exc, ChrysException) and len(exc.args) > 1:
|
|
455
|
+
inner = exc.args[1]
|
|
456
|
+
if isinstance(inner, BaseException) and inner is not exc.__cause__:
|
|
457
|
+
causes.append(inner)
|
|
458
|
+
return causes
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _failure_record_text(exc: BaseException) -> str:
|
|
462
|
+
"""Return the ``exception`` a failed result records: ``Type: message`` for *exc* and everything it came from.
|
|
463
|
+
|
|
464
|
+
The record is a tree, ``Type: message [member; …] (caused by cause; …)``:
|
|
465
|
+
an exception group lists its members in brackets (its ``str()`` only
|
|
466
|
+
counts them), and the causes are ``raise … from`` plus a ChrysException's
|
|
467
|
+
``inner_exception`` when that is a different exception. It follows only
|
|
468
|
+
these explicit links, never the implicit ``__context__`` of whatever was
|
|
469
|
+
being handled when the tool raised — unlike ``foundation.errors``' linear
|
|
470
|
+
chain walk, which reads that context to classify retryable errors.
|
|
471
|
+
|
|
472
|
+
The record is for people reading a saved session, never for the model. It
|
|
473
|
+
is never empty: readers take a non-empty record to mean the call failed.
|
|
474
|
+
"""
|
|
475
|
+
seen: set[int] = set()
|
|
476
|
+
|
|
477
|
+
def describe(current: BaseException) -> str:
|
|
478
|
+
seen.add(id(current))
|
|
479
|
+
if isinstance(current, BaseExceptionGroup):
|
|
480
|
+
message = _clip(current.message.strip(), _FAILURE_RECORD_MAX_MESSAGE_CHARS)
|
|
481
|
+
members = describe_each(current.exceptions)
|
|
482
|
+
else:
|
|
483
|
+
message = _exception_message(current)
|
|
484
|
+
members = ""
|
|
485
|
+
text = f"{type(current).__name__}: {message}" if message else type(current).__name__
|
|
486
|
+
if members:
|
|
487
|
+
text = f"{text} [{members}]"
|
|
488
|
+
causes = describe_each(_exception_causes(current))
|
|
489
|
+
return f"{text} (caused by {causes})" if causes else text
|
|
490
|
+
|
|
491
|
+
def describe_each(exceptions: Sequence[BaseException]) -> str:
|
|
492
|
+
parts: list[str] = []
|
|
493
|
+
for member in exceptions:
|
|
494
|
+
if id(member) in seen:
|
|
495
|
+
continue
|
|
496
|
+
if len(seen) >= _FAILURE_RECORD_MAX_EXCEPTIONS:
|
|
497
|
+
parts.append("…")
|
|
498
|
+
break
|
|
499
|
+
parts.append(describe(member))
|
|
500
|
+
return "; ".join(parts)
|
|
501
|
+
|
|
502
|
+
return _clip(describe(exc), _FAILURE_RECORD_MAX_CHARS)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _result_additional_properties(
|
|
506
|
+
function_call: Content,
|
|
507
|
+
invocation_context: FunctionInvocationContext | None = None,
|
|
508
|
+
) -> dict[str, Any]:
|
|
509
|
+
"""Return call properties for a function result, minus call provenance.
|
|
510
|
+
|
|
511
|
+
Every result-construction path propagates the call's properties; this
|
|
512
|
+
filters call-only provenance (kind, context, invocation ordinal) and any
|
|
513
|
+
stale call-side execution stamp or result metadata. Without the provenance
|
|
514
|
+
filter, the nested context dict would also be shared by reference between
|
|
515
|
+
call and result (the Content constructor's copy is shallow). A fresh stamp
|
|
516
|
+
and fresh result metadata come only from this invocation's context — this
|
|
517
|
+
fold is the single attachment point for ``_chrys_tool_result_metadata``,
|
|
518
|
+
at result construction, when the result's identity is unambiguous.
|
|
519
|
+
"""
|
|
520
|
+
result = {
|
|
521
|
+
key: value
|
|
522
|
+
for key, value in function_call.additional_properties.items()
|
|
523
|
+
if key
|
|
524
|
+
not in (
|
|
525
|
+
TOOL_CALL_KIND_METADATA_KEY,
|
|
526
|
+
TOOL_CALL_CONTEXT_METADATA_KEY,
|
|
527
|
+
TOOL_INVOCATION_ORDER_KEY,
|
|
528
|
+
EXECUTION_STAMP_KEY,
|
|
529
|
+
TOOL_RESULT_METADATA_KEY,
|
|
530
|
+
TRAJECTORY_TIMING_KEY,
|
|
531
|
+
ANALYTICS_ITEM_ID_KEY,
|
|
532
|
+
)
|
|
533
|
+
}
|
|
534
|
+
timing = _tool_trajectory_timing(function_call, invocation_context)
|
|
535
|
+
result[TRAJECTORY_TIMING_KEY] = timing
|
|
536
|
+
# The result is its own item (the call's operation id is inherited above
|
|
537
|
+
# so call and result share one tool operation). A pre-minted id on the
|
|
538
|
+
# invocation context keeps the raw-commit checkpoint and the terminal
|
|
539
|
+
# result naming the same item.
|
|
540
|
+
result[ANALYTICS_ITEM_ID_KEY] = _result_item_id(invocation_context)
|
|
541
|
+
if invocation_context is not None:
|
|
542
|
+
stamp = execution_stamp_from_metadata(invocation_context.metadata)
|
|
543
|
+
if stamp is not None:
|
|
544
|
+
result[EXECUTION_STAMP_KEY] = stamp
|
|
545
|
+
carried = invocation_context.metadata.get(TOOL_RESULT_METADATA_KEY)
|
|
546
|
+
if isinstance(carried, Mapping) and carried:
|
|
547
|
+
result[TOOL_RESULT_METADATA_KEY] = dict(carried)
|
|
548
|
+
return result
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _result_item_id(invocation_context: FunctionInvocationContext | None) -> str:
|
|
552
|
+
if invocation_context is not None:
|
|
553
|
+
pre_minted = invocation_context.metadata.get(TOOL_RESULT_ITEM_ID_METADATA_KEY)
|
|
554
|
+
if isinstance(pre_minted, str) and pre_minted:
|
|
555
|
+
return pre_minted
|
|
556
|
+
return new_analytics_id()
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _stamped_tool_context(props: Mapping[str, Any]) -> Mapping[str, Any] | None:
|
|
560
|
+
"""The static provenance segment stamped on a call, when there is one.
|
|
561
|
+
|
|
562
|
+
A call that never enters the pipeline has no builder output — only what
|
|
563
|
+
landing copied off the tool — so an MCP call still names its server.
|
|
564
|
+
"""
|
|
565
|
+
context = props.get(TOOL_CALL_CONTEXT_METADATA_KEY)
|
|
566
|
+
return context if isinstance(context, Mapping) and context else None
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
async def _record_unexecuted_tool_operation(
|
|
570
|
+
function_call: Content,
|
|
571
|
+
*,
|
|
572
|
+
outcome: str,
|
|
573
|
+
result_carrier_item_id: str | None = None,
|
|
574
|
+
result: Content | None = None,
|
|
575
|
+
queued: bool = False,
|
|
576
|
+
) -> None:
|
|
577
|
+
"""Emit the ``tool.operation`` pair for a call that never entered the pipeline.
|
|
578
|
+
|
|
579
|
+
Unknown-tool and invalid-argument calls still own a tool operation id
|
|
580
|
+
(stamped by response dispatch collection) and a result item, so the
|
|
581
|
+
trajectory closes them with their terminal outcome instead of leaving
|
|
582
|
+
dangling operations. A filtered call owns the operation but never gets a
|
|
583
|
+
result, and the pair closes without naming one.
|
|
584
|
+
"""
|
|
585
|
+
context = current_trajectory()
|
|
586
|
+
if context is None:
|
|
587
|
+
return
|
|
588
|
+
operation_id = read_operation_id(function_call.additional_properties)
|
|
589
|
+
if operation_id is None:
|
|
590
|
+
return
|
|
591
|
+
props = function_call.additional_properties
|
|
592
|
+
kind = props.get(TOOL_CALL_KIND_METADATA_KEY)
|
|
593
|
+
try:
|
|
594
|
+
started = tool_operation_started_draft(
|
|
595
|
+
context,
|
|
596
|
+
operation_id=operation_id,
|
|
597
|
+
tool_name=function_call.name or "",
|
|
598
|
+
tool_kind=kind if isinstance(kind, str) else "",
|
|
599
|
+
invocation_order=read_tool_invocation_order(props),
|
|
600
|
+
batch_index=None,
|
|
601
|
+
arguments=function_call.arguments,
|
|
602
|
+
call_item_id=read_analytics_item_id(props),
|
|
603
|
+
tool_context=_stamped_tool_context(props),
|
|
604
|
+
)
|
|
605
|
+
finished = tool_operation_finished_draft(
|
|
606
|
+
context,
|
|
607
|
+
operation_id=operation_id,
|
|
608
|
+
outcome=outcome,
|
|
609
|
+
duration_ms=0,
|
|
610
|
+
result_item_id=read_analytics_item_id(result.additional_properties) if result is not None else None,
|
|
611
|
+
result_carrier_item_id=result_carrier_item_id if result is not None else None,
|
|
612
|
+
error_kind=outcome,
|
|
613
|
+
)
|
|
614
|
+
except Exception:
|
|
615
|
+
logger.debug("Trajectory tool operation emit failed", exc_info=True)
|
|
616
|
+
return
|
|
617
|
+
if queued:
|
|
618
|
+
# The caller is unwinding from a cancellation: waiting for the write
|
|
619
|
+
# acknowledgement here would hold a Stop for as long as the writer
|
|
620
|
+
# takes to answer. Both lines take their sequence now and are
|
|
621
|
+
# acknowledged in the background — and a start the sink refuses
|
|
622
|
+
# outright still leaves nothing to close.
|
|
623
|
+
try:
|
|
624
|
+
if context.sink.emit_soon(started) is None:
|
|
625
|
+
context.sink.emit_soon(finished)
|
|
626
|
+
except Exception:
|
|
627
|
+
logger.debug("Trajectory tool operation emit failed", exc_info=True)
|
|
628
|
+
return
|
|
629
|
+
try:
|
|
630
|
+
opened = await context.sink.emit(started)
|
|
631
|
+
except Exception:
|
|
632
|
+
logger.debug("Trajectory tool operation emit failed", exc_info=True)
|
|
633
|
+
return
|
|
634
|
+
except BaseException:
|
|
635
|
+
# Cancelled while the opening line was in flight: the write is
|
|
636
|
+
# committed regardless, so the terminal has to follow it. Only this
|
|
637
|
+
# wait needs the rescue — the one below can first be cancelled once
|
|
638
|
+
# its own line is committed too.
|
|
639
|
+
with contextlib.suppress(Exception):
|
|
640
|
+
context.sink.emit_soon(finished)
|
|
641
|
+
raise
|
|
642
|
+
if opened is not EmitResult.WRITTEN:
|
|
643
|
+
# An unknown tool's name is whatever the model asked for, and one past
|
|
644
|
+
# the line budget makes the whole opening event unwritable: its slot
|
|
645
|
+
# becomes a gap. A terminal behind that gap closes an operation the
|
|
646
|
+
# log never opened, which is not a shape readers handle; an operation
|
|
647
|
+
# that opens and never closes is one they already do.
|
|
648
|
+
return
|
|
649
|
+
try:
|
|
650
|
+
await context.sink.emit(finished)
|
|
651
|
+
except Exception:
|
|
652
|
+
logger.debug("Trajectory tool operation emit failed", exc_info=True)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def _tool_trajectory_timing(
|
|
656
|
+
function_call: Content,
|
|
657
|
+
invocation_context: FunctionInvocationContext | None,
|
|
658
|
+
) -> dict[str, Any]:
|
|
659
|
+
"""Resolve one tool span and mirror it onto its persisted call content."""
|
|
660
|
+
timing = trajectory_timing_from_metadata(invocation_context.metadata) if invocation_context is not None else None
|
|
661
|
+
if timing is None:
|
|
662
|
+
timing = trajectory_timing_from_metadata(function_call.additional_properties)
|
|
663
|
+
if timing is None:
|
|
664
|
+
timing = build_instant_trajectory_timing()
|
|
665
|
+
function_call.additional_properties[TRAJECTORY_TIMING_KEY] = dict(timing)
|
|
666
|
+
return timing
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
async def _invoke_function_call(
|
|
670
|
+
function_call: Content,
|
|
671
|
+
*,
|
|
672
|
+
result_commit: ToolResultCommit | None,
|
|
673
|
+
tool_map: dict[str, FunctionTool],
|
|
674
|
+
custom_args: dict[str, Any],
|
|
675
|
+
invocation_session: AgentSession | None,
|
|
676
|
+
pipeline: FunctionMiddlewarePipeline,
|
|
677
|
+
live_tools: list[Any] | None,
|
|
678
|
+
response_truncated: bool = False,
|
|
679
|
+
function_call_may_be_truncated: bool = False,
|
|
680
|
+
same_tool_calls_in_batch: int = 1,
|
|
681
|
+
tool_result_ceiling_tokens: int | None = None,
|
|
682
|
+
result_carrier_item_id: str,
|
|
683
|
+
) -> Content:
|
|
684
|
+
"""Invoke one model-requested function call through the middleware pipeline.
|
|
685
|
+
|
|
686
|
+
A raised exception answers the model with ``tool_error_result_text``: a
|
|
687
|
+
fixed line, or a ``ModelVisibleToolError``'s own message. Argument-validation
|
|
688
|
+
failures include safe schema guidance so the model can repair its next call.
|
|
689
|
+
Each failed result records ``_failure_record_text`` as its ``exception``.
|
|
690
|
+
"""
|
|
691
|
+
from .middleware import FunctionInvocationContext
|
|
692
|
+
|
|
693
|
+
tool = tool_map.get(function_call.name) # type: ignore[arg-type]
|
|
694
|
+
if tool is None:
|
|
695
|
+
message = f'Requested function "{function_call.name}" not found.'
|
|
696
|
+
exc = KeyError(f'Function "{function_call.name}" not found.')
|
|
697
|
+
additional = _result_additional_properties(function_call)
|
|
698
|
+
additional.update(
|
|
699
|
+
{
|
|
700
|
+
TOOL_FAILED_METADATA_KEY: True,
|
|
701
|
+
TOOL_ERROR_KIND_METADATA_KEY: "tool_not_found",
|
|
702
|
+
TOOL_ERROR_MESSAGE_METADATA_KEY: message,
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
result = Content.from_function_result(
|
|
706
|
+
call_id=function_call.call_id, # type: ignore[arg-type]
|
|
707
|
+
result=f"Error: {message}",
|
|
708
|
+
exception=_failure_record_text(exc),
|
|
709
|
+
additional_properties=additional,
|
|
710
|
+
)
|
|
711
|
+
await _record_unexecuted_tool_operation(
|
|
712
|
+
function_call,
|
|
713
|
+
outcome=ToolOutcome.UNKNOWN_TOOL,
|
|
714
|
+
result_carrier_item_id=result_carrier_item_id,
|
|
715
|
+
result=result,
|
|
716
|
+
)
|
|
717
|
+
return result
|
|
718
|
+
|
|
719
|
+
_stamp_call_provenance(function_call, tool)
|
|
720
|
+
|
|
721
|
+
# Judged on the raw payload: parsing would wrap a non-object as {"raw": ...}
|
|
722
|
+
# and turn a falsy parsed value into {}.
|
|
723
|
+
arguments_not_object = _arguments_not_object(function_call.arguments)
|
|
724
|
+
parsed_args: dict[str, Any] = {} if arguments_not_object else dict(function_call.parse_arguments() or {})
|
|
725
|
+
|
|
726
|
+
# Filter out internal kwargs before passing to tools; conversation_id is an
|
|
727
|
+
# internal tracking id that must not be forwarded.
|
|
728
|
+
runtime_kwargs: dict[str, Any] = {
|
|
729
|
+
key: value
|
|
730
|
+
for key, value in custom_args.items()
|
|
731
|
+
if key not in {"_function_middleware_pipeline", "middleware", "conversation_id"}
|
|
732
|
+
}
|
|
733
|
+
if invocation_session is not None:
|
|
734
|
+
runtime_kwargs["session"] = invocation_session
|
|
735
|
+
|
|
736
|
+
# Pre-pipeline validation: bad arguments return an error
|
|
737
|
+
# result without entering the middleware pipeline (no approval dialog for
|
|
738
|
+
# a call that could never run).
|
|
739
|
+
argument_schema = tool.parameters()
|
|
740
|
+
typed_model_dump = not tool._schema_supplied and tool.input_model is not None
|
|
741
|
+
validation_input_model = tool.input_model if typed_model_dump else None
|
|
742
|
+
reject_unexpected = _rejects_unexpected_arguments(
|
|
743
|
+
argument_schema, typed_model_dump=typed_model_dump
|
|
744
|
+
) and not _accepts_arbitrary_argument_names(validation_input_model)
|
|
745
|
+
try:
|
|
746
|
+
if arguments_not_object:
|
|
747
|
+
raise TypeError(f"Arguments for '{tool.name}' are not a JSON object.")
|
|
748
|
+
unexpected_arguments = _unexpected_argument_names(
|
|
749
|
+
parsed_args,
|
|
750
|
+
argument_schema,
|
|
751
|
+
reject_unexpected=reject_unexpected,
|
|
752
|
+
input_model=validation_input_model,
|
|
753
|
+
)
|
|
754
|
+
if unexpected_arguments:
|
|
755
|
+
raise TypeError(f"Unexpected argument(s) for '{tool.name}'.")
|
|
756
|
+
if typed_model_dump:
|
|
757
|
+
try:
|
|
758
|
+
validation_args = deepcopy(parsed_args)
|
|
759
|
+
except Exception:
|
|
760
|
+
validation_args = parsed_args
|
|
761
|
+
validated = tool.input_model.model_validate(validation_args)
|
|
762
|
+
transformed_args = tool._dump_arguments(validated)
|
|
763
|
+
_validate_arguments_against_schema(
|
|
764
|
+
arguments=transformed_args,
|
|
765
|
+
schema=tool._serialization_schema,
|
|
766
|
+
tool_name=tool.name,
|
|
767
|
+
tuples_as_arrays=True,
|
|
768
|
+
values_prevalidated=True,
|
|
769
|
+
)
|
|
770
|
+
pipeline_args = transformed_args
|
|
771
|
+
else:
|
|
772
|
+
_validate_arguments_against_schema(arguments=parsed_args, schema=argument_schema, tool_name=tool.name)
|
|
773
|
+
pipeline_args = parsed_args
|
|
774
|
+
except (TypeError, ValidationError) as exc:
|
|
775
|
+
# ``response_truncated`` (finish_reason == "length") is a response-wide
|
|
776
|
+
# signal, so it alone doesn't prove THIS call's arguments were cut off —
|
|
777
|
+
# a fully-parsed but schema-invalid call, or one bad call among several,
|
|
778
|
+
# can ride along in a truncated response. Only report truncation when
|
|
779
|
+
# this call's own argument payload is actually incomplete/unparseable
|
|
780
|
+
# (raw JSON string), or when the final content block is a parsed mapping
|
|
781
|
+
# that looks incomplete (Anthropic blocking path). Otherwise it's a real
|
|
782
|
+
# argument error the model should fix by correcting the arguments, not by
|
|
783
|
+
# raising max_tokens.
|
|
784
|
+
arguments_unparseable = _arguments_unparseable(function_call.arguments)
|
|
785
|
+
arguments_cut_off = arguments_unparseable or (
|
|
786
|
+
function_call_may_be_truncated
|
|
787
|
+
and _arguments_look_like_truncated_mapping(function_call.arguments, argument_schema)
|
|
788
|
+
)
|
|
789
|
+
if response_truncated and arguments_cut_off:
|
|
790
|
+
message = (
|
|
791
|
+
"The tool call was cut off at the model's output token limit "
|
|
792
|
+
"(max_tokens) before its arguments were complete, so they could not "
|
|
793
|
+
"be parsed. Raise max_tokens for this model, or split the work into "
|
|
794
|
+
"smaller calls (e.g. write the file in parts)."
|
|
795
|
+
)
|
|
796
|
+
error_kind = "argument_truncated"
|
|
797
|
+
else:
|
|
798
|
+
message = _argument_validation_message(
|
|
799
|
+
tool_name=tool.name,
|
|
800
|
+
arguments=parsed_args,
|
|
801
|
+
arguments_unparseable=arguments_unparseable or arguments_not_object,
|
|
802
|
+
schema=argument_schema,
|
|
803
|
+
exception=exc,
|
|
804
|
+
reject_unexpected=reject_unexpected,
|
|
805
|
+
input_model=validation_input_model,
|
|
806
|
+
)
|
|
807
|
+
error_kind = "argument_parsing"
|
|
808
|
+
additional = _result_additional_properties(function_call)
|
|
809
|
+
additional.update(
|
|
810
|
+
{
|
|
811
|
+
TOOL_FAILED_METADATA_KEY: True,
|
|
812
|
+
TOOL_ERROR_KIND_METADATA_KEY: error_kind,
|
|
813
|
+
TOOL_ERROR_MESSAGE_METADATA_KEY: message,
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
result = Content.from_function_result(
|
|
817
|
+
call_id=function_call.call_id, # type: ignore[arg-type]
|
|
818
|
+
result=f"Error: {message}",
|
|
819
|
+
exception=_failure_record_text(exc),
|
|
820
|
+
additional_properties=additional,
|
|
821
|
+
)
|
|
822
|
+
await _record_unexecuted_tool_operation(
|
|
823
|
+
function_call,
|
|
824
|
+
outcome=ToolOutcome.INVALID_ARGUMENTS,
|
|
825
|
+
result_carrier_item_id=result_carrier_item_id,
|
|
826
|
+
result=result,
|
|
827
|
+
)
|
|
828
|
+
return result
|
|
829
|
+
|
|
830
|
+
args = dict(pipeline_args)
|
|
831
|
+
try:
|
|
832
|
+
pipeline_args_snapshot = deepcopy(args)
|
|
833
|
+
except Exception:
|
|
834
|
+
pipeline_args_snapshot = None
|
|
835
|
+
|
|
836
|
+
call_id = function_call.call_id
|
|
837
|
+
if call_id is None:
|
|
838
|
+
# Landing stamped this call an operation id and the batch already
|
|
839
|
+
# counted it as dispatched, so the terminal is owed here — the same
|
|
840
|
+
# debt the unknown-tool and invalid-argument exits above settle.
|
|
841
|
+
await _record_unexecuted_tool_operation(function_call, outcome=ToolOutcome.FILTERED)
|
|
842
|
+
raise KeyError(f'Function "{function_call.name}" is missing call_id.')
|
|
843
|
+
|
|
844
|
+
context = FunctionInvocationContext(
|
|
845
|
+
function=tool,
|
|
846
|
+
arguments=args,
|
|
847
|
+
session=invocation_session,
|
|
848
|
+
kwargs=runtime_kwargs.copy(),
|
|
849
|
+
tools=live_tools,
|
|
850
|
+
)
|
|
851
|
+
context.same_tool_calls_in_batch = same_tool_calls_in_batch
|
|
852
|
+
# Always pass call_id to middleware.
|
|
853
|
+
context.metadata["call_id"] = call_id
|
|
854
|
+
# Seed the kernel-stamped invocation ordinal for middleware readers
|
|
855
|
+
# (approval, event persistence). The call content is the single source;
|
|
856
|
+
# the middleware never renumbers a seeded context.
|
|
857
|
+
ordinal = read_tool_invocation_order(function_call.additional_properties)
|
|
858
|
+
if ordinal is not None:
|
|
859
|
+
context.metadata[TOOL_INVOCATION_ORDER_KEY] = ordinal
|
|
860
|
+
# Trajectory identities for middleware readers: the call's tool operation
|
|
861
|
+
# id (landing stamped it on the content) and the item id every result
|
|
862
|
+
# construction path below will give this call's result.
|
|
863
|
+
operation_id = read_operation_id(function_call.additional_properties)
|
|
864
|
+
if operation_id is not None:
|
|
865
|
+
context.metadata[OPERATION_ID_KEY] = operation_id
|
|
866
|
+
# The call's own item id travels the same way, so a call that runs names
|
|
867
|
+
# its request item exactly like one that never reaches the pipeline.
|
|
868
|
+
call_item_id = read_analytics_item_id(function_call.additional_properties)
|
|
869
|
+
if call_item_id is not None:
|
|
870
|
+
context.metadata[ANALYTICS_ITEM_ID_KEY] = call_item_id
|
|
871
|
+
context.metadata[TOOL_RESULT_ITEM_ID_METADATA_KEY] = new_analytics_id()
|
|
872
|
+
context.metadata[TOOL_RESULT_CARRIER_ITEM_ID_METADATA_KEY] = result_carrier_item_id
|
|
873
|
+
if result_commit is not None:
|
|
874
|
+
|
|
875
|
+
def _commit_sub_agent_interruption(metadata: Mapping[str, Any]) -> None:
|
|
876
|
+
result_commit.commit_interrupted(function_call, context, metadata)
|
|
877
|
+
|
|
878
|
+
context.metadata[SUB_AGENT_RESULT_COMMIT_CALLBACK_KEY] = _commit_sub_agent_interruption
|
|
879
|
+
|
|
880
|
+
async def final_function_handler(context_obj: Any) -> Any:
|
|
881
|
+
# Middleware sees normalized callable-keyspace values. An untouched
|
|
882
|
+
# context goes back through invoke in the original validation keyspace;
|
|
883
|
+
# a trusted mapping rewrite bypasses validation-keyspace conversion.
|
|
884
|
+
logger.info("Function name: %s", tool.name)
|
|
885
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
886
|
+
schema_names = tool.parameters().get("properties") or {}
|
|
887
|
+
logger.debug("Function arguments: %s", _describe_arguments(context_obj.arguments, schema_names))
|
|
888
|
+
# No failure log line here: the outer except below already reports
|
|
889
|
+
# failures (tool name + exception + "returning an error result") — a
|
|
890
|
+
# second line would be duplication (invoke itself is chrys-owned and
|
|
891
|
+
# telemetry/logging-free; the span records the exception).
|
|
892
|
+
start = perf_counter()
|
|
893
|
+
try:
|
|
894
|
+
arguments_unchanged = (
|
|
895
|
+
pipeline_args_snapshot is not None
|
|
896
|
+
and context_obj.arguments is args
|
|
897
|
+
and _middleware_arguments_equal(context_obj.arguments, pipeline_args_snapshot)
|
|
898
|
+
)
|
|
899
|
+
except Exception:
|
|
900
|
+
arguments_unchanged = False
|
|
901
|
+
invocation_arguments = parsed_args if arguments_unchanged else context_obj.arguments
|
|
902
|
+
arguments_in_callable_keyspace = (
|
|
903
|
+
typed_model_dump and not arguments_unchanged and isinstance(invocation_arguments, Mapping)
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
def _commit_raw_result(value: Any) -> None:
|
|
907
|
+
if result_commit is None:
|
|
908
|
+
return
|
|
909
|
+
# This callback runs before FunctionMiddleware ``finally`` blocks
|
|
910
|
+
# publish their measured span. The instant timing makes the raw
|
|
911
|
+
# crash checkpoint complete; terminal construction overwrites it
|
|
912
|
+
# from ``context.metadata`` after middleware unwinds.
|
|
913
|
+
additional = _result_additional_properties(function_call, context)
|
|
914
|
+
parent_metadata = sub_agent_parent_result_metadata.get()
|
|
915
|
+
if parent_metadata is not None:
|
|
916
|
+
carried: dict[str, Any] = {}
|
|
917
|
+
if parent_metadata.sub_agent_invocation_id:
|
|
918
|
+
carried["sub_agent_invocation_id"] = parent_metadata.sub_agent_invocation_id
|
|
919
|
+
if parent_metadata.sub_agent_log_file:
|
|
920
|
+
carried["sub_agent_log_file"] = parent_metadata.sub_agent_log_file
|
|
921
|
+
if parent_metadata.sub_agent_audit_complete:
|
|
922
|
+
carried["sub_agent_audit_complete"] = True
|
|
923
|
+
if carried:
|
|
924
|
+
additional[TOOL_RESULT_METADATA_KEY] = carried
|
|
925
|
+
result_commit.commit_raw(
|
|
926
|
+
Content.from_function_result(
|
|
927
|
+
call_id=call_id,
|
|
928
|
+
result=value,
|
|
929
|
+
additional_properties=additional,
|
|
930
|
+
)
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
try:
|
|
934
|
+
if TELEMETRY_GATE.enabled:
|
|
935
|
+
result = await _invoke_with_function_span(
|
|
936
|
+
tool,
|
|
937
|
+
context_obj,
|
|
938
|
+
call_id,
|
|
939
|
+
arguments=invocation_arguments,
|
|
940
|
+
arguments_in_callable_keyspace=arguments_in_callable_keyspace,
|
|
941
|
+
tool_result_ceiling_tokens=tool_result_ceiling_tokens,
|
|
942
|
+
)
|
|
943
|
+
else:
|
|
944
|
+
result = await tool.invoke(
|
|
945
|
+
arguments=invocation_arguments,
|
|
946
|
+
context=context_obj,
|
|
947
|
+
tool_call_id=call_id,
|
|
948
|
+
_arguments_in_callable_keyspace=arguments_in_callable_keyspace,
|
|
949
|
+
)
|
|
950
|
+
result = apply_result_ceiling(result, tool_result_ceiling_tokens)
|
|
951
|
+
except SyncToolCancelledAfterCompletion as exc:
|
|
952
|
+
# The worker finished before cancellation landed: journal the
|
|
953
|
+
# completed value as this slot's raw result so the interrupt
|
|
954
|
+
# handler preserves it instead of recording a fabricated
|
|
955
|
+
# interruption for work that actually ran.
|
|
956
|
+
exc.completed_result = apply_result_ceiling(exc.completed_result, tool_result_ceiling_tokens)
|
|
957
|
+
_commit_raw_result(exc.completed_result)
|
|
958
|
+
raise
|
|
959
|
+
_commit_raw_result(result)
|
|
960
|
+
logger.info("Function %s succeeded in %.3fs.", tool.name, perf_counter() - start)
|
|
961
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
962
|
+
logger.debug(
|
|
963
|
+
"Function result: %s",
|
|
964
|
+
_describe_result(result, skip_parsing=_is_skip_parsing_sentinel(tool.result_parser)),
|
|
965
|
+
)
|
|
966
|
+
return result
|
|
967
|
+
|
|
968
|
+
try:
|
|
969
|
+
function_result = await pipeline.execute(
|
|
970
|
+
context=context,
|
|
971
|
+
final_handler=final_function_handler,
|
|
972
|
+
)
|
|
973
|
+
return Content.from_function_result(
|
|
974
|
+
call_id=call_id,
|
|
975
|
+
result=function_result,
|
|
976
|
+
additional_properties=_result_additional_properties(function_call, context),
|
|
977
|
+
)
|
|
978
|
+
except asyncio.CancelledError:
|
|
979
|
+
# Function middleware ``finally`` blocks have completed at this
|
|
980
|
+
# boundary, so their measured timing and result metadata are now on
|
|
981
|
+
# the invocation context. Commit with that context before the outer
|
|
982
|
+
# batch-level cancellation fallback loses it.
|
|
983
|
+
if result_commit is not None:
|
|
984
|
+
result_commit.commit_interrupted(function_call, context)
|
|
985
|
+
raise
|
|
986
|
+
except MiddlewareTermination as term_exc:
|
|
987
|
+
# Re-raise to signal loop termination, capturing any middleware-set
|
|
988
|
+
# result first.
|
|
989
|
+
if context.result is not None:
|
|
990
|
+
term_exc.result = Content.from_function_result(
|
|
991
|
+
call_id=call_id,
|
|
992
|
+
result=context.result,
|
|
993
|
+
additional_properties=_result_additional_properties(function_call, context),
|
|
994
|
+
)
|
|
995
|
+
elif isinstance(term_exc.result, Content):
|
|
996
|
+
# Middleware may cache and reuse a prebuilt result across concurrent
|
|
997
|
+
# calls or later runs. Own a wrapper per invocation before binding
|
|
998
|
+
# call-scoped identity and metadata; its payload may contain
|
|
999
|
+
# external objects that are deliberately unsafe to deep-copy.
|
|
1000
|
+
owned_result = copy(term_exc.result)
|
|
1001
|
+
owned_result.call_id = call_id
|
|
1002
|
+
prebuilt_properties = term_exc.result.additional_properties
|
|
1003
|
+
# This call's properties go on top of whatever the prebuilt result
|
|
1004
|
+
# carried, the same fold every other result path uses: a result
|
|
1005
|
+
# cached across calls arrives wearing another invocation's identity,
|
|
1006
|
+
# and the tool operation and pre-minted item id have to be this
|
|
1007
|
+
# one's or the trajectory's terminal names an item nothing saved.
|
|
1008
|
+
properties = {**prebuilt_properties, **_result_additional_properties(function_call, context)}
|
|
1009
|
+
owned = prebuilt_properties.get(TOOL_RESULT_METADATA_KEY)
|
|
1010
|
+
if isinstance(owned, Mapping) and owned:
|
|
1011
|
+
# A middleware that built its own result Content owns whatever
|
|
1012
|
+
# metadata it chose to attach — fold first-write-wins only.
|
|
1013
|
+
properties[TOOL_RESULT_METADATA_KEY] = dict(owned)
|
|
1014
|
+
owned_result.additional_properties = properties
|
|
1015
|
+
term_exc.result = owned_result
|
|
1016
|
+
else:
|
|
1017
|
+
term_exc.result = Content.from_function_result(
|
|
1018
|
+
call_id=call_id,
|
|
1019
|
+
result=term_exc.result,
|
|
1020
|
+
additional_properties=_result_additional_properties(function_call, context),
|
|
1021
|
+
)
|
|
1022
|
+
raise
|
|
1023
|
+
except Exception as exc:
|
|
1024
|
+
logger.warning(
|
|
1025
|
+
"Function '%s' raised an exception; returning an error result to the model. Exception: %r",
|
|
1026
|
+
tool.name,
|
|
1027
|
+
exc,
|
|
1028
|
+
)
|
|
1029
|
+
return Content.from_function_result(
|
|
1030
|
+
call_id=function_call.call_id, # type: ignore[arg-type]
|
|
1031
|
+
result=tool_error_result_text(exc),
|
|
1032
|
+
# Never None: it marks the result failed for the consecutive-error
|
|
1033
|
+
# count and the providers' error flag, whatever the model reads.
|
|
1034
|
+
exception=_failure_record_text(exc),
|
|
1035
|
+
additional_properties=_result_additional_properties(function_call, context),
|
|
1036
|
+
)
|
|
1037
|
+
finally:
|
|
1038
|
+
# Backfill per-call provenance subkeys (builder context resolved from
|
|
1039
|
+
# the final middleware args) onto the persisted call content. Runs on
|
|
1040
|
+
# every pipeline exit; on cancellation the middleware wrote no
|
|
1041
|
+
# carriage, so there is nothing to merge.
|
|
1042
|
+
carried_context = context.metadata.get(TOOL_CALL_CONTEXT_METADATA_KEY)
|
|
1043
|
+
if isinstance(carried_context, Mapping):
|
|
1044
|
+
merge_tool_call_context_property(function_call.additional_properties, carried_context)
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
async def _execute_function_calls(
|
|
1048
|
+
*,
|
|
1049
|
+
function_calls: Sequence[Content],
|
|
1050
|
+
result_commits: Sequence[ToolResultCommit] | None,
|
|
1051
|
+
tool_options: dict[str, Any],
|
|
1052
|
+
custom_args: dict[str, Any],
|
|
1053
|
+
invocation_session: AgentSession | None,
|
|
1054
|
+
pipeline: FunctionMiddlewarePipeline,
|
|
1055
|
+
result_carrier_item_id: str,
|
|
1056
|
+
response_truncated: bool = False,
|
|
1057
|
+
truncated_final_function_call_ids: set[int] | None = None,
|
|
1058
|
+
tool_result_ceiling_tokens: int | None = None,
|
|
1059
|
+
dispatch_observer: Callable[[Sequence[Content]], None] | None = None,
|
|
1060
|
+
) -> tuple[list[Content], bool, bool]:
|
|
1061
|
+
"""Execute a batch concurrently and collect results, termination and error flags."""
|
|
1062
|
+
raw_tools = tool_options.get("tools")
|
|
1063
|
+
if not raw_tools:
|
|
1064
|
+
return [], False, False
|
|
1065
|
+
|
|
1066
|
+
# Normalize per batch: direct callers and progressive exposure can add
|
|
1067
|
+
# tools after agent preparation. Every executed tool must use FunctionTool.invoke.
|
|
1068
|
+
tools = normalize_tools(raw_tools)
|
|
1069
|
+
tool_options["tools"] = tools
|
|
1070
|
+
if not tools:
|
|
1071
|
+
return [], False, False
|
|
1072
|
+
# Rebuild from the live list so progressive exposure takes effect next batch.
|
|
1073
|
+
# Tool admission has already enforced unique names.
|
|
1074
|
+
tool_map: dict[str, FunctionTool] = {t.name: t for t in tools if isinstance(t, FunctionTool)}
|
|
1075
|
+
live_tools: list[Any] | None = tools
|
|
1076
|
+
|
|
1077
|
+
# Per-name call counts for this batch: middleware with singleton semantics
|
|
1078
|
+
# (e.g. whole-list-replacement tools) reads the stamped count to reject
|
|
1079
|
+
# same-batch duplicates deterministically instead of racing the gather.
|
|
1080
|
+
same_name_counts = Counter(fc.name for fc in function_calls)
|
|
1081
|
+
|
|
1082
|
+
commits: Sequence[ToolResultCommit | None]
|
|
1083
|
+
if result_commits is None:
|
|
1084
|
+
commits = (None,) * len(function_calls)
|
|
1085
|
+
else:
|
|
1086
|
+
if len(result_commits) != len(function_calls):
|
|
1087
|
+
raise ValueError("Tool-result commit bindings do not match the extracted call batch.")
|
|
1088
|
+
commits = result_commits
|
|
1089
|
+
|
|
1090
|
+
async def invoke_with_termination_handling(
|
|
1091
|
+
function_call: Content,
|
|
1092
|
+
result_commit: ToolResultCommit | None,
|
|
1093
|
+
) -> tuple[Content, bool]:
|
|
1094
|
+
"""Catch MiddlewareTermination, returning ``(result, should_terminate)``."""
|
|
1095
|
+
# The handover is per call and happens on this task's own first step,
|
|
1096
|
+
# not once for the batch: a task cancelled before it ever runs never
|
|
1097
|
+
# executes a line of this body, so its operation has to stay in the
|
|
1098
|
+
# loop's ledger for the reconciliation pass to close.
|
|
1099
|
+
if dispatch_observer is not None:
|
|
1100
|
+
dispatch_observer((function_call,))
|
|
1101
|
+
try:
|
|
1102
|
+
result = await _invoke_function_call(
|
|
1103
|
+
function_call,
|
|
1104
|
+
result_commit=result_commit,
|
|
1105
|
+
tool_map=tool_map,
|
|
1106
|
+
custom_args=custom_args,
|
|
1107
|
+
invocation_session=invocation_session,
|
|
1108
|
+
pipeline=pipeline,
|
|
1109
|
+
live_tools=live_tools,
|
|
1110
|
+
response_truncated=response_truncated,
|
|
1111
|
+
function_call_may_be_truncated=(
|
|
1112
|
+
response_truncated
|
|
1113
|
+
and truncated_final_function_call_ids is not None
|
|
1114
|
+
and id(function_call) in truncated_final_function_call_ids
|
|
1115
|
+
),
|
|
1116
|
+
same_tool_calls_in_batch=same_name_counts[function_call.name],
|
|
1117
|
+
tool_result_ceiling_tokens=tool_result_ceiling_tokens,
|
|
1118
|
+
result_carrier_item_id=result_carrier_item_id,
|
|
1119
|
+
)
|
|
1120
|
+
result = apply_result_ceiling(result, tool_result_ceiling_tokens)
|
|
1121
|
+
if result_commit is not None:
|
|
1122
|
+
result_commit.commit_final(result)
|
|
1123
|
+
return (result, False)
|
|
1124
|
+
except MiddlewareTermination as exc:
|
|
1125
|
+
# exc.result may already be a Content (set by _invoke_function_call)
|
|
1126
|
+
# or a raw value.
|
|
1127
|
+
if isinstance(exc.result, Content):
|
|
1128
|
+
bounded_result = apply_result_ceiling(exc.result, tool_result_ceiling_tokens)
|
|
1129
|
+
if result_commit is not None:
|
|
1130
|
+
result_commit.commit_final(bounded_result)
|
|
1131
|
+
return (bounded_result, True)
|
|
1132
|
+
result_content = Content.from_function_result(
|
|
1133
|
+
call_id=function_call.call_id, # type: ignore[arg-type]
|
|
1134
|
+
result=exc.result,
|
|
1135
|
+
additional_properties=_result_additional_properties(function_call),
|
|
1136
|
+
)
|
|
1137
|
+
result_content = apply_result_ceiling(result_content, tool_result_ceiling_tokens)
|
|
1138
|
+
if result_commit is not None:
|
|
1139
|
+
result_commit.commit_final(result_content)
|
|
1140
|
+
return (result_content, True)
|
|
1141
|
+
except asyncio.CancelledError:
|
|
1142
|
+
if result_commit is not None:
|
|
1143
|
+
result_commit.commit_interrupted(function_call, None)
|
|
1144
|
+
raise
|
|
1145
|
+
|
|
1146
|
+
tasks = [
|
|
1147
|
+
asyncio.create_task(invoke_with_termination_handling(function_call, result_commit))
|
|
1148
|
+
for function_call, result_commit in zip(function_calls, commits, strict=True)
|
|
1149
|
+
]
|
|
1150
|
+
try:
|
|
1151
|
+
settled = await asyncio.gather(*tasks, return_exceptions=True)
|
|
1152
|
+
except asyncio.CancelledError:
|
|
1153
|
+
for task in tasks:
|
|
1154
|
+
if not task.done():
|
|
1155
|
+
task.cancel()
|
|
1156
|
+
settling = asyncio.gather(*tasks, return_exceptions=True)
|
|
1157
|
+
while not settling.done():
|
|
1158
|
+
try:
|
|
1159
|
+
await asyncio.shield(settling)
|
|
1160
|
+
except asyncio.CancelledError:
|
|
1161
|
+
continue
|
|
1162
|
+
raise
|
|
1163
|
+
|
|
1164
|
+
for outcome in settled:
|
|
1165
|
+
if isinstance(outcome, BaseException):
|
|
1166
|
+
raise outcome
|
|
1167
|
+
execution_results = [outcome for outcome in settled if not isinstance(outcome, BaseException)]
|
|
1168
|
+
|
|
1169
|
+
contents: list[Content] = [result[0] for result in execution_results]
|
|
1170
|
+
should_terminate = any(result[1] for result in execution_results)
|
|
1171
|
+
had_errors = any(fcr.exception is not None for fcr in contents if fcr.type == "function_result")
|
|
1172
|
+
return contents, should_terminate, had_errors
|