code-puppy 0.0.345__tar.gz → 0.0.347__tar.gz
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.
- {code_puppy-0.0.345 → code_puppy-0.0.347}/PKG-INFO +23 -1
- {code_puppy-0.0.345 → code_puppy-0.0.347}/README.md +21 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/base_agent.py +139 -278
- code_puppy-0.0.347/code_puppy/agents/event_stream_handler.py +257 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/cli_runner.py +39 -3
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/config_commands.py +10 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/config.py +23 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/summarization_agent.py +11 -1
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/agent_tools.py +55 -11
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/vqa_agent.py +7 -1
- {code_puppy-0.0.345 → code_puppy-0.0.347}/pyproject.toml +2 -1
- {code_puppy-0.0.345 → code_puppy-0.0.347}/.gitignore +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/LICENSE +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/main.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/models.json +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/uvx_detection.py +0 -0
- {code_puppy-0.0.345 → code_puppy-0.0.347}/code_puppy/version_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.347
|
|
4
4
|
Summary: Code generation agent
|
|
5
5
|
Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
|
|
6
6
|
Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Code Generators
|
|
17
17
|
Requires-Python: <3.14,>=3.11
|
|
18
18
|
Requires-Dist: camoufox>=0.4.11
|
|
19
|
+
Requires-Dist: dbos>=2.5.0
|
|
19
20
|
Requires-Dist: fastapi>=0.111.0
|
|
20
21
|
Requires-Dist: httpx[http2]>=0.24.1
|
|
21
22
|
Requires-Dist: json-repair>=0.46.2
|
|
@@ -173,6 +174,27 @@ These providers are automatically configured with correct OpenAI-compatible endp
|
|
|
173
174
|
- **⚠️ Unsupported Providers** - Providers like Amazon Bedrock and Google Vertex that require special authentication are clearly marked
|
|
174
175
|
- **⚠️ No Tool Calling** - Models without tool calling support show a big warning since they can't use Code Puppy's file/shell tools
|
|
175
176
|
|
|
177
|
+
### Durable Execution
|
|
178
|
+
|
|
179
|
+
Code Puppy now supports **[DBOS](https://github.com/dbos-inc/dbos-transact-py)** durable execution.
|
|
180
|
+
|
|
181
|
+
When enabled, every agent is automatically wrapped as a `DBOSAgent`, checkpointing key interactions (including agent inputs, LLM responses, MCP calls, and tool calls) in a database for durability and recovery.
|
|
182
|
+
|
|
183
|
+
You can toggle DBOS via either of these options:
|
|
184
|
+
|
|
185
|
+
- CLI config (persists): `/set enable_dbos true` (or `false` to disable)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
Config takes precedence if set; otherwise the environment variable is used.
|
|
189
|
+
|
|
190
|
+
### Configuration
|
|
191
|
+
|
|
192
|
+
The following environment variables control DBOS behavior:
|
|
193
|
+
- `DBOS_CONDUCTOR_KEY`: If set, Code Puppy connects to the [DBOS Management Console](https://console.dbos.dev/). Make sure you first register an app named `dbos-code-puppy` on the console to generate a Conductor key. Default: `None`.
|
|
194
|
+
- `DBOS_LOG_LEVEL`: Logging verbosity: `CRITICAL`, `ERROR`, `WARNING`, `INFO`, or `DEBUG`. Default: `ERROR`.
|
|
195
|
+
- `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
|
|
196
|
+
- `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
|
|
197
|
+
|
|
176
198
|
### Custom Commands
|
|
177
199
|
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
178
200
|
|
|
@@ -133,6 +133,27 @@ These providers are automatically configured with correct OpenAI-compatible endp
|
|
|
133
133
|
- **⚠️ Unsupported Providers** - Providers like Amazon Bedrock and Google Vertex that require special authentication are clearly marked
|
|
134
134
|
- **⚠️ No Tool Calling** - Models without tool calling support show a big warning since they can't use Code Puppy's file/shell tools
|
|
135
135
|
|
|
136
|
+
### Durable Execution
|
|
137
|
+
|
|
138
|
+
Code Puppy now supports **[DBOS](https://github.com/dbos-inc/dbos-transact-py)** durable execution.
|
|
139
|
+
|
|
140
|
+
When enabled, every agent is automatically wrapped as a `DBOSAgent`, checkpointing key interactions (including agent inputs, LLM responses, MCP calls, and tool calls) in a database for durability and recovery.
|
|
141
|
+
|
|
142
|
+
You can toggle DBOS via either of these options:
|
|
143
|
+
|
|
144
|
+
- CLI config (persists): `/set enable_dbos true` (or `false` to disable)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
Config takes precedence if set; otherwise the environment variable is used.
|
|
148
|
+
|
|
149
|
+
### Configuration
|
|
150
|
+
|
|
151
|
+
The following environment variables control DBOS behavior:
|
|
152
|
+
- `DBOS_CONDUCTOR_KEY`: If set, Code Puppy connects to the [DBOS Management Console](https://console.dbos.dev/). Make sure you first register an app named `dbos-code-puppy` on the console to generate a Conductor key. Default: `None`.
|
|
153
|
+
- `DBOS_LOG_LEVEL`: Logging verbosity: `CRITICAL`, `ERROR`, `WARNING`, `INFO`, or `DEBUG`. Default: `ERROR`.
|
|
154
|
+
- `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
|
|
155
|
+
- `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
|
|
156
|
+
|
|
136
157
|
### Custom Commands
|
|
137
158
|
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
138
159
|
|
|
@@ -7,7 +7,6 @@ import signal
|
|
|
7
7
|
import threading
|
|
8
8
|
import uuid
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
-
from collections.abc import AsyncIterable
|
|
11
10
|
from typing import (
|
|
12
11
|
Any,
|
|
13
12
|
Callable,
|
|
@@ -24,16 +23,17 @@ from typing import (
|
|
|
24
23
|
import mcp
|
|
25
24
|
import pydantic
|
|
26
25
|
import pydantic_ai.models
|
|
26
|
+
from dbos import DBOS, SetWorkflowID
|
|
27
27
|
from pydantic_ai import Agent as PydanticAgent
|
|
28
28
|
from pydantic_ai import (
|
|
29
29
|
BinaryContent,
|
|
30
30
|
DocumentUrl,
|
|
31
31
|
ImageUrl,
|
|
32
|
-
PartEndEvent,
|
|
33
32
|
RunContext,
|
|
34
33
|
UsageLimitExceeded,
|
|
35
34
|
UsageLimits,
|
|
36
35
|
)
|
|
36
|
+
from pydantic_ai.durable_exec.dbos import DBOSAgent
|
|
37
37
|
from pydantic_ai.messages import (
|
|
38
38
|
ModelMessage,
|
|
39
39
|
ModelRequest,
|
|
@@ -46,6 +46,8 @@ from pydantic_ai.messages import (
|
|
|
46
46
|
)
|
|
47
47
|
from rich.text import Text
|
|
48
48
|
|
|
49
|
+
from code_puppy.agents.event_stream_handler import event_stream_handler
|
|
50
|
+
|
|
49
51
|
# Consolidated relative imports
|
|
50
52
|
from code_puppy.config import (
|
|
51
53
|
get_agent_pinned_model,
|
|
@@ -54,6 +56,7 @@ from code_puppy.config import (
|
|
|
54
56
|
get_global_model_name,
|
|
55
57
|
get_message_limit,
|
|
56
58
|
get_protected_token_count,
|
|
59
|
+
get_use_dbos,
|
|
57
60
|
get_value,
|
|
58
61
|
)
|
|
59
62
|
from code_puppy.error_logging import log_error
|
|
@@ -97,9 +100,6 @@ class BaseAgent(ABC):
|
|
|
97
100
|
# Cache for MCP tool definitions (for token estimation)
|
|
98
101
|
# This is populated after the first successful run when MCP tools are retrieved
|
|
99
102
|
self._mcp_tool_definitions_cache: List[Dict[str, Any]] = []
|
|
100
|
-
# Shared console for streaming output - should be set by cli_runner
|
|
101
|
-
# to avoid conflicts between spinner's Live display and response streaming
|
|
102
|
-
self._console: Optional[Any] = None
|
|
103
103
|
|
|
104
104
|
@property
|
|
105
105
|
@abstractmethod
|
|
@@ -1209,25 +1209,59 @@ class BaseAgent(ABC):
|
|
|
1209
1209
|
|
|
1210
1210
|
self._last_model_name = resolved_model_name
|
|
1211
1211
|
# expose for run_with_mcp
|
|
1212
|
+
# Wrap it with DBOS, but handle MCP servers separately to avoid serialization issues
|
|
1212
1213
|
global _reload_count
|
|
1213
1214
|
_reload_count += 1
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1215
|
+
if get_use_dbos():
|
|
1216
|
+
# Don't pass MCP servers to the agent constructor when using DBOS
|
|
1217
|
+
# This prevents the "cannot pickle async_generator object" error
|
|
1218
|
+
# MCP servers will be handled separately in run_with_mcp
|
|
1219
|
+
agent_without_mcp = PydanticAgent(
|
|
1220
|
+
model=model,
|
|
1221
|
+
instructions=instructions,
|
|
1222
|
+
output_type=str,
|
|
1223
|
+
retries=3,
|
|
1224
|
+
toolsets=[], # Don't include MCP servers here
|
|
1225
|
+
history_processors=[self.message_history_accumulator],
|
|
1226
|
+
model_settings=model_settings,
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
# Register regular tools (non-MCP) on the new agent
|
|
1230
|
+
agent_tools = self.get_available_tools()
|
|
1231
|
+
register_tools_for_agent(agent_without_mcp, agent_tools)
|
|
1232
|
+
|
|
1233
|
+
# Wrap with DBOS - pass event_stream_handler at construction time
|
|
1234
|
+
# so DBOSModel gets the handler for streaming output
|
|
1235
|
+
dbos_agent = DBOSAgent(
|
|
1236
|
+
agent_without_mcp,
|
|
1237
|
+
name=f"{self.name}-{_reload_count}",
|
|
1238
|
+
event_stream_handler=event_stream_handler,
|
|
1239
|
+
)
|
|
1240
|
+
self.pydantic_agent = dbos_agent
|
|
1241
|
+
self._code_generation_agent = dbos_agent
|
|
1227
1242
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1243
|
+
# Store filtered MCP servers separately for runtime use
|
|
1244
|
+
self._mcp_servers = filtered_mcp_servers
|
|
1245
|
+
else:
|
|
1246
|
+
# Normal path without DBOS - include filtered MCP servers in the agent
|
|
1247
|
+
# Re-create agent with filtered MCP servers
|
|
1248
|
+
p_agent = PydanticAgent(
|
|
1249
|
+
model=model,
|
|
1250
|
+
instructions=instructions,
|
|
1251
|
+
output_type=str,
|
|
1252
|
+
retries=3,
|
|
1253
|
+
toolsets=filtered_mcp_servers,
|
|
1254
|
+
history_processors=[self.message_history_accumulator],
|
|
1255
|
+
model_settings=model_settings,
|
|
1256
|
+
)
|
|
1257
|
+
# Register regular tools on the agent
|
|
1258
|
+
agent_tools = self.get_available_tools()
|
|
1259
|
+
register_tools_for_agent(p_agent, agent_tools)
|
|
1260
|
+
|
|
1261
|
+
self.pydantic_agent = p_agent
|
|
1262
|
+
self._code_generation_agent = p_agent
|
|
1263
|
+
self._mcp_servers = filtered_mcp_servers
|
|
1264
|
+
self._mcp_servers = mcp_servers
|
|
1231
1265
|
return self._code_generation_agent
|
|
1232
1266
|
|
|
1233
1267
|
def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgent:
|
|
@@ -1241,7 +1275,7 @@ class BaseAgent(ABC):
|
|
|
1241
1275
|
output_type: The Pydantic model or type for structured output.
|
|
1242
1276
|
|
|
1243
1277
|
Returns:
|
|
1244
|
-
A configured PydanticAgent with the custom output_type.
|
|
1278
|
+
A configured PydanticAgent (or DBOSAgent wrapper) with the custom output_type.
|
|
1245
1279
|
"""
|
|
1246
1280
|
from code_puppy.model_utils import prepare_prompt_for_model
|
|
1247
1281
|
from code_puppy.tools import register_tools_for_agent
|
|
@@ -1268,19 +1302,41 @@ class BaseAgent(ABC):
|
|
|
1268
1302
|
global _reload_count
|
|
1269
1303
|
_reload_count += 1
|
|
1270
1304
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1305
|
+
if get_use_dbos():
|
|
1306
|
+
temp_agent = PydanticAgent(
|
|
1307
|
+
model=model,
|
|
1308
|
+
instructions=instructions,
|
|
1309
|
+
output_type=output_type,
|
|
1310
|
+
retries=3,
|
|
1311
|
+
toolsets=[],
|
|
1312
|
+
history_processors=[self.message_history_accumulator],
|
|
1313
|
+
model_settings=model_settings,
|
|
1314
|
+
)
|
|
1315
|
+
agent_tools = self.get_available_tools()
|
|
1316
|
+
register_tools_for_agent(temp_agent, agent_tools)
|
|
1317
|
+
# Pass event_stream_handler at construction time for streaming output
|
|
1318
|
+
dbos_agent = DBOSAgent(
|
|
1319
|
+
temp_agent,
|
|
1320
|
+
name=f"{self.name}-structured-{_reload_count}",
|
|
1321
|
+
event_stream_handler=event_stream_handler,
|
|
1322
|
+
)
|
|
1323
|
+
return dbos_agent
|
|
1324
|
+
else:
|
|
1325
|
+
temp_agent = PydanticAgent(
|
|
1326
|
+
model=model,
|
|
1327
|
+
instructions=instructions,
|
|
1328
|
+
output_type=output_type,
|
|
1329
|
+
retries=3,
|
|
1330
|
+
toolsets=mcp_servers,
|
|
1331
|
+
history_processors=[self.message_history_accumulator],
|
|
1332
|
+
model_settings=model_settings,
|
|
1333
|
+
)
|
|
1334
|
+
agent_tools = self.get_available_tools()
|
|
1335
|
+
register_tools_for_agent(temp_agent, agent_tools)
|
|
1336
|
+
return temp_agent
|
|
1283
1337
|
|
|
1338
|
+
# It's okay to decorate it with DBOS.step even if not using DBOS; the decorator is a no-op in that case.
|
|
1339
|
+
@DBOS.step()
|
|
1284
1340
|
def message_history_accumulator(self, ctx: RunContext, messages: List[Any]):
|
|
1285
1341
|
_message_history = self.get_message_history()
|
|
1286
1342
|
message_history_hashes = set([self.hash_message(m) for m in _message_history])
|
|
@@ -1304,241 +1360,6 @@ class BaseAgent(ABC):
|
|
|
1304
1360
|
self.set_message_history(result_messages_filtered_empty_thinking)
|
|
1305
1361
|
return self.get_message_history()
|
|
1306
1362
|
|
|
1307
|
-
async def _event_stream_handler(
|
|
1308
|
-
self, ctx: RunContext, events: AsyncIterable[Any]
|
|
1309
|
-
) -> None:
|
|
1310
|
-
"""Handle streaming events from the agent run.
|
|
1311
|
-
|
|
1312
|
-
This method processes streaming events and emits TextPart, ThinkingPart,
|
|
1313
|
-
and ToolCallPart content with styled banners/tokens as they stream in.
|
|
1314
|
-
|
|
1315
|
-
Args:
|
|
1316
|
-
ctx: The run context.
|
|
1317
|
-
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
1318
|
-
"""
|
|
1319
|
-
from pydantic_ai import PartDeltaEvent, PartStartEvent
|
|
1320
|
-
from pydantic_ai.messages import (
|
|
1321
|
-
TextPartDelta,
|
|
1322
|
-
ThinkingPartDelta,
|
|
1323
|
-
ToolCallPartDelta,
|
|
1324
|
-
)
|
|
1325
|
-
from rich.console import Console
|
|
1326
|
-
from rich.markup import escape
|
|
1327
|
-
|
|
1328
|
-
from code_puppy.messaging.spinner import pause_all_spinners
|
|
1329
|
-
|
|
1330
|
-
# IMPORTANT: Use the shared console (set by cli_runner) to avoid conflicts
|
|
1331
|
-
# with the spinner's Live display. Multiple Console instances with separate
|
|
1332
|
-
# Live displays cause cursor positioning chaos and line duplication.
|
|
1333
|
-
if self._console is not None:
|
|
1334
|
-
console = self._console
|
|
1335
|
-
else:
|
|
1336
|
-
# Fallback if console not set (shouldn't happen in normal use)
|
|
1337
|
-
console = Console()
|
|
1338
|
-
|
|
1339
|
-
# Track which part indices we're currently streaming (for Text/Thinking/Tool parts)
|
|
1340
|
-
streaming_parts: set[int] = set()
|
|
1341
|
-
thinking_parts: set[int] = (
|
|
1342
|
-
set()
|
|
1343
|
-
) # Track which parts are thinking (for dim style)
|
|
1344
|
-
text_parts: set[int] = set() # Track which parts are text
|
|
1345
|
-
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
1346
|
-
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1347
|
-
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
1348
|
-
did_stream_anything = False # Track if we streamed any content
|
|
1349
|
-
|
|
1350
|
-
# Termflow streaming state for text parts
|
|
1351
|
-
from termflow import Parser as TermflowParser
|
|
1352
|
-
from termflow import Renderer as TermflowRenderer
|
|
1353
|
-
|
|
1354
|
-
termflow_parsers: dict[int, TermflowParser] = {}
|
|
1355
|
-
termflow_renderers: dict[int, TermflowRenderer] = {}
|
|
1356
|
-
termflow_line_buffers: dict[int, str] = {} # Buffer incomplete lines
|
|
1357
|
-
|
|
1358
|
-
def _print_thinking_banner() -> None:
|
|
1359
|
-
"""Print the THINKING banner with spinner pause and line clear."""
|
|
1360
|
-
nonlocal did_stream_anything
|
|
1361
|
-
import time
|
|
1362
|
-
|
|
1363
|
-
from code_puppy.config import get_banner_color
|
|
1364
|
-
|
|
1365
|
-
pause_all_spinners()
|
|
1366
|
-
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1367
|
-
# Clear line and print newline before banner
|
|
1368
|
-
console.print(" " * 50, end="\r")
|
|
1369
|
-
console.print() # Newline before banner
|
|
1370
|
-
# Bold banner with configurable color and lightning bolt
|
|
1371
|
-
thinking_color = get_banner_color("thinking")
|
|
1372
|
-
console.print(
|
|
1373
|
-
Text.from_markup(
|
|
1374
|
-
f"[bold white on {thinking_color}] THINKING [/bold white on {thinking_color}] [dim]⚡ "
|
|
1375
|
-
),
|
|
1376
|
-
end="",
|
|
1377
|
-
)
|
|
1378
|
-
did_stream_anything = True
|
|
1379
|
-
|
|
1380
|
-
def _print_response_banner() -> None:
|
|
1381
|
-
"""Print the AGENT RESPONSE banner with spinner pause and line clear."""
|
|
1382
|
-
nonlocal did_stream_anything
|
|
1383
|
-
import time
|
|
1384
|
-
|
|
1385
|
-
from code_puppy.config import get_banner_color
|
|
1386
|
-
|
|
1387
|
-
pause_all_spinners()
|
|
1388
|
-
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1389
|
-
# Clear line and print newline before banner
|
|
1390
|
-
console.print(" " * 50, end="\r")
|
|
1391
|
-
console.print() # Newline before banner
|
|
1392
|
-
response_color = get_banner_color("agent_response")
|
|
1393
|
-
console.print(
|
|
1394
|
-
Text.from_markup(
|
|
1395
|
-
f"[bold white on {response_color}] AGENT RESPONSE [/bold white on {response_color}]"
|
|
1396
|
-
)
|
|
1397
|
-
)
|
|
1398
|
-
did_stream_anything = True
|
|
1399
|
-
|
|
1400
|
-
async for event in events:
|
|
1401
|
-
# PartStartEvent - register the part but defer banner until content arrives
|
|
1402
|
-
if isinstance(event, PartStartEvent):
|
|
1403
|
-
part = event.part
|
|
1404
|
-
if isinstance(part, ThinkingPart):
|
|
1405
|
-
streaming_parts.add(event.index)
|
|
1406
|
-
thinking_parts.add(event.index)
|
|
1407
|
-
# If there's initial content, print banner + content now
|
|
1408
|
-
if part.content and part.content.strip():
|
|
1409
|
-
_print_thinking_banner()
|
|
1410
|
-
escaped = escape(part.content)
|
|
1411
|
-
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1412
|
-
banner_printed.add(event.index)
|
|
1413
|
-
elif isinstance(part, TextPart):
|
|
1414
|
-
streaming_parts.add(event.index)
|
|
1415
|
-
text_parts.add(event.index)
|
|
1416
|
-
# Initialize termflow streaming for this text part
|
|
1417
|
-
termflow_parsers[event.index] = TermflowParser()
|
|
1418
|
-
termflow_renderers[event.index] = TermflowRenderer(
|
|
1419
|
-
output=console.file, width=console.width
|
|
1420
|
-
)
|
|
1421
|
-
termflow_line_buffers[event.index] = ""
|
|
1422
|
-
# Handle initial content if present
|
|
1423
|
-
if part.content and part.content.strip():
|
|
1424
|
-
_print_response_banner()
|
|
1425
|
-
banner_printed.add(event.index)
|
|
1426
|
-
termflow_line_buffers[event.index] = part.content
|
|
1427
|
-
elif isinstance(part, ToolCallPart):
|
|
1428
|
-
streaming_parts.add(event.index)
|
|
1429
|
-
tool_parts.add(event.index)
|
|
1430
|
-
token_count[event.index] = 0 # Initialize token counter
|
|
1431
|
-
# Track tool name for display
|
|
1432
|
-
banner_printed.add(
|
|
1433
|
-
event.index
|
|
1434
|
-
) # Use banner_printed to track if we've shown tool info
|
|
1435
|
-
|
|
1436
|
-
# PartDeltaEvent - stream the content as it arrives
|
|
1437
|
-
elif isinstance(event, PartDeltaEvent):
|
|
1438
|
-
if event.index in streaming_parts:
|
|
1439
|
-
delta = event.delta
|
|
1440
|
-
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1441
|
-
if delta.content_delta:
|
|
1442
|
-
# For text parts, stream markdown with termflow
|
|
1443
|
-
if event.index in text_parts:
|
|
1444
|
-
# Print banner on first content
|
|
1445
|
-
if event.index not in banner_printed:
|
|
1446
|
-
_print_response_banner()
|
|
1447
|
-
banner_printed.add(event.index)
|
|
1448
|
-
|
|
1449
|
-
# Add content to line buffer
|
|
1450
|
-
termflow_line_buffers[event.index] += (
|
|
1451
|
-
delta.content_delta
|
|
1452
|
-
)
|
|
1453
|
-
|
|
1454
|
-
# Process complete lines
|
|
1455
|
-
parser = termflow_parsers[event.index]
|
|
1456
|
-
renderer = termflow_renderers[event.index]
|
|
1457
|
-
buffer = termflow_line_buffers[event.index]
|
|
1458
|
-
|
|
1459
|
-
while "\n" in buffer:
|
|
1460
|
-
line, buffer = buffer.split("\n", 1)
|
|
1461
|
-
events_to_render = parser.parse_line(line)
|
|
1462
|
-
renderer.render_all(events_to_render)
|
|
1463
|
-
|
|
1464
|
-
termflow_line_buffers[event.index] = buffer
|
|
1465
|
-
else:
|
|
1466
|
-
# For thinking parts, stream immediately (dim)
|
|
1467
|
-
if event.index not in banner_printed:
|
|
1468
|
-
_print_thinking_banner()
|
|
1469
|
-
banner_printed.add(event.index)
|
|
1470
|
-
escaped = escape(delta.content_delta)
|
|
1471
|
-
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1472
|
-
elif isinstance(delta, ToolCallPartDelta):
|
|
1473
|
-
# For tool calls, count chunks received
|
|
1474
|
-
token_count[event.index] += 1
|
|
1475
|
-
# Get tool name if available
|
|
1476
|
-
tool_name = getattr(delta, "tool_name_delta", "")
|
|
1477
|
-
count = token_count[event.index]
|
|
1478
|
-
# Display with tool wrench icon and tool name
|
|
1479
|
-
if tool_name:
|
|
1480
|
-
console.print(
|
|
1481
|
-
f" 🔧 Calling {tool_name}... {count} chunks ",
|
|
1482
|
-
end="\r",
|
|
1483
|
-
)
|
|
1484
|
-
else:
|
|
1485
|
-
console.print(
|
|
1486
|
-
f" 🔧 Calling tool... {count} chunks ",
|
|
1487
|
-
end="\r",
|
|
1488
|
-
)
|
|
1489
|
-
|
|
1490
|
-
# PartEndEvent - finish the streaming with a newline
|
|
1491
|
-
elif isinstance(event, PartEndEvent):
|
|
1492
|
-
if event.index in streaming_parts:
|
|
1493
|
-
# For text parts, finalize termflow rendering
|
|
1494
|
-
if event.index in text_parts:
|
|
1495
|
-
# Render any remaining buffered content
|
|
1496
|
-
if event.index in termflow_parsers:
|
|
1497
|
-
parser = termflow_parsers[event.index]
|
|
1498
|
-
renderer = termflow_renderers[event.index]
|
|
1499
|
-
remaining = termflow_line_buffers.get(event.index, "")
|
|
1500
|
-
|
|
1501
|
-
# Parse and render any remaining partial line
|
|
1502
|
-
if remaining.strip():
|
|
1503
|
-
events_to_render = parser.parse_line(remaining)
|
|
1504
|
-
renderer.render_all(events_to_render)
|
|
1505
|
-
|
|
1506
|
-
# Finalize the parser to close any open blocks
|
|
1507
|
-
final_events = parser.finalize()
|
|
1508
|
-
renderer.render_all(final_events)
|
|
1509
|
-
|
|
1510
|
-
# Clean up termflow state
|
|
1511
|
-
del termflow_parsers[event.index]
|
|
1512
|
-
del termflow_renderers[event.index]
|
|
1513
|
-
del termflow_line_buffers[event.index]
|
|
1514
|
-
# For tool parts, clear the chunk counter line
|
|
1515
|
-
elif event.index in tool_parts:
|
|
1516
|
-
# Clear the chunk counter line by printing spaces and returning
|
|
1517
|
-
console.print(" " * 50, end="\r")
|
|
1518
|
-
# For thinking parts, just print newline
|
|
1519
|
-
elif event.index in banner_printed:
|
|
1520
|
-
console.print() # Final newline after streaming
|
|
1521
|
-
|
|
1522
|
-
# Clean up token count
|
|
1523
|
-
token_count.pop(event.index, None)
|
|
1524
|
-
# Clean up all tracking sets
|
|
1525
|
-
streaming_parts.discard(event.index)
|
|
1526
|
-
thinking_parts.discard(event.index)
|
|
1527
|
-
text_parts.discard(event.index)
|
|
1528
|
-
tool_parts.discard(event.index)
|
|
1529
|
-
banner_printed.discard(event.index)
|
|
1530
|
-
|
|
1531
|
-
# Resume spinner if next part is NOT text/thinking/tool (avoid race condition)
|
|
1532
|
-
# If next part is None or handled differently, it's safe to resume
|
|
1533
|
-
# Note: spinner itself handles blank line before appearing
|
|
1534
|
-
from code_puppy.messaging.spinner import resume_all_spinners
|
|
1535
|
-
|
|
1536
|
-
next_kind = getattr(event, "next_part_kind", None)
|
|
1537
|
-
if next_kind not in ("text", "thinking", "tool-call"):
|
|
1538
|
-
resume_all_spinners()
|
|
1539
|
-
|
|
1540
|
-
# Spinner is resumed in PartEndEvent when appropriate (based on next_part_kind)
|
|
1541
|
-
|
|
1542
1363
|
def _spawn_ctrl_x_key_listener(
|
|
1543
1364
|
self,
|
|
1544
1365
|
stop_event: threading.Event,
|
|
@@ -1788,15 +1609,51 @@ class BaseAgent(ABC):
|
|
|
1788
1609
|
|
|
1789
1610
|
usage_limits = UsageLimits(request_limit=get_message_limit())
|
|
1790
1611
|
|
|
1791
|
-
# MCP servers
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1612
|
+
# Handle MCP servers - add them temporarily when using DBOS
|
|
1613
|
+
if (
|
|
1614
|
+
get_use_dbos()
|
|
1615
|
+
and hasattr(self, "_mcp_servers")
|
|
1616
|
+
and self._mcp_servers
|
|
1617
|
+
):
|
|
1618
|
+
# Temporarily add MCP servers to the DBOS agent using internal _toolsets
|
|
1619
|
+
original_toolsets = pydantic_agent._toolsets
|
|
1620
|
+
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
|
|
1621
|
+
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
|
|
1622
|
+
|
|
1623
|
+
try:
|
|
1624
|
+
# Set the workflow ID for DBOS context so DBOS and Code Puppy ID match
|
|
1625
|
+
with SetWorkflowID(group_id):
|
|
1626
|
+
result_ = await pydantic_agent.run(
|
|
1627
|
+
prompt_payload,
|
|
1628
|
+
message_history=self.get_message_history(),
|
|
1629
|
+
usage_limits=usage_limits,
|
|
1630
|
+
event_stream_handler=event_stream_handler,
|
|
1631
|
+
**kwargs,
|
|
1632
|
+
)
|
|
1633
|
+
return result_
|
|
1634
|
+
finally:
|
|
1635
|
+
# Always restore original toolsets
|
|
1636
|
+
pydantic_agent._toolsets = original_toolsets
|
|
1637
|
+
elif get_use_dbos():
|
|
1638
|
+
with SetWorkflowID(group_id):
|
|
1639
|
+
result_ = await pydantic_agent.run(
|
|
1640
|
+
prompt_payload,
|
|
1641
|
+
message_history=self.get_message_history(),
|
|
1642
|
+
usage_limits=usage_limits,
|
|
1643
|
+
event_stream_handler=event_stream_handler,
|
|
1644
|
+
**kwargs,
|
|
1645
|
+
)
|
|
1646
|
+
return result_
|
|
1647
|
+
else:
|
|
1648
|
+
# Non-DBOS path (MCP servers are already included)
|
|
1649
|
+
result_ = await pydantic_agent.run(
|
|
1650
|
+
prompt_payload,
|
|
1651
|
+
message_history=self.get_message_history(),
|
|
1652
|
+
usage_limits=usage_limits,
|
|
1653
|
+
event_stream_handler=event_stream_handler,
|
|
1654
|
+
**kwargs,
|
|
1655
|
+
)
|
|
1656
|
+
return result_
|
|
1800
1657
|
except* UsageLimitExceeded as ule:
|
|
1801
1658
|
emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
|
|
1802
1659
|
emit_info(
|
|
@@ -1811,8 +1668,12 @@ class BaseAgent(ABC):
|
|
|
1811
1668
|
)
|
|
1812
1669
|
except* asyncio.exceptions.CancelledError:
|
|
1813
1670
|
emit_info("Cancelled")
|
|
1671
|
+
if get_use_dbos():
|
|
1672
|
+
await DBOS.cancel_workflow_async(group_id)
|
|
1814
1673
|
except* InterruptedError as ie:
|
|
1815
1674
|
emit_info(f"Interrupted: {str(ie)}")
|
|
1675
|
+
if get_use_dbos():
|
|
1676
|
+
await DBOS.cancel_workflow_async(group_id)
|
|
1816
1677
|
except* Exception as other_error:
|
|
1817
1678
|
# Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
|
|
1818
1679
|
remaining_exceptions = []
|