code-puppy 0.0.375__tar.gz → 0.0.377__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 (233) hide show
  1. {code_puppy-0.0.375 → code_puppy-0.0.377}/PKG-INFO +1 -1
  2. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/base_agent.py +88 -64
  3. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/callbacks.py +57 -9
  4. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/model_settings_menu.py +13 -0
  5. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/gemini_model.py +63 -22
  6. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/model_factory.py +13 -0
  7. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/models.json +2 -2
  8. code_puppy-0.0.377/code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  9. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +72 -0
  10. code_puppy-0.0.377/code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +242 -0
  11. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/loop_controller.py +52 -48
  12. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/register_callbacks.py +13 -6
  13. {code_puppy-0.0.375 → code_puppy-0.0.377}/pyproject.toml +1 -1
  14. code_puppy-0.0.375/code_puppy/plugins/claude_code_oauth/__init__.py +0 -6
  15. {code_puppy-0.0.375 → code_puppy-0.0.377}/.gitignore +0 -0
  16. {code_puppy-0.0.375 → code_puppy-0.0.377}/LICENSE +0 -0
  17. {code_puppy-0.0.375 → code_puppy-0.0.377}/README.md +0 -0
  18. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/__init__.py +0 -0
  19. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/__main__.py +0 -0
  20. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/__init__.py +0 -0
  21. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_c_reviewer.py +0 -0
  22. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_code_puppy.py +0 -0
  23. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_code_reviewer.py +0 -0
  24. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  25. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_creator_agent.py +0 -0
  26. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  27. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_helios.py +0 -0
  28. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  29. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_manager.py +0 -0
  30. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_pack_leader.py +0 -0
  31. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_planning.py +0 -0
  32. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_python_programmer.py +0 -0
  33. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_python_reviewer.py +0 -0
  34. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_qa_expert.py +0 -0
  35. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_qa_kitten.py +0 -0
  36. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_security_auditor.py +0 -0
  37. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_terminal_qa.py +0 -0
  38. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  39. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/event_stream_handler.py +0 -0
  40. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/json_agent.py +0 -0
  41. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/__init__.py +0 -0
  42. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/bloodhound.py +0 -0
  43. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/husky.py +0 -0
  44. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/retriever.py +0 -0
  45. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/shepherd.py +0 -0
  46. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/terrier.py +0 -0
  47. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/pack/watchdog.py +0 -0
  48. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/prompt_reviewer.py +0 -0
  49. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/agents/subagent_stream_handler.py +0 -0
  50. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/__init__.py +0 -0
  51. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/app.py +0 -0
  52. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/main.py +0 -0
  53. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/pty_manager.py +0 -0
  54. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/routers/__init__.py +0 -0
  55. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/routers/agents.py +0 -0
  56. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/routers/commands.py +0 -0
  57. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/routers/config.py +0 -0
  58. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/routers/sessions.py +0 -0
  59. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/templates/terminal.html +0 -0
  60. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/api/websocket.py +0 -0
  61. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/chatgpt_codex_client.py +0 -0
  62. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/claude_cache_client.py +0 -0
  63. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/cli_runner.py +0 -0
  64. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/__init__.py +0 -0
  65. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/add_model_menu.py +0 -0
  66. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/agent_menu.py +0 -0
  67. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/attachments.py +0 -0
  68. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/autosave_menu.py +0 -0
  69. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/clipboard.py +0 -0
  70. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/colors_menu.py +0 -0
  71. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/command_handler.py +0 -0
  72. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/command_registry.py +0 -0
  73. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/config_commands.py +0 -0
  74. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/core_commands.py +0 -0
  75. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/diff_menu.py +0 -0
  76. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/file_path_completion.py +0 -0
  77. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/load_context_completion.py +0 -0
  78. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/__init__.py +0 -0
  79. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/base.py +0 -0
  80. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  81. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  82. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  83. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/edit_command.py +0 -0
  84. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/handler.py +0 -0
  85. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/help_command.py +0 -0
  86. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/install_command.py +0 -0
  87. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/install_menu.py +0 -0
  88. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/list_command.py +0 -0
  89. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/logs_command.py +0 -0
  90. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/remove_command.py +0 -0
  91. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/restart_command.py +0 -0
  92. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/search_command.py +0 -0
  93. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  94. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/start_command.py +0 -0
  95. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/status_command.py +0 -0
  96. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  97. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/stop_command.py +0 -0
  98. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/test_command.py +0 -0
  99. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/utils.py +0 -0
  100. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  101. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/mcp_completion.py +0 -0
  102. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/model_picker_completion.py +0 -0
  103. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/motd.py +0 -0
  104. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/onboarding_slides.py +0 -0
  105. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/onboarding_wizard.py +0 -0
  106. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/pin_command_completion.py +0 -0
  107. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  108. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/session_commands.py +0 -0
  109. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/uc_menu.py +0 -0
  110. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/command_line/utils.py +0 -0
  111. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/config.py +0 -0
  112. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/error_logging.py +0 -0
  113. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/gemini_code_assist.py +0 -0
  114. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/http_utils.py +0 -0
  115. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/keymap.py +0 -0
  116. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/main.py +0 -0
  117. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/__init__.py +0 -0
  118. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/async_lifecycle.py +0 -0
  119. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/blocking_startup.py +0 -0
  120. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  121. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/circuit_breaker.py +0 -0
  122. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/config_wizard.py +0 -0
  123. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/dashboard.py +0 -0
  124. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/error_isolation.py +0 -0
  125. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/examples/retry_example.py +0 -0
  126. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/health_monitor.py +0 -0
  127. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/managed_server.py +0 -0
  128. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/manager.py +0 -0
  129. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/mcp_logs.py +0 -0
  130. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/registry.py +0 -0
  131. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/retry_manager.py +0 -0
  132. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  133. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/status_tracker.py +0 -0
  134. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/mcp_/system_tools.py +0 -0
  135. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/__init__.py +0 -0
  136. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/bus.py +0 -0
  137. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/commands.py +0 -0
  138. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/markdown_patches.py +0 -0
  139. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/message_queue.py +0 -0
  140. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/messages.py +0 -0
  141. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/queue_console.py +0 -0
  142. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/renderers.py +0 -0
  143. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/rich_renderer.py +0 -0
  144. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/spinner/__init__.py +0 -0
  145. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  146. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  147. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/messaging/subagent_console.py +0 -0
  148. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/model_switching.py +0 -0
  149. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/model_utils.py +0 -0
  150. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/models_dev_api.json +0 -0
  151. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/models_dev_parser.py +0 -0
  152. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/__init__.py +0 -0
  153. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  154. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  155. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
  156. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  157. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  158. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  159. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
  160. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  161. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  162. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  163. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
  164. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
  165. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  166. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  167. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  168. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  169. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  170. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  171. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  172. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  173. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  174. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  175. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  176. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  177. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  178. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/example_custom_command/README.md +0 -0
  179. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  180. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  181. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  182. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
  183. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
  184. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
  185. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  186. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/__init__.py +0 -0
  187. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/agents.py +0 -0
  188. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/commands.py +0 -0
  189. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/models.py +0 -0
  190. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/state_manager.py +0 -0
  191. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/ralph/tools.py +0 -0
  192. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  193. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  194. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  195. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  196. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/universal_constructor/__init__.py +0 -0
  197. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/universal_constructor/models.py +0 -0
  198. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/universal_constructor/register_callbacks.py +0 -0
  199. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/universal_constructor/registry.py +0 -0
  200. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/plugins/universal_constructor/sandbox.py +0 -0
  201. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
  202. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/pydantic_patches.py +0 -0
  203. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/reopenable_async_client.py +0 -0
  204. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/round_robin_model.py +0 -0
  205. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/session_storage.py +0 -0
  206. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/status_display.py +0 -0
  207. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/summarization_agent.py +0 -0
  208. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/terminal_utils.py +0 -0
  209. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/__init__.py +0 -0
  210. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/agent_tools.py +0 -0
  211. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/__init__.py +0 -0
  212. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_control.py +0 -0
  213. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_interactions.py +0 -0
  214. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_locators.py +0 -0
  215. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_manager.py +0 -0
  216. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_navigation.py +0 -0
  217. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  218. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_scripts.py +0 -0
  219. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/browser_workflows.py +0 -0
  220. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/chromium_terminal_manager.py +0 -0
  221. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/terminal_command_tools.py +0 -0
  222. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/terminal_screenshot_tools.py +0 -0
  223. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/browser/terminal_tools.py +0 -0
  224. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/command_runner.py +0 -0
  225. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/common.py +0 -0
  226. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/display.py +0 -0
  227. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/file_modifications.py +0 -0
  228. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/file_operations.py +0 -0
  229. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/subagent_context.py +0 -0
  230. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/tools_content.py +0 -0
  231. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/tools/universal_constructor.py +0 -0
  232. {code_puppy-0.0.375 → code_puppy-0.0.377}/code_puppy/uvx_detection.py +0 -0
  233. {code_puppy-0.0.375 → code_puppy-0.0.377}/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.375
