code-puppy 0.0.324__tar.gz → 0.0.325__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.
Files changed (166) hide show
  1. {code_puppy-0.0.324 → code_puppy-0.0.325}/PKG-INFO +1 -1
  2. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/base_agent.py +58 -15
  3. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/add_model_menu.py +11 -0
  4. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/__init__.py +12 -0
  5. {code_puppy-0.0.324 → code_puppy-0.0.325}/pyproject.toml +1 -1
  6. {code_puppy-0.0.324 → code_puppy-0.0.325}/.gitignore +0 -0
  7. {code_puppy-0.0.324 → code_puppy-0.0.325}/LICENSE +0 -0
  8. {code_puppy-0.0.324 → code_puppy-0.0.325}/README.md +0 -0
  9. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/__init__.py +0 -0
  10. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/__main__.py +0 -0
  11. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/__init__.py +0 -0
  12. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_c_reviewer.py +0 -0
  13. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_code_puppy.py +0 -0
  14. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_code_reviewer.py +0 -0
  15. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  16. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_creator_agent.py +0 -0
  17. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  18. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  19. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_manager.py +0 -0
  20. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_planning.py +0 -0
  21. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_python_programmer.py +0 -0
  22. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_python_reviewer.py +0 -0
  23. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_qa_expert.py +0 -0
  24. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_qa_kitten.py +0 -0
  25. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_security_auditor.py +0 -0
  26. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  27. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/json_agent.py +0 -0
  28. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/agents/prompt_reviewer.py +0 -0
  29. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/callbacks.py +0 -0
  30. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/chatgpt_codex_client.py +0 -0
  31. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/claude_cache_client.py +0 -0
  32. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/cli_runner.py +0 -0
  33. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/__init__.py +0 -0
  34. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/attachments.py +0 -0
  35. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/autosave_menu.py +0 -0
  36. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/colors_menu.py +0 -0
  37. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/command_handler.py +0 -0
  38. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/command_registry.py +0 -0
  39. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/config_commands.py +0 -0
  40. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/core_commands.py +0 -0
  41. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/diff_menu.py +0 -0
  42. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/file_path_completion.py +0 -0
  43. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/load_context_completion.py +0 -0
  44. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/__init__.py +0 -0
  45. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/add_command.py +0 -0
  46. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/base.py +0 -0
  47. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  48. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  49. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  50. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/edit_command.py +0 -0
  51. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/handler.py +0 -0
  52. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/help_command.py +0 -0
  53. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/install_command.py +0 -0
  54. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/install_menu.py +0 -0
  55. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/list_command.py +0 -0
  56. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/logs_command.py +0 -0
  57. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/remove_command.py +0 -0
  58. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/restart_command.py +0 -0
  59. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/search_command.py +0 -0
  60. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  61. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/start_command.py +0 -0
  62. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/status_command.py +0 -0
  63. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  64. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/stop_command.py +0 -0
  65. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/test_command.py +0 -0
  66. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/utils.py +0 -0
  67. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  68. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/mcp_completion.py +0 -0
  69. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/model_picker_completion.py +0 -0
  70. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/model_settings_menu.py +0 -0
  71. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/motd.py +0 -0
  72. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/pin_command_completion.py +0 -0
  73. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  74. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/session_commands.py +0 -0
  75. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/command_line/utils.py +0 -0
  76. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/config.py +0 -0
  77. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/error_logging.py +0 -0
  78. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/gemini_code_assist.py +0 -0
  79. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/http_utils.py +0 -0
  80. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/keymap.py +0 -0
  81. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/main.py +0 -0
  82. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/__init__.py +0 -0
  83. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/async_lifecycle.py +0 -0
  84. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/blocking_startup.py +0 -0
  85. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  86. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/circuit_breaker.py +0 -0
  87. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/config_wizard.py +0 -0
  88. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/dashboard.py +0 -0
  89. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/error_isolation.py +0 -0
  90. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/examples/retry_example.py +0 -0
  91. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/health_monitor.py +0 -0
  92. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/managed_server.py +0 -0
  93. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/manager.py +0 -0
  94. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/mcp_logs.py +0 -0
  95. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/registry.py +0 -0
  96. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/retry_manager.py +0 -0
  97. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  98. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/status_tracker.py +0 -0
  99. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/mcp_/system_tools.py +0 -0
  100. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/__init__.py +0 -0
  101. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/bus.py +0 -0
  102. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/commands.py +0 -0
  103. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/markdown_patches.py +0 -0
  104. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/message_queue.py +0 -0
  105. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/messages.py +0 -0
  106. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/queue_console.py +0 -0
  107. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/renderers.py +0 -0
  108. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/rich_renderer.py +0 -0
  109. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/spinner/__init__.py +0 -0
  110. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  111. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  112. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/model_factory.py +0 -0
  113. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/model_utils.py +0 -0
  114. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/models.json +0 -0
  115. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/models_dev_api.json +0 -0
  116. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/models_dev_parser.py +0 -0
  117. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  118. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  119. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  120. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  121. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  122. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  123. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  124. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  125. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  126. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  127. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  128. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  129. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  130. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  131. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  132. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/example_custom_command/README.md +0 -0
  133. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  134. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  135. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  136. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  137. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  138. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  139. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  140. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  141. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/prompts/codex_system_prompt.md +0 -0
  142. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/pydantic_patches.py +0 -0
  143. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/reopenable_async_client.py +0 -0
  144. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/round_robin_model.py +0 -0
  145. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/session_storage.py +0 -0
  146. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/status_display.py +0 -0
  147. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/summarization_agent.py +0 -0
  148. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/terminal_utils.py +0 -0
  149. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/__init__.py +0 -0
  150. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/agent_tools.py +0 -0
  151. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/__init__.py +0 -0
  152. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_control.py +0 -0
  153. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_interactions.py +0 -0
  154. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_locators.py +0 -0
  155. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_navigation.py +0 -0
  156. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  157. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_scripts.py +0 -0
  158. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/browser_workflows.py +0 -0
  159. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  160. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/browser/vqa_agent.py +0 -0
  161. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/command_runner.py +0 -0
  162. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/common.py +0 -0
  163. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/file_modifications.py +0 -0
  164. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/file_operations.py +0 -0
  165. {code_puppy-0.0.324 → code_puppy-0.0.325}/code_puppy/tools/tools_content.py +0 -0
  166. {code_puppy-0.0.324 → code_puppy-0.0.325}/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.324
