code-puppy 0.0.350__tar.gz → 0.0.352__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 (184) hide show
  1. {code_puppy-0.0.350 → code_puppy-0.0.352}/PKG-INFO +1 -1
  2. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/claude_cache_client.py +249 -34
  3. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +90 -19
  4. {code_puppy-0.0.350 → code_puppy-0.0.352}/pyproject.toml +1 -1
  5. {code_puppy-0.0.350 → code_puppy-0.0.352}/.gitignore +0 -0
  6. {code_puppy-0.0.350 → code_puppy-0.0.352}/LICENSE +0 -0
  7. {code_puppy-0.0.350 → code_puppy-0.0.352}/README.md +0 -0
  8. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/__init__.py +0 -0
  9. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/__main__.py +0 -0
  10. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/__init__.py +0 -0
  11. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_c_reviewer.py +0 -0
  12. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_code_puppy.py +0 -0
  13. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_code_reviewer.py +0 -0
  14. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  15. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_creator_agent.py +0 -0
  16. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  17. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  18. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_manager.py +0 -0
  19. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_planning.py +0 -0
  20. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_python_programmer.py +0 -0
  21. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_python_reviewer.py +0 -0
  22. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_qa_expert.py +0 -0
  23. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_qa_kitten.py +0 -0
  24. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_security_auditor.py +0 -0
  25. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  26. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/base_agent.py +0 -0
  27. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/event_stream_handler.py +0 -0
  28. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/json_agent.py +0 -0
  29. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/agents/prompt_reviewer.py +0 -0
  30. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/callbacks.py +0 -0
  31. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/chatgpt_codex_client.py +0 -0
  32. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/cli_runner.py +0 -0
  33. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/__init__.py +0 -0
  34. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/add_model_menu.py +0 -0
  35. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/attachments.py +0 -0
  36. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/autosave_menu.py +0 -0
  37. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/clipboard.py +0 -0
  38. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/colors_menu.py +0 -0
  39. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/command_handler.py +0 -0
  40. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/command_registry.py +0 -0
  41. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/config_commands.py +0 -0
  42. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/core_commands.py +0 -0
  43. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/diff_menu.py +0 -0
  44. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/file_path_completion.py +0 -0
  45. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/load_context_completion.py +0 -0
  46. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/__init__.py +0 -0
  47. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/base.py +0 -0
  48. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  49. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  50. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  51. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/edit_command.py +0 -0
  52. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/handler.py +0 -0
  53. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/help_command.py +0 -0
  54. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/install_command.py +0 -0
  55. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/install_menu.py +0 -0
  56. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/list_command.py +0 -0
  57. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/logs_command.py +0 -0
  58. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/remove_command.py +0 -0
  59. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/restart_command.py +0 -0
  60. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/search_command.py +0 -0
  61. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  62. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/start_command.py +0 -0
  63. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/status_command.py +0 -0
  64. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  65. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/stop_command.py +0 -0
  66. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/test_command.py +0 -0
  67. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/utils.py +0 -0
  68. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  69. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/mcp_completion.py +0 -0
  70. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/model_picker_completion.py +0 -0
  71. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/model_settings_menu.py +0 -0
  72. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/motd.py +0 -0
  73. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/onboarding_slides.py +0 -0
  74. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/onboarding_wizard.py +0 -0
  75. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/pin_command_completion.py +0 -0
  76. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  77. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/session_commands.py +0 -0
  78. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/command_line/utils.py +0 -0
  79. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/config.py +0 -0
  80. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/error_logging.py +0 -0
  81. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/gemini_code_assist.py +0 -0
  82. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/http_utils.py +0 -0
  83. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/keymap.py +0 -0
  84. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/main.py +0 -0
  85. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/__init__.py +0 -0
  86. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/async_lifecycle.py +0 -0
  87. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/blocking_startup.py +0 -0
  88. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  89. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/circuit_breaker.py +0 -0
  90. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/config_wizard.py +0 -0
  91. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/dashboard.py +0 -0
  92. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/error_isolation.py +0 -0
  93. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/examples/retry_example.py +0 -0
  94. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/health_monitor.py +0 -0
  95. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/managed_server.py +0 -0
  96. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/manager.py +0 -0
  97. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/mcp_logs.py +0 -0
  98. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/registry.py +0 -0
  99. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/retry_manager.py +0 -0
  100. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  101. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/status_tracker.py +0 -0
  102. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/mcp_/system_tools.py +0 -0
  103. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/__init__.py +0 -0
  104. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/bus.py +0 -0
  105. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/commands.py +0 -0
  106. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/markdown_patches.py +0 -0
  107. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/message_queue.py +0 -0
  108. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/messages.py +0 -0
  109. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/queue_console.py +0 -0
  110. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/renderers.py +0 -0
  111. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/rich_renderer.py +0 -0
  112. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/spinner/__init__.py +0 -0
  113. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  114. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  115. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/model_factory.py +0 -0
  116. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/model_utils.py +0 -0
  117. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/models.json +0 -0
  118. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/models_dev_api.json +0 -0
  119. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/models_dev_parser.py +0 -0
  120. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/__init__.py +0 -0
  121. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  122. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  123. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  124. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  125. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  126. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
  127. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  128. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  129. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  130. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
  131. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
  132. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  133. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  134. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  135. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  136. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  137. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  138. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  139. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  140. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  141. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  142. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  143. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  144. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  145. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  146. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  147. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/example_custom_command/README.md +0 -0
  148. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  149. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  150. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  151. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  152. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  153. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  154. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  155. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  156. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
  157. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/prompts/codex_system_prompt.md +0 -0
  158. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/pydantic_patches.py +0 -0
  159. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/reopenable_async_client.py +0 -0
  160. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/round_robin_model.py +0 -0
  161. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/session_storage.py +0 -0
  162. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/status_display.py +0 -0
  163. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/summarization_agent.py +0 -0
  164. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/terminal_utils.py +0 -0
  165. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/__init__.py +0 -0
  166. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/agent_tools.py +0 -0
  167. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/__init__.py +0 -0
  168. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_control.py +0 -0
  169. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_interactions.py +0 -0
  170. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_locators.py +0 -0
  171. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_navigation.py +0 -0
  172. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  173. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_scripts.py +0 -0
  174. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/browser_workflows.py +0 -0
  175. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  176. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/browser/vqa_agent.py +0 -0
  177. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/command_runner.py +0 -0
  178. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/common.py +0 -0
  179. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/display.py +0 -0
  180. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/file_modifications.py +0 -0
  181. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/file_operations.py +0 -0
  182. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/tools/tools_content.py +0 -0
  183. {code_puppy-0.0.350 → code_puppy-0.0.352}/code_puppy/uvx_detection.py +0 -0
  184. {code_puppy-0.0.350 → code_puppy-0.0.352}/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.350
