iCode-TUI 0.29.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
|
@@ -0,0 +1,1618 @@
|
|
|
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
|
+
"""OpenTelemetry layers for chat calls, agent runs and tool execution.
|
|
6
|
+
|
|
7
|
+
``ChatTelemetryLayer`` wraps chat clients and ``AgentTelemetryLayer`` wraps
|
|
8
|
+
``Agent``. Both use the foundation-owned ``TELEMETRY_GATE``, defaulting off.
|
|
9
|
+
Settings parsing and provider setup belong to foundation observability.
|
|
10
|
+
|
|
11
|
+
Non-streaming dedup ContextVars must be set and reset inside the returned
|
|
12
|
+
coroutine. Setting them at call time would make ``create_task`` execution
|
|
13
|
+
reset tokens in a different context. Streaming spans activate around each
|
|
14
|
+
pull; cleanup closes the span before resetting ContextVars so a cross-context
|
|
15
|
+
reset cannot leave it open. Such reset failures are logged at debug level.
|
|
16
|
+
|
|
17
|
+
Tool definitions are canonicalized independently so one malformed declaration
|
|
18
|
+
cannot discard valid neighbors or raise into a run. Message events, span
|
|
19
|
+
attributes, histogram buckets and duration recording use the schema defined
|
|
20
|
+
here. Tracer and meter scope is ``chrys`` with its installed version; agent
|
|
21
|
+
provider names come from the host's ``AGENT_PROVIDER_NAME`` ClassVar, while
|
|
22
|
+
chat spans keep the model provider name.
|
|
23
|
+
|
|
24
|
+
The per-tool duration metric and function-name attribute now use
|
|
25
|
+
``chrys.function.*``. Dashboards and alerts consuming the earlier per-tool
|
|
26
|
+
metric schema must update their selectors.
|
|
27
|
+
|
|
28
|
+
The tool loop emits function spans and duration metrics around its single
|
|
29
|
+
invoke boundary. Direct ``FunctionTool.invoke`` remains telemetry-free.
|
|
30
|
+
Kernel dependencies stay within this package, allowed third-party packages
|
|
31
|
+
and foundation; the scope version is the package's ``__version__``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import contextlib
|
|
37
|
+
import contextvars
|
|
38
|
+
import json
|
|
39
|
+
import logging
|
|
40
|
+
import weakref
|
|
41
|
+
from collections.abc import Awaitable, Callable, Generator, Mapping, Sequence
|
|
42
|
+
from enum import Enum
|
|
43
|
+
from time import perf_counter, time_ns
|
|
44
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Final, Literal, Protocol, TypeGuard, cast, overload
|
|
45
|
+
|
|
46
|
+
from opentelemetry import context as otel_context
|
|
47
|
+
from opentelemetry import metrics, trace
|
|
48
|
+
|
|
49
|
+
from chrys import __version__
|
|
50
|
+
from chrys.foundation.observability.gate import TELEMETRY_GATE
|
|
51
|
+
|
|
52
|
+
from ._content import add_usage_details
|
|
53
|
+
from ._types import AgentResponse, ChatResponse, ResponseStream, merge_chat_options, normalize_messages
|
|
54
|
+
from .client import _PreparedRequestObserverClient
|
|
55
|
+
from .tools import FunctionTool, normalize_tools
|
|
56
|
+
|
|
57
|
+
if TYPE_CHECKING:
|
|
58
|
+
from ._content import Content, UsageDetails
|
|
59
|
+
from ._types import (
|
|
60
|
+
AgentResponseUpdate,
|
|
61
|
+
AgentRunInputs,
|
|
62
|
+
ChatResponseUpdate,
|
|
63
|
+
FinishReason,
|
|
64
|
+
FinishReasonLiteral,
|
|
65
|
+
Message,
|
|
66
|
+
ToolTypes,
|
|
67
|
+
)
|
|
68
|
+
from .middleware import ChatMiddleware, FunctionMiddleware
|
|
69
|
+
from .sessions import AgentSession
|
|
70
|
+
|
|
71
|
+
class _ChatTelemetryBase(Protocol):
|
|
72
|
+
def get_response(
|
|
73
|
+
self,
|
|
74
|
+
messages: Sequence[Message],
|
|
75
|
+
*,
|
|
76
|
+
stream: bool = False,
|
|
77
|
+
options: Mapping[str, Any] | None = None,
|
|
78
|
+
compaction_strategy: Any = None,
|
|
79
|
+
tokenizer: Any = None,
|
|
80
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
81
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
82
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
83
|
+
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
|
|
84
|
+
|
|
85
|
+
class _AgentTelemetryBase(Protocol):
|
|
86
|
+
def run(
|
|
87
|
+
self,
|
|
88
|
+
messages: AgentRunInputs | None = None,
|
|
89
|
+
*,
|
|
90
|
+
stream: bool = False,
|
|
91
|
+
session: AgentSession | None = None,
|
|
92
|
+
middleware: ChatMiddleware
|
|
93
|
+
| FunctionMiddleware
|
|
94
|
+
| Sequence[ChatMiddleware | FunctionMiddleware]
|
|
95
|
+
| None = None,
|
|
96
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
97
|
+
options: Mapping[str, Any] | None = None,
|
|
98
|
+
compaction_strategy: Any = None,
|
|
99
|
+
tokenizer: Any = None,
|
|
100
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
101
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
102
|
+
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
_ChatTelemetryBase = object
|
|
106
|
+
_AgentTelemetryBase = object
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _is_agent_response_stream(
|
|
110
|
+
value: object,
|
|
111
|
+
) -> TypeGuard[ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]:
|
|
112
|
+
"""Narrow the streaming branch of an agent-run result."""
|
|
113
|
+
return isinstance(value, ResponseStream)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
__all__ = [
|
|
117
|
+
"FUNCTION_SPAN_EXCLUDED_KWARGS",
|
|
118
|
+
"AgentTelemetryLayer",
|
|
119
|
+
"ChatTelemetryLayer",
|
|
120
|
+
"OtelAttr",
|
|
121
|
+
"capture_exception",
|
|
122
|
+
"create_mcp_client_span",
|
|
123
|
+
"get_function_duration_histogram",
|
|
124
|
+
"get_function_span",
|
|
125
|
+
"get_function_span_attributes",
|
|
126
|
+
"get_meter",
|
|
127
|
+
"get_tracer",
|
|
128
|
+
"set_mcp_span_error",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
logger = logging.getLogger(__name__)
|
|
132
|
+
|
|
133
|
+
_SCOPE_NAME: Final[str] = "chrys"
|
|
134
|
+
_SCOPE_VERSION: Final[str] = __version__
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Dedup channel between the agent and inner chat layers so usage and
|
|
138
|
+
# response_id are not double-counted
|
|
139
|
+
# on the agent span.
|
|
140
|
+
INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS: Final[contextvars.ContextVar[set[str] | None]] = contextvars.ContextVar(
|
|
141
|
+
"chrys_inner_response_telemetry_captured_fields", default=None
|
|
142
|
+
)
|
|
143
|
+
INNER_RESPONSE_ID_CAPTURED_FIELD: Final[str] = "response_id"
|
|
144
|
+
INNER_USAGE_CAPTURED_FIELD: Final[str] = "usage"
|
|
145
|
+
INNER_ACCUMULATED_USAGE: Final[contextvars.ContextVar[UsageDetails | None]] = contextvars.ContextVar(
|
|
146
|
+
"chrys_inner_accumulated_usage", default=None
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Shared histogram boundaries for token usage and operation duration.
|
|
150
|
+
TOKEN_USAGE_BUCKET_BOUNDARIES: Final[tuple[float, ...]] = (
|
|
151
|
+
1,
|
|
152
|
+
4,
|
|
153
|
+
16,
|
|
154
|
+
64,
|
|
155
|
+
256,
|
|
156
|
+
1024,
|
|
157
|
+
4096,
|
|
158
|
+
16384,
|
|
159
|
+
65536,
|
|
160
|
+
262144,
|
|
161
|
+
1048576,
|
|
162
|
+
4194304,
|
|
163
|
+
16777216,
|
|
164
|
+
67108864,
|
|
165
|
+
)
|
|
166
|
+
OPERATION_DURATION_BUCKET_BOUNDARIES: Final[tuple[float, ...]] = (
|
|
167
|
+
0.01,
|
|
168
|
+
0.02,
|
|
169
|
+
0.04,
|
|
170
|
+
0.08,
|
|
171
|
+
0.16,
|
|
172
|
+
0.32,
|
|
173
|
+
0.64,
|
|
174
|
+
1.28,
|
|
175
|
+
2.56,
|
|
176
|
+
5.12,
|
|
177
|
+
10.24,
|
|
178
|
+
20.48,
|
|
179
|
+
40.96,
|
|
180
|
+
81.92,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class OtelAttr(str, Enum): # noqa: UP042 - str+Enum preserves value-returning __str__/__repr__ semantics.
|
|
185
|
+
"""Attribute keys and metric names emitted by Chrys telemetry."""
|
|
186
|
+
|
|
187
|
+
OPERATION = "gen_ai.operation.name"
|
|
188
|
+
PROVIDER_NAME = "gen_ai.provider.name"
|
|
189
|
+
ERROR_TYPE = "error.type"
|
|
190
|
+
PORT = "server.port"
|
|
191
|
+
ADDRESS = "server.address"
|
|
192
|
+
# Request attributes
|
|
193
|
+
SEED = "gen_ai.request.seed"
|
|
194
|
+
ENCODING_FORMATS = "gen_ai.request.encoding_formats"
|
|
195
|
+
FREQUENCY_PENALTY = "gen_ai.request.frequency_penalty"
|
|
196
|
+
PRESENCE_PENALTY = "gen_ai.request.presence_penalty"
|
|
197
|
+
STOP_SEQUENCES = "gen_ai.request.stop_sequences"
|
|
198
|
+
TOP_K = "gen_ai.request.top_k"
|
|
199
|
+
CHOICE_COUNT = "gen_ai.request.choice.count"
|
|
200
|
+
REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens"
|
|
201
|
+
REQUEST_TEMPERATURE = "gen_ai.request.temperature"
|
|
202
|
+
REQUEST_TOP_P = "gen_ai.request.top_p"
|
|
203
|
+
REQUEST_MODEL = "gen_ai.request.model"
|
|
204
|
+
# Response attributes
|
|
205
|
+
FINISH_REASONS = "gen_ai.response.finish_reasons"
|
|
206
|
+
RESPONSE_ID = "gen_ai.response.id"
|
|
207
|
+
RESPONSE_MODEL = "gen_ai.response.model"
|
|
208
|
+
# Usage attributes
|
|
209
|
+
INPUT_TOKENS = "gen_ai.usage.input_tokens"
|
|
210
|
+
OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
|
|
211
|
+
CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens"
|
|
212
|
+
CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens"
|
|
213
|
+
REASONING_OUTPUT_TOKENS = "gen_ai.usage.reasoning.output_tokens"
|
|
214
|
+
# Tool attributes
|
|
215
|
+
TOOL_DEFINITIONS = "gen_ai.tool.definitions"
|
|
216
|
+
TOOL_CALL_ID = "gen_ai.tool.call.id"
|
|
217
|
+
TOOL_DESCRIPTION = "gen_ai.tool.description"
|
|
218
|
+
TOOL_NAME = "gen_ai.tool.name"
|
|
219
|
+
TOOL_TYPE = "gen_ai.tool.type"
|
|
220
|
+
TOOL_ARGUMENTS = "gen_ai.tool.call.arguments"
|
|
221
|
+
TOOL_RESULT = "gen_ai.tool.call.result"
|
|
222
|
+
# Agent attributes
|
|
223
|
+
AGENT_ID = "gen_ai.agent.id"
|
|
224
|
+
AGENT_NAME = "gen_ai.agent.name"
|
|
225
|
+
AGENT_DESCRIPTION = "gen_ai.agent.description"
|
|
226
|
+
CONVERSATION_ID = "gen_ai.conversation.id"
|
|
227
|
+
INPUT_MESSAGES = "gen_ai.input.messages"
|
|
228
|
+
OUTPUT_MESSAGES = "gen_ai.output.messages"
|
|
229
|
+
SYSTEM_INSTRUCTIONS = "gen_ai.system_instructions"
|
|
230
|
+
SYSTEM = "gen_ai.system"
|
|
231
|
+
SERVICE_NAME = "service.name"
|
|
232
|
+
SERVICE_VERSION = "service.version"
|
|
233
|
+
# Client metric attributes
|
|
234
|
+
T_UNIT = "tokens"
|
|
235
|
+
T_TYPE = "gen_ai.token.type"
|
|
236
|
+
T_TYPE_INPUT = "input"
|
|
237
|
+
T_TYPE_OUTPUT = "output"
|
|
238
|
+
DURATION_UNIT = "s"
|
|
239
|
+
LLM_OPERATION_DURATION = "gen_ai.client.operation.duration"
|
|
240
|
+
LLM_TOKEN_USAGE = "gen_ai.client.token.usage"
|
|
241
|
+
# Activity events
|
|
242
|
+
EVENT_NAME = "event.name"
|
|
243
|
+
SYSTEM_MESSAGE = "gen_ai.system.message"
|
|
244
|
+
USER_MESSAGE = "gen_ai.user.message"
|
|
245
|
+
ASSISTANT_MESSAGE = "gen_ai.assistant.message"
|
|
246
|
+
TOOL_MESSAGE = "gen_ai.tool.message"
|
|
247
|
+
CHOICE = "gen_ai.choice"
|
|
248
|
+
# Operation names
|
|
249
|
+
CHAT_COMPLETION_OPERATION = "chat"
|
|
250
|
+
AGENT_INVOKE_OPERATION = "invoke_agent"
|
|
251
|
+
TOOL_EXECUTION_OPERATION = "execute_tool"
|
|
252
|
+
# MCP attributes
|
|
253
|
+
MCP_METHOD_NAME = "mcp.method.name"
|
|
254
|
+
MCP_PROTOCOL_VERSION = "mcp.protocol.version"
|
|
255
|
+
MCP_SESSION_ID = "mcp.session.id"
|
|
256
|
+
PROMPT_NAME = "gen_ai.prompt.name"
|
|
257
|
+
NETWORK_TRANSPORT = "network.transport"
|
|
258
|
+
NETWORK_PROTOCOL_NAME = "network.protocol.name"
|
|
259
|
+
# Measurement attributes. Step 5 deliberately migrates these exported
|
|
260
|
+
# metric names to the Chrys-owned ``chrys.function.*`` schema.
|
|
261
|
+
MEASUREMENT_FUNCTION_TAG_NAME = "chrys.function.name"
|
|
262
|
+
MEASUREMENT_FUNCTION_INVOCATION_DURATION = "chrys.function.invocation.duration"
|
|
263
|
+
|
|
264
|
+
def __repr__(self) -> str:
|
|
265
|
+
"""Return the string representation of the enum member."""
|
|
266
|
+
return self.value
|
|
267
|
+
|
|
268
|
+
def __str__(self) -> str:
|
|
269
|
+
"""Return the string representation of the enum member."""
|
|
270
|
+
return self.value
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Event and finish-reason names used by message telemetry.
|
|
274
|
+
ROLE_EVENT_MAP = {
|
|
275
|
+
"system": OtelAttr.SYSTEM_MESSAGE,
|
|
276
|
+
"user": OtelAttr.USER_MESSAGE,
|
|
277
|
+
"assistant": OtelAttr.ASSISTANT_MESSAGE,
|
|
278
|
+
"tool": OtelAttr.TOOL_MESSAGE,
|
|
279
|
+
}
|
|
280
|
+
FINISH_REASON_MAP = {
|
|
281
|
+
"stop": "stop",
|
|
282
|
+
"content_filter": "content_filter",
|
|
283
|
+
"tool_calls": "tool_call",
|
|
284
|
+
"length": "length",
|
|
285
|
+
}
|
|
286
|
+
USAGE_DETAIL_TO_OTEL_ATTR: Final[tuple[tuple[str, OtelAttr], ...]] = (
|
|
287
|
+
("input_token_count", OtelAttr.INPUT_TOKENS),
|
|
288
|
+
("output_token_count", OtelAttr.OUTPUT_TOKENS),
|
|
289
|
+
("anthropic.cache_creation_input_tokens", OtelAttr.CACHE_CREATION_INPUT_TOKENS),
|
|
290
|
+
("anthropic.cache_read_input_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
|
|
291
|
+
("deepseek.prompt_cache_hit_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
|
|
292
|
+
("openai.cached_input_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
|
|
293
|
+
("prompt/cached_tokens", OtelAttr.CACHE_READ_INPUT_TOKENS),
|
|
294
|
+
("cache_creation_input_token_count", OtelAttr.CACHE_CREATION_INPUT_TOKENS),
|
|
295
|
+
("cache_read_input_token_count", OtelAttr.CACHE_READ_INPUT_TOKENS),
|
|
296
|
+
("reasoning_output_token_count", OtelAttr.REASONING_OUTPUT_TOKENS),
|
|
297
|
+
("openai.reasoning_tokens", OtelAttr.REASONING_OUTPUT_TOKENS),
|
|
298
|
+
("completion/reasoning_tokens", OtelAttr.REASONING_OUTPUT_TOKENS),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class MessageListTimestampFilter(logging.Filter):
|
|
303
|
+
"""Bump per-message log timestamps to preserve history order.
|
|
304
|
+
|
|
305
|
+
Chat-history events are emitted within nanoseconds of each other and many
|
|
306
|
+
backends truncate timestamp resolution; incrementing by 1 microsecond per
|
|
307
|
+
message index preserves a restorable order.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
INDEX_KEY: ClassVar[str] = "chat_message_index"
|
|
311
|
+
|
|
312
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
313
|
+
"""Increment the timestamp of INFO logs by 1 microsecond."""
|
|
314
|
+
if hasattr(record, self.INDEX_KEY):
|
|
315
|
+
idx = getattr(record, self.INDEX_KEY)
|
|
316
|
+
record.created += idx * 1e-6
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
logger.addFilter(MessageListTimestampFilter())
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def get_tracer(
|
|
324
|
+
instrumenting_module_name: str = _SCOPE_NAME,
|
|
325
|
+
instrumenting_library_version: str = _SCOPE_VERSION,
|
|
326
|
+
schema_url: str | None = None,
|
|
327
|
+
attributes: dict[str, Any] | None = None,
|
|
328
|
+
) -> trace.Tracer:
|
|
329
|
+
"""Get a tracer scoped to the installed Chrys version by default."""
|
|
330
|
+
return trace.get_tracer(
|
|
331
|
+
instrumenting_module_name=instrumenting_module_name,
|
|
332
|
+
instrumenting_library_version=instrumenting_library_version,
|
|
333
|
+
schema_url=schema_url,
|
|
334
|
+
attributes=attributes,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def get_meter(
|
|
339
|
+
name: str = _SCOPE_NAME,
|
|
340
|
+
version: str = _SCOPE_VERSION,
|
|
341
|
+
schema_url: str | None = None,
|
|
342
|
+
attributes: dict[str, Any] | None = None,
|
|
343
|
+
) -> metrics.Meter:
|
|
344
|
+
"""Get a Chrys-scoped meter, tolerating SDKs without meter attributes."""
|
|
345
|
+
try:
|
|
346
|
+
return metrics.get_meter(name=name, version=version, schema_url=schema_url, attributes=attributes)
|
|
347
|
+
except TypeError:
|
|
348
|
+
# Older OpenTelemetry releases do not support the attributes parameter.
|
|
349
|
+
return metrics.get_meter(name=name, version=version, schema_url=schema_url)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _get_duration_histogram() -> metrics.Histogram:
|
|
353
|
+
"""Create the model-operation duration histogram with shared duration buckets."""
|
|
354
|
+
return get_meter().create_histogram(
|
|
355
|
+
name=OtelAttr.LLM_OPERATION_DURATION,
|
|
356
|
+
unit=OtelAttr.DURATION_UNIT,
|
|
357
|
+
description="Captures the duration of operations of function-invoking chat clients",
|
|
358
|
+
explicit_bucket_boundaries_advisory=OPERATION_DURATION_BUCKET_BOUNDARIES,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _get_token_usage_histogram() -> metrics.Histogram:
|
|
363
|
+
"""Create the chat token-usage histogram with shared token buckets."""
|
|
364
|
+
return get_meter().create_histogram(
|
|
365
|
+
name=OtelAttr.LLM_TOKEN_USAGE,
|
|
366
|
+
unit=OtelAttr.T_UNIT,
|
|
367
|
+
description="Captures the token usage of chat clients",
|
|
368
|
+
explicit_bucket_boundaries_advisory=TOKEN_USAGE_BUCKET_BOUNDARIES,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def get_function_duration_histogram() -> metrics.Histogram:
|
|
373
|
+
"""Create the per-tool duration histogram through the proxy meter.
|
|
374
|
+
|
|
375
|
+
The loop caches it on first use and records only while ``TELEMETRY_GATE``
|
|
376
|
+
is enabled.
|
|
377
|
+
"""
|
|
378
|
+
return get_meter().create_histogram(
|
|
379
|
+
name=OtelAttr.MEASUREMENT_FUNCTION_INVOCATION_DURATION,
|
|
380
|
+
unit=OtelAttr.DURATION_UNIT,
|
|
381
|
+
description="Measures the duration of a function's execution",
|
|
382
|
+
explicit_bucket_boundaries_advisory=OPERATION_DURATION_BUCKET_BOUNDARIES,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# Runtime kwargs that are not JSON serializable stay out of sensitive
|
|
387
|
+
# TOOL_ARGUMENTS capture.
|
|
388
|
+
FUNCTION_SPAN_EXCLUDED_KWARGS: Final[frozenset[str]] = frozenset(
|
|
389
|
+
{
|
|
390
|
+
"chat_options",
|
|
391
|
+
"tools",
|
|
392
|
+
"tool_choice",
|
|
393
|
+
"session",
|
|
394
|
+
"conversation_id",
|
|
395
|
+
"options",
|
|
396
|
+
"response_format",
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def get_function_span_attributes(function: FunctionTool, tool_call_id: str | None = None) -> dict[str, str]:
|
|
402
|
+
"""Build tool identity and operation attributes for a function span."""
|
|
403
|
+
attributes: dict[str, str] = {
|
|
404
|
+
OtelAttr.OPERATION: OtelAttr.TOOL_EXECUTION_OPERATION,
|
|
405
|
+
OtelAttr.TOOL_NAME: function.name,
|
|
406
|
+
OtelAttr.TOOL_CALL_ID: tool_call_id or "unknown",
|
|
407
|
+
OtelAttr.TOOL_TYPE: "function",
|
|
408
|
+
}
|
|
409
|
+
if function.description:
|
|
410
|
+
attributes[OtelAttr.TOOL_DESCRIPTION] = function.description
|
|
411
|
+
return attributes
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def get_function_span(attributes: dict[str, str]) -> contextlib.AbstractContextManager[trace.Span]:
|
|
415
|
+
"""Start the current ``execute_tool {name}`` span; the caller records errors explicitly."""
|
|
416
|
+
return get_tracer().start_as_current_span(
|
|
417
|
+
name=f"{attributes[OtelAttr.OPERATION]} {attributes[OtelAttr.TOOL_NAME]}",
|
|
418
|
+
attributes=attributes,
|
|
419
|
+
set_status_on_exception=False,
|
|
420
|
+
end_on_exit=True,
|
|
421
|
+
record_exception=False,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@contextlib.contextmanager
|
|
426
|
+
def create_mcp_client_span(
|
|
427
|
+
method_name: str,
|
|
428
|
+
target: str | None = None,
|
|
429
|
+
attributes: dict[str, Any] | None = None,
|
|
430
|
+
) -> Generator[trace.Span, Any, Any]:
|
|
431
|
+
"""Create a Chrys-gated MCP client span."""
|
|
432
|
+
span_name = f"{method_name} {target}" if target else method_name
|
|
433
|
+
attrs: dict[str, Any] = {OtelAttr.MCP_METHOD_NAME: method_name}
|
|
434
|
+
if attributes:
|
|
435
|
+
attrs.update(attributes)
|
|
436
|
+
tracer = get_tracer() if TELEMETRY_GATE.enabled else trace.NoOpTracer()
|
|
437
|
+
span = tracer.start_span(span_name, kind=trace.SpanKind.CLIENT, attributes=attrs)
|
|
438
|
+
with trace.use_span(
|
|
439
|
+
span=span,
|
|
440
|
+
end_on_exit=True,
|
|
441
|
+
record_exception=True,
|
|
442
|
+
set_status_on_exception=True,
|
|
443
|
+
) as current_span:
|
|
444
|
+
yield current_span
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def set_mcp_span_error(span: trace.Span, error_type: str, description: str | None = None) -> None:
|
|
448
|
+
"""Set MCP span error status and ``error.type``."""
|
|
449
|
+
span.set_attribute(OtelAttr.ERROR_TYPE, error_type)
|
|
450
|
+
span.set_status(trace.StatusCode.ERROR, description=description)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _serialize_tool_definitions(tools: Any) -> str | None:
|
|
454
|
+
"""Serialize canonical OTel tool definitions without raising into a run.
|
|
455
|
+
|
|
456
|
+
Normalize once, then independently convert, canonicalize,
|
|
457
|
+
narrow, and serialize each tool so one malformed declaration cannot drop
|
|
458
|
+
the valid definitions around it.
|
|
459
|
+
"""
|
|
460
|
+
try:
|
|
461
|
+
normalized_tools = normalize_tools(tools)
|
|
462
|
+
except Exception:
|
|
463
|
+
logger.warning(
|
|
464
|
+
"Failed to normalize tool definitions for telemetry; skipping attribute.",
|
|
465
|
+
exc_info=True,
|
|
466
|
+
)
|
|
467
|
+
return None
|
|
468
|
+
if not normalized_tools:
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
fragments: list[str] = []
|
|
472
|
+
for tool_item in normalized_tools:
|
|
473
|
+
try:
|
|
474
|
+
definition = _build_tool_otel_definition(tool_item)
|
|
475
|
+
if definition is None:
|
|
476
|
+
continue
|
|
477
|
+
fragments.append(json.dumps(definition, ensure_ascii=False))
|
|
478
|
+
except Exception:
|
|
479
|
+
logger.warning(
|
|
480
|
+
"Failed to build or serialize telemetry definition for tool %r (%s); skipping tool.",
|
|
481
|
+
_tool_name_for_log(tool_item),
|
|
482
|
+
type(tool_item).__name__,
|
|
483
|
+
exc_info=True,
|
|
484
|
+
)
|
|
485
|
+
continue
|
|
486
|
+
return f"[{','.join(fragments)}]" if fragments else None
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _tool_name_for_log(tool_item: Any) -> str:
|
|
490
|
+
"""Return a best-effort tool name without risking the warning path."""
|
|
491
|
+
if isinstance(tool_item, FunctionTool):
|
|
492
|
+
return tool_item.name
|
|
493
|
+
if isinstance(tool_item, Mapping):
|
|
494
|
+
try:
|
|
495
|
+
nested = tool_item.get("function") if tool_item.get("type") == "function" else None
|
|
496
|
+
source = nested if isinstance(nested, Mapping) else tool_item
|
|
497
|
+
for key in ("name", "server_label", "type"):
|
|
498
|
+
value = source.get(key)
|
|
499
|
+
if isinstance(value, str) and value:
|
|
500
|
+
return value
|
|
501
|
+
except Exception:
|
|
502
|
+
pass
|
|
503
|
+
return type(tool_item).__name__
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _build_tool_otel_definition(tool_item: Any) -> dict[str, Any] | None:
|
|
507
|
+
"""Convert one tool declaration and canonicalize its OTel field set."""
|
|
508
|
+
raw: Any
|
|
509
|
+
if isinstance(tool_item, FunctionTool):
|
|
510
|
+
raw = tool_item.to_json_schema_spec()
|
|
511
|
+
else:
|
|
512
|
+
model_dump = getattr(tool_item, "model_dump", None)
|
|
513
|
+
if callable(model_dump):
|
|
514
|
+
raw = model_dump(exclude_none=True)
|
|
515
|
+
else:
|
|
516
|
+
to_dict = getattr(tool_item, "to_dict", None)
|
|
517
|
+
if callable(to_dict):
|
|
518
|
+
raw = to_dict()
|
|
519
|
+
elif isinstance(tool_item, Mapping):
|
|
520
|
+
raw = tool_item
|
|
521
|
+
else:
|
|
522
|
+
logger.warning(
|
|
523
|
+
"Can't parse tool to OpenTelemetry tool definition: %s.",
|
|
524
|
+
type(tool_item).__name__,
|
|
525
|
+
)
|
|
526
|
+
return None
|
|
527
|
+
|
|
528
|
+
if not isinstance(raw, Mapping):
|
|
529
|
+
logger.warning(
|
|
530
|
+
"Can't parse tool to OpenTelemetry tool definition: %s conversion returned %s.",
|
|
531
|
+
type(tool_item).__name__,
|
|
532
|
+
type(raw).__name__,
|
|
533
|
+
)
|
|
534
|
+
return None
|
|
535
|
+
return _otel_definition_from_mapping(raw)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _otel_definition_from_mapping(raw: Mapping[str, Any]) -> dict[str, Any] | None:
|
|
539
|
+
"""Flatten provider tool shapes and retain only OTel-safe fields."""
|
|
540
|
+
nested_function = raw.get("function") if raw.get("type") == "function" else None
|
|
541
|
+
if isinstance(nested_function, Mapping):
|
|
542
|
+
name = nested_function.get("name")
|
|
543
|
+
if not isinstance(name, str) or not name:
|
|
544
|
+
logger.warning("Can't parse tool to OpenTelemetry tool definition: missing 'name'.")
|
|
545
|
+
return None
|
|
546
|
+
return _otel_tool_definition(
|
|
547
|
+
"function",
|
|
548
|
+
name,
|
|
549
|
+
description=nested_function.get("description"),
|
|
550
|
+
parameters=nested_function.get("parameters"),
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
type_value = raw.get("type")
|
|
554
|
+
if not isinstance(type_value, str) or not type_value:
|
|
555
|
+
logger.warning("Can't parse tool to OpenTelemetry tool definition: missing 'type'.")
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
name_value = next(
|
|
559
|
+
(value for key in ("name", "server_label") if isinstance((value := raw.get(key)), str) and value),
|
|
560
|
+
type_value,
|
|
561
|
+
)
|
|
562
|
+
return _otel_tool_definition(
|
|
563
|
+
type_value,
|
|
564
|
+
name_value,
|
|
565
|
+
description=raw.get("description") or raw.get("server_description"),
|
|
566
|
+
parameters=raw.get("parameters") or raw.get("input_schema"),
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _otel_tool_definition(
|
|
571
|
+
type_value: str,
|
|
572
|
+
name_value: str,
|
|
573
|
+
*,
|
|
574
|
+
description: Any = None,
|
|
575
|
+
parameters: Any = None,
|
|
576
|
+
) -> dict[str, Any]:
|
|
577
|
+
"""Emit only ``type``, ``name``, and non-empty descriptive fields."""
|
|
578
|
+
definition: dict[str, Any] = {"type": type_value, "name": name_value}
|
|
579
|
+
if description:
|
|
580
|
+
definition["description"] = description
|
|
581
|
+
if parameters:
|
|
582
|
+
definition["parameters"] = parameters
|
|
583
|
+
return definition
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _get_data_bytes_as_str(content: Content) -> str | None:
|
|
587
|
+
"""Return the base64 payload for data URI content.
|
|
588
|
+
|
|
589
|
+
Return ``None`` for other content; reject data URIs without base64 encoding
|
|
590
|
+
with ``ValueError``.
|
|
591
|
+
"""
|
|
592
|
+
if content.type not in ("data", "uri"):
|
|
593
|
+
return None
|
|
594
|
+
uri = content.uri
|
|
595
|
+
if not uri:
|
|
596
|
+
return None
|
|
597
|
+
if not uri.startswith("data:"):
|
|
598
|
+
return None
|
|
599
|
+
if ";base64," not in uri:
|
|
600
|
+
raise ValueError("Data URI must use base64 encoding")
|
|
601
|
+
_, data = uri.split(";base64,", 1)
|
|
602
|
+
return data
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@contextlib.contextmanager
|
|
606
|
+
def _activate_span(span: trace.Span) -> Generator[None]:
|
|
607
|
+
"""Attach the span for one iterator pull and detach it in the same context."""
|
|
608
|
+
token = otel_context.attach(trace.set_span_in_context(span))
|
|
609
|
+
try:
|
|
610
|
+
yield
|
|
611
|
+
finally:
|
|
612
|
+
otel_context.detach(token)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
@contextlib.contextmanager
|
|
616
|
+
def _get_span(
|
|
617
|
+
attributes: dict[str, Any],
|
|
618
|
+
span_name_attribute: str,
|
|
619
|
+
) -> Generator[trace.Span, Any, Any]:
|
|
620
|
+
"""Activate an operation span and end it on context exit."""
|
|
621
|
+
operation = attributes.get(OtelAttr.OPERATION, "operation")
|
|
622
|
+
span_name = attributes.get(span_name_attribute, "unknown")
|
|
623
|
+
span = get_tracer().start_span(f"{operation} {span_name}")
|
|
624
|
+
span.set_attributes(attributes)
|
|
625
|
+
with trace.use_span(
|
|
626
|
+
span=span,
|
|
627
|
+
end_on_exit=True,
|
|
628
|
+
record_exception=False,
|
|
629
|
+
set_status_on_exception=False,
|
|
630
|
+
) as current_span:
|
|
631
|
+
yield current_span
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _start_streaming_span(attributes: dict[str, Any], span_name_attribute: str) -> trace.Span:
|
|
635
|
+
"""Start a streaming span without attaching it to the current context.
|
|
636
|
+
|
|
637
|
+
The caller owns ending it (cleanup hooks) and activating it around each
|
|
638
|
+
pull (:func:`_activate_span` via ``with_pull_context_manager``) —
|
|
639
|
+
attaching at creation would die in cross-context cleanup.
|
|
640
|
+
"""
|
|
641
|
+
operation = attributes.get(OtelAttr.OPERATION, "operation")
|
|
642
|
+
span_name = attributes.get(span_name_attribute, "unknown")
|
|
643
|
+
span = get_tracer().start_span(f"{operation} {span_name}")
|
|
644
|
+
span.set_attributes(attributes)
|
|
645
|
+
return span
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _get_instructions_from_options(options: Any) -> str | list[str] | None:
|
|
649
|
+
"""Read string or string-list instructions from an options mapping."""
|
|
650
|
+
if options is None:
|
|
651
|
+
return None
|
|
652
|
+
if isinstance(options, Mapping):
|
|
653
|
+
instructions = cast("Mapping[str, Any]", options).get("instructions")
|
|
654
|
+
if isinstance(instructions, str):
|
|
655
|
+
return instructions
|
|
656
|
+
if isinstance(instructions, list) and all(isinstance(item, str) for item in instructions):
|
|
657
|
+
return instructions
|
|
658
|
+
return None
|
|
659
|
+
return None
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
# Request-to-span mapping. Each entry:
|
|
663
|
+
# source_keys -> (otel_attribute_key, transform_func, check_options_first, default_value).
|
|
664
|
+
# The ``tools`` transform performs guarded, per-tool canonicalization so span
|
|
665
|
+
# construction cannot expose provider-only fields or raise into a live request.
|
|
666
|
+
OTEL_ATTR_MAP: dict[str | tuple[str, ...], tuple[str, Callable[[Any], Any] | None, bool, Any]] = {
|
|
667
|
+
"choice_count": (OtelAttr.CHOICE_COUNT, None, False, 1),
|
|
668
|
+
"operation_name": (OtelAttr.OPERATION, None, False, None),
|
|
669
|
+
"system_name": (OtelAttr.SYSTEM, None, False, None),
|
|
670
|
+
"provider_name": (OtelAttr.PROVIDER_NAME, None, False, None),
|
|
671
|
+
"service_url": (OtelAttr.ADDRESS, None, False, None),
|
|
672
|
+
"conversation_id": (OtelAttr.CONVERSATION_ID, None, True, None),
|
|
673
|
+
"seed": (OtelAttr.SEED, None, True, None),
|
|
674
|
+
"frequency_penalty": (OtelAttr.FREQUENCY_PENALTY, None, True, None),
|
|
675
|
+
"max_tokens": (OtelAttr.REQUEST_MAX_TOKENS, None, True, None),
|
|
676
|
+
"stop": (OtelAttr.STOP_SEQUENCES, None, True, None),
|
|
677
|
+
"temperature": (OtelAttr.REQUEST_TEMPERATURE, None, True, None),
|
|
678
|
+
"top_p": (OtelAttr.REQUEST_TOP_P, None, True, None),
|
|
679
|
+
"presence_penalty": (OtelAttr.PRESENCE_PENALTY, None, True, None),
|
|
680
|
+
"top_k": (OtelAttr.TOP_K, None, True, None),
|
|
681
|
+
"encoding_formats": (
|
|
682
|
+
OtelAttr.ENCODING_FORMATS,
|
|
683
|
+
lambda v: json.dumps(v if isinstance(v, list) else [v]),
|
|
684
|
+
True,
|
|
685
|
+
None,
|
|
686
|
+
),
|
|
687
|
+
"agent_id": (OtelAttr.AGENT_ID, None, False, None),
|
|
688
|
+
"agent_name": (OtelAttr.AGENT_NAME, None, False, None),
|
|
689
|
+
"agent_description": (OtelAttr.AGENT_DESCRIPTION, None, False, None),
|
|
690
|
+
"model": (OtelAttr.REQUEST_MODEL, None, True, None),
|
|
691
|
+
# Tools with validation - returns None if no valid tools
|
|
692
|
+
"tools": (
|
|
693
|
+
OtelAttr.TOOL_DEFINITIONS,
|
|
694
|
+
_serialize_tool_definitions,
|
|
695
|
+
True,
|
|
696
|
+
None,
|
|
697
|
+
),
|
|
698
|
+
# Error type extraction
|
|
699
|
+
"error": (OtelAttr.ERROR_TYPE, lambda e: type(e).__name__, False, None),
|
|
700
|
+
# thread_id overrides conversation_id - processed after conversation_id due to dict ordering
|
|
701
|
+
"thread_id": (OtelAttr.CONVERSATION_ID, None, False, None),
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
|
|
706
|
+
"""Map request settings to telemetry attributes, omitting unset or rejected values."""
|
|
707
|
+
attributes: dict[str, Any] = {}
|
|
708
|
+
options = kwargs.get("all_options", kwargs.get("options"))
|
|
709
|
+
options_mapping = cast("Mapping[str, Any]", options) if isinstance(options, Mapping) else None
|
|
710
|
+
|
|
711
|
+
for source_keys, (otel_key, transform_func, check_options, default_value) in OTEL_ATTR_MAP.items():
|
|
712
|
+
keys = (source_keys,) if isinstance(source_keys, str) else source_keys
|
|
713
|
+
|
|
714
|
+
value = None
|
|
715
|
+
for key in keys:
|
|
716
|
+
if check_options and options_mapping is not None:
|
|
717
|
+
value = options_mapping.get(key)
|
|
718
|
+
if value is None:
|
|
719
|
+
value = kwargs.get(key)
|
|
720
|
+
if value is not None:
|
|
721
|
+
break
|
|
722
|
+
|
|
723
|
+
if value is None and default_value is not None:
|
|
724
|
+
value = default_value
|
|
725
|
+
|
|
726
|
+
if value is not None:
|
|
727
|
+
result = transform_func(value) if transform_func else value
|
|
728
|
+
# Allow transform_func to return None to skip attribute
|
|
729
|
+
if result is not None:
|
|
730
|
+
attributes[otel_key] = result
|
|
731
|
+
|
|
732
|
+
return attributes
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def capture_exception(span: trace.Span, exception: BaseException, timestamp: int | None = None) -> None:
|
|
736
|
+
"""Record the exception and mark the span as failed."""
|
|
737
|
+
span.set_attribute(OtelAttr.ERROR_TYPE, type(exception).__name__)
|
|
738
|
+
span.record_exception(exception=exception, timestamp=timestamp)
|
|
739
|
+
span.set_status(status=trace.StatusCode.ERROR, description=repr(exception))
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def _capture_system_instructions(span: trace.Span, system_instructions: str | list[str] | None) -> None:
|
|
743
|
+
"""Capture system instructions on a span."""
|
|
744
|
+
if not system_instructions:
|
|
745
|
+
return
|
|
746
|
+
otel_sys_instructions = [
|
|
747
|
+
{"type": "text", "content": instruction} for instruction in _normalize_instructions(system_instructions)
|
|
748
|
+
]
|
|
749
|
+
span.set_attribute(OtelAttr.SYSTEM_INSTRUCTIONS, json.dumps(otel_sys_instructions, ensure_ascii=False))
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def _capture_current_agent_system_instructions(
|
|
753
|
+
agent_span: trace.Span,
|
|
754
|
+
chat_span: trace.Span,
|
|
755
|
+
system_instructions: str | list[str] | None,
|
|
756
|
+
) -> None:
|
|
757
|
+
"""Capture final chat instructions on the current agent span when the chat span belongs to it."""
|
|
758
|
+
if not system_instructions or not agent_span.is_recording():
|
|
759
|
+
return
|
|
760
|
+
|
|
761
|
+
agent_attributes_obj = getattr(agent_span, "attributes", None)
|
|
762
|
+
if not isinstance(agent_attributes_obj, Mapping):
|
|
763
|
+
return
|
|
764
|
+
agent_attributes = cast("Mapping[str, Any]", agent_attributes_obj)
|
|
765
|
+
if agent_attributes.get(OtelAttr.OPERATION.value) != OtelAttr.AGENT_INVOKE_OPERATION:
|
|
766
|
+
return
|
|
767
|
+
|
|
768
|
+
if not _instructions_preserve_existing_agent_instructions(agent_attributes, system_instructions):
|
|
769
|
+
return
|
|
770
|
+
|
|
771
|
+
chat_parent = getattr(chat_span, "parent", None)
|
|
772
|
+
agent_context = agent_span.get_span_context()
|
|
773
|
+
if (
|
|
774
|
+
chat_parent is None
|
|
775
|
+
or chat_parent.span_id != agent_context.span_id
|
|
776
|
+
or chat_parent.trace_id != agent_context.trace_id
|
|
777
|
+
):
|
|
778
|
+
return
|
|
779
|
+
|
|
780
|
+
_capture_system_instructions(agent_span, system_instructions)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def _normalize_instructions(system_instructions: str | list[str]) -> list[str]:
|
|
784
|
+
"""Normalize system instructions to telemetry text items."""
|
|
785
|
+
return system_instructions if isinstance(system_instructions, list) else [system_instructions]
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _instructions_preserve_existing_agent_instructions(
|
|
789
|
+
agent_attributes: Mapping[str, Any],
|
|
790
|
+
system_instructions: str | list[str],
|
|
791
|
+
) -> bool:
|
|
792
|
+
"""Return True when chat instructions preserve the agent span's existing instructions."""
|
|
793
|
+
existing = agent_attributes.get(OtelAttr.SYSTEM_INSTRUCTIONS)
|
|
794
|
+
if not isinstance(existing, str):
|
|
795
|
+
return True
|
|
796
|
+
|
|
797
|
+
try:
|
|
798
|
+
existing_items_obj = json.loads(existing)
|
|
799
|
+
except json.JSONDecodeError:
|
|
800
|
+
return False
|
|
801
|
+
|
|
802
|
+
if not isinstance(existing_items_obj, list):
|
|
803
|
+
return False
|
|
804
|
+
existing_items = cast("list[object]", existing_items_obj)
|
|
805
|
+
|
|
806
|
+
existing_contents: list[str] = []
|
|
807
|
+
for item in existing_items:
|
|
808
|
+
if not isinstance(item, Mapping):
|
|
809
|
+
continue
|
|
810
|
+
content = cast("Mapping[str, Any]", item).get("content")
|
|
811
|
+
if isinstance(content, str):
|
|
812
|
+
existing_contents.append(content)
|
|
813
|
+
|
|
814
|
+
existing_text = "\n".join(existing_contents)
|
|
815
|
+
new_text = "\n".join(_normalize_instructions(system_instructions))
|
|
816
|
+
return new_text == existing_text or new_text.startswith(f"{existing_text}\n")
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def _capture_messages(
|
|
820
|
+
span: trace.Span,
|
|
821
|
+
provider_name: str,
|
|
822
|
+
messages: AgentRunInputs,
|
|
823
|
+
system_instructions: str | list[str] | None = None,
|
|
824
|
+
output: bool = False,
|
|
825
|
+
finish_reason: FinishReasonLiteral | FinishReason | None = None,
|
|
826
|
+
) -> None:
|
|
827
|
+
"""Emit sensitive message events and capture their representation on the span."""
|
|
828
|
+
normalized_messages = normalize_messages(messages)
|
|
829
|
+
otel_messages: list[dict[str, Any]] = []
|
|
830
|
+
for index, message in enumerate(normalized_messages):
|
|
831
|
+
# Reuse the otel message representation for logging instead of calling to_dict()
|
|
832
|
+
# to avoid expensive Pydantic serialization overhead
|
|
833
|
+
otel_message = _to_otel_message(message)
|
|
834
|
+
logger.info(
|
|
835
|
+
otel_message,
|
|
836
|
+
extra={
|
|
837
|
+
OtelAttr.EVENT_NAME: OtelAttr.CHOICE if output else ROLE_EVENT_MAP.get(message.role),
|
|
838
|
+
OtelAttr.PROVIDER_NAME: provider_name,
|
|
839
|
+
MessageListTimestampFilter.INDEX_KEY: index,
|
|
840
|
+
},
|
|
841
|
+
)
|
|
842
|
+
otel_messages.append(otel_message)
|
|
843
|
+
# Total in both directions: an empty message list has no tail to annotate, and a
|
|
844
|
+
# provider-specific reason no map entry covers is emitted verbatim rather than
|
|
845
|
+
# raising past the caller's exception handler and mislabelling the span as failed.
|
|
846
|
+
if finish_reason and otel_messages:
|
|
847
|
+
otel_messages[-1]["finish_reason"] = FINISH_REASON_MAP.get(finish_reason, finish_reason)
|
|
848
|
+
span.set_attribute(
|
|
849
|
+
OtelAttr.OUTPUT_MESSAGES if output else OtelAttr.INPUT_MESSAGES, json.dumps(otel_messages, ensure_ascii=False)
|
|
850
|
+
)
|
|
851
|
+
_capture_system_instructions(span, system_instructions)
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def _to_otel_message(message: Message) -> dict[str, Any]:
|
|
855
|
+
"""Convert a message into its telemetry role and content parts."""
|
|
856
|
+
return {"role": message.role, "parts": [_to_otel_part(content) for content in message.contents]}
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _to_otel_part(content: Content) -> dict[str, Any] | None:
|
|
860
|
+
"""Convert content to a telemetry part, retaining unknown content through serialization."""
|
|
861
|
+
match content.type:
|
|
862
|
+
case "text":
|
|
863
|
+
return {"type": "text", "content": content.text}
|
|
864
|
+
case "text_reasoning":
|
|
865
|
+
return {"type": "reasoning", "content": content.text}
|
|
866
|
+
case "uri":
|
|
867
|
+
return {
|
|
868
|
+
"type": "uri",
|
|
869
|
+
"uri": content.uri,
|
|
870
|
+
"mime_type": content.media_type,
|
|
871
|
+
"modality": content.media_type.split("/")[0] if content.media_type else None,
|
|
872
|
+
}
|
|
873
|
+
case "data":
|
|
874
|
+
return {
|
|
875
|
+
"type": "blob",
|
|
876
|
+
"content": _get_data_bytes_as_str(content),
|
|
877
|
+
"mime_type": content.media_type,
|
|
878
|
+
"modality": content.media_type.split("/")[0] if content.media_type else None,
|
|
879
|
+
}
|
|
880
|
+
case "function_call":
|
|
881
|
+
return {"type": "tool_call", "id": content.call_id, "name": content.name, "arguments": content.arguments}
|
|
882
|
+
case "function_result":
|
|
883
|
+
return {
|
|
884
|
+
"type": "tool_call_response",
|
|
885
|
+
"id": content.call_id,
|
|
886
|
+
"response": content.result if content.result is not None else "",
|
|
887
|
+
}
|
|
888
|
+
case _:
|
|
889
|
+
# GenericPart in otel output messages json spec.
|
|
890
|
+
# just required type, and arbitrary other fields.
|
|
891
|
+
return content.to_dict(exclude_none=True)
|
|
892
|
+
return None
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
def _mark_inner_response_telemetry_captured(response: ChatResponse | AgentResponse) -> None:
|
|
896
|
+
"""Track response fields and accumulated usage already captured by the inner chat layer."""
|
|
897
|
+
captured_fields = INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.get()
|
|
898
|
+
if captured_fields is None:
|
|
899
|
+
return
|
|
900
|
+
if response.response_id:
|
|
901
|
+
captured_fields.add(INNER_RESPONSE_ID_CAPTURED_FIELD)
|
|
902
|
+
if response.usage_details:
|
|
903
|
+
captured_fields.add(INNER_USAGE_CAPTURED_FIELD)
|
|
904
|
+
accumulated = INNER_ACCUMULATED_USAGE.get()
|
|
905
|
+
if accumulated is not None:
|
|
906
|
+
INNER_ACCUMULATED_USAGE.set(add_usage_details(accumulated, response.usage_details))
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def _apply_accumulated_usage(attributes: dict[str, Any], captured_fields: set[str]) -> None:
|
|
910
|
+
"""Apply inner-call usage to the agent attributes once the inner layer has recorded it."""
|
|
911
|
+
if INNER_USAGE_CAPTURED_FIELD not in captured_fields:
|
|
912
|
+
return
|
|
913
|
+
accumulated = INNER_ACCUMULATED_USAGE.get()
|
|
914
|
+
if not accumulated:
|
|
915
|
+
return
|
|
916
|
+
_apply_usage_attributes(attributes, accumulated)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def _apply_usage_attributes(attributes: dict[str, Any], usage: Mapping[str, Any]) -> None:
|
|
920
|
+
"""Apply known usage details as standard OTel GenAI attributes."""
|
|
921
|
+
for usage_key, otel_attr in USAGE_DETAIL_TO_OTEL_ATTR:
|
|
922
|
+
value = usage.get(usage_key)
|
|
923
|
+
if value is None or isinstance(value, bool) or not isinstance(value, int):
|
|
924
|
+
continue
|
|
925
|
+
attributes.setdefault(otel_attr, value)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def _get_response_attributes(
|
|
929
|
+
attributes: dict[str, Any],
|
|
930
|
+
response: ChatResponse | AgentResponse,
|
|
931
|
+
*,
|
|
932
|
+
capture_response_id: bool = True,
|
|
933
|
+
capture_usage: bool = True,
|
|
934
|
+
) -> dict[str, Any]:
|
|
935
|
+
"""Add response identity, finish reason, model and selected usage fields to span attributes."""
|
|
936
|
+
if capture_response_id and response.response_id:
|
|
937
|
+
attributes[OtelAttr.RESPONSE_ID] = response.response_id
|
|
938
|
+
finish_reason = response.finish_reason
|
|
939
|
+
if not finish_reason:
|
|
940
|
+
finish_reason = (
|
|
941
|
+
getattr(response.raw_representation, "finish_reason", None) if response.raw_representation else None
|
|
942
|
+
)
|
|
943
|
+
if isinstance(finish_reason, str) and finish_reason:
|
|
944
|
+
attributes[OtelAttr.FINISH_REASONS] = json.dumps([finish_reason])
|
|
945
|
+
if model := getattr(response, "model", None):
|
|
946
|
+
attributes[OtelAttr.RESPONSE_MODEL] = model
|
|
947
|
+
if capture_usage and (usage := response.usage_details):
|
|
948
|
+
_apply_usage_attributes(attributes, usage)
|
|
949
|
+
return attributes
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
# Shared dimensions for GenAI duration and usage metrics.
|
|
953
|
+
GEN_AI_METRIC_ATTRIBUTES = (
|
|
954
|
+
OtelAttr.OPERATION,
|
|
955
|
+
OtelAttr.PROVIDER_NAME,
|
|
956
|
+
OtelAttr.REQUEST_MODEL,
|
|
957
|
+
OtelAttr.RESPONSE_MODEL,
|
|
958
|
+
OtelAttr.ADDRESS,
|
|
959
|
+
OtelAttr.PORT,
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def _capture_response(
|
|
964
|
+
span: trace.Span,
|
|
965
|
+
attributes: dict[str, Any],
|
|
966
|
+
operation_duration_histogram: metrics.Histogram | None = None,
|
|
967
|
+
token_usage_histogram: metrics.Histogram | None = None,
|
|
968
|
+
duration: float | None = None,
|
|
969
|
+
) -> None:
|
|
970
|
+
"""Write response attributes to the span and record duration and token metrics."""
|
|
971
|
+
span.set_attributes(attributes)
|
|
972
|
+
attrs: dict[str, Any] = {k: v for k, v in attributes.items() if k in GEN_AI_METRIC_ATTRIBUTES}
|
|
973
|
+
if token_usage_histogram and (input_tokens := attributes.get(OtelAttr.INPUT_TOKENS)) is not None:
|
|
974
|
+
token_usage_histogram.record(input_tokens, attributes={**attrs, OtelAttr.T_TYPE: OtelAttr.T_TYPE_INPUT})
|
|
975
|
+
if token_usage_histogram and (output_tokens := attributes.get(OtelAttr.OUTPUT_TOKENS)) is not None:
|
|
976
|
+
token_usage_histogram.record(output_tokens, {**attrs, OtelAttr.T_TYPE: OtelAttr.T_TYPE_OUTPUT})
|
|
977
|
+
if operation_duration_histogram and duration is not None:
|
|
978
|
+
if OtelAttr.ERROR_TYPE in attributes:
|
|
979
|
+
attrs[OtelAttr.ERROR_TYPE] = attributes[OtelAttr.ERROR_TYPE]
|
|
980
|
+
operation_duration_histogram.record(duration, attributes=attrs)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def _record_failed_duration(
|
|
984
|
+
histogram: metrics.Histogram | None,
|
|
985
|
+
attributes: Mapping[str, Any],
|
|
986
|
+
exception: BaseException,
|
|
987
|
+
duration: float | None,
|
|
988
|
+
) -> None:
|
|
989
|
+
"""Record a failed operation's duration under its ``error.type``, beside the successful ones."""
|
|
990
|
+
if histogram is None or duration is None:
|
|
991
|
+
return
|
|
992
|
+
attrs: dict[str, Any] = {k: v for k, v in attributes.items() if k in GEN_AI_METRIC_ATTRIBUTES}
|
|
993
|
+
attrs[OtelAttr.ERROR_TYPE] = type(exception).__name__
|
|
994
|
+
histogram.record(duration, attributes=attrs)
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
def _stream_error_of(stream: Any) -> Exception | None:
|
|
998
|
+
"""Read a stream failure, defaulting to None when the stream exposes no error field."""
|
|
999
|
+
return getattr(stream, "_stream_error", None)
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def _stream_abandoned(stream: Any) -> bool:
|
|
1003
|
+
"""Return whether the stream was closed before producing a final response.
|
|
1004
|
+
|
|
1005
|
+
An abandoned stream's partial updates must never be finalized — a
|
|
1006
|
+
``get_final_response()`` here would run result hooks (``after_run``
|
|
1007
|
+
context providers included) on a response the run never produced.
|
|
1008
|
+
"""
|
|
1009
|
+
return getattr(stream, "_abandoned", False)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
class _StreamingSpan:
|
|
1013
|
+
"""Own span closure, elapsed time and stream hooks without retaining the stream.
|
|
1014
|
+
|
|
1015
|
+
With a *duration_histogram*, a failed stream records its duration there
|
|
1016
|
+
under its ``error.type``.
|
|
1017
|
+
"""
|
|
1018
|
+
|
|
1019
|
+
def __init__(
|
|
1020
|
+
self,
|
|
1021
|
+
span: trace.Span,
|
|
1022
|
+
*,
|
|
1023
|
+
attributes: Mapping[str, Any],
|
|
1024
|
+
duration_histogram: metrics.Histogram | None = None,
|
|
1025
|
+
) -> None:
|
|
1026
|
+
self._span = span
|
|
1027
|
+
self._attributes = attributes
|
|
1028
|
+
self._duration_histogram = duration_histogram
|
|
1029
|
+
self._closed = False
|
|
1030
|
+
self.duration: float | None = None
|
|
1031
|
+
self._start_time = perf_counter()
|
|
1032
|
+
|
|
1033
|
+
def close(self) -> None:
|
|
1034
|
+
if self._closed:
|
|
1035
|
+
return
|
|
1036
|
+
self._closed = True
|
|
1037
|
+
self._span.end()
|
|
1038
|
+
|
|
1039
|
+
def _record_duration(self) -> None:
|
|
1040
|
+
self.duration = perf_counter() - self._start_time
|
|
1041
|
+
|
|
1042
|
+
def _fail(self, exception: BaseException) -> None:
|
|
1043
|
+
capture_exception(span=self._span, exception=exception, timestamp=time_ns())
|
|
1044
|
+
_record_failed_duration(self._duration_histogram, self._attributes, exception, self.duration)
|
|
1045
|
+
|
|
1046
|
+
def should_finalize(self, stream: ResponseStream[Any, Any]) -> bool:
|
|
1047
|
+
stream_error = _stream_error_of(stream)
|
|
1048
|
+
if stream_error is not None:
|
|
1049
|
+
# Skip result hooks such as after_run context providers on error
|
|
1050
|
+
# paths. Capture the error on the span before returning.
|
|
1051
|
+
self._fail(stream_error)
|
|
1052
|
+
return False
|
|
1053
|
+
# Closed before completion (cancel/stall abandonment): partial
|
|
1054
|
+
# updates are not a response and must not be finalized.
|
|
1055
|
+
return not _stream_abandoned(stream)
|
|
1056
|
+
|
|
1057
|
+
async def final_response[FinalT](self, stream: ResponseStream[Any, FinalT]) -> FinalT:
|
|
1058
|
+
"""Finalize *stream* from its cleanup hook; a failing finalizer or result hook fails it once.
|
|
1059
|
+
|
|
1060
|
+
The failure is recorded on the span and re-raised to the consumer.
|
|
1061
|
+
Swallowed, it would leave the stream unfinalized, and the stream
|
|
1062
|
+
finalizes again right after its cleanup hooks, re-running every result
|
|
1063
|
+
hook that already succeeded.
|
|
1064
|
+
"""
|
|
1065
|
+
try:
|
|
1066
|
+
return await stream.get_final_response()
|
|
1067
|
+
except Exception as exception:
|
|
1068
|
+
self._fail(exception)
|
|
1069
|
+
raise
|
|
1070
|
+
|
|
1071
|
+
def wrap[UpdateT, FinalT](
|
|
1072
|
+
self,
|
|
1073
|
+
stream: ResponseStream[UpdateT, FinalT],
|
|
1074
|
+
finalize_stream: Callable[[], Awaitable[None]],
|
|
1075
|
+
) -> ResponseStream[UpdateT, FinalT]:
|
|
1076
|
+
# Activate around each pull so inner chat, HTTP and tool spans inherit
|
|
1077
|
+
# this parent. Attach and detach stay in the same async context.
|
|
1078
|
+
wrapped_stream = (
|
|
1079
|
+
stream.with_cleanup_hook(self._record_duration)
|
|
1080
|
+
.with_cleanup_hook(finalize_stream)
|
|
1081
|
+
.with_pull_context_manager(lambda: _activate_span(self._span))
|
|
1082
|
+
)
|
|
1083
|
+
# Retain only this scaffold, never the stream or its finalizer closure,
|
|
1084
|
+
# so an unconsumed stream can be collected and its span closed once.
|
|
1085
|
+
weakref.finalize(wrapped_stream, self.close)
|
|
1086
|
+
return wrapped_stream
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
class ChatTelemetryLayer(_ChatTelemetryBase):
|
|
1090
|
+
"""Trace chat calls through ``super().get_response()`` under ``TELEMETRY_GATE``.
|
|
1091
|
+
|
|
1092
|
+
Telemetry must wrap ``get_response`` and must never define
|
|
1093
|
+
``_inner_get_response``: the wire client below this layer implements
|
|
1094
|
+
that hook, and request reporting rides on it.
|
|
1095
|
+
"""
|
|
1096
|
+
|
|
1097
|
+
def __init__(self, *args: Any, otel_provider_name: str | None = None, **kwargs: Any) -> None:
|
|
1098
|
+
"""Initialize the client before constructing telemetry attributes and histograms."""
|
|
1099
|
+
super().__init__(*args, **kwargs)
|
|
1100
|
+
self.token_usage_histogram = _get_token_usage_histogram()
|
|
1101
|
+
self.duration_histogram = _get_duration_histogram()
|
|
1102
|
+
self.otel_provider_name = otel_provider_name or getattr(self, "OTEL_PROVIDER_NAME", "unknown")
|
|
1103
|
+
|
|
1104
|
+
@staticmethod
|
|
1105
|
+
def _backfill_request_model(span: trace.Span, attributes: dict[str, Any]) -> None:
|
|
1106
|
+
"""Backfill the request model and span name from the served response model."""
|
|
1107
|
+
response_model = attributes.get(OtelAttr.RESPONSE_MODEL)
|
|
1108
|
+
if response_model and attributes.get(OtelAttr.REQUEST_MODEL, "unknown") == "unknown":
|
|
1109
|
+
attributes[OtelAttr.REQUEST_MODEL] = response_model
|
|
1110
|
+
operation = attributes.get(OtelAttr.OPERATION, "operation")
|
|
1111
|
+
span.update_name(f"{operation} {response_model}")
|
|
1112
|
+
|
|
1113
|
+
@overload
|
|
1114
|
+
def get_response(
|
|
1115
|
+
self,
|
|
1116
|
+
messages: Sequence[Message],
|
|
1117
|
+
*,
|
|
1118
|
+
stream: Literal[False] = False,
|
|
1119
|
+
options: Mapping[str, Any] | None = None,
|
|
1120
|
+
compaction_strategy: Any = None,
|
|
1121
|
+
tokenizer: Any = None,
|
|
1122
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1123
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1124
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
1125
|
+
) -> Awaitable[ChatResponse[Any]]: ...
|
|
1126
|
+
|
|
1127
|
+
@overload
|
|
1128
|
+
def get_response(
|
|
1129
|
+
self,
|
|
1130
|
+
messages: Sequence[Message],
|
|
1131
|
+
*,
|
|
1132
|
+
stream: Literal[True],
|
|
1133
|
+
options: Mapping[str, Any] | None = None,
|
|
1134
|
+
compaction_strategy: Any = None,
|
|
1135
|
+
tokenizer: Any = None,
|
|
1136
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1137
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1138
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
1139
|
+
) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
|
|
1140
|
+
|
|
1141
|
+
@overload
|
|
1142
|
+
def get_response(
|
|
1143
|
+
self,
|
|
1144
|
+
messages: Sequence[Message],
|
|
1145
|
+
*,
|
|
1146
|
+
stream: bool,
|
|
1147
|
+
options: Mapping[str, Any] | None = None,
|
|
1148
|
+
compaction_strategy: Any = None,
|
|
1149
|
+
tokenizer: Any = None,
|
|
1150
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1151
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1152
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
1153
|
+
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
|
|
1154
|
+
|
|
1155
|
+
def get_response( # ty: ignore[invalid-method-override] # Overloads refine the cooperative mixin protocol.
|
|
1156
|
+
self,
|
|
1157
|
+
messages: Sequence[Message],
|
|
1158
|
+
*,
|
|
1159
|
+
stream: bool = False,
|
|
1160
|
+
options: Mapping[str, Any] | None = None,
|
|
1161
|
+
compaction_strategy: Any = None,
|
|
1162
|
+
tokenizer: Any = None,
|
|
1163
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1164
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1165
|
+
request_message_observer: Callable[[Sequence[Message]], None] | None = None,
|
|
1166
|
+
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
|
|
1167
|
+
"""Trace chat responses with OpenTelemetry spans and metrics."""
|
|
1168
|
+
super_get_response = super().get_response # type: ignore[misc]
|
|
1169
|
+
merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
|
|
1170
|
+
prepared_observer_kwargs = (
|
|
1171
|
+
{"request_message_observer": request_message_observer}
|
|
1172
|
+
if request_message_observer is not None and isinstance(self, _PreparedRequestObserverClient)
|
|
1173
|
+
else {}
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
if not TELEMETRY_GATE.enabled:
|
|
1177
|
+
return super_get_response( # type: ignore[no-any-return]
|
|
1178
|
+
messages=messages,
|
|
1179
|
+
stream=stream,
|
|
1180
|
+
options=options,
|
|
1181
|
+
compaction_strategy=compaction_strategy,
|
|
1182
|
+
tokenizer=tokenizer,
|
|
1183
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1184
|
+
client_kwargs=merged_client_kwargs,
|
|
1185
|
+
**prepared_observer_kwargs,
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
opts: Mapping[str, Any] = options or {}
|
|
1189
|
+
provider_name = str(self.otel_provider_name)
|
|
1190
|
+
model = merged_client_kwargs.get("model") or opts.get("model") or getattr(self, "model", None) or "unknown"
|
|
1191
|
+
service_url_func = getattr(self, "service_url", None)
|
|
1192
|
+
service_url = str(service_url_func() if callable(service_url_func) else "unknown")
|
|
1193
|
+
attributes = _get_span_attributes(
|
|
1194
|
+
operation_name=OtelAttr.CHAT_COMPLETION_OPERATION,
|
|
1195
|
+
provider_name=provider_name,
|
|
1196
|
+
model=model,
|
|
1197
|
+
service_url=service_url,
|
|
1198
|
+
**merged_client_kwargs,
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
if stream:
|
|
1202
|
+
agent_span = trace.get_current_span()
|
|
1203
|
+
span = _start_streaming_span(attributes, OtelAttr.REQUEST_MODEL)
|
|
1204
|
+
|
|
1205
|
+
if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
|
|
1206
|
+
system_instructions = _get_instructions_from_options(opts)
|
|
1207
|
+
_capture_current_agent_system_instructions(agent_span, span, system_instructions)
|
|
1208
|
+
_capture_messages(
|
|
1209
|
+
span=span,
|
|
1210
|
+
provider_name=provider_name,
|
|
1211
|
+
messages=messages,
|
|
1212
|
+
system_instructions=system_instructions,
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
stream_span = _StreamingSpan(span, attributes=attributes, duration_histogram=self.duration_histogram)
|
|
1216
|
+
|
|
1217
|
+
try:
|
|
1218
|
+
with _activate_span(span):
|
|
1219
|
+
result_stream = cast(
|
|
1220
|
+
"ResponseStream[ChatResponseUpdate, ChatResponse[Any]]",
|
|
1221
|
+
super_get_response(
|
|
1222
|
+
messages=messages,
|
|
1223
|
+
stream=True,
|
|
1224
|
+
options=opts,
|
|
1225
|
+
compaction_strategy=compaction_strategy,
|
|
1226
|
+
tokenizer=tokenizer,
|
|
1227
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1228
|
+
client_kwargs=merged_client_kwargs,
|
|
1229
|
+
**prepared_observer_kwargs,
|
|
1230
|
+
),
|
|
1231
|
+
)
|
|
1232
|
+
except Exception as exception:
|
|
1233
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
1234
|
+
stream_span.close()
|
|
1235
|
+
raise
|
|
1236
|
+
|
|
1237
|
+
async def _finalize_stream() -> None:
|
|
1238
|
+
try:
|
|
1239
|
+
if not stream_span.should_finalize(result_stream):
|
|
1240
|
+
return
|
|
1241
|
+
response: ChatResponse[Any] = await stream_span.final_response(result_stream)
|
|
1242
|
+
try:
|
|
1243
|
+
duration = stream_span.duration
|
|
1244
|
+
response_attributes = _get_response_attributes(attributes, response)
|
|
1245
|
+
self._backfill_request_model(span, response_attributes)
|
|
1246
|
+
_capture_response(
|
|
1247
|
+
span=span,
|
|
1248
|
+
attributes=response_attributes,
|
|
1249
|
+
token_usage_histogram=self.token_usage_histogram,
|
|
1250
|
+
operation_duration_histogram=self.duration_histogram,
|
|
1251
|
+
duration=duration,
|
|
1252
|
+
)
|
|
1253
|
+
_mark_inner_response_telemetry_captured(response)
|
|
1254
|
+
if (
|
|
1255
|
+
TELEMETRY_GATE.sensitive_enabled
|
|
1256
|
+
and isinstance(response, ChatResponse)
|
|
1257
|
+
and response.messages
|
|
1258
|
+
and span.is_recording()
|
|
1259
|
+
):
|
|
1260
|
+
_capture_messages(
|
|
1261
|
+
span=span,
|
|
1262
|
+
provider_name=provider_name,
|
|
1263
|
+
messages=response.messages,
|
|
1264
|
+
finish_reason=response.finish_reason, # type: ignore[arg-type]
|
|
1265
|
+
output=True,
|
|
1266
|
+
)
|
|
1267
|
+
except Exception as exception:
|
|
1268
|
+
# Telemetry's own failure never fails the call.
|
|
1269
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
1270
|
+
finally:
|
|
1271
|
+
stream_span.close()
|
|
1272
|
+
|
|
1273
|
+
return stream_span.wrap(result_stream, _finalize_stream)
|
|
1274
|
+
|
|
1275
|
+
async def _get_response() -> ChatResponse:
|
|
1276
|
+
agent_span = trace.get_current_span()
|
|
1277
|
+
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.REQUEST_MODEL) as span:
|
|
1278
|
+
if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
|
|
1279
|
+
system_instructions = _get_instructions_from_options(opts)
|
|
1280
|
+
_capture_current_agent_system_instructions(agent_span, span, system_instructions)
|
|
1281
|
+
_capture_messages(
|
|
1282
|
+
span=span,
|
|
1283
|
+
provider_name=provider_name,
|
|
1284
|
+
messages=messages,
|
|
1285
|
+
system_instructions=system_instructions,
|
|
1286
|
+
)
|
|
1287
|
+
start_time_stamp = perf_counter()
|
|
1288
|
+
try:
|
|
1289
|
+
response = cast(
|
|
1290
|
+
"ChatResponse[Any]",
|
|
1291
|
+
await super_get_response(
|
|
1292
|
+
messages=messages,
|
|
1293
|
+
stream=False,
|
|
1294
|
+
options=opts,
|
|
1295
|
+
compaction_strategy=compaction_strategy,
|
|
1296
|
+
tokenizer=tokenizer,
|
|
1297
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
1298
|
+
client_kwargs=merged_client_kwargs,
|
|
1299
|
+
**prepared_observer_kwargs,
|
|
1300
|
+
),
|
|
1301
|
+
)
|
|
1302
|
+
except Exception as exception:
|
|
1303
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
1304
|
+
_record_failed_duration(
|
|
1305
|
+
self.duration_histogram, attributes, exception, perf_counter() - start_time_stamp
|
|
1306
|
+
)
|
|
1307
|
+
raise
|
|
1308
|
+
duration = perf_counter() - start_time_stamp
|
|
1309
|
+
response_attributes = _get_response_attributes(attributes, response)
|
|
1310
|
+
self._backfill_request_model(span, response_attributes)
|
|
1311
|
+
_capture_response(
|
|
1312
|
+
span=span,
|
|
1313
|
+
attributes=response_attributes,
|
|
1314
|
+
token_usage_histogram=self.token_usage_histogram,
|
|
1315
|
+
operation_duration_histogram=self.duration_histogram,
|
|
1316
|
+
duration=duration,
|
|
1317
|
+
)
|
|
1318
|
+
_mark_inner_response_telemetry_captured(response)
|
|
1319
|
+
if TELEMETRY_GATE.sensitive_enabled and response.messages and span.is_recording():
|
|
1320
|
+
_capture_messages(
|
|
1321
|
+
span=span,
|
|
1322
|
+
provider_name=provider_name,
|
|
1323
|
+
messages=response.messages,
|
|
1324
|
+
finish_reason=response.finish_reason,
|
|
1325
|
+
output=True,
|
|
1326
|
+
)
|
|
1327
|
+
return response # type: ignore[return-value,no-any-return]
|
|
1328
|
+
|
|
1329
|
+
return _get_response()
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
class AgentTelemetryLayer(_AgentTelemetryBase):
|
|
1333
|
+
"""Trace agent runs under ``TELEMETRY_GATE``.
|
|
1334
|
+
|
|
1335
|
+
Non-streaming dedup ContextVars live inside the returned coroutine so it
|
|
1336
|
+
can be driven by any task. Streaming finalization closes the span before
|
|
1337
|
+
resetting those variables and tolerates cross-context reset failures.
|
|
1338
|
+
"""
|
|
1339
|
+
|
|
1340
|
+
def __init__(
|
|
1341
|
+
self,
|
|
1342
|
+
*args: Any,
|
|
1343
|
+
otel_agent_provider_name: str | None = None,
|
|
1344
|
+
otel_provider_name: str | None = None,
|
|
1345
|
+
**kwargs: Any,
|
|
1346
|
+
) -> None:
|
|
1347
|
+
"""Read the provider name before initializing the core, then construct histograms."""
|
|
1348
|
+
self.otel_provider_name = (
|
|
1349
|
+
otel_agent_provider_name or otel_provider_name or getattr(self, "AGENT_PROVIDER_NAME", "unknown")
|
|
1350
|
+
)
|
|
1351
|
+
super().__init__(*args, **kwargs)
|
|
1352
|
+
self.token_usage_histogram = _get_token_usage_histogram()
|
|
1353
|
+
self.duration_histogram = _get_duration_histogram()
|
|
1354
|
+
|
|
1355
|
+
def _trace_agent_invocation(
|
|
1356
|
+
self,
|
|
1357
|
+
*,
|
|
1358
|
+
messages: AgentRunInputs | None,
|
|
1359
|
+
session: AgentSession | None,
|
|
1360
|
+
merged_options: Mapping[str, Any],
|
|
1361
|
+
client_kwargs: Mapping[str, Any] | None,
|
|
1362
|
+
stream: bool,
|
|
1363
|
+
execute: Callable[[], Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]],
|
|
1364
|
+
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
|
|
1365
|
+
"""Trace an agent invocation while delegating execution to ``execute``."""
|
|
1366
|
+
if not TELEMETRY_GATE.enabled:
|
|
1367
|
+
return execute()
|
|
1368
|
+
|
|
1369
|
+
provider_name = str(self.otel_provider_name)
|
|
1370
|
+
merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
|
|
1371
|
+
attributes = _get_span_attributes(
|
|
1372
|
+
operation_name=OtelAttr.AGENT_INVOKE_OPERATION,
|
|
1373
|
+
provider_name=provider_name,
|
|
1374
|
+
agent_id=getattr(self, "id", "unknown"),
|
|
1375
|
+
agent_name=getattr(self, "name", None) or getattr(self, "id", "unknown"),
|
|
1376
|
+
agent_description=getattr(self, "description", None),
|
|
1377
|
+
thread_id=session.service_session_id if session else None,
|
|
1378
|
+
all_options=dict(merged_options),
|
|
1379
|
+
**merged_client_kwargs,
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
if stream:
|
|
1383
|
+
# Set dedup ContextVars at stream creation; the inner chat layer
|
|
1384
|
+
# reads them during consumer pulls. Normal callers create and
|
|
1385
|
+
# consume in the same task; finalization also tolerates resets
|
|
1386
|
+
# from another context after closing the span.
|
|
1387
|
+
inner_response_telemetry_captured_fields: set[str] = set()
|
|
1388
|
+
captured_fields_token = INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.set(
|
|
1389
|
+
inner_response_telemetry_captured_fields
|
|
1390
|
+
)
|
|
1391
|
+
accumulated_usage_token = INNER_ACCUMULATED_USAGE.set({})
|
|
1392
|
+
|
|
1393
|
+
span = _start_streaming_span(attributes, OtelAttr.AGENT_NAME)
|
|
1394
|
+
|
|
1395
|
+
if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
|
|
1396
|
+
_capture_messages(
|
|
1397
|
+
span=span,
|
|
1398
|
+
provider_name=provider_name,
|
|
1399
|
+
messages=messages,
|
|
1400
|
+
system_instructions=_get_instructions_from_options(dict(merged_options)),
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
stream_span = _StreamingSpan(span, attributes=attributes)
|
|
1404
|
+
|
|
1405
|
+
def _reset_inner_vars() -> None:
|
|
1406
|
+
# The finalizer may run in a different context than the sync
|
|
1407
|
+
# set above (only when a caller hands the un-consumed stream
|
|
1408
|
+
# across tasks); a cross-context reset must not break span
|
|
1409
|
+
# finalization, so it degrades to a debug log. The vars are
|
|
1410
|
+
# task-scoped copies — a leaked set in a dead context is inert.
|
|
1411
|
+
try:
|
|
1412
|
+
INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.reset(captured_fields_token)
|
|
1413
|
+
INNER_ACCUMULATED_USAGE.reset(accumulated_usage_token)
|
|
1414
|
+
except ValueError:
|
|
1415
|
+
logger.debug("Skipped cross-context reset of inner telemetry ContextVars")
|
|
1416
|
+
|
|
1417
|
+
try:
|
|
1418
|
+
with _activate_span(span):
|
|
1419
|
+
run_result: object = execute()
|
|
1420
|
+
if _is_agent_response_stream(run_result):
|
|
1421
|
+
result_stream = run_result
|
|
1422
|
+
elif isinstance(run_result, Awaitable):
|
|
1423
|
+
result_stream = ResponseStream.from_awaitable(
|
|
1424
|
+
cast(
|
|
1425
|
+
"Awaitable[ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]",
|
|
1426
|
+
run_result,
|
|
1427
|
+
)
|
|
1428
|
+
)
|
|
1429
|
+
else:
|
|
1430
|
+
raise RuntimeError("Streaming telemetry requires a ResponseStream result.")
|
|
1431
|
+
except Exception as exception:
|
|
1432
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
1433
|
+
_reset_inner_vars()
|
|
1434
|
+
stream_span.close()
|
|
1435
|
+
raise
|
|
1436
|
+
|
|
1437
|
+
async def _finalize_stream() -> None:
|
|
1438
|
+
try:
|
|
1439
|
+
if not stream_span.should_finalize(result_stream):
|
|
1440
|
+
return
|
|
1441
|
+
response: AgentResponse[Any] = await stream_span.final_response(result_stream)
|
|
1442
|
+
try:
|
|
1443
|
+
duration = stream_span.duration
|
|
1444
|
+
response_attributes = _get_response_attributes(
|
|
1445
|
+
attributes,
|
|
1446
|
+
response,
|
|
1447
|
+
capture_response_id=INNER_RESPONSE_ID_CAPTURED_FIELD
|
|
1448
|
+
not in inner_response_telemetry_captured_fields,
|
|
1449
|
+
capture_usage=INNER_USAGE_CAPTURED_FIELD not in inner_response_telemetry_captured_fields,
|
|
1450
|
+
)
|
|
1451
|
+
_apply_accumulated_usage(response_attributes, inner_response_telemetry_captured_fields)
|
|
1452
|
+
_capture_response(span=span, attributes=response_attributes, duration=duration)
|
|
1453
|
+
if (
|
|
1454
|
+
TELEMETRY_GATE.sensitive_enabled
|
|
1455
|
+
and isinstance(response, AgentResponse)
|
|
1456
|
+
and response.messages
|
|
1457
|
+
and span.is_recording()
|
|
1458
|
+
):
|
|
1459
|
+
_capture_messages(
|
|
1460
|
+
span=span,
|
|
1461
|
+
provider_name=provider_name,
|
|
1462
|
+
messages=response.messages,
|
|
1463
|
+
output=True,
|
|
1464
|
+
)
|
|
1465
|
+
except Exception as exception:
|
|
1466
|
+
# Telemetry's own failure never fails the run.
|
|
1467
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
1468
|
+
finally:
|
|
1469
|
+
# Close first: a cross-context reset must never leave the span open.
|
|
1470
|
+
stream_span.close()
|
|
1471
|
+
_reset_inner_vars()
|
|
1472
|
+
|
|
1473
|
+
return stream_span.wrap(result_stream, _finalize_stream)
|
|
1474
|
+
|
|
1475
|
+
async def _run() -> AgentResponse[Any]:
|
|
1476
|
+
# Set AND reset inside this coroutine so both run in the awaiting
|
|
1477
|
+
# task's context, regardless of which task created the coroutine —
|
|
1478
|
+
# the inner chat layer executes within ``await execute()`` below
|
|
1479
|
+
# and therefore sees the vars regardless.
|
|
1480
|
+
inner_response_telemetry_captured_fields: set[str] = set()
|
|
1481
|
+
captured_fields_token = INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.set(
|
|
1482
|
+
inner_response_telemetry_captured_fields
|
|
1483
|
+
)
|
|
1484
|
+
accumulated_usage_token = INNER_ACCUMULATED_USAGE.set({})
|
|
1485
|
+
try:
|
|
1486
|
+
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span:
|
|
1487
|
+
if TELEMETRY_GATE.sensitive_enabled and messages and span.is_recording():
|
|
1488
|
+
_capture_messages(
|
|
1489
|
+
span=span,
|
|
1490
|
+
provider_name=provider_name,
|
|
1491
|
+
messages=messages,
|
|
1492
|
+
system_instructions=_get_instructions_from_options(dict(merged_options)),
|
|
1493
|
+
)
|
|
1494
|
+
start_time_stamp = perf_counter()
|
|
1495
|
+
try:
|
|
1496
|
+
response = await cast("Awaitable[AgentResponse[Any]]", execute())
|
|
1497
|
+
except Exception as exception:
|
|
1498
|
+
capture_exception(span=span, exception=exception, timestamp=time_ns())
|
|
1499
|
+
raise
|
|
1500
|
+
duration = perf_counter() - start_time_stamp
|
|
1501
|
+
if response:
|
|
1502
|
+
response_attributes = _get_response_attributes(
|
|
1503
|
+
attributes,
|
|
1504
|
+
response,
|
|
1505
|
+
capture_response_id=INNER_RESPONSE_ID_CAPTURED_FIELD
|
|
1506
|
+
not in inner_response_telemetry_captured_fields,
|
|
1507
|
+
capture_usage=INNER_USAGE_CAPTURED_FIELD not in inner_response_telemetry_captured_fields,
|
|
1508
|
+
)
|
|
1509
|
+
_apply_accumulated_usage(response_attributes, inner_response_telemetry_captured_fields)
|
|
1510
|
+
_capture_response(span=span, attributes=response_attributes, duration=duration)
|
|
1511
|
+
if TELEMETRY_GATE.sensitive_enabled and response.messages and span.is_recording():
|
|
1512
|
+
_capture_messages(
|
|
1513
|
+
span=span,
|
|
1514
|
+
provider_name=provider_name,
|
|
1515
|
+
messages=response.messages,
|
|
1516
|
+
output=True,
|
|
1517
|
+
)
|
|
1518
|
+
return response
|
|
1519
|
+
finally:
|
|
1520
|
+
INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS.reset(captured_fields_token)
|
|
1521
|
+
INNER_ACCUMULATED_USAGE.reset(accumulated_usage_token)
|
|
1522
|
+
|
|
1523
|
+
return _run()
|
|
1524
|
+
|
|
1525
|
+
@overload
|
|
1526
|
+
def run(
|
|
1527
|
+
self,
|
|
1528
|
+
messages: AgentRunInputs | None = None,
|
|
1529
|
+
*,
|
|
1530
|
+
stream: Literal[False] = False,
|
|
1531
|
+
session: AgentSession | None = None,
|
|
1532
|
+
middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
|
|
1533
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1534
|
+
options: Mapping[str, Any] | None = None,
|
|
1535
|
+
compaction_strategy: Any = None,
|
|
1536
|
+
tokenizer: Any = None,
|
|
1537
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1538
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1539
|
+
) -> Awaitable[AgentResponse[Any]]: ...
|
|
1540
|
+
|
|
1541
|
+
@overload
|
|
1542
|
+
def run(
|
|
1543
|
+
self,
|
|
1544
|
+
messages: AgentRunInputs | None = None,
|
|
1545
|
+
*,
|
|
1546
|
+
stream: Literal[True],
|
|
1547
|
+
session: AgentSession | None = None,
|
|
1548
|
+
middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
|
|
1549
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1550
|
+
options: Mapping[str, Any] | None = None,
|
|
1551
|
+
compaction_strategy: Any = None,
|
|
1552
|
+
tokenizer: Any = None,
|
|
1553
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1554
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1555
|
+
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
|
|
1556
|
+
|
|
1557
|
+
@overload
|
|
1558
|
+
def run(
|
|
1559
|
+
self,
|
|
1560
|
+
messages: AgentRunInputs | None = None,
|
|
1561
|
+
*,
|
|
1562
|
+
stream: bool,
|
|
1563
|
+
session: AgentSession | None = None,
|
|
1564
|
+
middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
|
|
1565
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1566
|
+
options: Mapping[str, Any] | None = None,
|
|
1567
|
+
compaction_strategy: Any = None,
|
|
1568
|
+
tokenizer: Any = None,
|
|
1569
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1570
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1571
|
+
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
|
|
1572
|
+
|
|
1573
|
+
def run( # ty: ignore[invalid-method-override] # Overloads refine the cooperative mixin protocol.
|
|
1574
|
+
self,
|
|
1575
|
+
messages: AgentRunInputs | None = None,
|
|
1576
|
+
*,
|
|
1577
|
+
stream: bool = False,
|
|
1578
|
+
session: AgentSession | None = None,
|
|
1579
|
+
middleware: ChatMiddleware | FunctionMiddleware | Sequence[ChatMiddleware | FunctionMiddleware] | None = None,
|
|
1580
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
|
1581
|
+
options: Mapping[str, Any] | None = None,
|
|
1582
|
+
compaction_strategy: Any = None,
|
|
1583
|
+
tokenizer: Any = None,
|
|
1584
|
+
function_invocation_kwargs: Mapping[str, Any] | None = None,
|
|
1585
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
1586
|
+
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
|
|
1587
|
+
"""Trace agent runs with OpenTelemetry spans and metrics."""
|
|
1588
|
+
super_run = cast(
|
|
1589
|
+
"Callable[..., Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]]",
|
|
1590
|
+
super().run, # type: ignore[misc]
|
|
1591
|
+
)
|
|
1592
|
+
super_run_kwargs: dict[str, Any] = {
|
|
1593
|
+
"messages": messages,
|
|
1594
|
+
"stream": stream,
|
|
1595
|
+
"session": session,
|
|
1596
|
+
"tools": tools,
|
|
1597
|
+
"options": options,
|
|
1598
|
+
"compaction_strategy": compaction_strategy,
|
|
1599
|
+
"tokenizer": tokenizer,
|
|
1600
|
+
"function_invocation_kwargs": function_invocation_kwargs,
|
|
1601
|
+
"client_kwargs": client_kwargs,
|
|
1602
|
+
}
|
|
1603
|
+
if middleware is not None:
|
|
1604
|
+
super_run_kwargs["middleware"] = middleware
|
|
1605
|
+
|
|
1606
|
+
default_options = dict(getattr(self, "default_options", {}))
|
|
1607
|
+
merged_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
|
|
1608
|
+
merged_options: dict[str, Any] = merge_chat_options(
|
|
1609
|
+
default_options, dict(options) if options is not None else {}
|
|
1610
|
+
)
|
|
1611
|
+
return self._trace_agent_invocation(
|
|
1612
|
+
messages=messages,
|
|
1613
|
+
session=session,
|
|
1614
|
+
merged_options=merged_options,
|
|
1615
|
+
client_kwargs=merged_client_kwargs,
|
|
1616
|
+
stream=stream,
|
|
1617
|
+
execute=lambda: super_run(**super_run_kwargs),
|
|
1618
|
+
)
|