3
+ Version: 0.0.325
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
@@ -1340,15 +1340,19 @@ class BaseAgent(ABC):
1340
1340
  ) -> None:
1341
1341
  """Handle streaming events from the agent run.
1342
1342
 
1343
- This method processes streaming events and emits TextPart and ThinkingPart
1344
- content with styled banners as they stream in.
1343
+ This method processes streaming events and emits TextPart, ThinkingPart,
1344
+ and ToolCallPart content with styled banners/tokens as they stream in.
1345
1345
 
1346
1346
  Args:
1347
1347
  ctx: The run context.
1348
1348
  events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
1349
1349
  """
1350
1350
  from pydantic_ai import PartDeltaEvent, PartStartEvent
1351
- from pydantic_ai.messages import TextPartDelta, ThinkingPartDelta
1351
+ from pydantic_ai.messages import (
1352
+ TextPartDelta,
1353
+ ThinkingPartDelta,
1354
+ ToolCallPartDelta,
1355
+ )
1352
1356
  from rich.console import Console
1353
1357
  from rich.markdown import Markdown
1354
1358
  from rich.markup import escape
@@ -1364,15 +1368,16 @@ class BaseAgent(ABC):
1364
1368
  # Fallback if console not set (shouldn't happen in normal use)
1365
1369
  console = Console()
1366
1370
 
1367
- # Track which part indices we're currently streaming (for Text/Thinking parts)
1371
+ # Track which part indices we're currently streaming (for Text/Thinking/Tool parts)
1368
1372
  streaming_parts: set[int] = set()
1369
1373
  thinking_parts: set[int] = (
1370
1374
  set()
1371
1375
  ) # Track which parts are thinking (for dim style)
1372
1376
  text_parts: set[int] = set() # Track which parts are text
1377
+ tool_parts: set[int] = set() # Track which parts are tool calls
1373
1378
  banner_printed: set[int] = set() # Track if banner was already printed
1374
1379
  text_buffer: dict[int, list[str]] = {} # Buffer text for final markdown render