3
+ Version: 0.0.352
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
@@ -5,6 +5,11 @@ ClaudeCacheAsyncClient: httpx client that tries to patch /v1/messages bodies.
5
5
  We now also expose `patch_anthropic_client_messages` which monkey-patches
6
6
  AsyncAnthropic.messages.create() so we can inject cache_control BEFORE
7
7
  serialization, avoiding httpx/Pydantic internals.
8
+
9
+ This module also handles:
10
+ - Tool name prefixing/unprefixing for Claude Code OAuth compatibility
11
+ - Header transformations (anthropic-beta, user-agent)
12
+ - URL modifications (adding ?beta=true query param)
8
13
  """
9
14
 
10
15
  from __future__ import annotations
@@ -12,8 +17,10 @@ from __future__ import annotations
12
17
  import base64
13
18
  import json
14
19
  import logging
20
+ import re
15
21
  import time
16
22
  from typing import Any, Callable, MutableMapping
23
+ from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
17
24
 
18
25
  import httpx
19
26
 
@@ -22,6 +29,13 @@ logger = logging.getLogger(__name__)
22
29
  # Refresh token if it's older than 1 hour (3600 seconds)
23
30
  TOKEN_MAX_AGE_SECONDS = 3600
24
31
 
32
+ # Tool name prefix for Claude Code OAuth compatibility
33
+ # Tools are prefixed on outgoing requests and unprefixed on incoming responses
34
+ TOOL_PREFIX = "cp_"
35
+
36
+ # User-Agent to send with Claude Code OAuth requests
37
+ CLAUDE_CLI_USER_AGENT = "claude-cli/2.1.2 (external, cli)"
38
+
25
39
  try:
26
40
  from anthropic import AsyncAnthropic
27
41
  except ImportError: # pragma: no cover - optional dep
@@ -29,6 +43,22 @@ except ImportError: # pragma: no cover - optional dep
29
43
 
30
44
 
31
45
  class ClaudeCacheAsyncClient(httpx.AsyncClient):
46
+ """Async HTTP client with Claude Code OAuth transformations.
47
+
48
+ Handles:
49
+ - Cache control injection for prompt caching
50
+ - Tool name prefixing on outgoing requests
51
+ - Tool name unprefixing on incoming streaming responses
52
+ - Header transformations (anthropic-beta, user-agent)
53
+ - URL modifications (adding ?beta=true)
54
+ - Proactive token refresh
55
+ """
56
+
57
+ # Regex pattern for unprefixing tool names in streaming responses
58
+ _TOOL_UNPREFIX_PATTERN = re.compile(
59
+ rf'"name"\s*:\s*"{re.escape(TOOL_PREFIX)}([^"]+)"'
60
+ )
61
+
32
62
  def _get_jwt_age_seconds(self, token: str | None) -> float | None:
33
63
  """Decode a JWT and return its age in seconds.
