code-puppy 0.0.335__tar.gz → 0.0.337__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 (181) hide show
  1. {code_puppy-0.0.335 → code_puppy-0.0.337}/PKG-INFO +1 -1
  2. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/http_utils.py +93 -130
  3. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/messages.py +3 -0
  4. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/rich_renderer.py +114 -22
  5. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +67 -11
  6. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/transport.py +127 -58
  7. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/command_runner.py +1 -0
  8. {code_puppy-0.0.335 → code_puppy-0.0.337}/pyproject.toml +1 -1
  9. {code_puppy-0.0.335 → code_puppy-0.0.337}/.gitignore +0 -0
  10. {code_puppy-0.0.335 → code_puppy-0.0.337}/LICENSE +0 -0
  11. {code_puppy-0.0.335 → code_puppy-0.0.337}/README.md +0 -0
  12. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/__init__.py +0 -0
  13. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/__main__.py +0 -0
  14. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/__init__.py +0 -0
  15. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_c_reviewer.py +0 -0
  16. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_code_puppy.py +0 -0
  17. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_code_reviewer.py +0 -0
  18. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  19. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_creator_agent.py +0 -0
  20. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  21. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  22. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_manager.py +0 -0
  23. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_planning.py +0 -0
  24. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_python_programmer.py +0 -0
  25. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_python_reviewer.py +0 -0
  26. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_qa_expert.py +0 -0
  27. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_qa_kitten.py +0 -0
  28. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_security_auditor.py +0 -0
  29. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  30. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/base_agent.py +0 -0
  31. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/json_agent.py +0 -0
  32. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/agents/prompt_reviewer.py +0 -0
  33. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/callbacks.py +0 -0
  34. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/chatgpt_codex_client.py +0 -0
  35. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/claude_cache_client.py +0 -0
  36. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/cli_runner.py +0 -0
  37. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/__init__.py +0 -0
  38. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/add_model_menu.py +0 -0
  39. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/attachments.py +0 -0
  40. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/autosave_menu.py +0 -0
  41. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/colors_menu.py +0 -0
  42. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/command_handler.py +0 -0
  43. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/command_registry.py +0 -0
  44. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/config_commands.py +0 -0
  45. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/core_commands.py +0 -0
  46. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/diff_menu.py +0 -0
  47. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/file_path_completion.py +0 -0
  48. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/load_context_completion.py +0 -0
  49. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/__init__.py +0 -0
  50. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/add_command.py +0 -0
  51. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/base.py +0 -0
  52. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  53. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  54. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  55. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/edit_command.py +0 -0
  56. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/handler.py +0 -0
  57. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/help_command.py +0 -0
  58. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/install_command.py +0 -0
  59. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/install_menu.py +0 -0
  60. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/list_command.py +0 -0
  61. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/logs_command.py +0 -0
  62. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/remove_command.py +0 -0
  63. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/restart_command.py +0 -0
  64. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/search_command.py +0 -0
  65. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  66. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/start_command.py +0 -0
  67. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/status_command.py +0 -0
  68. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  69. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/stop_command.py +0 -0
  70. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/test_command.py +0 -0
  71. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/utils.py +0 -0
  72. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  73. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/mcp_completion.py +0 -0
  74. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/model_picker_completion.py +0 -0
  75. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/model_settings_menu.py +0 -0
  76. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/motd.py +0 -0
  77. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/onboarding_slides.py +0 -0
  78. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/onboarding_wizard.py +0 -0
  79. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/pin_command_completion.py +0 -0
  80. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  81. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/session_commands.py +0 -0
  82. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/command_line/utils.py +0 -0
  83. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/config.py +0 -0
  84. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/error_logging.py +0 -0
  85. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/gemini_code_assist.py +0 -0
  86. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/keymap.py +0 -0
  87. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/main.py +0 -0
  88. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/__init__.py +0 -0
  89. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/async_lifecycle.py +0 -0
  90. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/blocking_startup.py +0 -0
  91. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  92. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/circuit_breaker.py +0 -0
  93. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/config_wizard.py +0 -0
  94. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/dashboard.py +0 -0
  95. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/error_isolation.py +0 -0
  96. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/examples/retry_example.py +0 -0
  97. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/health_monitor.py +0 -0
  98. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/managed_server.py +0 -0
  99. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/manager.py +0 -0
  100. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/mcp_logs.py +0 -0
  101. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/registry.py +0 -0
  102. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/retry_manager.py +0 -0
  103. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  104. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/status_tracker.py +0 -0
  105. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/mcp_/system_tools.py +0 -0
  106. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/__init__.py +0 -0
  107. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/bus.py +0 -0
  108. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/commands.py +0 -0
  109. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/markdown_patches.py +0 -0
  110. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/message_queue.py +0 -0
  111. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/queue_console.py +0 -0
  112. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/renderers.py +0 -0
  113. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/spinner/__init__.py +0 -0
  114. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  115. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  116. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/model_factory.py +0 -0
  117. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/model_utils.py +0 -0
  118. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/models.json +0 -0
  119. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/models_dev_api.json +0 -0
  120. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/models_dev_parser.py +0 -0
  121. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/__init__.py +0 -0
  122. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  123. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  124. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  125. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  126. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  127. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
  128. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  129. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  130. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  131. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
  132. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  133. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  134. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  135. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  136. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  137. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  138. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  139. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  140. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  141. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  142. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  143. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  144. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  145. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  146. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  147. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/example_custom_command/README.md +0 -0
  148. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  149. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  150. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  151. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  152. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  153. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  154. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  155. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  156. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/prompts/codex_system_prompt.md +0 -0
  157. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/pydantic_patches.py +0 -0
  158. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/reopenable_async_client.py +0 -0
  159. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/round_robin_model.py +0 -0
  160. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/session_storage.py +0 -0
  161. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/status_display.py +0 -0
  162. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/summarization_agent.py +0 -0
  163. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/terminal_utils.py +0 -0
  164. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/__init__.py +0 -0
  165. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/agent_tools.py +0 -0
  166. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/__init__.py +0 -0
  167. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_control.py +0 -0
  168. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_interactions.py +0 -0
  169. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_locators.py +0 -0
  170. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_navigation.py +0 -0
  171. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  172. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_scripts.py +0 -0
  173. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/browser_workflows.py +0 -0
  174. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  175. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/browser/vqa_agent.py +0 -0
  176. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/common.py +0 -0
  177. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/file_modifications.py +0 -0
  178. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/file_operations.py +0 -0
  179. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/tools/tools_content.py +0 -0
  180. {code_puppy-0.0.335 → code_puppy-0.0.337}/code_puppy/uvx_detection.py +0 -0
  181. {code_puppy-0.0.335 → code_puppy-0.0.337}/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.335