1375
- token_count: dict[int, int] = {} # Track token count per text part
1380
+ token_count: dict[int, int] = {} # Track token count per text/tool part
1376
1381
  did_stream_anything = False # Track if we streamed any content
1377
1382
 
1378
1383
  def _print_thinking_banner() -> None:
@@ -1442,7 +1447,16 @@ class BaseAgent(ABC):
1442
1447
  # Buffer initial content if present
1443
1448
  if part.content and part.content.strip():
1444
1449
  text_buffer[event.index].append(part.content)
1445
- token_count[event.index] += 1
1450
+ # Use len(content) / 3 for token estimation (more accurate than chunk counting)
1451
+ token_count[event.index] += len(part.content) // 3
1452
+ elif isinstance(part, ToolCallPart):
1453
+ streaming_parts.add(event.index)
1454
+ tool_parts.add(event.index)
1455
+ token_count[event.index] = 0 # Initialize token counter
1456
+ # Track tool name for display
1457
+ banner_printed.add(
1458
+ event.index
1459
+ ) # Use banner_printed to track if we've shown tool info
1446
1460
 
1447
1461
  # PartDeltaEvent - stream the content as it arrives
1448
1462
  elif isinstance(event, PartDeltaEvent):
@@ -1460,7 +1474,10 @@ class BaseAgent(ABC):
1460
1474
  banner_printed.add(event.index)
1461
1475
  # Accumulate text for final markdown render
1462
1476
  text_buffer[event.index].append(delta.content_delta)
1463
- token_count[event.index] += 1
1477
+ # Use len(content) / 3 for token estimation
1478
+ token_count[event.index] += (
1479
+ len(delta.content_delta) // 3
1480
+ )
1464
1481
  # Update token counter in place (single line)
1465
1482
  count = token_count[event.index]
1466
1483
  sys.stdout.write(
@@ -1474,14 +1491,32 @@ class BaseAgent(ABC):
1474
1491
  banner_printed.add(event.index)
1475
1492
  escaped = escape(delta.content_delta)
1476
1493
  console.print(f"[dim]{escaped}[/dim]", end="")
1494
+ elif isinstance(delta, ToolCallPartDelta):
1495
+ import sys
1496
+
1497
+ # For tool calls, show token counter (use string repr for estimation)
1498
+ token_count[event.index] += len(str(delta)) // 3
1499
+ # Get tool name if available
1500
+ tool_name = getattr(delta, "tool_name_delta", "")
1501
+ count = token_count[event.index]
1502
+ # Display with tool wrench icon and tool name
1503
+ if tool_name:
1504
+ sys.stdout.write(
1505
+ f"\r\x1b[K 🔧 Calling {tool_name}... {count} tokens"
1506
+ )
1507
+ else:
1508
+ sys.stdout.write(
1509
+ f"\r\x1b[K 🔧 Calling tool... {count} tokens"
1510
+ )
1511
+ sys.stdout.flush()
1477
1512
 
1478
1513
  # PartEndEvent - finish the streaming with a newline
1479
1514
  elif isinstance(event, PartEndEvent):
1480
1515
  if event.index in streaming_parts:
1516
+ import sys
1517
+
1481
1518
  # For text parts, clear counter line and render markdown
1482
1519
  if event.index in text_parts:
1483
- import sys
1484
-
1485
1520
  # Clear the token counter line
1486
1521
  sys.stdout.write("\r\x1b[K")
1487
1522
  sys.stdout.flush()
@@ -1494,24 +1529,31 @@ class BaseAgent(ABC):
1494
1529
  except Exception:
1495
1530
  pass
1496
1531
  del text_buffer[event.index]
1497
- # Clean up token count
1498
- token_count.pop(event.index, None)
1532
+ # For tool parts, clear the token counter line
1533
+ elif event.index in tool_parts:
1534
+ # Clear the token counter line
1535
+ sys.stdout.write("\r\x1b[K")
1536
+ sys.stdout.flush()
1499
1537
  # For thinking parts, just print newline
1500
1538
  elif event.index in banner_printed:
1501
1539
  console.print() # Final newline after streaming
1540
+
1541
+ # Clean up token count
1542
+ token_count.pop(event.index, None)
1502
1543
  # Clean up all tracking sets
1503
1544
  streaming_parts.discard(event.index)
1504
1545
  thinking_parts.discard(event.index)
1505
1546
  text_parts.discard(event.index)
1547
+ tool_parts.discard(event.index)
1506
1548
  banner_printed.discard(event.index)
1507
1549
 
1508
- # Resume spinner if next part is NOT text/thinking (avoid race condition)
1509
- # If next part is a tool call or None, it's safe to resume
1550
+ # Resume spinner if next part is NOT text/thinking/tool (avoid race condition)
1551
+ # If next part is None or handled differently, it's safe to resume
1510
1552
  # Note: spinner itself handles blank line before appearing
1511
1553
  from code_puppy.messaging.spinner import resume_all_spinners
1512
1554
 
1513
1555
  next_kind = getattr(event, "next_part_kind", None)
1514
- if next_kind not in ("text", "thinking"):
1556
+ if next_kind not in ("text", "thinking", "tool-call"):
1515
1557
  resume_all_spinners()
1516
1558
 
1517
1559
  # Spinner is resumed in PartEndEvent when appropriate (based on next_part_kind)
@@ -1910,9 +1952,10 @@ class BaseAgent(ABC):
1910
1952
  def graceful_sigint_handler(_sig, _frame):
1911
1953
  # When using keyboard-based cancel, SIGINT should be a no-op
1912
1954
  # (just show a hint to user about the configured cancel key)
1913
- from code_puppy.keymap import get_cancel_agent_display_name
1914
1955
  import sys
1915
1956
 
1957
+ from code_puppy.keymap import get_cancel_agent_display_name
1958
+
1916
1959
  cancel_key = get_cancel_agent_display_name()
1917
1960
  if sys.platform == "win32":
1918
1961
  # On Windows, we use keyboard listener, so SIGINT might still fire
@@ -571,6 +571,7 @@ class AddModelMenu:
571
571
  "cerebras": "cerebras",
572
572
  "cohere": "custom_openai",
573
573
  "perplexity": "custom_openai",
574
+ "minimax": "custom_anthropic",
574
575
  }
