stackwright-puppy 0.0.472.post1__py3-none-any.whl

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 (309) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/_builder.py +285 -0
  5. code_puppy/agents/_compaction.py +485 -0
  6. code_puppy/agents/_diagnostics.py +173 -0
  7. code_puppy/agents/_history.py +273 -0
  8. code_puppy/agents/_key_listeners.py +150 -0
  9. code_puppy/agents/_non_streaming_render.py +149 -0
  10. code_puppy/agents/_runtime.py +523 -0
  11. code_puppy/agents/agent_c_reviewer.py +154 -0
  12. code_puppy/agents/agent_code_puppy.py +96 -0
  13. code_puppy/agents/agent_code_reviewer.py +89 -0
  14. code_puppy/agents/agent_cpp_reviewer.py +131 -0
  15. code_puppy/agents/agent_creator_agent.py +631 -0
  16. code_puppy/agents/agent_golang_reviewer.py +150 -0
  17. code_puppy/agents/agent_helios.py +123 -0
  18. code_puppy/agents/agent_javascript_reviewer.py +159 -0
  19. code_puppy/agents/agent_manager.py +742 -0
  20. code_puppy/agents/agent_pack_leader.py +402 -0
  21. code_puppy/agents/agent_planning.py +164 -0
  22. code_puppy/agents/agent_python_programmer.py +168 -0
  23. code_puppy/agents/agent_python_reviewer.py +89 -0
  24. code_puppy/agents/agent_qa_expert.py +162 -0
  25. code_puppy/agents/agent_qa_kitten.py +207 -0
  26. code_puppy/agents/agent_scheduler.py +120 -0
  27. code_puppy/agents/agent_security_auditor.py +180 -0
  28. code_puppy/agents/agent_terminal_qa.py +322 -0
  29. code_puppy/agents/agent_typescript_reviewer.py +165 -0
  30. code_puppy/agents/base_agent.py +189 -0
  31. code_puppy/agents/event_stream_handler.py +349 -0
  32. code_puppy/agents/json_agent.py +202 -0
  33. code_puppy/agents/pack/__init__.py +32 -0
  34. code_puppy/agents/pack/bloodhound.py +303 -0
  35. code_puppy/agents/pack/retriever.py +392 -0
  36. code_puppy/agents/pack/shepherd.py +344 -0
  37. code_puppy/agents/pack/terrier.py +286 -0
  38. code_puppy/agents/pack/watchdog.py +366 -0
  39. code_puppy/agents/prompt_reviewer.py +144 -0
  40. code_puppy/agents/subagent_stream_handler.py +277 -0
  41. code_puppy/api/__init__.py +13 -0
  42. code_puppy/api/app.py +169 -0
  43. code_puppy/api/main.py +21 -0
  44. code_puppy/api/pty_manager.py +453 -0
  45. code_puppy/api/routers/__init__.py +12 -0
  46. code_puppy/api/routers/agents.py +36 -0
  47. code_puppy/api/routers/commands.py +217 -0
  48. code_puppy/api/routers/config.py +75 -0
  49. code_puppy/api/routers/sessions.py +234 -0
  50. code_puppy/api/templates/terminal.html +361 -0
  51. code_puppy/api/websocket.py +154 -0
  52. code_puppy/callbacks.py +790 -0
  53. code_puppy/chatgpt_codex_client.py +338 -0
  54. code_puppy/claude_cache_client.py +773 -0
  55. code_puppy/cli_runner.py +1063 -0
  56. code_puppy/command_line/__init__.py +1 -0
  57. code_puppy/command_line/add_model_menu.py +1332 -0
  58. code_puppy/command_line/agent_menu.py +675 -0
  59. code_puppy/command_line/attachments.py +395 -0
  60. code_puppy/command_line/autosave_menu.py +716 -0
  61. code_puppy/command_line/clipboard.py +527 -0
  62. code_puppy/command_line/colors_menu.py +532 -0
  63. code_puppy/command_line/command_handler.py +293 -0
  64. code_puppy/command_line/command_registry.py +150 -0
  65. code_puppy/command_line/config_commands.py +719 -0
  66. code_puppy/command_line/core_commands.py +764 -0
  67. code_puppy/command_line/diff_menu.py +865 -0
  68. code_puppy/command_line/file_path_completion.py +73 -0
  69. code_puppy/command_line/load_context_completion.py +52 -0
  70. code_puppy/command_line/mcp/__init__.py +10 -0
  71. code_puppy/command_line/mcp/base.py +32 -0
  72. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  73. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  74. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  75. code_puppy/command_line/mcp/edit_command.py +148 -0
  76. code_puppy/command_line/mcp/handler.py +138 -0
  77. code_puppy/command_line/mcp/help_command.py +147 -0
  78. code_puppy/command_line/mcp/install_command.py +214 -0
  79. code_puppy/command_line/mcp/install_menu.py +705 -0
  80. code_puppy/command_line/mcp/list_command.py +94 -0
  81. code_puppy/command_line/mcp/logs_command.py +235 -0
  82. code_puppy/command_line/mcp/remove_command.py +82 -0
  83. code_puppy/command_line/mcp/restart_command.py +100 -0
  84. code_puppy/command_line/mcp/search_command.py +123 -0
  85. code_puppy/command_line/mcp/start_all_command.py +135 -0
  86. code_puppy/command_line/mcp/start_command.py +117 -0
  87. code_puppy/command_line/mcp/status_command.py +184 -0
  88. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  89. code_puppy/command_line/mcp/stop_command.py +80 -0
  90. code_puppy/command_line/mcp/test_command.py +107 -0
  91. code_puppy/command_line/mcp/utils.py +129 -0
  92. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  93. code_puppy/command_line/mcp_completion.py +174 -0
  94. code_puppy/command_line/model_picker_completion.py +512 -0
  95. code_puppy/command_line/model_settings_menu.py +986 -0
  96. code_puppy/command_line/onboarding_slides.py +179 -0
  97. code_puppy/command_line/onboarding_wizard.py +342 -0
  98. code_puppy/command_line/pagination.py +43 -0
  99. code_puppy/command_line/pin_command_completion.py +329 -0
  100. code_puppy/command_line/prompt_toolkit_completion.py +850 -0
  101. code_puppy/command_line/session_commands.py +304 -0
  102. code_puppy/command_line/shell_passthrough.py +145 -0
  103. code_puppy/command_line/skills_completion.py +160 -0
  104. code_puppy/command_line/uc_menu.py +908 -0
  105. code_puppy/command_line/utils.py +93 -0
  106. code_puppy/command_line/wiggum_state.py +78 -0
  107. code_puppy/config.py +1972 -0
  108. code_puppy/error_logging.py +134 -0
  109. code_puppy/gemini_code_assist.py +385 -0
  110. code_puppy/gemini_model.py +840 -0
  111. code_puppy/hook_engine/README.md +105 -0
  112. code_puppy/hook_engine/__init__.py +21 -0
  113. code_puppy/hook_engine/aliases.py +155 -0
  114. code_puppy/hook_engine/engine.py +221 -0
  115. code_puppy/hook_engine/executor.py +296 -0
  116. code_puppy/hook_engine/matcher.py +156 -0
  117. code_puppy/hook_engine/models.py +240 -0
  118. code_puppy/hook_engine/registry.py +106 -0
  119. code_puppy/hook_engine/validator.py +144 -0
  120. code_puppy/http_utils.py +361 -0
  121. code_puppy/keymap.py +128 -0
  122. code_puppy/list_filtering.py +26 -0
  123. code_puppy/main.py +10 -0
  124. code_puppy/mcp_/__init__.py +66 -0
  125. code_puppy/mcp_/async_lifecycle.py +286 -0
  126. code_puppy/mcp_/blocking_startup.py +469 -0
  127. code_puppy/mcp_/captured_stdio_server.py +275 -0
  128. code_puppy/mcp_/circuit_breaker.py +290 -0
  129. code_puppy/mcp_/config_wizard.py +507 -0
  130. code_puppy/mcp_/dashboard.py +308 -0
  131. code_puppy/mcp_/error_isolation.py +407 -0
  132. code_puppy/mcp_/examples/retry_example.py +226 -0
  133. code_puppy/mcp_/health_monitor.py +589 -0
  134. code_puppy/mcp_/managed_server.py +428 -0
  135. code_puppy/mcp_/manager.py +872 -0
  136. code_puppy/mcp_/mcp_logs.py +224 -0
  137. code_puppy/mcp_/registry.py +451 -0
  138. code_puppy/mcp_/retry_manager.py +337 -0
  139. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  140. code_puppy/mcp_/status_tracker.py +355 -0
  141. code_puppy/mcp_/system_tools.py +209 -0
  142. code_puppy/mcp_prompts/__init__.py +1 -0
  143. code_puppy/mcp_prompts/hook_creator.py +103 -0
  144. code_puppy/messaging/__init__.py +255 -0
  145. code_puppy/messaging/bus.py +613 -0
  146. code_puppy/messaging/commands.py +167 -0
  147. code_puppy/messaging/markdown_patches.py +57 -0
  148. code_puppy/messaging/message_queue.py +361 -0
  149. code_puppy/messaging/messages.py +569 -0
  150. code_puppy/messaging/queue_console.py +271 -0
  151. code_puppy/messaging/renderers.py +311 -0
  152. code_puppy/messaging/rich_renderer.py +1158 -0
  153. code_puppy/messaging/spinner/__init__.py +83 -0
  154. code_puppy/messaging/spinner/console_spinner.py +240 -0
  155. code_puppy/messaging/spinner/spinner_base.py +95 -0
  156. code_puppy/messaging/subagent_console.py +460 -0
  157. code_puppy/model_factory.py +938 -0
  158. code_puppy/model_switching.py +63 -0
  159. code_puppy/model_utils.py +156 -0
  160. code_puppy/models.json +165 -0
  161. code_puppy/models_dev_api.json +1 -0
  162. code_puppy/models_dev_parser.py +592 -0
  163. code_puppy/plugins/__init__.py +186 -0
  164. code_puppy/plugins/agent_skills/__init__.py +22 -0
  165. code_puppy/plugins/agent_skills/config.py +175 -0
  166. code_puppy/plugins/agent_skills/discovery.py +136 -0
  167. code_puppy/plugins/agent_skills/downloader.py +392 -0
  168. code_puppy/plugins/agent_skills/installer.py +22 -0
  169. code_puppy/plugins/agent_skills/metadata.py +219 -0
  170. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  171. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  172. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  173. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  174. code_puppy/plugins/agent_skills/skills_install_menu.py +691 -0
  175. code_puppy/plugins/agent_skills/skills_menu.py +797 -0
  176. code_puppy/plugins/aws_bedrock/__init__.py +14 -0
  177. code_puppy/plugins/aws_bedrock/config.py +101 -0
  178. code_puppy/plugins/aws_bedrock/register_callbacks.py +243 -0
  179. code_puppy/plugins/aws_bedrock/utils.py +155 -0
  180. code_puppy/plugins/azure_foundry/README.md +238 -0
  181. code_puppy/plugins/azure_foundry/__init__.py +15 -0
  182. code_puppy/plugins/azure_foundry/config.py +127 -0
  183. code_puppy/plugins/azure_foundry/discovery.py +189 -0
  184. code_puppy/plugins/azure_foundry/register_callbacks.py +497 -0
  185. code_puppy/plugins/azure_foundry/token.py +182 -0
  186. code_puppy/plugins/azure_foundry/utils.py +348 -0
  187. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  188. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  189. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  190. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +174 -0
  191. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  192. code_puppy/plugins/chatgpt_oauth/utils.py +530 -0
  193. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  194. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  195. code_puppy/plugins/claude_code_hooks/register_callbacks.py +176 -0
  196. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  197. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  198. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  199. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  200. code_puppy/plugins/claude_code_oauth/fast_mode.py +128 -0
  201. code_puppy/plugins/claude_code_oauth/prompt_handler.py +65 -0
  202. code_puppy/plugins/claude_code_oauth/register_callbacks.py +515 -0
  203. code_puppy/plugins/claude_code_oauth/test_fast_mode.py +167 -0
  204. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  205. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  206. code_puppy/plugins/claude_code_oauth/utils.py +649 -0
  207. code_puppy/plugins/copilot_auth/__init__.py +11 -0
  208. code_puppy/plugins/copilot_auth/config.py +93 -0
  209. code_puppy/plugins/copilot_auth/reasoning_client.py +411 -0
  210. code_puppy/plugins/copilot_auth/register_callbacks.py +463 -0
  211. code_puppy/plugins/copilot_auth/utils.py +587 -0
  212. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  213. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  214. code_puppy/plugins/example_custom_command/README.md +280 -0
  215. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  216. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  217. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  218. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  219. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  220. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  221. code_puppy/plugins/hook_creator/__init__.py +1 -0
  222. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  223. code_puppy/plugins/hook_manager/__init__.py +1 -0
  224. code_puppy/plugins/hook_manager/config.py +290 -0
  225. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  226. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  227. code_puppy/plugins/oauth_puppy_html.py +228 -0
  228. code_puppy/plugins/ollama_setup/__init__.py +5 -0
  229. code_puppy/plugins/ollama_setup/completer.py +38 -0
  230. code_puppy/plugins/ollama_setup/register_callbacks.py +412 -0
  231. code_puppy/plugins/pop_command/__init__.py +1 -0
  232. code_puppy/plugins/pop_command/register_callbacks.py +191 -0
  233. code_puppy/plugins/scheduler/__init__.py +1 -0
  234. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  235. code_puppy/plugins/scheduler/scheduler_menu.py +538 -0
  236. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  237. code_puppy/plugins/shell_safety/__init__.py +6 -0
  238. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  239. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  240. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  241. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  242. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  243. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  244. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  245. code_puppy/plugins/universal_constructor/models.py +138 -0
  246. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  247. code_puppy/plugins/universal_constructor/registry.py +302 -0
  248. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  249. code_puppy/provider_identity.py +107 -0
  250. code_puppy/pydantic_patches.py +356 -0
  251. code_puppy/reopenable_async_client.py +232 -0
  252. code_puppy/round_robin_model.py +150 -0
  253. code_puppy/scheduler/__init__.py +41 -0
  254. code_puppy/scheduler/__main__.py +9 -0
  255. code_puppy/scheduler/cli.py +118 -0
  256. code_puppy/scheduler/config.py +126 -0
  257. code_puppy/scheduler/daemon.py +280 -0
  258. code_puppy/scheduler/executor.py +155 -0
  259. code_puppy/scheduler/platform.py +19 -0
  260. code_puppy/scheduler/platform_unix.py +22 -0
  261. code_puppy/scheduler/platform_win.py +32 -0
  262. code_puppy/session_storage.py +338 -0
  263. code_puppy/status_display.py +257 -0
  264. code_puppy/summarization_agent.py +174 -0
  265. code_puppy/terminal_utils.py +418 -0
  266. code_puppy/tools/__init__.py +501 -0
  267. code_puppy/tools/agent_tools.py +646 -0
  268. code_puppy/tools/ask_user_question/__init__.py +26 -0
  269. code_puppy/tools/ask_user_question/constants.py +73 -0
  270. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  271. code_puppy/tools/ask_user_question/handler.py +234 -0
  272. code_puppy/tools/ask_user_question/models.py +304 -0
  273. code_puppy/tools/ask_user_question/registration.py +39 -0
  274. code_puppy/tools/ask_user_question/renderers.py +309 -0
  275. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  276. code_puppy/tools/ask_user_question/theme.py +155 -0
  277. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  278. code_puppy/tools/browser/__init__.py +37 -0
  279. code_puppy/tools/browser/browser_control.py +289 -0
  280. code_puppy/tools/browser/browser_interactions.py +545 -0
  281. code_puppy/tools/browser/browser_locators.py +640 -0
  282. code_puppy/tools/browser/browser_manager.py +378 -0
  283. code_puppy/tools/browser/browser_navigation.py +251 -0
  284. code_puppy/tools/browser/browser_screenshot.py +179 -0
  285. code_puppy/tools/browser/browser_scripts.py +462 -0
  286. code_puppy/tools/browser/browser_workflows.py +221 -0
  287. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  288. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  289. code_puppy/tools/browser/terminal_screenshot_tools.py +677 -0
  290. code_puppy/tools/browser/terminal_tools.py +525 -0
  291. code_puppy/tools/command_runner.py +1346 -0
  292. code_puppy/tools/common.py +1409 -0
  293. code_puppy/tools/display.py +84 -0
  294. code_puppy/tools/file_modifications.py +913 -0
  295. code_puppy/tools/file_operations.py +802 -0
  296. code_puppy/tools/scheduler_tools.py +412 -0
  297. code_puppy/tools/skills_tools.py +244 -0
  298. code_puppy/tools/subagent_context.py +158 -0
  299. code_puppy/tools/tools_content.py +50 -0
  300. code_puppy/tools/universal_constructor.py +893 -0
  301. code_puppy/uvx_detection.py +242 -0
  302. code_puppy/version_checker.py +82 -0
  303. stackwright_puppy-0.0.472.post1.data/data/code_puppy/models.json +165 -0
  304. stackwright_puppy-0.0.472.post1.data/data/code_puppy/models_dev_api.json +1 -0
  305. stackwright_puppy-0.0.472.post1.dist-info/METADATA +809 -0
  306. stackwright_puppy-0.0.472.post1.dist-info/RECORD +309 -0
  307. stackwright_puppy-0.0.472.post1.dist-info/WHEEL +4 -0
  308. stackwright_puppy-0.0.472.post1.dist-info/entry_points.txt +4 -0
  309. stackwright_puppy-0.0.472.post1.dist-info/licenses/LICENSE +21 -0