3
+ Version: 0.0.337
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,10 +5,10 @@ This module provides functions for creating properly configured HTTP clients.
5
5
  """
6
6
 
7
7
  import asyncio
8
- import logging
9
8
  import os
10
9
  import socket
11
10
  import time
11
+ from dataclasses import dataclass
12
12
  from typing import Any, Dict, Optional, Union
13
13
 
14
14
  import httpx
@@ -16,7 +16,69 @@ import requests
16
16
 
17
17
  from code_puppy.config import get_http2
18
18
 
19
- logger = logging.getLogger(__name__)
19
+
20
+ @dataclass
21
+ class ProxyConfig:
22
+ """Configuration for proxy and SSL settings."""
23
+
24
+ verify: Union[bool, str, None]
25
+ trust_env: bool
26
+ proxy_url: str | None
27
+ disable_retry: bool
28
+ http2_enabled: bool
29
+
30
+
31
+ def _resolve_proxy_config(verify: Union[bool, str, None] = None) -> ProxyConfig:
32
+ """Resolve proxy, SSL, and retry settings from environment.
33
+
34
+ This centralizes the logic for detecting proxies, determining SSL verification,
35
+ and checking if retry transport should be disabled.
36
+ """
37
+ if verify is None:
38
+ verify = get_cert_bundle_path()
39
+
40
+ http2_enabled = get_http2()
41
+
42
+ disable_retry = os.environ.get(
43
+ "CODE_PUPPY_DISABLE_RETRY_TRANSPORT", ""
44
+ ).lower() in ("1", "true", "yes")
45
+
46
+ has_proxy = bool(
47
+ os.environ.get("HTTP_PROXY")
48
+ or os.environ.get("HTTPS_PROXY")
49
+ or os.environ.get("http_proxy")
50
+ or os.environ.get("https_proxy")
51
+ )
52
+
53
+ # Determine trust_env and verify based on proxy/retry settings
54
+ if disable_retry:
55
+ # Test mode: disable SSL verification for proxy testing
56
+ verify = False
57
+ trust_env = True
58
+ elif has_proxy:
59
+ # Production proxy: keep SSL verification enabled
60
+ trust_env = True
61
+ else:
62
+ trust_env = False
63
+
64
+ # Extract proxy URL
65
+ proxy_url = None
66
+ if has_proxy:
67
+ proxy_url = (
68
+ os.environ.get("HTTPS_PROXY")
69
+ or os.environ.get("https_proxy")
70
+ or os.environ.get("HTTP_PROXY")
71
+ or os.environ.get("http_proxy")
72
+ )
73
+
74
+ return ProxyConfig(
75
+ verify=verify,
76
+ trust_env=trust_env,
77
+ proxy_url=proxy_url,
78
+ disable_retry=disable_retry,
79
+ http2_enabled=http2_enabled,
80
+ )
81
+
20
82
 
21
83
  try:
22
84
  from .reopenable_async_client import ReopenableAsyncClient
@@ -58,14 +120,7 @@ class RetryingAsyncClient(httpx.AsyncClient):
58
120
 
59
121
  for attempt in range(self.max_retries + 1):
60
122
  try:
61
- # Clone request for retry (streams might be consumed)
62
- # But only if it's not the first attempt
63
- req_to_send = request
64
- if attempt > 0:
65
- # httpx requests are reusable, but we need to be careful with streams
66
- pass
67
-
68
- response = await super().send(req_to_send, **kwargs)
123
+ response = await super().send(request, **kwargs)
69
124
  last_response = response
70
125
 
71
126
  # Check for retryable status
@@ -128,7 +183,7 @@ class RetryingAsyncClient(httpx.AsyncClient):
128
183
  return last_response
129
184
 
130
185
 
131
- def get_cert_bundle_path() -> str:
186
+ def get_cert_bundle_path() -> str | None:
132
187
  # First check if SSL_CERT_FILE environment variable is set
133
188
  ssl_cert_file = os.environ.get("SSL_CERT_FILE")
134
189
  if ssl_cert_file and os.path.exists(ssl_cert_file):
@@ -164,66 +219,26 @@ def create_async_client(
164
219
  headers: Optional[Dict[str, str]] = None,
165
220
  retry_status_codes: tuple = (429, 502, 503, 504),
166
221
  ) -> httpx.AsyncClient:
167
- if verify is None:
168
- verify = get_cert_bundle_path()
169
-
170
- # Check if HTTP/2 is enabled in config
171
- http2_enabled = get_http2()
172
-
173
- # Check if custom retry transport should be disabled (e.g., for integration tests with proxies)
174
- disable_retry_transport = os.environ.get(
175
- "CODE_PUPPY_DISABLE_RETRY_TRANSPORT", ""
176
- ).lower() in ("1", "true", "yes")
177
-
178
- # Check if proxy environment variables are set
179
- has_proxy = bool(
180
- os.environ.get("HTTP_PROXY")
181
- or os.environ.get("HTTPS_PROXY")
182
- or os.environ.get("http_proxy")
183
- or os.environ.get("https_proxy")
184
- )
185
-
186
- # When retry transport is disabled (test mode), disable SSL verification
187
- # for proxy testing. For production proxies, SSL should still be verified!
188
- if disable_retry_transport:
189
- verify = False
190
- trust_env = True
191
- elif has_proxy:
192
- # Production proxy detected - keep SSL verification enabled for security
193
- trust_env = True
194
- else:
195
- trust_env = False
196
-
197
- # Extract proxy URL if needed
198
- proxy_url = None
199
- if has_proxy:
200
- proxy_url = (
201
- os.environ.get("HTTPS_PROXY")
202
- or os.environ.get("https_proxy")
203
- or os.environ.get("HTTP_PROXY")
204
- or os.environ.get("http_proxy")
205
- )
222
+ config = _resolve_proxy_config(verify)
206
223
 
207
- # Use RetryingAsyncClient if retries are enabled
208
- if not disable_retry_transport:
224
+ if not config.disable_retry:
209
225
  return RetryingAsyncClient(
210
226
  retry_status_codes=retry_status_codes,
211
- proxy=proxy_url,
212
- verify=verify,
227
+ proxy=config.proxy_url,
228
+ verify=config.verify,
213
229
  headers=headers or {},
214
230
  timeout=timeout,
215
- http2=http2_enabled,
216
- trust_env=trust_env,
231
+ http2=config.http2_enabled,
232
+ trust_env=config.trust_env,
217
233
  )
218
234
  else:
219
- # Regular client for testing
220
235
  return httpx.AsyncClient(
221
- proxy=proxy_url,
222
- verify=verify,
236
+ proxy=config.proxy_url,
237
+ verify=config.verify,
223
238
  headers=headers or {},
224
239
  timeout=timeout,
225
- http2=http2_enabled,
226
- trust_env=trust_env,
240
+ http2=config.http2_enabled,
241
+ trust_env=config.trust_env,
227
242
  )
228
243
 
229
244
 
@@ -273,85 +288,33 @@ def create_reopenable_async_client(
273
288
  headers: Optional[Dict[str, str]] = None,
274
289
  retry_status_codes: tuple = (429, 502, 503, 504),
275
290
  ) -> Union[ReopenableAsyncClient, httpx.AsyncClient]:
276
- if verify is None:
277
- verify = get_cert_bundle_path()
278
-
279
- # Check if HTTP/2 is enabled in config
280
- http2_enabled = get_http2()
281
-
282
- # Check if custom retry transport should be disabled (e.g., for integration tests with proxies)
283
- disable_retry_transport = os.environ.get(
284
- "CODE_PUPPY_DISABLE_RETRY_TRANSPORT", ""
285
- ).lower() in ("1", "true", "yes")
286
-
287
- # Check if proxy environment variables are set
288
- has_proxy = bool(
289
- os.environ.get("HTTP_PROXY")
290
- or os.environ.get("HTTPS_PROXY")
291
- or os.environ.get("http_proxy")
292
- or os.environ.get("https_proxy")
293
- )
294
-
295
- # When retry transport is disabled (test mode), disable SSL verification
296
- if disable_retry_transport:
297
- verify = False
298
- trust_env = True
299
- elif has_proxy:
300
- trust_env = True
301
- else:
302
- trust_env = False
291
+ config = _resolve_proxy_config(verify)
303
292
 
304
- # Extract proxy URL if needed
305
- proxy_url = None
306
- if has_proxy:
307
- proxy_url = (
308
- os.environ.get("HTTPS_PROXY")
309
- or os.environ.get("https_proxy")
310
- or os.environ.get("HTTP_PROXY")
311
- or os.environ.get("http_proxy")
312
- )
293
+ base_kwargs = {
294
+ "proxy": config.proxy_url,
295
+ "verify": config.verify,
296
+ "headers": headers or {},
297
+ "timeout": timeout,
298
+ "http2": config.http2_enabled,
299
+ "trust_env": config.trust_env,
300
+ }
313
301
 
314
302
  if ReopenableAsyncClient is not None:
315
- # Use RetryingAsyncClient if retries are enabled
316
303
  client_class = (
317
- RetryingAsyncClient if not disable_retry_transport else httpx.AsyncClient
304
+ RetryingAsyncClient if not config.disable_retry else httpx.AsyncClient
318
305
  )
319
-
320
- # Pass retry config only if using RetryingAsyncClient
321
- kwargs = {
322
- "proxy": proxy_url,
323
- "verify": verify,
324
- "headers": headers or {},
325
- "timeout": timeout,
326
- "http2": http2_enabled,
327
- "trust_env": trust_env,
328
- }
329
-
330
- if not disable_retry_transport:
306
+ kwargs = {**base_kwargs, "client_class": client_class}
307
+ if not config.disable_retry:
331
308
  kwargs["retry_status_codes"] = retry_status_codes
332
-
333
- return ReopenableAsyncClient(client_class=client_class, **kwargs)
309
+ return ReopenableAsyncClient(**kwargs)
334
310
  else:
335
- # Fallback to RetryingAsyncClient
336
- if not disable_retry_transport:
311
+ # Fallback to RetryingAsyncClient or plain AsyncClient
312
+ if not config.disable_retry:
337
313
  return RetryingAsyncClient(
338
- retry_status_codes=retry_status_codes,
339
- proxy=proxy_url,
340
- verify=verify,
341
- headers=headers or {},
342
- timeout=timeout,
343
- http2=http2_enabled,
344
- trust_env=trust_env,
314
+ retry_status_codes=retry_status_codes, **base_kwargs
345
315
  )
346
316
  else:
347
- return httpx.AsyncClient(
348
- proxy=proxy_url,
349
- verify=verify,
350
- headers=headers or {},
351
- timeout=timeout,
352
- http2=http2_enabled,
353
- trust_env=trust_env,
354
- )
317
+ return httpx.AsyncClient(**base_kwargs)
355
318
 
356
319
 
357
320
  def is_cert_bundle_available() -> bool:
@@ -209,6 +209,9 @@ class ShellStartMessage(BaseMessage):
209
209
  default=None, description="Working directory for the command"
210
210
  )
211
211
  timeout: int = Field(default=60, description="Timeout in seconds")
212
+ background: bool = Field(
213
+ default=False, description="Whether command runs in background mode"
214
+ )
212
215
 
213
216
 
214
217
  class ShellLineMessage(BaseMessage):
@@ -348,7 +348,17 @@ class RichConsoleRenderer:
348
348
  # =========================================================================
349
349
 
350
350
  def _render_file_listing(self, msg: FileListingMessage) -> None:
351
- """Render a directory listing matching the old Rich-formatted output."""
351
+ """Render a compact directory listing with directory summaries.
352
+
353
+ Instead of listing every file, we group by directory and show:
354
+ - Directory name
355
+ - Number of files
356
+ - Total size
357
+ - Number of subdirectories
358
+ """
359
+ import os
360
+ from collections import defaultdict
361
+
352
362
  # Header on single line
353
363
  rec_flag = f"(recursive={msg.recursive})"
354
364
  banner = self._format_banner("directory_listing", "DIRECTORY LISTING")
@@ -357,32 +367,104 @@ class RichConsoleRenderer:
357
367
  f"📂 [bold cyan]{msg.directory}[/bold cyan] [dim]{rec_flag}[/dim]\n"
358
368
  )
359
369
 
360
- # Directory header
361
- dir_name = msg.directory.rstrip("/").split("/")[-1] or msg.directory
362
- self._console.print(f"📁 [bold blue]{dir_name}[/bold blue]")
370
+ # Build a tree structure: {parent_path: {files: [], dirs: set(), size: int}}
371
+ # Each key is a directory path, value contains direct children stats
372
+ dir_stats: dict = defaultdict(
373
+ lambda: {"files": [], "subdirs": set(), "total_size": 0}
374
+ )
375
+
376
+ # Root directory is represented as ""
377
+ root_key = ""
363
378
 
364
- # Build tree structure from flat list
365
379
  for entry in msg.files:
366
- # Calculate indentation based on depth
367
- prefix = ""
368
- for d in range(entry.depth + 1):
369
- if d == entry.depth:
370
- prefix += "└── "
371
- else:
372
- prefix += " "
380
+ path = entry.path
381
+ parent = os.path.dirname(path) if os.path.dirname(path) else root_key
373
382
 
374
383
  if entry.type == "dir":
375
- self._console.print(f"{prefix}📁 [bold blue]{entry.path}/[/bold blue]")
384
+ # Register this dir as a subdir of its parent
385
+ dir_stats[parent]["subdirs"].add(path)
386
+ # Ensure the dir itself exists in stats (even if empty)
387
+ _ = dir_stats[path]
376
388
  else:
377
- icon = self._get_file_icon(entry.path)
378
- if entry.size > 0:
379
- size_str = f" [dim]({self._format_size(entry.size)})[/dim]"
380
- else:
381
- size_str = ""
389
+ # It's a file - add to parent's stats
390
+ dir_stats[parent]["files"].append(entry)
391
+ dir_stats[parent]["total_size"] += entry.size
392
+
393
+ def render_dir_tree(dir_path: str, depth: int = 0) -> None:
394
+ """Recursively render directory with compact summary."""
395
+ stats = dir_stats.get(
396
+ dir_path, {"files": [], "subdirs": set(), "total_size": 0}
397
+ )
398
+ files = stats["files"]
399
+ subdirs = sorted(stats["subdirs"])
400
+
401
+ # Calculate total size including subdirectories (recursive)
402
+ def get_recursive_size(d: str) -> int:
403
+ s = dir_stats.get(d, {"files": [], "subdirs": set(), "total_size": 0})
404
+ size = s["total_size"]
405
+ for sub in s["subdirs"]:
406
+ size += get_recursive_size(sub)
407
+ return size
408
+
409
+ def get_recursive_file_count(d: str) -> int:
410
+ s = dir_stats.get(d, {"files": [], "subdirs": set(), "total_size": 0})
411
+ count = len(s["files"])
412
+ for sub in s["subdirs"]:
413
+ count += get_recursive_file_count(sub)
414
+ return count
415
+
416
+ indent = " " * depth
417
+
418
+ # For root level, just show contents
419
+ if dir_path == root_key:
420
+ # Show files at root level (depth 0)
421
+ for f in sorted(files, key=lambda x: x.path):
422
+ icon = self._get_file_icon(f.path)
423
+ name = os.path.basename(f.path)
424
+ size_str = (
425
+ f" [dim]({self._format_size(f.size)})[/dim]"
426
+ if f.size > 0
427
+ else ""
428
+ )
429
+ self._console.print(
430
+ f"{indent}{icon} [green]{name}[/green]{size_str}"
431
+ )
432
+
433
+ # Show subdirs at root level
434
+ for subdir in subdirs:
435
+ render_dir_tree(subdir, depth)
436
+ else:
437
+ # Show directory with summary
438
+ dir_name = os.path.basename(dir_path)
439
+ rec_size = get_recursive_size(dir_path)
440
+ rec_file_count = get_recursive_file_count(dir_path)
441
+ subdir_count = len(subdirs)
442
+
443
+ # Build summary parts
444
+ parts = []
445
+ if rec_file_count > 0:
446
+ parts.append(
447
+ f"{rec_file_count} file{'s' if rec_file_count != 1 else ''}"
448
+ )
449
+ if subdir_count > 0:
450
+ parts.append(
451
+ f"{subdir_count} subdir{'s' if subdir_count != 1 else ''}"
452
+ )
453
+ if rec_size > 0:
454
+ parts.append(self._format_size(rec_size))
455
+
456
+ summary = f" [dim]({', '.join(parts)})[/dim]" if parts else ""
382
457
  self._console.print(
383
- f"{prefix}{icon} [green]{entry.path}[/green]{size_str}"
458
+ f"{indent}📁 [bold blue]{dir_name}/[/bold blue]{summary}"
384
459
  )
385
460
 
461
+ # Recursively show subdirectories
462
+ for subdir in subdirs:
463
+ render_dir_tree(subdir, depth + 1)
464
+
465
+ # Render the tree starting from root
466
+ render_dir_tree(root_key, 0)
467
+
386
468
  # Summary
387
469
  self._console.print("\n[bold cyan]Summary:[/bold cyan]")
388
470
  self._console.print(
@@ -538,15 +620,25 @@ class RichConsoleRenderer:
538
620
  safe_command = escape_rich_markup(msg.command)
539
621
  # Header showing command is starting
540
622
  banner = self._format_banner("shell_command", "SHELL COMMAND")
541
- self._console.print(f"\n{banner} 🚀 [dim]$ {safe_command}[/dim]")
623
+
624
+ # Add background indicator if running in background mode
625
+ if msg.background:
626
+ self._console.print(
627
+ f"\n{banner} 🚀 [dim]$ {safe_command}[/dim] [bold magenta][BACKGROUND 🌙][/bold magenta]"
628
+ )
629
+ else:
630
+ self._console.print(f"\n{banner} 🚀 [dim]$ {safe_command}[/dim]")
542
631
 
543
632
  # Show working directory if specified
544
633
  if msg.cwd:
545
634
  safe_cwd = escape_rich_markup(msg.cwd)
546
635
  self._console.print(f"[dim]📂 Working directory: {safe_cwd}[/dim]")
547
636
 
548
- # Show timeout
549
- self._console.print(f"[dim]⏱ Timeout: {msg.timeout}s[/dim]")
637
+ # Show timeout or background status
638
+ if msg.background:
639
+ self._console.print("[dim]⏱ Runs detached (no timeout)[/dim]")
640
+ else:
641
+ self._console.print(f"[dim]⏱ Timeout: {msg.timeout}s[/dim]")
550
642
 
551
643
  def _render_shell_line(self, msg: ShellLineMessage) -> None:
552
644
  """Render shell output line preserving ANSI codes."""
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import base64
3
4
  import json
4
5
  import logging
5
6
  from collections.abc import AsyncIterator
@@ -54,7 +55,15 @@ class AntigravityModel(GoogleModel):
54
55
  messages: list[ModelMessage],
55
56
  model_request_parameters: ModelRequestParameters,
56
57
  ) -> tuple[ContentDict | None, list[dict]]:
57
- """Map messages to Google GenAI format, preserving thinking signatures."""
58
+ """Map messages to Google GenAI format, preserving thinking signatures.
59
+
60
+ IMPORTANT: For Gemini with parallel function calls, the API expects:
61
+ - Model message: [FC1 + signature, FC2, ...] (all function calls together)
62
+ - User message: [FR1, FR2, ...] (all function responses together)
63
+
64
+ If messages are interleaved (FC1, FR1, FC2, FR2), the API returns 400.
65
+ This method merges consecutive same-role messages to fix this.
66
+ """
58
67
  contents: list[dict] = []
59
68
  system_parts: list[PartDict] = []
60
69
 
@@ -67,7 +76,16 @@ class AntigravityModel(GoogleModel):
67
76
  system_parts.append({"text": part.content})
68
77
  elif isinstance(part, UserPromptPart):
69
78
  # Use parent's _map_user_prompt
70
- message_parts.extend(await self._map_user_prompt(part))
79
+ mapped_parts = await self._map_user_prompt(part)
80
+ # Sanitize bytes to base64 for JSON serialization
81
+ for mp in mapped_parts:
82
+ if "inline_data" in mp and "data" in mp["inline_data"]:
83
+ data = mp["inline_data"]["data"]
84
+ if isinstance(data, bytes):
85
+ mp["inline_data"]["data"] = base64.b64encode(
86
+ data
87
+ ).decode("utf-8")
88
+ message_parts.extend(mapped_parts)
71
89
  elif isinstance(part, ToolReturnPart):
72
90
  message_parts.append(
73
91
  {
@@ -95,7 +113,12 @@ class AntigravityModel(GoogleModel):
95
113
  assert_never(part)
96
114
 
97
115
  if message_parts:
98
- contents.append({"role": "user", "parts": message_parts})
116
+ # Merge with previous user message if exists (for parallel function responses)
117
+ if contents and contents[-1].get("role") == "user":
118
+ contents[-1]["parts"].extend(message_parts)
119
+ else:
120
+ contents.append({"role": "user", "parts": message_parts})
121
+
99
122
  elif isinstance(m, ModelResponse):
100
123
  # USE CUSTOM HELPER HERE
101
124
  # Pass model name so we can handle Claude vs Gemini signature placement
@@ -103,7 +126,11 @@ class AntigravityModel(GoogleModel):
103
126
  m, self.system, self._model_name
104
127
  )
105
128
  if maybe_content:
106
- contents.append(maybe_content)
129
+ # Merge with previous model message if exists (for parallel function calls)
130
+ if contents and contents[-1].get("role") == "model":
131
+ contents[-1]["parts"].extend(maybe_content["parts"])
132
+ else:
133
+ contents.append(maybe_content)
107
134
  else:
108
135
  assert_never(m)
109
136
 
@@ -435,6 +462,13 @@ class AntigravityStreamingResponse(StreamedResponse):
435
462
  return self._timestamp_val
436
463
 
437
464
 
465
+ # Bypass signature for when no real thought signature is available.
466
+ # Gemini API requires EVERY function call to have a thoughtSignature field.
467
+ # When there's no thinking block or no signature was captured, we use this bypass.
468
+ # This specific key is the official bypass token for Gemini 3 Pro.
469
+ BYPASS_THOUGHT_SIGNATURE = "context_engineering_is_the_way_to_go"
470
+
471
+
438
472
  def _antigravity_content_model_response(
439
473
  m: ModelResponse, provider_name: str, model_name: str = ""
440
474
  ) -> ContentDict | None:
@@ -443,6 +477,10 @@ def _antigravity_content_model_response(
443
477
  Handles different signature protocols:
444
478
  - Claude models: signature goes ON the thinking block itself
445
479
  - Gemini models: signature goes on the NEXT part (function_call or text) after thinking
480
+
481
+ IMPORTANT: For Gemini, EVERY function call MUST have a thoughtSignature field.
482
+ If no real signature is available (no preceding ThinkingPart, or ThinkingPart
483
+ had no signature), we use BYPASS_THOUGHT_SIGNATURE as a fallback.
446
484
  """