575
576
 
576
577
  # Determine the model type
@@ -600,6 +601,16 @@ class AddModelMenu:
600
601
  api_key_env = f"${provider.env[0]}" if provider.env else "$API_KEY"
601
602
  config["custom_endpoint"] = {"url": api_url, "api_key": api_key_env}
602
603
 
604
+ # Special handling for minimax: uses custom_anthropic but needs custom_endpoint
605
+ # and the URL needs /v1 stripped (comes as https://api.minimax.io/anthropic/v1)
606
+ if provider.id == "minimax" and provider.api:
607
+ api_url = provider.api
608
+ # Strip /v1 suffix if present
609
+ if api_url.endswith("/v1"):
610
+ api_url = api_url[:-3]
611
+ api_key_env = f"${provider.env[0]}" if provider.env else "$API_KEY"
612
+ config["custom_endpoint"] = {"url": api_url, "api_key": api_key_env}
613
+
603
614
  # Add context length if available
604
615
  if model.context_length and model.context_length > 0:
605
616
  config["context_length"] = model.context_length
@@ -18,6 +18,9 @@ def _load_builtin_plugins(plugins_dir: Path) -> list[str]:
18
18
 
19
19
  Returns list of successfully loaded plugin names.
20
20
  """
21
+ # Import safety permission check for shell_safety plugin
22
+ from code_puppy.config import get_safety_permission_level
23
+
21
24
  loaded = []
22
25
 
23
26
  for item in plugins_dir.iterdir():
@@ -26,6 +29,15 @@ def _load_builtin_plugins(plugins_dir: Path) -> list[str]:
26
29
  callbacks_file = item / "register_callbacks.py"
27
30
 
28
31
  if callbacks_file.exists():
32
+ # Skip shell_safety plugin unless safety_permission_level is "low" or "none"
33
+ if plugin_name == "shell_safety":
34
+ safety_level = get_safety_permission_level()
35
+ if safety_level not in ("none", "low"):
36
+ logger.debug(
37
+ f"Skipping shell_safety plugin - safety_permission_level is '{safety_level}' (needs 'low' or 'none')"
38
+ )
39
+ continue
40
+
29
41
  try:
30
42
  module_name = f"code_puppy.plugins.{plugin_name}.register_callbacks"
31
43
  importlib.import_module(module_name)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.324"
7
+ version = "0.0.325"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
File without changes
File without changes
File without changes