3
+ Version: 0.0.377
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
@@ -47,7 +47,10 @@ from pydantic_ai.messages import (
47
47
  from rich.text import Text
48
48
 
49
49
  from code_puppy.agents.event_stream_handler import event_stream_handler
50
- from code_puppy.callbacks import on_agent_response_complete
50
+ from code_puppy.callbacks import (
51
+ on_agent_run_end,
52
+ on_agent_run_start,
53
+ )
51
54
 
52
55
  # Consolidated relative imports
53
56
  from code_puppy.config import (
@@ -404,35 +407,27 @@ class BaseAgent(ABC):
404
407
  total_tokens = 0
405
408
 
406
409
  # 1. Estimate tokens for system prompt / instructions
407
- # For Claude Code models, the full system prompt is prepended to the first
408
- # user message (already in message history), so we only count the short
409
- # fixed instructions. For other models, count the full system prompt.
410
+ # Use prepare_prompt_for_model() to get the correct instructions for token counting.
411
+ # For models that prepend system prompt to user message (claude-code, antigravity),
412
+ # this returns the short fixed instructions. For other models, returns full prompt.
410
413
  try:
411
- from code_puppy.model_utils import (
412
- get_antigravity_instructions,
413
- get_claude_code_instructions,
414
- is_antigravity_model,
415
- is_claude_code_model,
416
- )
414
+ from code_puppy.model_utils import prepare_prompt_for_model
417
415
 
418
416
  model_name = (
419
417
  self.get_model_name() if hasattr(self, "get_model_name") else ""
420
418
  )
421
- if is_claude_code_model(model_name):
422
- # For Claude Code models, only count the short fixed instructions
423
- # The full system prompt is already in the message history
424
- instructions = get_claude_code_instructions()
425
- total_tokens += self.estimate_token_count(instructions)
426
- elif is_antigravity_model(model_name):
427
- # For Antigravity models, only count the short fixed instructions
428
- # The full system prompt is already in the message history
429
- instructions = get_antigravity_instructions()
430
- total_tokens += self.estimate_token_count(instructions)
431
- else:
432
- # For other models, count the full system prompt
433
- system_prompt = self.get_full_system_prompt()
434
- if system_prompt:
435
- total_tokens += self.estimate_token_count(system_prompt)
419
+ system_prompt = self.get_full_system_prompt()
420
+
421
+ # Get the instructions that will be used (handles model-specific logic via hooks)
422
+ prepared = prepare_prompt_for_model(
423
+ model_name=model_name,
424
+ system_prompt=system_prompt,
425
+ user_prompt="", # Empty - we just need the instructions
426
+ prepend_system_to_user=False, # Don't modify prompt, just get instructions
427
+ )
428
+
429
+ if prepared.instructions:
430
+ total_tokens += self.estimate_token_count(prepared.instructions)
436
431
  except Exception:
437
432
  pass # If we can't get system prompt, skip it
438
433
 
@@ -1590,21 +1585,25 @@ class BaseAgent(ABC):
1590
1585
  if output_type is not None:
1591
1586
  pydantic_agent = self._create_agent_with_output_type(output_type)
1592
1587
 
1593
- # Handle claude-code, chatgpt-codex, and antigravity models: prepend system prompt to first user message
1594
- from code_puppy.model_utils import (
1595
- is_antigravity_model,
1596
- is_claude_code_model,
1597
- )
1588
+ # Handle model-specific prompt transformations via prepare_prompt_for_model()
1589
+ # This uses the get_model_system_prompt hook, so plugins can register their own handlers
1590
+ from code_puppy.model_utils import prepare_prompt_for_model
1598
1591
 
1599
- if is_claude_code_model(self.get_model_name()) or is_antigravity_model(
1600
- self.get_model_name()
1601
- ):
1602
- if len(self.get_message_history()) == 0:
1603
- system_prompt = self.get_full_system_prompt()
1604
- puppy_rules = self.load_puppy_rules()
1605
- if puppy_rules:
1606
- system_prompt += f"\n{puppy_rules}"
1607
- prompt = system_prompt + "\n\n" + prompt
1592
+ # Only prepend system prompt on first message (empty history)
1593
+ should_prepend = len(self.get_message_history()) == 0
1594
+ if should_prepend:
1595
+ system_prompt = self.get_full_system_prompt()
1596
+ puppy_rules = self.load_puppy_rules()
1597
+ if puppy_rules:
1598
+ system_prompt += f"\n{puppy_rules}"
1599
+
1600
+ prepared = prepare_prompt_for_model(
1601
+ model_name=self.get_model_name(),
1602
+ system_prompt=system_prompt,
1603
+ user_prompt=prompt,
1604
+ prepend_system_to_user=True,
1605
+ )
1606
+ prompt = prepared.user_prompt
1608
1607
 
1609
1608
  # Build combined prompt payload when attachments are provided.
1610
1609
  attachment_parts: List[Any] = []
@@ -1751,6 +1750,17 @@ class BaseAgent(ABC):
1751
1750
  # Create the task FIRST
1752
1751
  agent_task = asyncio.create_task(run_agent_task())
1753
1752
 
1753
+ # Fire agent_run_start hook - plugins can use this to start background tasks
1754
+ # (e.g., token refresh heartbeats for OAuth models)
1755
+ try:
1756
+ await on_agent_run_start(
1757
+ agent_name=self.name,
1758
+ model_name=self.get_model_name(),
1759
+ session_id=group_id,
1760
+ )
1761
+ except Exception:
1762
+ pass # Don't fail agent run if hook fails
1763
+
1754
1764
  # Import shell process status helper
1755
1765
 
1756
1766
  loop = asyncio.get_running_loop()
@@ -1832,39 +1842,53 @@ class BaseAgent(ABC):
1832
1842
  except Exception:
1833
1843
  pass # Don't fail the run if cache update fails
1834
1844
 
1835
- # Trigger agent_response_complete callback for workflow orchestration
1836
- try:
1837
- # Extract the response text from the result
1838
- response_text = ""
1839
- if result is not None:
1840
- if hasattr(result, "data"):
1841
- response_text = str(result.data) if result.data else ""
1842
- elif hasattr(result, "output"):
1843
- response_text = str(result.output) if result.output else ""
1844
- else:
1845
- response_text = str(result)
1846
-
1847
- # Fire the callback - don't await to avoid blocking return
1848
- # Use asyncio.create_task to run it in background
1849
- asyncio.create_task(
1850
- on_agent_response_complete(
1851
- agent_name=self.name,
1852
- response_text=response_text,
1853
- session_id=group_id,
1854
- metadata={"model": self.get_model_name()},
1855
- )
1856
- )
1857
- except Exception:
1858
- pass # Don't fail the run if callback fails
1845
+ # Extract response text for the callback
1846
+ _run_response_text = ""
1847
+ if result is not None:
1848
+ if hasattr(result, "data"):
1849
+ _run_response_text = str(result.data) if result.data else ""
1850
+ elif hasattr(result, "output"):
1851
+ _run_response_text = str(result.output) if result.output else ""
1852
+ else:
1853
+ _run_response_text = str(result)
1859
1854
 
1855
+ _run_success = True
1856
+ _run_error = None
1860
1857
  return result
1861
1858
  except asyncio.CancelledError:
1859
+ _run_success = False
1860
+ _run_error = None # Cancellation is not an error
1861
+ _run_response_text = ""
1862
1862
  agent_task.cancel()
1863
1863
  except KeyboardInterrupt:
1864
- # Handle direct keyboard interrupt during await
1864
+ _run_success = False
1865
+ _run_error = None # User interrupt is not an error
1866
+ _run_response_text = ""
1865
1867
  if not agent_task.done():
1866
1868
  agent_task.cancel()
1869
+ except Exception as e:
1870
+ _run_success = False
1871
+ _run_error = e
1872
+ _run_response_text = ""
1873
+ raise
1867
1874
  finally:
1875
+ # Fire agent_run_end hook - plugins can use this for:
1876
+ # - Stopping background tasks (token refresh heartbeats)
1877
+ # - Workflow orchestration (Ralph's autonomous loop)
1878
+ # - Logging/analytics
1879
+ try:
1880
+ await on_agent_run_end(
1881
+ agent_name=self.name,
1882
+ model_name=self.get_model_name(),
1883
+ session_id=group_id,
1884
+ success=_run_success,
1885
+ error=_run_error,
1886
+ response_text=_run_response_text,
1887
+ metadata={"model": self.get_model_name()},
1888
+ )
1889
+ except Exception:
1890
+ pass # Don't fail cleanup if hook fails
1891
+
1868
1892
  # Stop keyboard listener if it was started
1869
1893
  if key_listener_stop_event is not None:
1870
1894
  key_listener_stop_event.set()
@@ -25,7 +25,8 @@ PhaseType = Literal[
25
25
  "register_agents",
26
26
  "register_model_type",
27
27
  "get_model_system_prompt",
28
- "agent_response_complete",
28
+ "agent_run_start",
29
+ "agent_run_end",
29
30
  ]
30
31
  CallbackFunc = Callable[..., Any]
31
32
 
@@ -51,7 +52,8 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
51
52
  "register_agents": [],
52
53
  "register_model_type": [],
53
54
  "get_model_system_prompt": [],
54
- "agent_response_complete": [],
55
+ "agent_run_start": [],
56
+ "agent_run_end": [],
55
57
  }
56
58
 
57
59
  logger = logging.getLogger(__name__)
@@ -446,26 +448,72 @@ def on_get_model_system_prompt(
446
448
  )
447
449
 
448
450
 
449
- async def on_agent_response_complete(
451
+ async def on_agent_run_start(
450
452
  agent_name: str,
451
- response_text: str,
453
+ model_name: str,
452
454
  session_id: str | None = None,
455
+ ) -> List[Any]:
456
+ """Trigger callbacks when an agent run starts.
457
+
458
+ This fires at the beginning of run_with_mcp, before the agent task is created.
459
+ Useful for:
460
+ - Starting background tasks (like token refresh heartbeats)
461
+ - Logging/analytics
462
+ - Resource allocation
463
+
464
+ Args:
465
+ agent_name: Name of the agent starting
466
+ model_name: Name of the model being used
467
+ session_id: Optional session identifier
468
+
469
+ Returns:
470
+ List of results from registered callbacks.
471
+ """
472
+ return await _trigger_callbacks(
473
+ "agent_run_start", agent_name, model_name, session_id
474
+ )
475
+
476
+
477
+ async def on_agent_run_end(
478
+ agent_name: str,
479
+ model_name: str,
480
+ session_id: str | None = None,
481
+ success: bool = True,
482
+ error: Exception | None = None,
483
+ response_text: str | None = None,
453
484
  metadata: dict | None = None,
454
485
  ) -> List[Any]:
455
- """Trigger callbacks after an agent completes its full response.
486
+ """Trigger callbacks when an agent run ends.
487
+
488
+ This fires at the end of run_with_mcp, in the finally block.
489
+ Always fires regardless of success/failure/cancellation.
456
490
 
457
- This fires after all tool calls are resolved and the agent has finished.
458
491
  Useful for:
492
+ - Stopping background tasks (like token refresh heartbeats)
459
493
  - Workflow orchestration (like Ralph's autonomous loop)
460
494
  - Logging/analytics
495
+ - Resource cleanup
461
496
  - Detecting completion signals in responses
462
497
 
463
498
  Args:
464
- agent_name: Name of the agent that completed
465
- response_text: The final text response from the agent
499
+ agent_name: Name of the agent that finished
500
+ model_name: Name of the model that was used
466
501
  session_id: Optional session identifier
502
+ success: Whether the run completed successfully
503
+ error: Exception if the run failed, None otherwise
504
+ response_text: The final text response from the agent (if successful)
467
505
  metadata: Optional dict with additional context (tokens used, etc.)
506
+
507
+ Returns:
508
+ List of results from registered callbacks.
468
509
  """
469
510
  return await _trigger_callbacks(
470
- "agent_response_complete", agent_name, response_text, session_id, metadata
511
+ "agent_run_end",
512
+ agent_name,
513
+ model_name,
514
+ session_id,
515
+ success,
516
+ error,
517
+ response_text,
518
+ metadata,
471
519
  )
@@ -106,6 +106,19 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
106
106
  "type": "boolean",
107
107
  "default": False,
108
108
  },
109
+ "thinking_enabled": {
110
+ "name": "Thinking Enabled",
111
+ "description": "Enable thinking mode for Gemini 3 Pro models. When enabled, the model will show its reasoning process.",
112
+ "type": "boolean",
113
+ "default": True,
114
+ },
115
+ "thinking_level": {
116
+ "name": "Thinking Level",
117
+ "description": "Controls the depth of thinking for Gemini 3 Pro models. Low = faster responses, High = more thorough reasoning.",
118
+ "type": "choice",
119
+ "choices": ["low", "high"],
120
+ "default": "low",
121
+ },
109
122
  }
110
123
 
111
124
 
@@ -40,6 +40,10 @@ from pydantic_ai.usage import RequestUsage
40
40
 
41
41
  logger = logging.getLogger(__name__)
42
42
 
43
+ # Bypass thought signature for Gemini when no pending signature is available
44
+ # This allows function calls to work with thinking models
45
+ BYPASS_THOUGHT_SIGNATURE = "context_engineering_is_the_way_to_go"
46
+
43
47
 
44
48
  def generate_tool_call_id() -> str:
45
49
  """Generate a unique tool call ID."""
@@ -410,27 +414,48 @@ class GeminiModel(Model):
410
414
  return system_instruction, contents
411
415
 
412
416
  def _map_model_response(self, m: ModelResponse) -> dict[str, Any] | None:
413
- """Map a ModelResponse to Gemini content format."""
417
+ """Map a ModelResponse to Gemini content format.
418
+
419
+ For Gemini thinking models, we need to track thought signatures from
420
+ ThinkingParts and apply them to subsequent function_call parts.
421
+ """
414
422
  parts: list[dict[str, Any]] = []
423
+ pending_signature: str | None = None
415
424
 
416
425
  for item in m.parts:
417
426
  if isinstance(item, ToolCallPart):
418
- parts.append(
419
- {
420
- "function_call": {
421
- "name": item.tool_name,
422
- "args": item.args_as_dict(),
423
- "id": item.tool_call_id,
424
- }
427
+ part_dict: dict[str, Any] = {
428
+ "function_call": {
429
+ "name": item.tool_name,
430
+ "args": item.args_as_dict(),
431
+ "id": item.tool_call_id,
425
432
  }
433
+ }
434
+ # Gemini thinking models REQUIRE thoughtSignature on function calls
435
+ # Use pending signature from thinking or bypass signature
436
+ part_dict["thoughtSignature"] = (
437
+ pending_signature
438
+ if pending_signature is not None
439
+ else BYPASS_THOUGHT_SIGNATURE
426
440
  )
441
+ parts.append(part_dict)
427
442
  elif isinstance(item, TextPart):
428
- parts.append({"text": item.content})
443
+ part_dict = {"text": item.content}
444
+ # Apply pending signature to text parts too if present
445
+ if pending_signature is not None:
446
+ part_dict["thoughtSignature"] = pending_signature
447
+ pending_signature = None
448
+ parts.append(part_dict)
429
449
  elif isinstance(item, ThinkingPart):
430
450
  if item.content:
431
- part_dict: dict[str, Any] = {"text": item.content, "thought": True}
451
+ part_dict = {"text": item.content, "thought": True}
432
452
  if item.signature:
433
453
  part_dict["thoughtSignature"] = item.signature
454
+ # Store signature for subsequent parts
455
+ pending_signature = item.signature
456
+ else:
457
+ # No signature on thinking part, use bypass
458
+ pending_signature = BYPASS_THOUGHT_SIGNATURE
434
459
  parts.append(part_dict)
435
460
 
436
461
  if not parts:
@@ -462,18 +487,34 @@ class GeminiModel(Model):
462
487
  config: dict[str, Any] = {}
463
488
 
464
489
  if model_settings:
465
- if (
466
- hasattr(model_settings, "temperature")
467
- and model_settings.temperature is not None
468
- ):
469
- config["temperature"] = model_settings.temperature
470
- if hasattr(model_settings, "top_p") and model_settings.top_p is not None:
471
- config["topP"] = model_settings.top_p
472
- if (
473
- hasattr(model_settings, "max_tokens")
474
- and model_settings.max_tokens is not None
475
- ):
476
- config["maxOutputTokens"] = model_settings.max_tokens
490
+ # ModelSettings is a TypedDict, so use .get() for all access
491
+ temperature = model_settings.get("temperature")
492
+ if temperature is not None:
493
+ config["temperature"] = temperature
494
+
495
+ top_p = model_settings.get("top_p")
496
+ if top_p is not None:
497
+ config["topP"] = top_p
498
+
499
+ max_tokens = model_settings.get("max_tokens")
500
+ if max_tokens is not None:
501
+ config["maxOutputTokens"] = max_tokens
502
+
503
+ # Handle Gemini 3 Pro thinking settings
504
+ thinking_enabled = model_settings.get("thinking_enabled")
505
+ thinking_level = model_settings.get("thinking_level")
506
+
507
+ # Build thinkingConfig if thinking settings are present
508
+ if thinking_enabled is False:
509
+ # Disable thinking by not including thinkingConfig
510
+ pass
511
+ elif thinking_level is not None:
512
+ # Gemini 3 Pro uses thinkingLevel with values "low" or "high"
513
+ # includeThoughts=True is required to surface the thinking in the response
514
+ config["thinkingConfig"] = {
515
+ "thinkingLevel": thinking_level,
516
+ "includeThoughts": True,
517
+ }
477
518
 
478
519
  return config
479
520
 
@@ -74,6 +74,7 @@ def make_model_settings(
74
74
  get_effective_model_settings,
75
75
  get_openai_reasoning_effort,
76
76
  get_openai_verbosity,
77
+ model_supports_setting,
77
78
  )
78
79
 
79
80
  model_settings_dict: dict = {}
@@ -131,6 +132,18 @@ def make_model_settings(
131
132
  }
132
133
  model_settings = AnthropicModelSettings(**model_settings_dict)
133
134
 
135
+ # Handle Gemini thinking models (Gemini-3)
136
+ # Check if model supports thinking settings and apply defaults
137
+ if model_supports_setting(model_name, "thinking_level"):
138
+ # Apply defaults if not explicitly set by user
139
+ # Default: thinking_enabled=True, thinking_level="low"
140
+ if "thinking_enabled" not in model_settings_dict:
141
+ model_settings_dict["thinking_enabled"] = True
142
+ if "thinking_level" not in model_settings_dict:
143
+ model_settings_dict["thinking_level"] = "low"
144
+ # Recreate settings with Gemini thinking config
145
+ model_settings = ModelSettings(**model_settings_dict)
146
+
134
147
  return model_settings
135
148
 
136
149
 
@@ -33,13 +33,13 @@
33
33
  "type": "gemini",
34
34
  "name": "gemini-3-pro-preview",
35
35
  "context_length": 200000,
36
- "supported_settings": ["temperature", "top_p"]
36
+ "supported_settings": ["temperature", "top_p", "thinking_enabled", "thinking_level"]
37
37
  },
38
38
  "Gemini-3-Long-Context": {
39
39
  "type": "gemini",
40
40
  "name": "gemini-3-pro-preview",
41
41
  "context_length": 1000000,
42
- "supported_settings": ["temperature", "top_p"]
42
+ "supported_settings": ["temperature", "top_p", "thinking_enabled", "thinking_level"]
43
43
  },
44
44
  "gpt-5.1": {
45
45
  "type": "openai",
@@ -0,0 +1,25 @@
1
+ """
2
+ Claude Code OAuth Plugin for Code Puppy
3
+
4
+ This plugin provides OAuth authentication for Claude Code and automatically
5
+ adds available models to the extra_models.json configuration.
6
+
7
+ The plugin also includes a token refresh heartbeat for maintaining fresh
8
+ tokens during long-running agentic operations.
9
+ """
10
+
11
+ from .token_refresh_heartbeat import (
12
+ TokenRefreshHeartbeat,
13
+ force_token_refresh,
14
+ get_current_heartbeat,
15
+ is_heartbeat_running,
16
+ token_refresh_heartbeat_context,
17
+ )
18
+
19
+ __all__ = [
20
+ "TokenRefreshHeartbeat",
21
+ "token_refresh_heartbeat_context",
22
+ "is_heartbeat_running",
23
+ "get_current_heartbeat",
24
+ "force_token_refresh",
25
+ ]
@@ -363,6 +363,78 @@ def _register_model_types() -> List[Dict[str, Any]]:
363
363
  return [{"type": "claude_code", "handler": _create_claude_code_model}]
364
364
 
365
365
 
366
+ # Global storage for the token refresh heartbeat
367
+ # Using a dict to allow multiple concurrent agent runs (keyed by session_id)
368
+ _active_heartbeats: Dict[str, Any] = {}
369
+
370
+
371
+ async def _on_agent_run_start(
372
+ agent_name: str,
373
+ model_name: str,
374
+ session_id: Optional[str] = None,
375
+ ) -> None:
376
+ """Start token refresh heartbeat for Claude Code OAuth models.
377
+
378
+ This callback is triggered when an agent run starts. If the model is a
379
+ Claude Code OAuth model, we start a background heartbeat to keep the
380
+ token fresh during long-running operations.
381
+ """
382
+ # Only start heartbeat for Claude Code models
383
+ if not model_name.startswith("claude-code"):
384
+ return
385
+
386
+ try:
387
+ from .token_refresh_heartbeat import TokenRefreshHeartbeat
388
+
389
+ heartbeat = TokenRefreshHeartbeat()
390
+ await heartbeat.start()
391
+
392
+ # Store heartbeat for cleanup, keyed by session_id
393
+ key = session_id or "default"
394
+ _active_heartbeats[key] = heartbeat
395
+ logger.debug(
396
+ "Started token refresh heartbeat for session %s (model: %s)",
397
+ key,
398
+ model_name,
399
+ )
400
+ except ImportError:
401
+ logger.debug("Token refresh heartbeat module not available")
402
+ except Exception as exc:
403
+ logger.debug("Failed to start token refresh heartbeat: %s", exc)
404
+
405
+
406
+ async def _on_agent_run_end(
407
+ agent_name: str,
408
+ model_name: str,
409
+ session_id: Optional[str] = None,
410
+ success: bool = True,
411
+ error: Optional[Exception] = None,
412
+ response_text: Optional[str] = None,
413
+ metadata: Optional[Dict[str, Any]] = None,
414
+ ) -> None:
415
+ """Stop token refresh heartbeat when agent run ends.
416
+
417
+ This callback is triggered when an agent run completes (success or failure).
418
+ We stop any heartbeat that was started for this session.
419
+ """
420
+ # We don't use response_text or metadata, just cleanup the heartbeat
421
+ key = session_id or "default"
422
+ heartbeat = _active_heartbeats.pop(key, None)
423
+
424
+ if heartbeat is not None:
425
+ try:
426
+ await heartbeat.stop()
427
+ logger.debug(
428
+ "Stopped token refresh heartbeat for session %s (refreshed %d times)",
429
+ key,
430
+ heartbeat.refresh_count,
431
+ )
432
+ except Exception as exc:
433
+ logger.debug("Error stopping token refresh heartbeat: %s", exc)
434
+
435
+
366
436
  register_callback("custom_command_help", _custom_help)
367
437
  register_callback("custom_command", _handle_custom_command)
368
438
  register_callback("register_model_type", _register_model_types)
439
+ register_callback("agent_run_start", _on_agent_run_start)
440
+ register_callback("agent_run_end", _on_agent_run_end)