447
485
  parts: list[PartDict] = []
448
486
 
@@ -451,6 +489,7 @@ def _antigravity_content_model_response(
451
489
  is_gemini = "gemini" in model_name.lower()
452
490
 
453
491
  # For Gemini: save signature from ThinkingPart to attach to next part
492
+ # Initialize to None - we'll use BYPASS_THOUGHT_SIGNATURE if still None when needed
454
493
  pending_signature: str | None = None
455
494
 
456
495
  for item in m.parts:
@@ -462,16 +501,24 @@ def _antigravity_content_model_response(
462
501
  )
463
502
  part["function_call"] = function_call
464
503
 
465
- # For Gemini: attach pending signature to function call
466
- if is_gemini and pending_signature:
467
- part["thoughtSignature"] = pending_signature
468
- pending_signature = None
504
+ # For Gemini: ALWAYS attach a thoughtSignature to function calls.
505
+ # Use the real signature if available, otherwise use bypass.
506
+ # NOTE: Do NOT clear pending_signature here! Multiple tool calls
507
+ # in a row (e.g., parallel function calls) all need the same
508
+ # signature from the preceding ThinkingPart.
509
+ if is_gemini:
510
+ part["thoughtSignature"] = (
511
+ pending_signature
512
+ if pending_signature is not None
513
+ else BYPASS_THOUGHT_SIGNATURE
514
+ )
469
515
 
470
516
  elif isinstance(item, TextPart):
471
517
  part["text"] = item.content
472
518
 
473
- # For Gemini: attach pending signature to text part
474
- if is_gemini and pending_signature:
519
+ # For Gemini: attach pending signature to text part if available
520
+ # Clear signature after text since text typically ends a response
521
+ if is_gemini and pending_signature is not None:
475
522
  part["thoughtSignature"] = pending_signature
476
523
  pending_signature = None
477
524
 
@@ -490,6 +537,10 @@ def _antigravity_content_model_response(
490
537
  else:
491
538
  # Default: try both (put on thinking block)
492
539
  part["thoughtSignature"] = item.signature
540
+ elif is_gemini:
541
+ # ThinkingPart exists but has no signature - use bypass
542
+ # This ensures subsequent tool calls still get a signature
543
+ pending_signature = BYPASS_THOUGHT_SIGNATURE
493
544
 
494
545
  elif isinstance(item, BuiltinToolCallPart):
495
546
  # Skip code execution for now
@@ -501,8 +552,13 @@ def _antigravity_content_model_response(
501
552
 
502
553
  elif isinstance(item, FilePart):
503
554
  content = item.content
555
+ # Ensure data is base64 string, not bytes
556
+ data_val = content.data
557
+ if isinstance(data_val, bytes):
558
+ data_val = base64.b64encode(data_val).decode("utf-8")
559
+
504
560
  inline_data_dict: BlobDict = {
505
- "data": content.data,
561
+ "data": data_val,
506
562
  "mime_type": content.media_type,
507
563
  }
508
564
  part["inline_data"] = inline_data_dict