code-puppy 0.0.320__tar.gz → 0.0.338__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.320 → code_puppy-0.0.338}/PKG-INFO +25 -49
- {code_puppy-0.0.320 → code_puppy-0.0.338}/README.md +23 -48
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/base_agent.py +200 -53
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/claude_cache_client.py +46 -2
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/cli_runner.py +107 -27
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/add_model_menu.py +15 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/autosave_menu.py +5 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/colors_menu.py +5 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/config_commands.py +24 -1
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/core_commands.py +51 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/diff_menu.py +5 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/custom_server_form.py +4 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/install_menu.py +5 -1
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/model_settings_menu.py +5 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/motd.py +13 -7
- code_puppy-0.0.338/code_puppy/command_line/onboarding_slides.py +180 -0
- code_puppy-0.0.338/code_puppy/command_line/onboarding_wizard.py +340 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/config.py +3 -2
- code_puppy-0.0.338/code_puppy/http_utils.py +338 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/keymap.py +8 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/messages.py +3 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/rich_renderer.py +114 -22
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/model_factory.py +102 -15
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/models.json +4 -4
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/__init__.py +12 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/antigravity_model.py +668 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/transport.py +664 -0
- code_puppy-0.0.338/code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/utils.py +126 -7
- code_puppy-0.0.338/code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/register_callbacks.py +44 -3
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/reopenable_async_client.py +8 -8
- code_puppy-0.0.338/code_puppy/terminal_utils.py +418 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/command_runner.py +22 -6
- code_puppy-0.0.338/code_puppy/uvx_detection.py +242 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/pyproject.toml +2 -1
- code_puppy-0.0.320/code_puppy/http_utils.py +0 -416
- code_puppy-0.0.320/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -186
- code_puppy-0.0.320/code_puppy/terminal_utils.py +0 -126
- {code_puppy-0.0.320 → code_puppy-0.0.338}/.gitignore +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/LICENSE +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/main.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.320 → code_puppy-0.0.338}/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.338
|
|
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
|
|
@@ -34,6 +34,7 @@ Requires-Dist: rich>=13.4.2
|
|
|
34
34
|
Requires-Dist: ripgrep==14.1.0
|
|
35
35
|
Requires-Dist: ruff>=0.11.11
|
|
36
36
|
Requires-Dist: tenacity>=8.2.0
|
|
37
|
+
Requires-Dist: termflow-md>=0.1.6
|
|
37
38
|
Requires-Dist: uvicorn>=0.30.0
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
|
|
@@ -100,66 +101,32 @@ uvx code-puppy -i
|
|
|
100
101
|
|
|
101
102
|
### UV (Recommended)
|
|
102
103
|
|
|
104
|
+
#### macOS / Linux
|
|
105
|
+
|
|
103
106
|
```bash
|
|
104
107
|
# Install UV if you don't have it
|
|
105
108
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
109
|
-
source ~/.zshrc # or ~/.bashrc
|
|
110
|
-
|
|
111
|
-
# Install and run code-puppy
|
|
112
|
-
uvx code-puppy -i
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
|
|
116
|
-
|
|
117
|
-
### pip (Alternative)
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
pip install code-puppy
|
|
110
|
+
uvx code-puppy
|
|
121
111
|
```
|
|
122
112
|
|
|
123
|
-
|
|
113
|
+
#### Windows
|
|
124
114
|
|
|
125
|
-
|
|
115
|
+
On Windows, we recommend installing code-puppy as a global tool for the best experience with keyboard shortcuts (Ctrl+C/Ctrl+X cancellation):
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# Set environment variable permanently
|
|
131
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
132
|
-
source ~/.zshrc # or ~/.bashrc
|
|
117
|
+
```powershell
|
|
118
|
+
# Install UV if you don't have it (run in PowerShell as Admin)
|
|
119
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
133
120
|
|
|
134
|
-
|
|
135
|
-
uvx code-puppy # No need for --managed-python flag anymore
|
|
121
|
+
uvx code-puppy
|
|
136
122
|
```
|
|
137
123
|
|
|
138
|
-
|
|
124
|
+
## Changelog (By Kittylog!)
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
# Check which Python UV will use
|
|
142
|
-
uv python find
|
|
143
|
-
|
|
144
|
-
# Or check the current project's Python
|
|
145
|
-
uv run python --version
|
|
146
|
-
```
|
|
126
|
+
[📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
|
|
147
127
|
|
|
148
128
|
## Usage
|
|
149
129
|
|
|
150
|
-
### Custom Commands
|
|
151
|
-
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.
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
# Create a custom command
|
|
155
|
-
echo "# Code Review
|
|
156
|
-
|
|
157
|
-
Please review this code for security issues." > .claude/commands/review.md
|
|
158
|
-
|
|
159
|
-
# Use it in Code Puppy
|
|
160
|
-
/review with focus on authentication
|
|
161
|
-
```
|
|
162
|
-
|
|
163
130
|
### Adding Models from models.dev 🆕
|
|
164
131
|
|
|
165
132
|
While there are several models configured right out of the box from providers like Synthetic, Cerebras, OpenAI, Google, and Anthropic, Code Puppy integrates with [models.dev](https://models.dev) to let you browse and add models from **65+ providers** with a single command:
|
|
@@ -227,6 +194,18 @@ The following environment variables control DBOS behavior:
|
|
|
227
194
|
- `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.
|
|
228
195
|
- `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).
|
|
229
196
|
|
|
197
|
+
### Custom Commands
|
|
198
|
+
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.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Create a custom command
|
|
202
|
+
echo "# Code Review
|
|
203
|
+
|
|
204
|
+
Please review this code for security issues." > .claude/commands/review.md
|
|
205
|
+
|
|
206
|
+
# Use it in Code Puppy
|
|
207
|
+
/review with focus on authentication
|
|
208
|
+
```
|
|
230
209
|
|
|
231
210
|
## Requirements
|
|
232
211
|
|
|
@@ -246,9 +225,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
|
|
|
246
225
|
|
|
247
226
|
Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
|
|
248
227
|
|
|
249
|
-
Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
|
|
250
|
-
|
|
251
|
-
|
|
252
228
|
## Round Robin Model Distribution
|
|
253
229
|
|
|
254
230
|
Code Puppy supports **Round Robin model distribution** to help you overcome rate limits and distribute load across multiple AI models. This feature automatically cycles through configured models with each request, maximizing your API usage while staying within rate limits.
|
|
@@ -61,66 +61,32 @@ uvx code-puppy -i
|
|
|
61
61
|
|
|
62
62
|
### UV (Recommended)
|
|
63
63
|
|
|
64
|
+
#### macOS / Linux
|
|
65
|
+
|
|
64
66
|
```bash
|
|
65
67
|
# Install UV if you don't have it
|
|
66
68
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
70
|
-
source ~/.zshrc # or ~/.bashrc
|
|
71
|
-
|
|
72
|
-
# Install and run code-puppy
|
|
73
|
-
uvx code-puppy -i
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
|
|
77
|
-
|
|
78
|
-
### pip (Alternative)
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
pip install code-puppy
|
|
70
|
+
uvx code-puppy
|
|
82
71
|
```
|
|
83
72
|
|
|
84
|
-
|
|
73
|
+
#### Windows
|
|
85
74
|
|
|
86
|
-
|
|
75
|
+
On Windows, we recommend installing code-puppy as a global tool for the best experience with keyboard shortcuts (Ctrl+C/Ctrl+X cancellation):
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Set environment variable permanently
|
|
92
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
93
|
-
source ~/.zshrc # or ~/.bashrc
|
|
77
|
+
```powershell
|
|
78
|
+
# Install UV if you don't have it (run in PowerShell as Admin)
|
|
79
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
uvx code-puppy # No need for --managed-python flag anymore
|
|
81
|
+
uvx code-puppy
|
|
97
82
|
```
|
|
98
83
|
|
|
99
|
-
|
|
84
|
+
## Changelog (By Kittylog!)
|
|
100
85
|
|
|
101
|
-
|
|
102
|
-
# Check which Python UV will use
|
|
103
|
-
uv python find
|
|
104
|
-
|
|
105
|
-
# Or check the current project's Python
|
|
106
|
-
uv run python --version
|
|
107
|
-
```
|
|
86
|
+
[📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
|
|
108
87
|
|
|
109
88
|
## Usage
|
|
110
89
|
|
|
111
|
-
### Custom Commands
|
|
112
|
-
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.
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
# Create a custom command
|
|
116
|
-
echo "# Code Review
|
|
117
|
-
|
|
118
|
-
Please review this code for security issues." > .claude/commands/review.md
|
|
119
|
-
|
|
120
|
-
# Use it in Code Puppy
|
|
121
|
-
/review with focus on authentication
|
|
122
|
-
```
|
|
123
|
-
|
|
124
90
|
### Adding Models from models.dev 🆕
|
|
125
91
|
|
|
126
92
|
While there are several models configured right out of the box from providers like Synthetic, Cerebras, OpenAI, Google, and Anthropic, Code Puppy integrates with [models.dev](https://models.dev) to let you browse and add models from **65+ providers** with a single command:
|
|
@@ -188,6 +154,18 @@ The following environment variables control DBOS behavior:
|
|
|
188
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.
|
|
189
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).
|
|
190
156
|
|
|
157
|
+
### Custom Commands
|
|
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.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Create a custom command
|
|
162
|
+
echo "# Code Review
|
|
163
|
+
|
|
164
|
+
Please review this code for security issues." > .claude/commands/review.md
|
|
165
|
+
|
|
166
|
+
# Use it in Code Puppy
|
|
167
|
+
/review with focus on authentication
|
|
168
|
+
```
|
|
191
169
|
|
|
192
170
|
## Requirements
|
|
193
171
|
|
|
@@ -207,9 +185,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
|
|
|
207
185
|
|
|
208
186
|
Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
|
|
209
187
|
|
|
210
|
-
Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
|
|
211
|
-
|
|
212
|
-
|
|
213
188
|
## Round Robin Model Distribution
|
|
214
189
|
|
|
215
190
|
Code Puppy supports **Round Robin model distribution** to help you overcome rate limits and distribute load across multiple AI models. This feature automatically cycles through configured models with each request, maximizing your API usage while staying within rate limits.
|
|
@@ -8,7 +8,18 @@ import threading
|
|
|
8
8
|
import uuid
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
10
|
from collections.abc import AsyncIterable
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import (
|
|
12
|
+
Any,
|
|
13
|
+
Callable,
|
|
14
|
+
Dict,
|
|
15
|
+
List,
|
|
16
|
+
Optional,
|
|
17
|
+
Sequence,
|
|
18
|
+
Set,
|
|
19
|
+
Tuple,
|
|
20
|
+
Type,
|
|
21
|
+
Union,
|
|
22
|
+
)
|
|
12
23
|
|
|
13
24
|
import mcp
|
|
14
25
|
import pydantic
|
|
@@ -1230,6 +1241,74 @@ class BaseAgent(ABC):
|
|
|
1230
1241
|
self._mcp_servers = mcp_servers
|
|
1231
1242
|
return self._code_generation_agent
|
|
1232
1243
|
|
|
1244
|
+
def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgent:
|
|
1245
|
+
"""Create a temporary agent configured with a custom output_type.
|
|
1246
|
+
|
|
1247
|
+
This is used when structured output is requested via run_with_mcp.
|
|
1248
|
+
The agent is created fresh with the same configuration as the main agent
|
|
1249
|
+
but with the specified output_type instead of str.
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
output_type: The Pydantic model or type for structured output.
|
|
1253
|
+
|
|
1254
|
+
Returns:
|
|
1255
|
+
A configured PydanticAgent (or DBOSAgent wrapper) with the custom output_type.
|
|
1256
|
+
"""
|
|
1257
|
+
from code_puppy.model_utils import prepare_prompt_for_model
|
|
1258
|
+
from code_puppy.tools import register_tools_for_agent
|
|
1259
|
+
|
|
1260
|
+
model_name = self.get_model_name()
|
|
1261
|
+
models_config = ModelFactory.load_config()
|
|
1262
|
+
model, resolved_model_name = self._load_model_with_fallback(
|
|
1263
|
+
model_name, models_config, str(uuid.uuid4())
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
instructions = self.get_system_prompt()
|
|
1267
|
+
puppy_rules = self.load_puppy_rules()
|
|
1268
|
+
if puppy_rules:
|
|
1269
|
+
instructions += f"\n{puppy_rules}"
|
|
1270
|
+
|
|
1271
|
+
mcp_servers = getattr(self, "_mcp_servers", []) or []
|
|
1272
|
+
model_settings = make_model_settings(resolved_model_name)
|
|
1273
|
+
|
|
1274
|
+
prepared = prepare_prompt_for_model(
|
|
1275
|
+
model_name, instructions, "", prepend_system_to_user=False
|
|
1276
|
+
)
|
|
1277
|
+
instructions = prepared.instructions
|
|
1278
|
+
|
|
1279
|
+
global _reload_count
|
|
1280
|
+
_reload_count += 1
|
|
1281
|
+
|
|
1282
|
+
if get_use_dbos():
|
|
1283
|
+
temp_agent = PydanticAgent(
|
|
1284
|
+
model=model,
|
|
1285
|
+
instructions=instructions,
|
|
1286
|
+
output_type=output_type,
|
|
1287
|
+
retries=3,
|
|
1288
|
+
toolsets=[],
|
|
1289
|
+
history_processors=[self.message_history_accumulator],
|
|
1290
|
+
model_settings=model_settings,
|
|
1291
|
+
)
|
|
1292
|
+
agent_tools = self.get_available_tools()
|
|
1293
|
+
register_tools_for_agent(temp_agent, agent_tools)
|
|
1294
|
+
dbos_agent = DBOSAgent(
|
|
1295
|
+
temp_agent, name=f"{self.name}-structured-{_reload_count}"
|
|
1296
|
+
)
|
|
1297
|
+
return dbos_agent
|
|
1298
|
+
else:
|
|
1299
|
+
temp_agent = PydanticAgent(
|
|
1300
|
+
model=model,
|
|
1301
|
+
instructions=instructions,
|
|
1302
|
+
output_type=output_type,
|
|
1303
|
+
retries=3,
|
|
1304
|
+
toolsets=mcp_servers,
|
|
1305
|
+
history_processors=[self.message_history_accumulator],
|
|
1306
|
+
model_settings=model_settings,
|
|
1307
|
+
)
|
|
1308
|
+
agent_tools = self.get_available_tools()
|
|
1309
|
+
register_tools_for_agent(temp_agent, agent_tools)
|
|
1310
|
+
return temp_agent
|
|
1311
|
+
|
|
1233
1312
|
# It's okay to decorate it with DBOS.step even if not using DBOS; the decorator is a no-op in that case.
|
|
1234
1313
|
@DBOS.step()
|
|
1235
1314
|
def message_history_accumulator(self, ctx: RunContext, messages: List[Any]):
|
|
@@ -1260,17 +1339,20 @@ class BaseAgent(ABC):
|
|
|
1260
1339
|
) -> None:
|
|
1261
1340
|
"""Handle streaming events from the agent run.
|
|
1262
1341
|
|
|
1263
|
-
This method processes streaming events and emits TextPart
|
|
1264
|
-
content with styled banners as they stream in.
|
|
1342
|
+
This method processes streaming events and emits TextPart, ThinkingPart,
|
|
1343
|
+
and ToolCallPart content with styled banners/tokens as they stream in.
|
|
1265
1344
|
|
|
1266
1345
|
Args:
|
|
1267
1346
|
ctx: The run context.
|
|
1268
1347
|
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
1269
1348
|
"""
|
|
1270
1349
|
from pydantic_ai import PartDeltaEvent, PartStartEvent
|
|
1271
|
-
from pydantic_ai.messages import
|
|
1350
|
+
from pydantic_ai.messages import (
|
|
1351
|
+
TextPartDelta,
|
|
1352
|
+
ThinkingPartDelta,
|
|
1353
|
+
ToolCallPartDelta,
|
|
1354
|
+
)
|
|
1272
1355
|
from rich.console import Console
|
|
1273
|
-
from rich.markdown import Markdown
|
|
1274
1356
|
from rich.markup import escape
|
|
1275
1357
|
|
|
1276
1358
|
from code_puppy.messaging.spinner import pause_all_spinners
|
|
@@ -1284,29 +1366,36 @@ class BaseAgent(ABC):
|
|
|
1284
1366
|
# Fallback if console not set (shouldn't happen in normal use)
|
|
1285
1367
|
console = Console()
|
|
1286
1368
|
|
|
1287
|
-
# Track which part indices we're currently streaming (for Text/Thinking parts)
|
|
1369
|
+
# Track which part indices we're currently streaming (for Text/Thinking/Tool parts)
|
|
1288
1370
|
streaming_parts: set[int] = set()
|
|
1289
1371
|
thinking_parts: set[int] = (
|
|
1290
1372
|
set()
|
|
1291
1373
|
) # Track which parts are thinking (for dim style)
|
|
1292
1374
|
text_parts: set[int] = set() # Track which parts are text
|
|
1375
|
+
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
1293
1376
|
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1294
|
-
|
|
1295
|
-
token_count: dict[int, int] = {} # Track token count per text part
|
|
1377
|
+
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
1296
1378
|
did_stream_anything = False # Track if we streamed any content
|
|
1297
1379
|
|
|
1380
|
+
# Termflow streaming state for text parts
|
|
1381
|
+
from termflow import Parser as TermflowParser
|
|
1382
|
+
from termflow import Renderer as TermflowRenderer
|
|
1383
|
+
|
|
1384
|
+
termflow_parsers: dict[int, TermflowParser] = {}
|
|
1385
|
+
termflow_renderers: dict[int, TermflowRenderer] = {}
|
|
1386
|
+
termflow_line_buffers: dict[int, str] = {} # Buffer incomplete lines
|
|
1387
|
+
|
|
1298
1388
|
def _print_thinking_banner() -> None:
|
|
1299
1389
|
"""Print the THINKING banner with spinner pause and line clear."""
|
|
1300
1390
|
nonlocal did_stream_anything
|
|
1301
|
-
import sys
|
|
1302
1391
|
import time
|
|
1303
1392
|
|
|
1304
1393
|
from code_puppy.config import get_banner_color
|
|
1305
1394
|
|
|
1306
1395
|
pause_all_spinners()
|
|
1307
1396
|
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1308
|
-
|
|
1309
|
-
|
|
1397
|
+
# Clear line and print newline before banner
|
|
1398
|
+
console.print(" " * 50, end="\r")
|
|
1310
1399
|
console.print() # Newline before banner
|
|
1311
1400
|
# Bold banner with configurable color and lightning bolt
|
|
1312
1401
|
thinking_color = get_banner_color("thinking")
|
|
@@ -1316,21 +1405,19 @@ class BaseAgent(ABC):
|
|
|
1316
1405
|
),
|
|
1317
1406
|
end="",
|
|
1318
1407
|
)
|
|
1319
|
-
sys.stdout.flush()
|
|
1320
1408
|
did_stream_anything = True
|
|
1321
1409
|
|
|
1322
1410
|
def _print_response_banner() -> None:
|
|
1323
1411
|
"""Print the AGENT RESPONSE banner with spinner pause and line clear."""
|
|
1324
1412
|
nonlocal did_stream_anything
|
|
1325
|
-
import sys
|
|
1326
1413
|
import time
|
|
1327
1414
|
|
|
1328
1415
|
from code_puppy.config import get_banner_color
|
|
1329
1416
|
|
|
1330
1417
|
pause_all_spinners()
|
|
1331
1418
|
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1332
|
-
|
|
1333
|
-
|
|
1419
|
+
# Clear line and print newline before banner
|
|
1420
|
+
console.print(" " * 50, end="\r")
|
|
1334
1421
|
console.print() # Newline before banner
|
|
1335
1422
|
response_color = get_banner_color("agent_response")
|
|
1336
1423
|
console.print(
|
|
@@ -1338,7 +1425,6 @@ class BaseAgent(ABC):
|
|
|
1338
1425
|
f"[bold white on {response_color}] AGENT RESPONSE [/bold white on {response_color}]"
|
|
1339
1426
|
)
|
|
1340
1427
|
)
|
|
1341
|
-
sys.stdout.flush()
|
|
1342
1428
|
did_stream_anything = True
|
|
1343
1429
|
|
|
1344
1430
|
async for event in events:
|
|
@@ -1357,12 +1443,25 @@ class BaseAgent(ABC):
|
|
|
1357
1443
|
elif isinstance(part, TextPart):
|
|
1358
1444
|
streaming_parts.add(event.index)
|
|
1359
1445
|
text_parts.add(event.index)
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1446
|
+
# Initialize termflow streaming for this text part
|
|
1447
|
+
termflow_parsers[event.index] = TermflowParser()
|
|
1448
|
+
termflow_renderers[event.index] = TermflowRenderer(
|
|
1449
|
+
output=console.file, width=console.width
|
|
1450
|
+
)
|
|
1451
|
+
termflow_line_buffers[event.index] = ""
|
|
1452
|
+
# Handle initial content if present
|
|
1363
1453
|
if part.content and part.content.strip():
|
|
1364
|
-
|
|
1365
|
-
|
|
1454
|
+
_print_response_banner()
|
|
1455
|
+
banner_printed.add(event.index)
|
|
1456
|
+
termflow_line_buffers[event.index] = part.content
|
|
1457
|
+
elif isinstance(part, ToolCallPart):
|
|
1458
|
+
streaming_parts.add(event.index)
|
|
1459
|
+
tool_parts.add(event.index)
|
|
1460
|
+
token_count[event.index] = 0 # Initialize token counter
|
|
1461
|
+
# Track tool name for display
|
|
1462
|
+
banner_printed.add(
|
|
1463
|
+
event.index
|
|
1464
|
+
) # Use banner_printed to track if we've shown tool info
|
|
1366
1465
|
|
|
1367
1466
|
# PartDeltaEvent - stream the content as it arrives
|
|
1368
1467
|
elif isinstance(event, PartDeltaEvent):
|
|
@@ -1370,23 +1469,29 @@ class BaseAgent(ABC):
|
|
|
1370
1469
|
delta = event.delta
|
|
1371
1470
|
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1372
1471
|
if delta.content_delta:
|
|
1373
|
-
# For text parts,
|
|
1472
|
+
# For text parts, stream markdown with termflow
|
|
1374
1473
|
if event.index in text_parts:
|
|
1375
|
-
import sys
|
|
1376
|
-
|
|
1377
1474
|
# Print banner on first content
|
|
1378
1475
|
if event.index not in banner_printed:
|
|
1379
1476
|
_print_response_banner()
|
|
1380
1477
|
banner_printed.add(event.index)
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
count = token_count[event.index]
|
|
1386
|
-
sys.stdout.write(
|
|
1387
|
-
f"\r\x1b[K ⏳ Receiving... {count} tokens"
|
|
1478
|
+
|
|
1479
|
+
# Add content to line buffer
|
|
1480
|
+
termflow_line_buffers[event.index] += (
|
|
1481
|
+
delta.content_delta
|
|
1388
1482
|
)
|
|
1389
|
-
|
|
1483
|
+
|
|
1484
|
+
# Process complete lines
|
|
1485
|
+
parser = termflow_parsers[event.index]
|
|
1486
|
+
renderer = termflow_renderers[event.index]
|
|
1487
|
+
buffer = termflow_line_buffers[event.index]
|
|
1488
|
+
|
|
1489
|
+
while "\n" in buffer:
|
|
1490
|
+
line, buffer = buffer.split("\n", 1)
|
|
1491
|
+
events_to_render = parser.parse_line(line)
|
|
1492
|
+
renderer.render_all(events_to_render)
|
|
1493
|
+
|
|
1494
|
+
termflow_line_buffers[event.index] = buffer
|
|
1390
1495
|
else:
|
|
1391
1496
|
# For thinking parts, stream immediately (dim)
|
|
1392
1497
|
if event.index not in banner_printed:
|
|
@@ -1394,44 +1499,72 @@ class BaseAgent(ABC):
|
|
|
1394
1499
|
banner_printed.add(event.index)
|
|
1395
1500
|
escaped = escape(delta.content_delta)
|
|
1396
1501
|
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1502
|
+
elif isinstance(delta, ToolCallPartDelta):
|
|
1503
|
+
# For tool calls, count chunks received
|
|
1504
|
+
token_count[event.index] += 1
|
|
1505
|
+
# Get tool name if available
|
|
1506
|
+
tool_name = getattr(delta, "tool_name_delta", "")
|
|
1507
|
+
count = token_count[event.index]
|
|
1508
|
+
# Display with tool wrench icon and tool name
|
|
1509
|
+
if tool_name:
|
|
1510
|
+
console.print(
|
|
1511
|
+
f" 🔧 Calling {tool_name}... {count} chunks ",
|
|
1512
|
+
end="\r",
|
|
1513
|
+
)
|
|
1514
|
+
else:
|
|
1515
|
+
console.print(
|
|
1516
|
+
f" 🔧 Calling tool... {count} chunks ",
|
|
1517
|
+
end="\r",
|
|
1518
|
+
)
|
|
1397
1519
|
|
|
1398
1520
|
# PartEndEvent - finish the streaming with a newline
|
|
1399
1521
|
elif isinstance(event, PartEndEvent):
|
|
1400
1522
|
if event.index in streaming_parts:
|
|
1401
|
-
# For text parts,
|
|
1523
|
+
# For text parts, finalize termflow rendering
|
|
1402
1524
|
if event.index in text_parts:
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1525
|
+
# Render any remaining buffered content
|
|
1526
|
+
if event.index in termflow_parsers:
|
|
1527
|
+
parser = termflow_parsers[event.index]
|
|
1528
|
+
renderer = termflow_renderers[event.index]
|
|
1529
|
+
remaining = termflow_line_buffers.get(event.index, "")
|
|
1530
|
+
|
|
1531
|
+
# Parse and render any remaining partial line
|
|
1532
|
+
if remaining.strip():
|
|
1533
|
+
events_to_render = parser.parse_line(remaining)
|
|
1534
|
+
renderer.render_all(events_to_render)
|
|
1535
|
+
|
|
1536
|
+
# Finalize the parser to close any open blocks
|
|
1537
|
+
final_events = parser.finalize()
|
|
1538
|
+
renderer.render_all(final_events)
|
|
1539
|
+
|
|
1540
|
+
# Clean up termflow state
|
|
1541
|
+
del termflow_parsers[event.index]
|
|
1542
|
+
del termflow_renderers[event.index]
|
|
1543
|
+
del termflow_line_buffers[event.index]
|
|
1544
|
+
# For tool parts, clear the chunk counter line
|
|
1545
|
+
elif event.index in tool_parts:
|
|
1546
|
+
# Clear the chunk counter line by printing spaces and returning
|
|
1547
|
+
console.print(" " * 50, end="\r")
|
|
1419
1548
|
# For thinking parts, just print newline
|
|
1420
1549
|
elif event.index in banner_printed:
|
|
1421
1550
|
console.print() # Final newline after streaming
|
|
1551
|
+
|
|
1552
|
+
# Clean up token count
|
|
1553
|
+
token_count.pop(event.index, None)
|
|
1422
1554
|
# Clean up all tracking sets
|
|
1423
1555
|
streaming_parts.discard(event.index)
|
|
1424
1556
|
thinking_parts.discard(event.index)
|
|
1425
1557
|
text_parts.discard(event.index)
|
|
1558
|
+
tool_parts.discard(event.index)
|
|
1426
1559
|
banner_printed.discard(event.index)
|
|
1427
1560
|
|
|
1428
|
-
# Resume spinner if next part is NOT text/thinking (avoid race condition)
|
|
1429
|
-
# If next part is
|
|
1561
|
+
# Resume spinner if next part is NOT text/thinking/tool (avoid race condition)
|
|
1562
|
+
# If next part is None or handled differently, it's safe to resume
|
|
1430
1563
|
# Note: spinner itself handles blank line before appearing
|
|
1431
1564
|
from code_puppy.messaging.spinner import resume_all_spinners
|
|
1432
1565
|
|
|
1433
1566
|
next_kind = getattr(event, "next_part_kind", None)
|
|
1434
|
-
if next_kind not in ("text", "thinking"):
|
|
1567
|
+
if next_kind not in ("text", "thinking", "tool-call"):
|
|
1435
1568
|
resume_all_spinners()
|
|
1436
1569
|
|
|
1437
1570
|
# Spinner is resumed in PartEndEvent when appropriate (based on next_part_kind)
|
|
@@ -1590,6 +1723,7 @@ class BaseAgent(ABC):
|
|
|
1590
1723
|
*,
|
|
1591
1724
|
attachments: Optional[Sequence[BinaryContent]] = None,
|
|
1592
1725
|
link_attachments: Optional[Sequence[Union[ImageUrl, DocumentUrl]]] = None,
|
|
1726
|
+
output_type: Optional[Type[Any]] = None,
|
|
1593
1727
|
**kwargs,
|
|
1594
1728
|
) -> Any:
|
|
1595
1729
|
"""Run the agent with MCP servers, attachments, and full cancellation support.
|
|
@@ -1598,10 +1732,13 @@ class BaseAgent(ABC):
|
|
|
1598
1732
|
prompt: Primary user prompt text (may be empty when attachments present).
|
|
1599
1733
|
attachments: Local binary payloads (e.g., dragged images) to include.
|
|
1600
1734
|
link_attachments: Remote assets (image/document URLs) to include.
|
|
1735
|
+
output_type: Optional Pydantic model or type for structured output.
|
|
1736
|
+
When provided, creates a temporary agent configured to return
|
|
1737
|
+
this type instead of the default string output.
|
|
1601
1738
|
**kwargs: Additional arguments forwarded to `pydantic_ai.Agent.run`.
|
|
1602
1739
|
|
|
1603
1740
|
Returns:
|
|
1604
|
-
The agent's response.
|
|
1741
|
+
The agent's response (typed according to output_type if specified).
|
|
1605
1742
|
|
|
1606
1743
|
Raises:
|
|
1607
1744
|
asyncio.CancelledError: When execution is cancelled by user.
|
|
@@ -1625,6 +1762,11 @@ class BaseAgent(ABC):
|
|
|
1625
1762
|
pydantic_agent = (
|
|
1626
1763
|
self._code_generation_agent or self.reload_code_generation_agent()
|
|
1627
1764
|
)
|
|
1765
|
+
|
|
1766
|
+
# If a custom output_type is specified, create a temporary agent with that type
|
|
1767
|
+
if output_type is not None:
|
|
1768
|
+
pydantic_agent = self._create_agent_with_output_type(output_type)
|
|
1769
|
+
|
|
1628
1770
|
# Handle claude-code and chatgpt-codex models: prepend system prompt to first user message
|
|
1629
1771
|
from code_puppy.model_utils import is_chatgpt_codex_model, is_claude_code_model
|
|
1630
1772
|
|
|
@@ -1821,7 +1963,12 @@ class BaseAgent(ABC):
|
|
|
1821
1963
|
def graceful_sigint_handler(_sig, _frame):
|
|
1822
1964
|
# When using keyboard-based cancel, SIGINT should be a no-op
|
|
1823
1965
|
# (just show a hint to user about the configured cancel key)
|
|
1966
|
+
# Also reset terminal to prevent bricking on Windows+uvx
|
|
1824
1967
|
from code_puppy.keymap import get_cancel_agent_display_name
|
|
1968
|
+
from code_puppy.terminal_utils import reset_windows_terminal_full
|
|
1969
|
+
|
|
1970
|
+
# Reset terminal state first to prevent bricking
|
|
1971
|
+
reset_windows_terminal_full()
|
|
1825
1972
|
|
|
1826
1973
|
cancel_key = get_cancel_agent_display_name()
|
|
1827
1974
|
emit_info(f"Use {cancel_key} to cancel the agent task.")
|