iCode-TUI 0.29.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
chrys/kernel/client.py
ADDED
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
3
|
+
# Contains code adapted from Microsoft Agent Framework (MIT License; see NOTICE).
|
|
4
|
+
|
|
5
|
+
"""Chrys-owned chat client base.
|
|
6
|
+
|
|
7
|
+
The ``BaseChatClient`` subset Chrys consumes:
|
|
8
|
+
option validation, response-stream construction, compaction preparation, and
|
|
9
|
+
the public ``get_response`` dispatcher. The ``as_agent`` and
|
|
10
|
+
embedding surfaces are intentionally not ported.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import math
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from collections.abc import AsyncIterable, Awaitable, Callable, Collection, Iterator, Mapping, Sequence
|
|
21
|
+
from contextlib import contextmanager
|
|
22
|
+
from contextvars import ContextVar
|
|
23
|
+
from copy import copy
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol, TypeGuard, TypeIs, cast, overload
|
|
26
|
+
|
|
27
|
+
from chrys.foundation.errors import ProviderResponseError
|
|
28
|
+
from chrys.foundation.trajectory.context import TRAJECTORY_EXCHANGE_KWARG, ExchangeTrace, side_call_scope
|
|
29
|
+
from chrys.foundation.trajectory.envelope import ActorRole
|
|
30
|
+
|
|
31
|
+
from ._serialization import SerializationMixin, make_json_safe
|
|
32
|
+
from ._types import ChatResponse, ChatResponseUpdate, Message, ResponseStream, validate_chat_options
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from .compaction import CompactionCallContext, TokenizerProtocol
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_log = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
# A streamed wire pull's stall watchdog also times the work that runs before
|
|
41
|
+
# the provider's first byte: compaction and its LAST_WORDS side call. That
|
|
42
|
+
# work reports liveness through this callback, which the watchdog installs in
|
|
43
|
+
# the context it times, so the watchdog times idle gaps instead of the whole
|
|
44
|
+
# pull. A timer nested inside (a side call's own read timeout) chains to it.
|
|
45
|
+
# Outside a watchdog it is None.
|
|
46
|
+
_WIRE_PROGRESS: ContextVar[Callable[[], None] | None] = ContextVar("chrys_wire_progress", default=None)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def report_wire_progress() -> None:
|
|
50
|
+
"""Restart the idle timers of the enclosing stall watchdogs, if any are running."""
|
|
51
|
+
callback = _WIRE_PROGRESS.get()
|
|
52
|
+
if callback is not None:
|
|
53
|
+
callback()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@contextmanager
|
|
57
|
+
def wire_progress_scope(on_progress: Callable[[], None]) -> Iterator[None]:
|
|
58
|
+
"""Report progress from the block, and every task it spawns, to *on_progress*.
|
|
59
|
+
|
|
60
|
+
A report also reaches the scopes this one is nested in: an inner timer
|
|
61
|
+
never hides progress from the watchdog waiting on its work. Spawned tasks
|
|
62
|
+
copy the context, so they can report after the block exits: every
|
|
63
|
+
callback must tolerate a report once its timer is gone.
|
|
64
|
+
"""
|
|
65
|
+
enclosing = _WIRE_PROGRESS.get()
|
|
66
|
+
|
|
67
|
+
def _report() -> None:
|
|
68
|
+
on_progress()
|
|
69
|
+
if enclosing is not None:
|
|
70
|
+
enclosing()
|
|
71
|
+
|
|
72
|
+
token = _WIRE_PROGRESS.set(_report)
|
|
73
|
+
try:
|
|
74
|
+
yield
|
|
75
|
+
finally:
|
|
76
|
+
_WIRE_PROGRESS.reset(token)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def start_with_wire_progress(awaitable: Awaitable[Any], on_progress: Callable[[], None]) -> asyncio.Future[Any]:
|
|
80
|
+
"""Schedule *awaitable* as a task whose context reports progress to *on_progress*.
|
|
81
|
+
|
|
82
|
+
The task copies the context at creation, so the callback reaches
|
|
83
|
+
everything the task runs and nothing the caller runs afterwards.
|
|
84
|
+
"""
|
|
85
|
+
with wire_progress_scope(on_progress):
|
|
86
|
+
return asyncio.ensure_future(awaitable)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _report_side_call_update(_update: ChatResponseUpdate) -> None:
|
|
90
|
+
report_wire_progress()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Output-cap spellings in provider conflict-priority order. The canonical
|
|
94
|
+
# ``max_tokens`` alias wins when more than one is supplied.
|
|
95
|
+
OUTPUT_CAP_OPTION_ALIASES: tuple[str, ...] = ("max_tokens", "max_output_tokens", "max_completion_tokens")
|
|
96
|
+
|
|
97
|
+
# Option keys that must never reach a LAST_WORDS side call.
|
|
98
|
+
#
|
|
99
|
+
# Continuation markers would chain the throwaway request into service-side
|
|
100
|
+
# conversation state: the OpenAI Responses serializer treats every one of
|
|
101
|
+
# conversation_id / previous_response_id / conversation as a service-side
|
|
102
|
+
# storage marker, the raw provider keys pass through its run options
|
|
103
|
+
# untouched, and a continuation_token short-circuits _inner_get_response
|
|
104
|
+
# into retrieving an existing background response — the side call's messages
|
|
105
|
+
# would be ignored and a live response returned as the note. The side call
|
|
106
|
+
# sends a self-contained scoped slice, so it needs no continuation state to be
|
|
107
|
+
# useful.
|
|
108
|
+
_SIDE_CALL_CONTINUATION_KEYS = ("conversation_id", "previous_response_id", "conversation", "continuation_token")
|
|
109
|
+
CONVERSATION_HANDLE_KEYS = ("conversation_id", "previous_response_id", "conversation")
|
|
110
|
+
_FORCED_STATELESS_OPTION_KEYS = (*CONVERSATION_HANDLE_KEYS, "continuation_token", "background")
|
|
111
|
+
# Output-shaping keys would force the note into schema JSON — non-empty, so
|
|
112
|
+
# it would pass the have_note guard. Beyond the first-class response_format,
|
|
113
|
+
# provider-native passthroughs survive serialization: OpenAI Responses honors
|
|
114
|
+
# a raw ``text`` (carrying ``format``) / ``text_format``, and the Anthropic
|
|
115
|
+
# serializer forwards a raw ``output_format`` as a request kwarg.
|
|
116
|
+
_SIDE_CALL_OUTPUT_SHAPING_KEYS = ("response_format", "text", "text_format", "output_format", "output_config")
|
|
117
|
+
# Execution-mode keys: an inherited Responses ``background=True`` would
|
|
118
|
+
# launch a never-polled background response per attempt (its in_progress
|
|
119
|
+
# status parses to empty text, so the retry loop would keep spawning
|
|
120
|
+
# orphans). The side call must run as a plain foreground request.
|
|
121
|
+
_SIDE_CALL_EXECUTION_MODE_KEYS = ("background",)
|
|
122
|
+
# Keys scrubbed from a forwarded ``extra_body`` mapping: provider SDKs merge
|
|
123
|
+
# ``extra_body`` into the request body AFTER the named parameters, so a
|
|
124
|
+
# nested spelling of any of these would silently override the side call's
|
|
125
|
+
# top-level sanitization (``store=False``, ``tool_choice="none"``, the
|
|
126
|
+
# output cap) on the wire.
|
|
127
|
+
_SIDE_CALL_EXTRA_BODY_STRIP_KEYS = frozenset(
|
|
128
|
+
{
|
|
129
|
+
*_SIDE_CALL_CONTINUATION_KEYS,
|
|
130
|
+
*_SIDE_CALL_OUTPUT_SHAPING_KEYS,
|
|
131
|
+
*_SIDE_CALL_EXECUTION_MODE_KEYS,
|
|
132
|
+
*OUTPUT_CAP_OPTION_ALIASES,
|
|
133
|
+
"store",
|
|
134
|
+
"tool_choice",
|
|
135
|
+
# Input-bearing keys: a nested spelling would replace the scoped
|
|
136
|
+
# LAST_WORDS slice or its instruction — or re-enable tools — once
|
|
137
|
+
# the SDK merges ``extra_body`` over the prepared body. The profile
|
|
138
|
+
# loader strips these from user config, but programmatic options and
|
|
139
|
+
# client kwargs never pass through it.
|
|
140
|
+
"input",
|
|
141
|
+
"messages",
|
|
142
|
+
"prompt",
|
|
143
|
+
"tools",
|
|
144
|
+
"system",
|
|
145
|
+
"instructions",
|
|
146
|
+
"stream",
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True, slots=True)
|
|
152
|
+
class StorageModeResolution:
|
|
153
|
+
"""First-request storage mode plus handle-free request copies."""
|
|
154
|
+
|
|
155
|
+
service_side: bool
|
|
156
|
+
handles_present: bool
|
|
157
|
+
options_without_handles: dict[str, Any]
|
|
158
|
+
client_kwargs_without_handles: dict[str, Any]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _copy_without_conversation_handles(
|
|
162
|
+
payload: Mapping[str, Any] | None,
|
|
163
|
+
*,
|
|
164
|
+
force_stateless: bool = False,
|
|
165
|
+
) -> tuple[dict[str, Any], bool]:
|
|
166
|
+
copied = dict(payload or {})
|
|
167
|
+
stripped_keys = _FORCED_STATELESS_OPTION_KEYS if force_stateless else CONVERSATION_HANDLE_KEYS
|
|
168
|
+
handles_present = any(key in copied for key in stripped_keys)
|
|
169
|
+
for key in stripped_keys:
|
|
170
|
+
copied.pop(key, None)
|
|
171
|
+
extra_body = copied.get("extra_body")
|
|
172
|
+
if isinstance(extra_body, Mapping):
|
|
173
|
+
clean_extra_body = dict(extra_body)
|
|
174
|
+
handles_present = handles_present or any(key in clean_extra_body for key in stripped_keys)
|
|
175
|
+
for key in stripped_keys:
|
|
176
|
+
clean_extra_body.pop(key, None)
|
|
177
|
+
copied["extra_body"] = clean_extra_body
|
|
178
|
+
return copied, handles_present
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def conversation_handle_value(value: Any) -> str | None:
|
|
182
|
+
"""Normalize one conversation-handle spelling to its string id.
|
|
183
|
+
|
|
184
|
+
``conversation`` admits a mapping form carrying the id under ``"id"``;
|
|
185
|
+
the other spellings carry the id directly. Anything else returns None —
|
|
186
|
+
an unrecognized shape is foreign state this layer must not interpret.
|
|
187
|
+
"""
|
|
188
|
+
if isinstance(value, Mapping):
|
|
189
|
+
value = value.get("id")
|
|
190
|
+
if isinstance(value, str) and value:
|
|
191
|
+
return value
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def continuation_token_response_id(value: Any) -> str | None:
|
|
196
|
+
"""Normalize a continuation token to the response id it would retrieve.
|
|
197
|
+
|
|
198
|
+
Provider tokens are opaque TypedDicts; the Responses client's token
|
|
199
|
+
carries the background response's id under ``"response_id"`` and
|
|
200
|
+
short-circuits the request into retrieving that response outright while
|
|
201
|
+
the request messages are ignored. Tokens of any other shape return
|
|
202
|
+
None — they cannot be interpreted at this layer.
|
|
203
|
+
"""
|
|
204
|
+
if isinstance(value, Mapping):
|
|
205
|
+
response_id = value.get("response_id")
|
|
206
|
+
if isinstance(response_id, str) and response_id:
|
|
207
|
+
return response_id
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def collect_conversation_handles(payload: Mapping[str, Any] | None) -> list[str]:
|
|
212
|
+
"""Collect every normalized conversation handle riding a request mapping.
|
|
213
|
+
|
|
214
|
+
Scans each ``CONVERSATION_HANDLE_KEYS`` spelling at the top level and
|
|
215
|
+
nested inside ``extra_body``: provider SDKs merge ``extra_body`` into the
|
|
216
|
+
request body after the named parameters, so a nested spelling reaches the
|
|
217
|
+
wire as continuation state all the same. A top-level
|
|
218
|
+
``continuation_token`` counts too — its response id is continuation
|
|
219
|
+
state exactly like ``previous_response_id`` (nested extra_body copies
|
|
220
|
+
never reach the retrieve short-circuit, so they don't participate).
|
|
221
|
+
"""
|
|
222
|
+
handles: list[str] = []
|
|
223
|
+
if not payload:
|
|
224
|
+
return handles
|
|
225
|
+
extra_body = payload.get("extra_body")
|
|
226
|
+
for source in (payload, extra_body if isinstance(extra_body, Mapping) else None):
|
|
227
|
+
if source is None:
|
|
228
|
+
continue
|
|
229
|
+
for key in CONVERSATION_HANDLE_KEYS:
|
|
230
|
+
handle = conversation_handle_value(source.get(key))
|
|
231
|
+
if handle is not None:
|
|
232
|
+
handles.append(handle)
|
|
233
|
+
token_response_id = continuation_token_response_id(payload.get("continuation_token"))
|
|
234
|
+
if token_response_id is not None:
|
|
235
|
+
handles.append(token_response_id)
|
|
236
|
+
return handles
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def strip_invalidated_conversation_handles(
|
|
240
|
+
payload: Mapping[str, Any] | None,
|
|
241
|
+
invalidated: Collection[str],
|
|
242
|
+
) -> dict[str, Any]:
|
|
243
|
+
"""Copy a request mapping without the handles a session has invalidated.
|
|
244
|
+
|
|
245
|
+
The provider-facing reflection view: history providers decide whether
|
|
246
|
+
the service owns a run's history by looking at the request's
|
|
247
|
+
continuation handles, so a handle the wire choke point strips must not
|
|
248
|
+
appear live to them. Removal-only and copy-on-write across the same
|
|
249
|
+
spellings :func:`collect_conversation_handles` scans; live handles pass
|
|
250
|
+
through untouched.
|
|
251
|
+
"""
|
|
252
|
+
copied = dict(payload or {})
|
|
253
|
+
if not invalidated:
|
|
254
|
+
return copied
|
|
255
|
+
for key in CONVERSATION_HANDLE_KEYS:
|
|
256
|
+
handle = conversation_handle_value(copied.get(key))
|
|
257
|
+
if handle is not None and handle in invalidated:
|
|
258
|
+
del copied[key]
|
|
259
|
+
token_response_id = continuation_token_response_id(copied.get("continuation_token"))
|
|
260
|
+
if token_response_id is not None and token_response_id in invalidated:
|
|
261
|
+
del copied["continuation_token"]
|
|
262
|
+
extra_body = copied.get("extra_body")
|
|
263
|
+
if isinstance(extra_body, Mapping):
|
|
264
|
+
poisoned_keys = [
|
|
265
|
+
key
|
|
266
|
+
for key in CONVERSATION_HANDLE_KEYS
|
|
267
|
+
if (nested := conversation_handle_value(extra_body.get(key))) is not None and nested in invalidated
|
|
268
|
+
]
|
|
269
|
+
if poisoned_keys:
|
|
270
|
+
clean_extra_body = dict(extra_body)
|
|
271
|
+
for key in poisoned_keys:
|
|
272
|
+
del clean_extra_body[key]
|
|
273
|
+
copied["extra_body"] = clean_extra_body
|
|
274
|
+
return copied
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def resolve_storage_mode_and_handles(
|
|
278
|
+
options: Mapping[str, Any] | None,
|
|
279
|
+
*,
|
|
280
|
+
stores_by_default: bool,
|
|
281
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
282
|
+
force_stateless: bool = False,
|
|
283
|
+
) -> StorageModeResolution:
|
|
284
|
+
"""Resolve effective storage and copy both request mappings without handles.
|
|
285
|
+
|
|
286
|
+
The wire truth lives in ``options``: every current client derives
|
|
287
|
+
``store`` from options alone, with a provider ``extra_body`` value
|
|
288
|
+
overriding the named parameter (a present ``None`` reaches the wire as
|
|
289
|
+
JSON ``null``, selecting the provider's storage default). Client
|
|
290
|
+
kwargs never reach the request,
|
|
291
|
+
so a kwargs-side ``store`` may only *escalate* the judgment toward
|
|
292
|
+
service-side — misclassifying a stored request as local would license
|
|
293
|
+
in-place re-creates that duplicate hosted work, while the reverse merely
|
|
294
|
+
retries more conservatively. Conversation handles never decide storage
|
|
295
|
+
mode; the first request's effective ``store`` value does.
|
|
296
|
+
|
|
297
|
+
``force_stateless`` is a client capability veto: it keeps the request in
|
|
298
|
+
local-history/retry mode regardless of every ``store`` spelling and
|
|
299
|
+
removes conversation, continuation, and background state from the
|
|
300
|
+
reflected copies. The provider-facing options remain the caller's own
|
|
301
|
+
view so a raw client can reject unsupported execution modes explicitly.
|
|
302
|
+
"""
|
|
303
|
+
effective_options = options or {}
|
|
304
|
+
effective_kwargs = client_kwargs or {}
|
|
305
|
+
option_extra = effective_options.get("extra_body")
|
|
306
|
+
kwargs_extra = effective_kwargs.get("extra_body")
|
|
307
|
+
if isinstance(option_extra, Mapping) and "store" in option_extra:
|
|
308
|
+
# A *present* extra_body key overrides the named parameter even when
|
|
309
|
+
# its value is None: the merged request body then carries
|
|
310
|
+
# ``"store": null`` and the provider applies its storage default —
|
|
311
|
+
# so fall to ``stores_by_default`` rather than the named value.
|
|
312
|
+
explicit_store = option_extra["store"]
|
|
313
|
+
else:
|
|
314
|
+
explicit_store = effective_options.get("store")
|
|
315
|
+
kwargs_escalation = bool(
|
|
316
|
+
(kwargs_extra.get("store") if isinstance(kwargs_extra, Mapping) else None) or effective_kwargs.get("store")
|
|
317
|
+
)
|
|
318
|
+
clean_options, option_handles = _copy_without_conversation_handles(
|
|
319
|
+
effective_options,
|
|
320
|
+
force_stateless=force_stateless,
|
|
321
|
+
)
|
|
322
|
+
clean_kwargs, kwargs_handles = _copy_without_conversation_handles(
|
|
323
|
+
effective_kwargs,
|
|
324
|
+
force_stateless=force_stateless,
|
|
325
|
+
)
|
|
326
|
+
return StorageModeResolution(
|
|
327
|
+
service_side=False
|
|
328
|
+
if force_stateless
|
|
329
|
+
else (bool(explicit_store) if explicit_store is not None else stores_by_default) or kwargs_escalation,
|
|
330
|
+
handles_present=option_handles or kwargs_handles,
|
|
331
|
+
options_without_handles=clean_options,
|
|
332
|
+
client_kwargs_without_handles=clean_kwargs,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _scrub_side_call_extra_body(payload: dict[str, Any]) -> None:
|
|
337
|
+
"""Replace a Mapping ``extra_body`` with a copy minus wire-overriding keys."""
|
|
338
|
+
extra_body = payload.get("extra_body")
|
|
339
|
+
if isinstance(extra_body, Mapping):
|
|
340
|
+
payload["extra_body"] = {
|
|
341
|
+
key: value for key, value in extra_body.items() if key not in _SIDE_CALL_EXTRA_BODY_STRIP_KEYS
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _tool_choice_forces_tool_use(tool_choice: Any) -> bool:
|
|
346
|
+
"""Return whether a standard or provider-native choice forbids prose-only output."""
|
|
347
|
+
if isinstance(tool_choice, str):
|
|
348
|
+
return tool_choice.lower() in {"required", "any"}
|
|
349
|
+
if not isinstance(tool_choice, Mapping):
|
|
350
|
+
tool_choice = make_json_safe(tool_choice)
|
|
351
|
+
if not isinstance(tool_choice, Mapping):
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
mode = tool_choice.get("mode")
|
|
355
|
+
if isinstance(mode, str):
|
|
356
|
+
return mode.lower() in {"required", "any"}
|
|
357
|
+
choice_type = tool_choice.get("type")
|
|
358
|
+
if isinstance(choice_type, str) and choice_type.lower() in {"required", "any", "function", "tool", "custom"}:
|
|
359
|
+
return True
|
|
360
|
+
return bool(tool_choice.get("required_function_name"))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _estimate_tool_definition_tokens(options: Mapping[str, Any], tokenizer: TokenizerProtocol | None) -> int:
|
|
364
|
+
"""Estimate the live call's tool definitions without touching provider objects."""
|
|
365
|
+
tools = options.get("tools")
|
|
366
|
+
if tools is None or tokenizer is None:
|
|
367
|
+
return 0
|
|
368
|
+
serialized = json.dumps(make_json_safe(tools), ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
369
|
+
return max(0, tokenizer.count_tokens(serialized))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _estimate_value_tokens(value: Any, tokenizer: TokenizerProtocol) -> int:
|
|
373
|
+
if isinstance(value, type):
|
|
374
|
+
# A class-valued option (e.g. a Pydantic ``response_format``) wires
|
|
375
|
+
# as its JSON schema; ``make_json_safe`` would instead walk the class
|
|
376
|
+
# ``__dict__`` (core schema, validators, …) and inflate a one-field
|
|
377
|
+
# model to ~100k serialized characters.
|
|
378
|
+
schema_method = getattr(value, "model_json_schema", None)
|
|
379
|
+
try:
|
|
380
|
+
value = schema_method() if callable(schema_method) else str(value)
|
|
381
|
+
except Exception:
|
|
382
|
+
value = str(value)
|
|
383
|
+
text = (
|
|
384
|
+
value
|
|
385
|
+
if isinstance(value, str)
|
|
386
|
+
else json.dumps(
|
|
387
|
+
make_json_safe(value),
|
|
388
|
+
ensure_ascii=False,
|
|
389
|
+
sort_keys=True,
|
|
390
|
+
separators=(",", ":"),
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
return max(0, tokenizer.count_tokens(text))
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# Instruction-bearing plus every provider-native output-shaping spelling —
|
|
397
|
+
# the same set the side call scrubs, because the same keys carry wire payload.
|
|
398
|
+
_REQUEST_OVERHEAD_OPTION_KEYS = ("instructions", "system", *_SIDE_CALL_OUTPUT_SHAPING_KEYS)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _estimate_fixed_request_overhead_tokens(
|
|
402
|
+
options: Mapping[str, Any],
|
|
403
|
+
client_kwargs: Mapping[str, Any],
|
|
404
|
+
tokenizer: TokenizerProtocol | None,
|
|
405
|
+
*,
|
|
406
|
+
tool_definition_tokens: int | None = None,
|
|
407
|
+
) -> int:
|
|
408
|
+
"""Estimate fixed request content injected outside the message list."""
|
|
409
|
+
if tokenizer is None:
|
|
410
|
+
return 0
|
|
411
|
+
total = (
|
|
412
|
+
_estimate_tool_definition_tokens(options, tokenizer)
|
|
413
|
+
if tool_definition_tokens is None
|
|
414
|
+
else tool_definition_tokens
|
|
415
|
+
)
|
|
416
|
+
# ``extra_body`` merges into the request body after the named parameters,
|
|
417
|
+
# so nested spellings of these keys ship on the wire too.
|
|
418
|
+
extra_bodies = [
|
|
419
|
+
body for body in (client_kwargs.get("extra_body"), options.get("extra_body")) if isinstance(body, Mapping)
|
|
420
|
+
]
|
|
421
|
+
for key in _REQUEST_OVERHEAD_OPTION_KEYS:
|
|
422
|
+
# Providers disagree on options-vs-kwargs precedence for these keys
|
|
423
|
+
# (Anthropic ultimately wires ``options["instructions"]`` over a
|
|
424
|
+
# kwargs-derived ``system``), so admission counts the larger
|
|
425
|
+
# candidate: overestimating only trims output room, undercounting
|
|
426
|
+
# could overflow the context.
|
|
427
|
+
candidates = [client_kwargs.get(key), options.get(key), *(body.get(key) for body in extra_bodies)]
|
|
428
|
+
estimates = [_estimate_value_tokens(value, tokenizer) for value in candidates if value is not None]
|
|
429
|
+
if estimates:
|
|
430
|
+
total += max(estimates)
|
|
431
|
+
return total
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _thinking_budget_tokens(options: Mapping[str, Any], client_kwargs: Mapping[str, Any]) -> int:
|
|
435
|
+
thinking = client_kwargs["thinking"] if "thinking" in client_kwargs else options.get("thinking")
|
|
436
|
+
if not isinstance(thinking, Mapping) or thinking.get("type") != "enabled":
|
|
437
|
+
return 0
|
|
438
|
+
budget = thinking.get("budget_tokens")
|
|
439
|
+
if isinstance(budget, int) and not isinstance(budget, bool) and budget > 0:
|
|
440
|
+
return budget
|
|
441
|
+
return 0
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# Headroom for estimate drift since the last calibration: the calibrated
|
|
445
|
+
# input estimate can trail the provider's count by a few tokens, and
|
|
446
|
+
# input + output past the window is a hard 400.
|
|
447
|
+
_ADMISSION_SAFETY_MARGIN_MIN_TOKENS = 256
|
|
448
|
+
_ADMISSION_SAFETY_MARGIN_PCT = 1
|
|
449
|
+
# The margin never squeezes a cap below this, and never lifts one past the room.
|
|
450
|
+
_ADMISSION_MIN_USEFUL_OUTPUT_TOKENS = 1_024
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _admission_safety_margin(max_context_tokens: int) -> int:
|
|
454
|
+
return max(_ADMISSION_SAFETY_MARGIN_MIN_TOKENS, math.ceil(max_context_tokens * _ADMISSION_SAFETY_MARGIN_PCT / 100))
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _clamp_output_cap_for_context(
|
|
458
|
+
options: Mapping[str, Any],
|
|
459
|
+
*,
|
|
460
|
+
strategy: Any,
|
|
461
|
+
request_overhead_tokens: int,
|
|
462
|
+
client_kwargs: Mapping[str, Any],
|
|
463
|
+
provider_min_output_cap_tokens: int = 1,
|
|
464
|
+
) -> dict[str, Any]:
|
|
465
|
+
"""Clamp present output caps to the calibrated room left in the context.
|
|
466
|
+
|
|
467
|
+
The admitted cap keeps a safety margin inside the room while the room is
|
|
468
|
+
wide; when it is narrow, the margin yields down to a minimum useful output
|
|
469
|
+
but the cap still never exceeds the room (only a provider's legal minimum
|
|
470
|
+
can).
|
|
471
|
+
"""
|
|
472
|
+
from .compaction import CompactionAdmissionState
|
|
473
|
+
|
|
474
|
+
copied = dict(options)
|
|
475
|
+
if not isinstance(strategy, CompactionAdmissionState):
|
|
476
|
+
return copied
|
|
477
|
+
present_aliases = [alias for alias in OUTPUT_CAP_OPTION_ALIASES if alias in copied]
|
|
478
|
+
if not present_aliases:
|
|
479
|
+
_log.debug("Context admission skipped output-cap clamp because no cap alias is present")
|
|
480
|
+
return copied
|
|
481
|
+
|
|
482
|
+
overhead = max(strategy.system_overhead_tokens, request_overhead_tokens)
|
|
483
|
+
estimated_input = math.ceil((strategy.last_included_tokens + overhead) * strategy.calibration_ratio)
|
|
484
|
+
room = strategy.max_context_tokens - estimated_input
|
|
485
|
+
margin = _admission_safety_margin(strategy.max_context_tokens)
|
|
486
|
+
guarded_room = min(room, max(room - margin, _ADMISSION_MIN_USEFUL_OUTPUT_TOKENS))
|
|
487
|
+
thinking_budget = _thinking_budget_tokens(copied, client_kwargs)
|
|
488
|
+
min_legal = max(provider_min_output_cap_tokens, thinking_budget + 1 if thinking_budget else 1)
|
|
489
|
+
admitted_cap = max(guarded_room, min_legal)
|
|
490
|
+
clamped: list[tuple[str, int, int]] = []
|
|
491
|
+
for alias in present_aliases:
|
|
492
|
+
value = copied[alias]
|
|
493
|
+
if isinstance(value, int) and not isinstance(value, bool) and value > admitted_cap:
|
|
494
|
+
copied[alias] = admitted_cap
|
|
495
|
+
clamped.append((alias, value, admitted_cap))
|
|
496
|
+
if clamped:
|
|
497
|
+
_log.warning(
|
|
498
|
+
"Clamped model output cap for context admission: estimated_input=%d, max_context_tokens=%d, "
|
|
499
|
+
"room=%d, margin=%d, min_legal=%d, caps=%s",
|
|
500
|
+
estimated_input,
|
|
501
|
+
strategy.max_context_tokens,
|
|
502
|
+
room,
|
|
503
|
+
margin,
|
|
504
|
+
min_legal,
|
|
505
|
+
", ".join(f"{key}:{old}->{new}" for key, old, new in clamped),
|
|
506
|
+
)
|
|
507
|
+
return copied
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class _PreparedRequestObserverClient:
|
|
511
|
+
"""Marker for clients that consume a post-prepare request observer.
|
|
512
|
+
|
|
513
|
+
``ChatMiddlewareLayer`` keeps its own final-handler observation for every
|
|
514
|
+
inner client. Marked clients additionally receive the observer so request
|
|
515
|
+
preparation below that layer (notably compaction summary insertion) can
|
|
516
|
+
generate fresh wrapper views and register their contents immediately before
|
|
517
|
+
the provider call. The marker prevents the chrys-only callback from
|
|
518
|
+
leaking into arbitrary custom-client kwargs.
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _is_chat_response_stream(
|
|
523
|
+
value: object,
|
|
524
|
+
) -> TypeIs[ResponseStream[ChatResponseUpdate, ChatResponse[Any]]]:
|
|
525
|
+
"""Narrow the response-stream branch of the client result contract."""
|
|
526
|
+
return isinstance(value, ResponseStream)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _is_message_list(messages: Sequence[Message]) -> TypeGuard[list[Message]]:
|
|
530
|
+
"""Narrow a mutable caller-owned message sequence without copying it."""
|
|
531
|
+
return isinstance(messages, list)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _wire_message_view(message: Message) -> Message:
|
|
535
|
+
"""Per-call wrapper view of an outgoing conversation message.
|
|
536
|
+
|
|
537
|
+
Fresh wrapper + fresh contents list, so a client that mutates a received
|
|
538
|
+
message in place (appending contents to a history message) can rewrite
|
|
539
|
+
neither landed history nor caller session-state objects — the structural
|
|
540
|
+
corruption vectors (duplicate ordinals, duplicate objects, corrupted
|
|
541
|
+
stored history). ``additional_properties`` stays THE wrapper's dict on
|
|
542
|
+
purpose: message-level metadata is the stack's sanctioned write-through
|
|
543
|
+
channel (compaction exclusion flags land through it), and metadata writes
|
|
544
|
+
cannot violate the transcript's structural invariants. Content objects
|
|
545
|
+
stay shared like everywhere else.
|
|
546
|
+
|
|
547
|
+
The single view builder for both outgoing directions: the tool loop's
|
|
548
|
+
wire views of conversation messages and the final provider-request views
|
|
549
|
+
built below.
|
|
550
|
+
"""
|
|
551
|
+
view = copy(message)
|
|
552
|
+
view.contents = list(message.contents)
|
|
553
|
+
return view
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _prepare_provider_request_messages(
|
|
557
|
+
messages: Sequence[Message],
|
|
558
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
559
|
+
) -> list[Message]:
|
|
560
|
+
"""Build final provider views and observe the exact outgoing contents."""
|
|
561
|
+
wire_messages = [_wire_message_view(message) for message in messages]
|
|
562
|
+
if request_message_observer is not None:
|
|
563
|
+
request_message_observer(wire_messages)
|
|
564
|
+
return wire_messages
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
class _ClientLastWordsCompleter:
|
|
568
|
+
"""``LastWordsCompleter`` bound to one ``get_response`` invocation.
|
|
569
|
+
|
|
570
|
+
Scoped LAST_WORDS side call: sends the strategy-chosen slice plus one
|
|
571
|
+
appended user instruction through the same client, stream mode,
|
|
572
|
+
options, tool definitions, and client kwargs as the live call.
|
|
573
|
+
``tool_choice`` is forced to ``"none"`` whenever tools ride along:
|
|
574
|
+
instruction-only suppression proved insufficient (models still attempted
|
|
575
|
+
typed tool calls), and the prefix-cache argument for inheriting the live
|
|
576
|
+
value died with the physically scoped slice — the side call's message
|
|
577
|
+
prefix already diverges from the live call, so the options delta forfeits
|
|
578
|
+
nothing. The no-tools instruction text (composed by the strategy layer)
|
|
579
|
+
stays because ``tool_choice`` cannot stop tool-call markup emitted as
|
|
580
|
+
plain text, with the response guard below as the hard backstop. Goes through
|
|
581
|
+
``_inner_get_response`` — not ``get_response`` — so the side call never
|
|
582
|
+
re-enters compaction. Returns only the note text; the side-call response
|
|
583
|
+
object never escapes.
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
def __init__(
|
|
587
|
+
self,
|
|
588
|
+
client: BaseChatClient,
|
|
589
|
+
*,
|
|
590
|
+
stream: bool,
|
|
591
|
+
options: Mapping[str, Any],
|
|
592
|
+
client_kwargs: Mapping[str, Any],
|
|
593
|
+
kwargs_cap_ceiling: int | None = None,
|
|
594
|
+
) -> None:
|
|
595
|
+
self._client = client
|
|
596
|
+
self._stream = stream
|
|
597
|
+
self._options = options
|
|
598
|
+
self._client_kwargs = client_kwargs
|
|
599
|
+
self._kwargs_cap_ceiling = kwargs_cap_ceiling
|
|
600
|
+
|
|
601
|
+
async def complete_last_words(
|
|
602
|
+
self,
|
|
603
|
+
base_messages: Sequence[Message],
|
|
604
|
+
instruction: str,
|
|
605
|
+
*,
|
|
606
|
+
max_output_tokens: int,
|
|
607
|
+
on_usage: Callable[[Mapping[str, Any]], None] | None = None,
|
|
608
|
+
) -> str:
|
|
609
|
+
from .compaction import (
|
|
610
|
+
LastWordsToolCallError,
|
|
611
|
+
internal_side_call_scope,
|
|
612
|
+
messages_contain_tool_calls,
|
|
613
|
+
raise_if_context_window_filled,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# Shallow copy with top-level-key overrides only: nested values (the
|
|
617
|
+
# tools list in particular) stay shared by reference and are never
|
|
618
|
+
# mutated; the live call's options dict is never touched.
|
|
619
|
+
side_options: dict[str, Any] = dict(self._options)
|
|
620
|
+
side_options["max_tokens"] = (
|
|
621
|
+
min(max_output_tokens, self._kwargs_cap_ceiling)
|
|
622
|
+
if self._kwargs_cap_ceiling is not None
|
|
623
|
+
else max_output_tokens
|
|
624
|
+
)
|
|
625
|
+
# Hard-disable tool use for the summarization side call — see the
|
|
626
|
+
# class docstring. Only when tools are actually present: "none"
|
|
627
|
+
# without tools is meaningless and providers differ on rejecting the
|
|
628
|
+
# combination, so a tool-less side call keeps the live value (or its
|
|
629
|
+
# absence) untouched.
|
|
630
|
+
if side_options.get("tools"):
|
|
631
|
+
side_options["tool_choice"] = "none"
|
|
632
|
+
# The throwaway summarization response must never become a chainable
|
|
633
|
+
# node in service-side storage (OpenAI Responses store mode). Only
|
|
634
|
+
# providers that store by default (or were asked to) get the key —
|
|
635
|
+
# other providers would reject an unknown request parameter.
|
|
636
|
+
if "store" in side_options or type(self._client).STORES_BY_DEFAULT:
|
|
637
|
+
side_options["store"] = False
|
|
638
|
+
for key in (*_SIDE_CALL_CONTINUATION_KEYS, *_SIDE_CALL_OUTPUT_SHAPING_KEYS, *_SIDE_CALL_EXECUTION_MODE_KEYS):
|
|
639
|
+
side_options.pop(key, None)
|
|
640
|
+
_scrub_side_call_extra_body(side_options)
|
|
641
|
+
|
|
642
|
+
side_messages = _prepare_provider_request_messages([*base_messages, Message("user", [instruction])])
|
|
643
|
+
# The scope keeps response instrumentation layered onto
|
|
644
|
+
# ``_inner_get_response`` (intermediate-text publication in
|
|
645
|
+
# particular) silent: even if the model ignores the no-tools
|
|
646
|
+
# instruction and the guard below rejects the response, nothing
|
|
647
|
+
# about it may be published or persisted. It covers the stream
|
|
648
|
+
# drain too — stream hooks fire during ``get_final_response()``.
|
|
649
|
+
forwarded_kwargs = {
|
|
650
|
+
key: value
|
|
651
|
+
for key, value in self._client_kwargs.items()
|
|
652
|
+
if key
|
|
653
|
+
not in (
|
|
654
|
+
*_SIDE_CALL_CONTINUATION_KEYS,
|
|
655
|
+
*_SIDE_CALL_OUTPUT_SHAPING_KEYS,
|
|
656
|
+
*_SIDE_CALL_EXECUTION_MODE_KEYS,
|
|
657
|
+
"store",
|
|
658
|
+
)
|
|
659
|
+
}
|
|
660
|
+
_scrub_side_call_extra_body(forwarded_kwargs)
|
|
661
|
+
# A streamed note arrives chunk by chunk: every chunk (and each
|
|
662
|
+
# attempt's dispatch) restarts the live call's stall watchdog, which
|
|
663
|
+
# still cancels a side call that goes idle.
|
|
664
|
+
report_wire_progress()
|
|
665
|
+
try:
|
|
666
|
+
with internal_side_call_scope(), side_call_scope(ActorRole.COMPLETER):
|
|
667
|
+
result = self._client._inner_get_response(
|
|
668
|
+
messages=side_messages,
|
|
669
|
+
stream=self._stream,
|
|
670
|
+
options=side_options,
|
|
671
|
+
**forwarded_kwargs,
|
|
672
|
+
)
|
|
673
|
+
response: ChatResponse[Any]
|
|
674
|
+
if _is_chat_response_stream(result):
|
|
675
|
+
response = await result.with_transform_hook(_report_side_call_update).get_final_response()
|
|
676
|
+
else:
|
|
677
|
+
awaited = await result
|
|
678
|
+
response = (
|
|
679
|
+
await awaited.with_transform_hook(_report_side_call_update).get_final_response()
|
|
680
|
+
if isinstance(awaited, ResponseStream)
|
|
681
|
+
else awaited
|
|
682
|
+
)
|
|
683
|
+
except ProviderResponseError as err:
|
|
684
|
+
# A response the adapter failed consumed provider tokens too.
|
|
685
|
+
if on_usage is not None and err.usage_details:
|
|
686
|
+
on_usage(err.usage_details)
|
|
687
|
+
raise
|
|
688
|
+
report_wire_progress()
|
|
689
|
+
# Report spend before any acceptance decision: a response the guard
|
|
690
|
+
# rejects below still consumed real provider tokens.
|
|
691
|
+
if on_usage is not None and response.usage_details:
|
|
692
|
+
on_usage(response.usage_details)
|
|
693
|
+
raise_if_context_window_filled(response)
|
|
694
|
+
if messages_contain_tool_calls(response.messages):
|
|
695
|
+
raise LastWordsToolCallError(
|
|
696
|
+
"last-words side call returned tool-call content despite the no-tools instruction"
|
|
697
|
+
)
|
|
698
|
+
# Verbatim, not ``.text``: outer whitespace (leading indentation in
|
|
699
|
+
# particular) is meaningful to the structured-note validator.
|
|
700
|
+
return response.raw_text
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class SupportsAclose(Protocol):
|
|
704
|
+
"""A chat client stack's one close entry; each layer forwards it inward."""
|
|
705
|
+
|
|
706
|
+
async def aclose(self) -> None: ...
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class BaseChatClient(SerializationMixin, _PreparedRequestObserverClient, ABC):
|
|
710
|
+
"""Abstract base class for loop-free chat clients."""
|
|
711
|
+
|
|
712
|
+
OTEL_PROVIDER_NAME: ClassVar[str] = "unknown"
|
|
713
|
+
DEFAULT_EXCLUDE: ClassVar[set[str]] = {
|
|
714
|
+
"additional_properties",
|
|
715
|
+
"compaction_strategy",
|
|
716
|
+
"tokenizer",
|
|
717
|
+
}
|
|
718
|
+
STORES_BY_DEFAULT: ClassVar[bool] = False
|
|
719
|
+
# Provider dialects that cannot persist or resume any request state set
|
|
720
|
+
# this independently from the ordinary storage default.
|
|
721
|
+
FORCES_STATELESS: ClassVar[bool] = False
|
|
722
|
+
MIN_OUTPUT_CAP_TOKENS: ClassVar[int] = 1
|
|
723
|
+
|
|
724
|
+
def __init__(
|
|
725
|
+
self,
|
|
726
|
+
*,
|
|
727
|
+
compaction_strategy: Any = None,
|
|
728
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
729
|
+
additional_properties: dict[str, Any] | None = None,
|
|
730
|
+
) -> None:
|
|
731
|
+
self.additional_properties = additional_properties or {}
|
|
732
|
+
self.compaction_strategy = compaction_strategy
|
|
733
|
+
self.tokenizer = tokenizer
|
|
734
|
+
super().__init__()
|
|
735
|
+
|
|
736
|
+
async def aclose(self) -> None:
|
|
737
|
+
"""Release provider resources; clients that own none keep this no-op."""
|
|
738
|
+
|
|
739
|
+
def to_dict(self, *, exclude: set[str] | None = None, exclude_none: bool = True) -> dict[str, Any]:
|
|
740
|
+
"""Serialize the client, lifting additional properties to the root."""
|
|
741
|
+
result = super().to_dict(exclude=exclude, exclude_none=exclude_none)
|
|
742
|
+
if self.additional_properties:
|
|
743
|
+
result.update(self.additional_properties)
|
|
744
|
+
return result
|
|
745
|
+
|
|
746
|
+
async def _validate_options(self, options: Mapping[str, Any]) -> dict[str, Any]:
|
|
747
|
+
"""Validate and normalize chat options."""
|
|
748
|
+
return await validate_chat_options(dict(options))
|
|
749
|
+
|
|
750
|
+
def _finalize_response_updates(
|
|
751
|
+
self,
|
|
752
|
+
updates: Sequence[ChatResponseUpdate],
|
|
753
|
+
*,
|
|
754
|
+
response_format: Any | None = None,
|
|
755
|
+
) -> ChatResponse[Any]:
|
|
756
|
+
"""Finalize streamed updates into one chat response."""
|
|
757
|
+
return ChatResponse.from_updates(updates, output_format_type=response_format)
|
|
758
|
+
|
|
759
|
+
def _build_response_stream(
|
|
760
|
+
self,
|
|
761
|
+
stream: AsyncIterable[ChatResponseUpdate] | Awaitable[AsyncIterable[ChatResponseUpdate]],
|
|
762
|
+
*,
|
|
763
|
+
response_format: Any | None = None,
|
|
764
|
+
) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
|
|
765
|
+
"""Create a response stream with the standard finalizer."""
|
|
766
|
+
return ResponseStream(
|
|
767
|
+
stream,
|
|
768
|
+
finalizer=lambda updates: self._finalize_response_updates(updates, response_format=response_format),
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
async def _prepare_messages_for_model_call(
|
|
772
|
+
self,
|
|
773
|
+
messages: Sequence[Message],
|
|
774
|
+
*,
|
|
775
|
+
compaction_strategy: Any = None,
|
|
776
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
777
|
+
context: CompactionCallContext | None = None,
|
|
778
|
+
) -> list[Message]:
|
|
779
|
+
"""Apply annotation/compaction before a provider model call."""
|
|
780
|
+
prepared_messages = list(messages)
|
|
781
|
+
if compaction_strategy is None:
|
|
782
|
+
if tokenizer is None:
|
|
783
|
+
return prepared_messages
|
|
784
|
+
from .compaction import annotate_message_groups
|
|
785
|
+
|
|
786
|
+
annotate_message_groups(prepared_messages, tokenizer=tokenizer)
|
|
787
|
+
return prepared_messages
|
|
788
|
+
|
|
789
|
+
from .compaction import apply_compaction
|
|
790
|
+
|
|
791
|
+
if _is_message_list(messages):
|
|
792
|
+
working_messages: list[Message] = messages
|
|
793
|
+
else:
|
|
794
|
+
working_messages = prepared_messages
|
|
795
|
+
return await apply_compaction(
|
|
796
|
+
working_messages,
|
|
797
|
+
strategy=compaction_strategy,
|
|
798
|
+
tokenizer=tokenizer,
|
|
799
|
+
context=context,
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
def _resolve_compaction_overrides(
|
|
803
|
+
self,
|
|
804
|
+
*,
|
|
805
|
+
compaction_strategy: Any = None,
|
|
806
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
807
|
+
) -> dict[str, Any]:
|
|
808
|
+
current_compaction_strategy = self.compaction_strategy
|
|
809
|
+
current_tokenizer = self.tokenizer
|
|
810
|
+
ret: dict[str, Any] = {}
|
|
811
|
+
if current_compaction_strategy is not None or compaction_strategy is not None:
|
|
812
|
+
ret["compaction_strategy"] = (
|
|
813
|
+
current_compaction_strategy if compaction_strategy is None else compaction_strategy
|
|
814
|
+
)
|
|
815
|
+
if current_tokenizer is not None or tokenizer is not None:
|
|
816
|
+
ret["tokenizer"] = current_tokenizer if tokenizer is None else tokenizer
|
|
817
|
+
return ret
|
|
818
|
+
|
|
819
|
+
def _build_compaction_call_context(
|
|
820
|
+
self,
|
|
821
|
+
*,
|
|
822
|
+
stream: bool,
|
|
823
|
+
options: Mapping[str, Any] | None,
|
|
824
|
+
client_kwargs: Mapping[str, Any],
|
|
825
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
826
|
+
kwargs_cap_ceiling: int | None = None,
|
|
827
|
+
) -> CompactionCallContext:
|
|
828
|
+
"""Build the per-call context offered to the compaction strategy.
|
|
829
|
+
|
|
830
|
+
Single construction point for the LAST_WORDS completer — every stack
|
|
831
|
+
that routes compaction through ``_prepare_messages_for_model_call``
|
|
832
|
+
(``get_response`` here, which ``MockChatClient`` also calls beneath its
|
|
833
|
+
tool loop) must build the context through this method so gating stays
|
|
834
|
+
uniform.
|
|
835
|
+
|
|
836
|
+
Side-call behavior under OpenAI Responses server-side storage still
|
|
837
|
+
needs live smoke validation. Until then, store-mode profiles get no
|
|
838
|
+
completer and Phase 4 keeps the validated reconstruction fallback.
|
|
839
|
+
"""
|
|
840
|
+
from .compaction import CompactionCallContext
|
|
841
|
+
|
|
842
|
+
effective_options = options or {}
|
|
843
|
+
storage = resolve_storage_mode_and_handles(
|
|
844
|
+
effective_options,
|
|
845
|
+
stores_by_default=type(self).STORES_BY_DEFAULT,
|
|
846
|
+
client_kwargs=client_kwargs,
|
|
847
|
+
force_stateless=type(self).FORCES_STATELESS,
|
|
848
|
+
)
|
|
849
|
+
forced_tool_choice = _tool_choice_forces_tool_use(effective_options.get("tool_choice"))
|
|
850
|
+
completer = (
|
|
851
|
+
None
|
|
852
|
+
if storage.service_side or forced_tool_choice
|
|
853
|
+
else _ClientLastWordsCompleter(
|
|
854
|
+
self,
|
|
855
|
+
stream=stream,
|
|
856
|
+
options=effective_options,
|
|
857
|
+
client_kwargs=client_kwargs,
|
|
858
|
+
kwargs_cap_ceiling=kwargs_cap_ceiling,
|
|
859
|
+
)
|
|
860
|
+
)
|
|
861
|
+
effective_tokenizer = tokenizer or self.tokenizer
|
|
862
|
+
tool_definition_tokens = _estimate_tool_definition_tokens(effective_options, effective_tokenizer)
|
|
863
|
+
exchange_trace = client_kwargs.get(TRAJECTORY_EXCHANGE_KWARG)
|
|
864
|
+
return CompactionCallContext(
|
|
865
|
+
last_words_completer=completer,
|
|
866
|
+
tool_definition_tokens=tool_definition_tokens,
|
|
867
|
+
request_overhead_tokens=_estimate_fixed_request_overhead_tokens(
|
|
868
|
+
effective_options,
|
|
869
|
+
client_kwargs,
|
|
870
|
+
effective_tokenizer,
|
|
871
|
+
tool_definition_tokens=tool_definition_tokens,
|
|
872
|
+
),
|
|
873
|
+
next_exchange_operation_id=(
|
|
874
|
+
exchange_trace.operation_id if isinstance(exchange_trace, ExchangeTrace) else None
|
|
875
|
+
),
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
def _normalize_wire_inputs(
|
|
879
|
+
self,
|
|
880
|
+
options: Mapping[str, Any] | None,
|
|
881
|
+
client_kwargs: Mapping[str, Any],
|
|
882
|
+
) -> tuple[Any, dict[str, Any], int | None]:
|
|
883
|
+
"""Snapshot admission inputs and fold kwargs output caps into options."""
|
|
884
|
+
if options is None:
|
|
885
|
+
options_snapshot: Any = {}
|
|
886
|
+
elif isinstance(options, Mapping):
|
|
887
|
+
options_snapshot = dict(options)
|
|
888
|
+
tools = options_snapshot.get("tools")
|
|
889
|
+
if isinstance(tools, list | tuple):
|
|
890
|
+
options_snapshot["tools"] = list(tools)
|
|
891
|
+
elif isinstance(tools, Mapping):
|
|
892
|
+
options_snapshot["tools"] = dict(tools)
|
|
893
|
+
thinking = options_snapshot.get("thinking")
|
|
894
|
+
if isinstance(thinking, Mapping):
|
|
895
|
+
options_snapshot["thinking"] = dict(thinking)
|
|
896
|
+
else:
|
|
897
|
+
return options, dict(client_kwargs), None
|
|
898
|
+
|
|
899
|
+
sanitized_client_kwargs = dict(client_kwargs)
|
|
900
|
+
kwargs_caps: dict[str, Any] = {}
|
|
901
|
+
for alias in OUTPUT_CAP_OPTION_ALIASES:
|
|
902
|
+
if alias in sanitized_client_kwargs:
|
|
903
|
+
kwargs_caps[alias] = sanitized_client_kwargs.pop(alias)
|
|
904
|
+
if kwargs_caps:
|
|
905
|
+
# A kwargs cap must keep overriding an options cap even when the
|
|
906
|
+
# spellings differ: provider serializers resolve alias conflicts
|
|
907
|
+
# toward canonical ``max_tokens``, so leaving the options spelling
|
|
908
|
+
# in place would invert the historical kwargs-wins precedence.
|
|
909
|
+
for alias in OUTPUT_CAP_OPTION_ALIASES:
|
|
910
|
+
options_snapshot.pop(alias, None)
|
|
911
|
+
options_snapshot.update(kwargs_caps)
|
|
912
|
+
kwargs_cap_ceiling = next(
|
|
913
|
+
(
|
|
914
|
+
value
|
|
915
|
+
for alias in OUTPUT_CAP_OPTION_ALIASES
|
|
916
|
+
if isinstance((value := kwargs_caps.get(alias)), int) and not isinstance(value, bool) and value > 0
|
|
917
|
+
),
|
|
918
|
+
None,
|
|
919
|
+
)
|
|
920
|
+
return options_snapshot, sanitized_client_kwargs, kwargs_cap_ceiling
|
|
921
|
+
|
|
922
|
+
async def _prepare_wire_call(
|
|
923
|
+
self,
|
|
924
|
+
messages: Sequence[Message],
|
|
925
|
+
*,
|
|
926
|
+
call_context: CompactionCallContext,
|
|
927
|
+
options_snapshot: Mapping[str, Any],
|
|
928
|
+
sanitized_client_kwargs: Mapping[str, Any],
|
|
929
|
+
compaction_overrides: Mapping[str, Any],
|
|
930
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
931
|
+
) -> tuple[list[Message], dict[str, Any]]:
|
|
932
|
+
"""Apply compaction and context admission for one wire attempt."""
|
|
933
|
+
prepared = await self._prepare_messages_for_model_call(
|
|
934
|
+
messages,
|
|
935
|
+
context=call_context,
|
|
936
|
+
**compaction_overrides,
|
|
937
|
+
)
|
|
938
|
+
wire_messages = _prepare_provider_request_messages(prepared, request_message_observer)
|
|
939
|
+
wire_options = _clamp_output_cap_for_context(
|
|
940
|
+
options_snapshot,
|
|
941
|
+
strategy=compaction_overrides.get("compaction_strategy"),
|
|
942
|
+
request_overhead_tokens=call_context.request_overhead_tokens,
|
|
943
|
+
client_kwargs=sanitized_client_kwargs,
|
|
944
|
+
provider_min_output_cap_tokens=type(self).MIN_OUTPUT_CAP_TOKENS,
|
|
945
|
+
)
|
|
946
|
+
# The provider's time to first byte starts now, not when compaction did.
|
|
947
|
+
report_wire_progress()
|
|
948
|
+
return wire_messages, wire_options
|
|
949
|
+
|
|
950
|
+
@abstractmethod
|
|
951
|
+
def _inner_get_response(
|
|
952
|
+
self,
|
|
953
|
+
*,
|
|
954
|
+
messages: Sequence[Message],
|
|
955
|
+
stream: bool,
|
|
956
|
+
options: Mapping[str, Any],
|
|
957
|
+
**kwargs: Any,
|
|
958
|
+
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
|
|
959
|
+
"""Send prepared messages to the model service."""
|
|
960
|
+
|
|
961
|
+
@overload
|
|
962
|
+
def get_response(
|
|
963
|
+
self,
|
|
964
|
+
messages: Sequence[Message],
|
|
965
|
+
*,
|
|
966
|
+
stream: Literal[False] = ...,
|
|
967
|
+
options: Mapping[str, Any] | None = None,
|
|
968
|
+
compaction_strategy: Any = None,
|
|
969
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
970
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
971
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
972
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
973
|
+
) -> Awaitable[ChatResponse[Any]]: ...
|
|
974
|
+
|
|
975
|
+
@overload
|
|
976
|
+
def get_response(
|
|
977
|
+
self,
|
|
978
|
+
messages: Sequence[Message],
|
|
979
|
+
*,
|
|
980
|
+
stream: Literal[True],
|
|
981
|
+
options: Mapping[str, Any] | None = None,
|
|
982
|
+
compaction_strategy: Any = None,
|
|
983
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
984
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
985
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
986
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
987
|
+
) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
|
|
988
|
+
|
|
989
|
+
def get_response(
|
|
990
|
+
self,
|
|
991
|
+
messages: Sequence[Message],
|
|
992
|
+
*,
|
|
993
|
+
stream: bool = False,
|
|
994
|
+
options: Mapping[str, Any] | None = None,
|
|
995
|
+
compaction_strategy: Any = None,
|
|
996
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
997
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
998
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
999
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
1000
|
+
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
|
|
1001
|
+
"""Get a non-streaming or streaming chat response."""
|
|
1002
|
+
del function_invocation_kwargs
|
|
1003
|
+
compaction_overrides = self._resolve_compaction_overrides(
|
|
1004
|
+
compaction_strategy=compaction_strategy,
|
|
1005
|
+
tokenizer=tokenizer,
|
|
1006
|
+
)
|
|
1007
|
+
merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
|
|
1008
|
+
options_snapshot, sanitized_client_kwargs, kwargs_cap_ceiling = self._normalize_wire_inputs(
|
|
1009
|
+
options,
|
|
1010
|
+
merged_client_kwargs,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
# Non-Mapping options bypass compaction too: they cannot be admitted
|
|
1014
|
+
# or clamped, and ``options or {}`` in the context build would
|
|
1015
|
+
# silently swallow falsey invalid values instead of letting the
|
|
1016
|
+
# provider reject them like every other path does.
|
|
1017
|
+
if not compaction_overrides or not isinstance(options_snapshot, Mapping):
|
|
1018
|
+
wire_messages = _prepare_provider_request_messages(messages, request_message_observer)
|
|
1019
|
+
return self._inner_get_response(
|
|
1020
|
+
messages=wire_messages,
|
|
1021
|
+
stream=stream,
|
|
1022
|
+
options=cast("Mapping[str, Any]", options_snapshot),
|
|
1023
|
+
**sanitized_client_kwargs,
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
call_context = self._build_compaction_call_context(
|
|
1027
|
+
stream=stream,
|
|
1028
|
+
options=cast("Mapping[str, Any]", options_snapshot),
|
|
1029
|
+
client_kwargs=sanitized_client_kwargs,
|
|
1030
|
+
tokenizer=compaction_overrides.get("tokenizer"),
|
|
1031
|
+
kwargs_cap_ceiling=kwargs_cap_ceiling,
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
if stream:
|
|
1035
|
+
|
|
1036
|
+
async def _get_stream() -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
|
|
1037
|
+
prepared_messages, wire_options = await self._prepare_wire_call(
|
|
1038
|
+
messages,
|
|
1039
|
+
call_context=call_context,
|
|
1040
|
+
options_snapshot=cast("Mapping[str, Any]", options_snapshot),
|
|
1041
|
+
sanitized_client_kwargs=sanitized_client_kwargs,
|
|
1042
|
+
compaction_overrides=compaction_overrides,
|
|
1043
|
+
request_message_observer=request_message_observer,
|
|
1044
|
+
)
|
|
1045
|
+
stream_response = self._inner_get_response(
|
|
1046
|
+
messages=prepared_messages,
|
|
1047
|
+
stream=True,
|
|
1048
|
+
options=wire_options,
|
|
1049
|
+
**sanitized_client_kwargs,
|
|
1050
|
+
)
|
|
1051
|
+
if _is_chat_response_stream(stream_response):
|
|
1052
|
+
return stream_response
|
|
1053
|
+
awaited_stream_response = await stream_response
|
|
1054
|
+
if _is_chat_response_stream(awaited_stream_response):
|
|
1055
|
+
return awaited_stream_response
|
|
1056
|
+
raise ValueError("Streaming responses must return a ResponseStream.")
|
|
1057
|
+
|
|
1058
|
+
return ResponseStream.from_awaitable(_get_stream())
|
|
1059
|
+
|
|
1060
|
+
async def _get_response() -> ChatResponse[Any]:
|
|
1061
|
+
prepared_messages, wire_options = await self._prepare_wire_call(
|
|
1062
|
+
messages,
|
|
1063
|
+
call_context=call_context,
|
|
1064
|
+
options_snapshot=cast("Mapping[str, Any]", options_snapshot),
|
|
1065
|
+
sanitized_client_kwargs=sanitized_client_kwargs,
|
|
1066
|
+
compaction_overrides=compaction_overrides,
|
|
1067
|
+
request_message_observer=request_message_observer,
|
|
1068
|
+
)
|
|
1069
|
+
response = cast(
|
|
1070
|
+
"Awaitable[ChatResponse[Any]]",
|
|
1071
|
+
self._inner_get_response(
|
|
1072
|
+
messages=prepared_messages,
|
|
1073
|
+
stream=False,
|
|
1074
|
+
options=wire_options,
|
|
1075
|
+
**sanitized_client_kwargs,
|
|
1076
|
+
),
|
|
1077
|
+
)
|
|
1078
|
+
return await response
|
|
1079
|
+
|
|
1080
|
+
return _get_response()
|
|
1081
|
+
|
|
1082
|
+
def service_url(self) -> str:
|
|
1083
|
+
"""Return the provider service URL when a subclass exposes one."""
|
|
1084
|
+
return "Unknown"
|