code_puppy/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ import importlib.metadata
2
+
3
+ # Biscuit was here! 🐶
4
+ try:
5
+ _detected_version = importlib.metadata.version("code-puppy")
6
+ # Ensure we never end up with None or empty string
7
+ __version__ = _detected_version if _detected_version else "0.0.0-dev"
8
+ except Exception:
9
+ # Fallback for dev environments where metadata might not be available
10
+ __version__ = "0.0.0-dev"
code_puppy/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ Entry point for running code-puppy as a module.
3
+
4
+ This allows the package to be run with: python -m code_puppy
5
+ """
6
+
7
+ from code_puppy.main import main_entry
8
+
9
+ if __name__ == "__main__":
10
+ main_entry()
@@ -0,0 +1,31 @@
1
+ """Agent management system for code-puppy.
2
+
3
+ This module provides functionality for switching between different agent
4
+ configurations, each with their own system prompts and tool sets.
5
+ """
6
+
7
+ from .agent_manager import (
8
+ clone_agent,
9
+ delete_clone_agent,
10
+ get_agent_descriptions,
11
+ get_available_agents,
12
+ get_current_agent,
13
+ is_clone_agent_name,
14
+ load_agent,
15
+ refresh_agents,
16
+ set_current_agent,
17
+ )
18
+ from .subagent_stream_handler import subagent_stream_handler
19
+
20
+ __all__ = [
21
+ "clone_agent",
22
+ "delete_clone_agent",
23
+ "get_available_agents",
24
+ "get_current_agent",
25
+ "is_clone_agent_name",
26
+ "set_current_agent",
27
+ "load_agent",
28
+ "get_agent_descriptions",
29
+ "refresh_agents",
30
+ "subagent_stream_handler",
31
+ ]
@@ -0,0 +1,285 @@
1
+ """Pydantic-ai agent construction + MCP wiring, extracted from ``BaseAgent``.
2
+
3
+ Collapses the previous duplicated DBOS vs non-DBOS build paths and the parallel
4
+ ``_create_agent_with_output_type`` method into a single ``build_pydantic_agent``
5
+ entry point. Everything else in here (puppy rules loading, MCP server loading,
6
+ model fallback, MCP tool filtering) is a pure free function.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import uuid
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional, Set, Tuple
14
+
15
+ from pydantic_ai import Agent as PydanticAgent
16
+ from pydantic_ai.durable_exec.dbos import DBOSAgent
17
+ from rich.text import Text
18
+
19
+ from code_puppy.agents._compaction import make_history_processor
20
+ from code_puppy.agents.event_stream_handler import event_stream_handler
21
+ from code_puppy.config import (
22
+ CONFIG_DIR,
23
+ get_global_model_name,
24
+ get_use_dbos,
25
+ get_value,
26
+ )
27
+ from code_puppy.mcp_ import get_mcp_manager
28
+ from code_puppy.messaging import emit_error, emit_info, emit_warning
29
+ from code_puppy.model_factory import ModelFactory, make_model_settings
30
+
31
+ # Module-level counter used when naming DBOSAgent instances. Incremented every
32
+ # time ``build_pydantic_agent`` wraps with DBOS so each workflow has a unique name.
33
+ _reload_count = 0
34
+
35
+ _AGENT_RULE_FILES = ("AGENTS.md", "AGENT.md", "agents.md", "agent.md")
36
+
37
+
38
+ def load_puppy_rules() -> Optional[str]:
39
+ """Load AGENT(S).md from global config dir and/or the current project dir.
40
+
41
+ Global rules (``~/.code_puppy/AGENTS.md``) come first; project-local rules
42
+ are appended, allowing projects to override/extend global ones. Returns
43
+ ``None`` if neither exists.
44
+ """
45
+ global_rules: Optional[str] = None
46
+ for name in _AGENT_RULE_FILES:
47
+ candidate = Path(CONFIG_DIR) / name
48
+ if candidate.exists():
49
+ global_rules = candidate.read_text(encoding="utf-8-sig")
50
+ break
51
+
52
+ project_rules: Optional[str] = None
53
+ for name in _AGENT_RULE_FILES:
54
+ candidate = Path(name)
55
+ if candidate.exists():
56
+ project_rules = candidate.read_text(encoding="utf-8-sig")
57
+ break
58
+
59
+ rules = [r for r in (global_rules, project_rules) if r]
60
+ return "\n\n".join(rules) if rules else None
61
+
62
+
63
+ def load_mcp_servers(
64
+ extra_headers: Optional[Dict[str, str]] = None,
65
+ ) -> List[Any]:
66
+ """Return pydantic-ai compatible MCP servers, or ``[]`` if disabled."""
67
+ del extra_headers # accepted for API compatibility; manager owns headers
68
+ mcp_disabled = get_value("disable_mcp_servers")
69
+ if mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on"):
70
+ return []
71
+ return get_mcp_manager().get_servers_for_agent()
72
+
73
+
74
+ def reload_mcp_servers() -> List[Any]:
75
+ """Force re-sync from ``mcp_servers.json`` and return updated servers."""
76
+ manager = get_mcp_manager()
77
+ manager.sync_from_config()
78
+ return manager.get_servers_for_agent()
79
+
80
+
81
+ def load_model_with_fallback(
82
+ requested_model_name: str,
83
+ models_config: Dict[str, Any],
84
+ message_group: str,
85
+ ) -> Tuple[Any, str]:
86
+ """Load the requested model, or fall back to a sensible alternative.
87
+
88
+ Falls back in order: the globally configured model, then any other
89
+ configured model. Raises ``ValueError`` only if nothing loads.
90
+ """
91
+ try:
92
+ return ModelFactory.get_model(
93
+ requested_model_name, models_config
94
+ ), requested_model_name
95
+ except ValueError as exc:
96
+ available = list(models_config.keys())
97
+ available_str = (
98
+ ", ".join(sorted(available)) if available else "no configured models"
99
+ )
100
+ emit_warning(
101
+ f"Model '{requested_model_name}' not found. Available models: {available_str}",
102
+ message_group=message_group,
103
+ )
104
+
105
+ candidates: List[str] = []
106
+ global_candidate = get_global_model_name()
107
+ if global_candidate:
108
+ candidates.append(global_candidate)
109
+ for candidate in available:
110
+ if candidate not in candidates:
111
+ candidates.append(candidate)
112
+
113
+ for candidate in candidates:
114
+ if not candidate or candidate == requested_model_name:
115
+ continue
116
+ try:
117
+ model = ModelFactory.get_model(candidate, models_config)
118
+ emit_info(
119
+ f"Using fallback model: {candidate}", message_group=message_group
120
+ )
121
+ return model, candidate
122
+ except ValueError:
123
+ continue
124
+
125
+ friendly = (
126
+ "No valid model could be loaded. Update the model configuration or "
127
+ "set a valid model with `config set`."
128
+ )
129
+ emit_error(friendly, message_group=message_group)
130
+ raise ValueError(friendly) from exc
131
+
132
+
133
+ def filter_conflicting_mcp_tools(
134
+ mcp_servers: List[Any],
135
+ existing_tool_names: Set[str],
136
+ ) -> List[Any]:
137
+ """Strip any MCP tools whose names collide with already-registered tools.
138
+
139
+ Returns a new list of MCP toolsets (possibly containing filtered ``ToolSet``
140
+ replacements). If a server doesn't expose a ``.tools`` attribute we pass it
141
+ through unchanged — better to risk a duplicate than to drop the whole server.
142
+ """
143
+ if not mcp_servers or not existing_tool_names:
144
+ return list(mcp_servers) if mcp_servers else []
145
+
146
+ from pydantic_ai.tools import ToolSet
147
+
148
+ filtered: List[Any] = []
149
+ for server in mcp_servers:
150
+ server_tools = getattr(server, "tools", None)
151
+ if server_tools is None:
152
+ filtered.append(server)
153
+ continue
154
+
155
+ kept = {
156
+ name: func
157
+ for name, func in server_tools.items()
158
+ if name not in existing_tool_names
159
+ }
160
+ if not kept:
161
+ continue # whole server was conflicts — drop it
162
+
163
+ replacement = ToolSet()
164
+ for name, func in kept.items():
165
+ replacement._tools[name] = func
166
+ filtered.append(replacement)
167
+
168
+ return filtered
169
+
170
+
171
+ def _assemble_instructions(agent: Any, resolved_model_name: str) -> str:
172
+ """Compose full system prompt + puppy rules + extended-thinking note."""
173
+ from code_puppy.model_utils import prepare_prompt_for_model
174
+ from code_puppy.tools import (
175
+ EXTENDED_THINKING_PROMPT_NOTE,
176
+ has_extended_thinking_active,
177
+ )
178
+
179
+ instructions = agent.get_full_system_prompt()
180
+ puppy_rules = load_puppy_rules()
181
+ if puppy_rules:
182
+ instructions += f"\n{puppy_rules}"
183
+
184
+ if has_extended_thinking_active(resolved_model_name):
185
+ instructions += EXTENDED_THINKING_PROMPT_NOTE
186
+
187
+ prepared = prepare_prompt_for_model(
188
+ agent.get_model_name(), instructions, "", prepend_system_to_user=False
189
+ )
190
+ return prepared.instructions
191
+
192
+
193
+ def build_pydantic_agent(
194
+ agent: Any,
195
+ output_type: Any = str,
196
+ message_group: Optional[str] = None,
197
+ ) -> Any:
198
+ """Build (and wire up) the pydantic-ai agent for ``agent``.
199
+
200
+ Replaces the old ``reload_code_generation_agent`` + ``_create_agent_with_output_type``
201
+ pair. Side effects on ``agent``:
202
+
203
+ - ``agent._puppy_rules = None`` (invalidates any cached rules)
204
+ - ``agent.cur_model`` ← resolved pydantic-ai model
205
+ - ``agent._last_model_name`` ← resolved model name
206
+ - ``agent.pydantic_agent`` ← the final (possibly DBOS-wrapped) agent
207
+ - ``agent._code_generation_agent`` ← same as ``pydantic_agent``
208
+ - ``agent._mcp_servers`` ← MCP toolsets (post-filter)
209
+
210
+ The build happens in two passes: we construct once with ``toolsets=[]`` so
211
+ we can introspect registered tool names, then rebuild with MCP servers
212
+ filtered against those names to prevent collisions. DBOS keeps MCP out of
213
+ the constructor entirely — the runtime injects it via ``_toolsets`` swap.
214
+ """
215
+ global _reload_count
216
+
217
+ from code_puppy.tools import register_tools_for_agent
218
+
219
+ agent._puppy_rules = None
220
+ message_group = message_group or str(uuid.uuid4())
221
+
222
+ models_config = ModelFactory.load_config()
223
+ model, resolved_model_name = load_model_with_fallback(
224
+ agent.get_model_name(), models_config, message_group
225
+ )
226
+ instructions = _assemble_instructions(agent, resolved_model_name)
227
+ mcp_servers = load_mcp_servers()
228
+ model_settings = make_model_settings(resolved_model_name)
229
+ history_processor = make_history_processor(agent)
230
+
231
+ def _new_pydantic_agent(toolsets: List[Any]) -> PydanticAgent:
232
+ return PydanticAgent(
233
+ model=model,
234
+ instructions=instructions,
235
+ output_type=output_type,
236
+ retries=3,
237
+ toolsets=toolsets,
238
+ history_processors=[history_processor],
239
+ model_settings=model_settings,
240
+ )
241
+
242
+ # Pass 1: build with empty toolsets so we can see what pydantic-ai + our
243
+ # tool registry actually produced, and filter MCP to avoid name clashes.
244
+ probe_agent = _new_pydantic_agent(toolsets=[])
245
+ agent_tools = agent.get_available_tools()
246
+ register_tools_for_agent(probe_agent, agent_tools, model_name=resolved_model_name)
247
+
248
+ existing_tool_names: Set[str] = set(getattr(probe_agent, "_tools", {}) or {})
249
+ filtered_mcp_servers = filter_conflicting_mcp_tools(
250
+ mcp_servers, existing_tool_names
251
+ )
252
+
253
+ dropped = len(mcp_servers) - len(filtered_mcp_servers)
254
+ if dropped:
255
+ emit_info(
256
+ Text.from_markup(f"[dim]Filtered {dropped} conflicting MCP tools[/dim]")
257
+ )
258
+
259
+ # Pass 2: real build. DBOS path keeps MCP out of the constructor because
260
+ # pydantic-ai's DBOS integration can't pickle async_generator toolsets.
261
+ use_dbos = get_use_dbos()
262
+ final_toolsets = [] if use_dbos else filtered_mcp_servers
263
+ final_pydantic = _new_pydantic_agent(toolsets=final_toolsets)
264
+ register_tools_for_agent(
265
+ final_pydantic, agent_tools, model_name=resolved_model_name
266
+ )
267
+
268
+ agent.cur_model = model
269
+ agent._last_model_name = resolved_model_name
270
+ agent._mcp_servers = filtered_mcp_servers
271
+
272
+ if use_dbos:
273
+ _reload_count += 1
274
+ wrapped = DBOSAgent(
275
+ final_pydantic,
276
+ name=f"{agent.name}-{_reload_count}",
277
+ event_stream_handler=event_stream_handler,
278
+ )
279
+ agent.pydantic_agent = wrapped
280
+ agent._code_generation_agent = wrapped
281
+ return wrapped
282
+
283
+ agent.pydantic_agent = final_pydantic
284
+ agent._code_generation_agent = final_pydantic
285
+ return final_pydantic