34
64
 
@@ -107,9 +137,100 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
107
137
  )
108
138
  return should_refresh
109
139
 
140
+ @staticmethod
141
+ def _prefix_tool_names(body: bytes) -> bytes | None:
142
+ """Prefix all tool names in the request body with TOOL_PREFIX.
143
+
144
+ This is required for Claude Code OAuth compatibility - tools must be
145
+ prefixed on outgoing requests and unprefixed on incoming responses.
146
+ """
147
+ try:
148
+ data = json.loads(body.decode("utf-8"))
149
+ except Exception:
150
+ return None
151
+
152
+ if not isinstance(data, dict):
153
+ return None
154
+
155
+ tools = data.get("tools")
156
+ if not isinstance(tools, list) or not tools:
157
+ return None
158
+
159
+ modified = False
160
+ for tool in tools:
161
+ if isinstance(tool, dict) and "name" in tool:
162
+ name = tool["name"]
163
+ if name and not name.startswith(TOOL_PREFIX):
164
+ tool["name"] = f"{TOOL_PREFIX}{name}"
165
+ modified = True
166
+
167
+ if not modified:
168
+ return None
169
+
170
+ return json.dumps(data).encode("utf-8")
171
+
172
+ def _unprefix_tool_names_in_text(self, text: str) -> str:
173
+ """Remove TOOL_PREFIX from tool names in streaming response text."""
174
+ return self._TOOL_UNPREFIX_PATTERN.sub(r'"name": "\1"', text)
175
+
176
+ @staticmethod
177
+ def _transform_headers_for_claude_code(
178
+ headers: MutableMapping[str, str],
179
+ ) -> None:
180
+ """Transform headers for Claude Code OAuth compatibility.
181
+
182
+ - Sets user-agent to claude-cli
183
+ - Merges anthropic-beta headers appropriately
184
+ - Removes x-api-key (using Bearer auth instead)
185
+ """
186
+ # Set user-agent
187
+ headers["user-agent"] = CLAUDE_CLI_USER_AGENT
188
+
189
+ # Handle anthropic-beta header
190
+ incoming_beta = headers.get("anthropic-beta", "")
191
+ incoming_betas = [b.strip() for b in incoming_beta.split(",") if b.strip()]
192
+
193
+ # Check if claude-code beta was explicitly requested
194
+ include_claude_code = "claude-code-20250219" in incoming_betas
195
+
196
+ # Build merged betas list
197
+ merged_betas = [
198
+ "oauth-2025-04-20",
199
+ "interleaved-thinking-2025-05-14",
200
+ ]
201
+ if include_claude_code:
202
+ merged_betas.append("claude-code-20250219")
203
+
204
+ headers["anthropic-beta"] = ",".join(merged_betas)
205
+
206
+ # Remove x-api-key if present (we use Bearer auth)
207
+ for key in ["x-api-key", "X-API-Key", "X-Api-Key"]:
208
+ if key in headers:
209
+ del headers[key]
210
+
211
+ @staticmethod
212
+ def _add_beta_query_param(url: httpx.URL) -> httpx.URL:
213
+ """Add ?beta=true query parameter to the URL if not already present."""
214
+ # Parse the URL
215
+ parsed = urlparse(str(url))
216
+ query_params = parse_qs(parsed.query)
217
+
218
+ # Only add if not already present
219
+ if "beta" not in query_params:
220
+ query_params["beta"] = ["true"]
221
+ # Rebuild query string
222
+ new_query = urlencode(query_params, doseq=True)
223
+ # Rebuild URL
224
+ new_parsed = parsed._replace(query=new_query)
225
+ return httpx.URL(urlunparse(new_parsed))
226
+
227
+ return url
228
+
110
229
  async def send(
111
230
  self, request: httpx.Request, *args: Any, **kwargs: Any
112
231
  ) -> httpx.Response: # type: ignore[override]
232
+ is_messages_endpoint = request.url.path.endswith("/v1/messages")
233
+
113
234
  # Proactive token refresh: check JWT age before every request
114
235
  if not request.extensions.get("claude_oauth_refresh_attempted"):
115
236
  try:
@@ -131,50 +252,88 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
131
252
  except Exception as exc:
132
253
  logger.debug("Error during proactive token refresh check: %s", exc)
133
254
 
134
- try:
135
- if request.url.path.endswith("/v1/messages"):
255
+ # Apply Claude Code OAuth transformations for /v1/messages
256
+ if is_messages_endpoint:
257
+ try:
136
258
  body_bytes = self._extract_body_bytes(request)
259
+ headers = dict(request.headers)
260
+ url = request.url
261
+ body_modified = False
262
+ headers_modified = False
263
+
264
+ # 1. Transform headers for Claude Code OAuth
265
+ self._transform_headers_for_claude_code(headers)
266
+ headers_modified = True
267
+
268
+ # 2. Add ?beta=true query param
269
+ url = self._add_beta_query_param(url)
270
+
271
+ # 3. Prefix tool names in request body
137
272
  if body_bytes:
138
- updated = self._inject_cache_control(body_bytes)
139
- if updated is not None:
140
- # Rebuild a request with the updated body and transplant internals
141
- try:
142
- rebuilt = self.build_request(
143
- method=request.method,
144
- url=request.url,
145
- headers=request.headers,
146
- content=updated,
147
- )
148
-
149
- # Copy core internals so httpx uses the modified body/stream
150
- if hasattr(rebuilt, "_content"):
151
- setattr(request, "_content", rebuilt._content) # type: ignore[attr-defined]
152
- if hasattr(rebuilt, "stream"):
153
- request.stream = rebuilt.stream
154
- if hasattr(rebuilt, "extensions"):
155
- request.extensions = rebuilt.extensions
156
-
157
- # Ensure Content-Length matches the new body
158
- request.headers["Content-Length"] = str(len(updated))
159
-
160
- except Exception:
161
- # Swallow instrumentation errors; do not break real calls.
162
- pass
163
- except Exception:
164
- # Swallow wrapper errors; do not break real calls.
165
- pass
273
+ prefixed_body = self._prefix_tool_names(body_bytes)
274
+ if prefixed_body is not None:
275
+ body_bytes = prefixed_body
276
+ body_modified = True
277
+
278
+ # 4. Inject cache_control
279
+ cached_body = self._inject_cache_control(body_bytes)
280
+ if cached_body is not None:
281
+ body_bytes = cached_body
282
+ body_modified = True
283
+
284
+ # Rebuild request if anything changed
285
+ if body_modified or headers_modified or url != request.url:
286
+ try:
287
+ rebuilt = self.build_request(
288
+ method=request.method,
289
+ url=url,
290
+ headers=headers,
291
+ content=body_bytes,
292
+ )
293
+
294
+ # Copy core internals so httpx uses the modified body/stream
295
+ if hasattr(rebuilt, "_content"):
296
+ setattr(request, "_content", rebuilt._content) # type: ignore[attr-defined]
297
+ if hasattr(rebuilt, "stream"):
298
+ request.stream = rebuilt.stream
299
+ if hasattr(rebuilt, "extensions"):
300
+ request.extensions = rebuilt.extensions
301
+
302
+ # Update URL
303
+ request.url = url
304
+
305
+ # Update headers
306
+ for key, value in headers.items():
307
+ request.headers[key] = value
308
+
309
+ # Ensure Content-Length matches the new body
310
+ if body_bytes:
311
+ request.headers["Content-Length"] = str(len(body_bytes))
312
+
313
+ except Exception as exc:
314
+ logger.debug("Error rebuilding request: %s", exc)
315
+
316
+ except Exception as exc:
317
+ logger.debug("Error in Claude Code transformations: %s", exc)
318
+
319
+ # Send the request
166
320
  response = await super().send(request, *args, **kwargs)
321
+
322
+ # Transform streaming response to unprefix tool names
323
+ if is_messages_endpoint and response.status_code == 200:
324
+ try:
325
+ response = self._wrap_response_with_tool_unprefixing(response, request)
326
+ except Exception as exc:
327
+ logger.debug("Error wrapping response for tool unprefixing: %s", exc)
328
+
329
+ # Handle auth errors with token refresh
167
330
  try:
168
- # Check for both 401 and 400 - Anthropic/Cloudflare may return 400 for auth errors
169
- # Also check if it's a Cloudflare HTML error response
170
331
  if response.status_code in (400, 401) and not request.extensions.get(
171
332
  "claude_oauth_refresh_attempted"
172
333
  ):
173
- # Determine if this is an auth error (including Cloudflare HTML errors)
174
334
  is_auth_error = response.status_code == 401
175
335
 
176
336
  if response.status_code == 400:
177
- # Check if this is a Cloudflare HTML error
178
337
  is_auth_error = self._is_cloudflare_html_error(response)
179
338
  if is_auth_error:
180
339
  logger.info(
@@ -203,8 +362,64 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
203
362
  logger.warning("Token refresh failed, returning original error")
204
363
  except Exception as exc:
205
364
  logger.debug("Error during token refresh attempt: %s", exc)
365
+
206
366
  return response
207
367
 
368
+ def _wrap_response_with_tool_unprefixing(
369
+ self, response: httpx.Response, request: httpx.Request
370
+ ) -> httpx.Response:
371
+ """Wrap a streaming response to unprefix tool names.
372
+
373
+ Creates a new response with a transformed stream that removes the
374
+ TOOL_PREFIX from tool names in the response body.
375
+ """
376
+ original_stream = response.stream
377
+ unprefix_fn = self._unprefix_tool_names_in_text
378
+
379
+ class UnprefixingStream(httpx.AsyncByteStream):
380
+ """Async byte stream that unprefixes tool names.
381
+
382
+ Inherits from httpx.AsyncByteStream to ensure proper stream interface.
383
+ """
384
+
385
+ def __init__(self, inner_stream: Any) -> None:
386
+ self._inner = inner_stream
387
+
388
+ async def __aiter__(self):
389
+ async for chunk in self._inner:
390
+ if isinstance(chunk, bytes):
391
+ text = chunk.decode("utf-8", errors="replace")
392
+ text = unprefix_fn(text)
393
+ yield text.encode("utf-8")
394
+ else:
395
+ yield chunk
396
+
397
+ async def aclose(self) -> None:
398
+ if hasattr(self._inner, "aclose"):
399
+ try:
400
+ result = self._inner.aclose()
401
+ # Handle both sync and async aclose
402
+ if hasattr(result, "__await__"):
403
+ await result
404
+ except Exception:
405
+ pass # Ignore close errors
406
+ elif hasattr(self._inner, "close"):
407
+ try:
408
+ self._inner.close()
409
+ except Exception:
410
+ pass
411
+
412
+ # Create a new response with the transformed stream
413
+ # Must include request for raise_for_status() to work
414
+ new_response = httpx.Response(
415
+ status_code=response.status_code,
416
+ headers=response.headers,
417
+ stream=UnprefixingStream(original_stream),
418
+ extensions=response.extensions,
419
+ request=request,
420
+ )
421
+ return new_response
422
+
208
423
  @staticmethod
209
424
  def _extract_body_bytes(request: httpx.Request) -> bytes | None:
210
425
  # Try public content first
@@ -215,9 +215,39 @@ class AntigravityModel(GoogleModel):
215
215
  response = await client.post(url, json=body)
216
216
 
217
217
  if response.status_code != 200:
218
- raise RuntimeError(
219
- f"Antigravity API Error {response.status_code}: {response.text}"
220
- )
218
+ # Check for corrupted thought signature error and retry
219
+ # Error 400: { error: { code: 400, message: Corrupted thought signature., status: INVALID_ARGUMENT } }
220
+ error_text = response.text
221
+ if (
222
+ response.status_code == 400
223
+ and "Corrupted thought signature" in error_text
224
+ ):
225
+ logger.warning(
226
+ "Received 400 Corrupted thought signature. Backfilling signatures and retrying."
227
+ )
228
+ _backfill_thought_signatures(messages)
229
+
230
+ # Re-map messages
231
+ system_instruction, contents = await self._map_messages(
232
+ messages, model_request_parameters
233
+ )
234
+
235
+ # Update body
236
+ body["contents"] = contents
237
+ if system_instruction:
238
+ body["systemInstruction"] = system_instruction
239
+
240
+ # Retry request
241
+ response = await client.post(url, json=body)
242
+ # Check error again after retry
243
+ if response.status_code != 200:
244
+ raise RuntimeError(
245
+ f"Antigravity API Error {response.status_code}: {response.text}"
246
+ )
247
+ else:
248
+ raise RuntimeError(
249
+ f"Antigravity API Error {response.status_code}: {error_text}"
250
+ )
221
251
 
222
252
  data = response.json()
223
253
 
@@ -318,24 +348,56 @@ class AntigravityModel(GoogleModel):
318
348
 
319
349
  # Create async generator for SSE events
320
350
  async def stream_chunks() -> AsyncIterator[dict[str, Any]]:
321
- async with client.stream("POST", url, json=body) as response:
322
- if response.status_code != 200:
323
- text = await response.aread()
324
- raise RuntimeError(
325
- f"Antigravity API Error {response.status_code}: {text.decode()}"
326
- )
351
+ retry_count = 0
352
+ while retry_count < 2:
353
+ should_retry = False
354
+ async with client.stream("POST", url, json=body) as response:
355
+ if response.status_code != 200:
356
+ text = await response.aread()
357
+ error_msg = text.decode()
358
+ if (
359
+ response.status_code == 400
360
+ and "Corrupted thought signature" in error_msg
361
+ and retry_count == 0
362
+ ):
363
+ should_retry = True
364
+ else:
365
+ raise RuntimeError(
366
+ f"Antigravity API Error {response.status_code}: {error_msg}"
367
+ )
327
368
 
328
- async for line in response.aiter_lines():
329
- line = line.strip()
330
- if not line:
331
- continue
332
- if line.startswith("data: "):
333
- json_str = line[6:] # Remove 'data: ' prefix
334
- if json_str:
335
- try:
336
- yield json.loads(json_str)
337
- except json.JSONDecodeError:
369
+ if not should_retry:
370
+ async for line in response.aiter_lines():
371
+ line = line.strip()
372
+ if not line:
338
373
  continue
374
+ if line.startswith("data: "):
375
+ json_str = line[6:] # Remove 'data: ' prefix
376
+ if json_str:
377
+ try:
378
+ yield json.loads(json_str)
379
+ except json.JSONDecodeError:
380
+ continue
381
+ return
382
+
383
+ # Handle retry outside the context manager
384
+ if should_retry:
385
+ logger.warning(
386
+ "Received 400 Corrupted thought signature in stream. Backfilling and retrying."
387
+ )
388
+ _backfill_thought_signatures(messages)
389
+
390
+ # Re-map messages
391
+ system_instruction, contents = await self._map_messages(
392
+ messages, model_request_parameters
393
+ )
394
+
395
+ # Update body in place
396
+ body["contents"] = contents
397
+ if system_instruction:
398
+ body["systemInstruction"] = system_instruction
399
+
400
+ retry_count += 1
339
401
 
340
402
  # Create streaming response
341
403
  streamed = AntigravityStreamingResponse(
@@ -666,3 +728,12 @@ def _antigravity_process_response_from_parts(
666
728
  provider_details=vendor_details,
667
729
  provider_name=provider_name,
668
730
  )
731
+
732
+
733
+ def _backfill_thought_signatures(messages: list[ModelMessage]) -> None:
734
+ """Backfill all thinking parts with the bypass signature."""
735
+ for m in messages:
736
+ if isinstance(m, ModelResponse):
737
+ for part in m.parts:
738
+ if isinstance(part, ThinkingPart):
739
+ object.__setattr__(part, "signature", BYPASS_THOUGHT_SIGNATURE)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.350"
7
+ version = "0.0.352"
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