code-puppy-core-plugins 0.0.2__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 (313) hide show
  1. code_puppy_core_plugins/__init__.py +5 -0
  2. code_puppy_core_plugins/acp/README.md +266 -0
  3. code_puppy_core_plugins/acp/__init__.py +8 -0
  4. code_puppy_core_plugins/acp/agent.py +494 -0
  5. code_puppy_core_plugins/acp/bridge.py +309 -0
  6. code_puppy_core_plugins/acp/capabilities.py +73 -0
  7. code_puppy_core_plugins/acp/commands.py +116 -0
  8. code_puppy_core_plugins/acp/content.py +115 -0
  9. code_puppy_core_plugins/acp/io_delegation.py +231 -0
  10. code_puppy_core_plugins/acp/mcp_config.py +66 -0
  11. code_puppy_core_plugins/acp/permissions.py +159 -0
  12. code_puppy_core_plugins/acp/persistence.py +197 -0
  13. code_puppy_core_plugins/acp/register_callbacks.py +110 -0
  14. code_puppy_core_plugins/acp/replay.py +114 -0
  15. code_puppy_core_plugins/acp/session.py +329 -0
  16. code_puppy_core_plugins/acp/session_config.py +179 -0
  17. code_puppy_core_plugins/acp/state.py +135 -0
  18. code_puppy_core_plugins/agent_skills/__init__.py +22 -0
  19. code_puppy_core_plugins/agent_skills/config.py +208 -0
  20. code_puppy_core_plugins/agent_skills/discovery.py +337 -0
  21. code_puppy_core_plugins/agent_skills/downloader.py +392 -0
  22. code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
  23. code_puppy_core_plugins/agent_skills/installer.py +22 -0
  24. code_puppy_core_plugins/agent_skills/metadata.py +219 -0
  25. code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
  26. code_puppy_core_plugins/agent_skills/provider.py +52 -0
  27. code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
  28. code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
  29. code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
  30. code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
  31. code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
  32. code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
  33. code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
  34. code_puppy_core_plugins/aws_bedrock/config.py +101 -0
  35. code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
  36. code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
  37. code_puppy_core_plugins/azure_foundry/README.md +238 -0
  38. code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
  39. code_puppy_core_plugins/azure_foundry/config.py +126 -0
  40. code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
  41. code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
  42. code_puppy_core_plugins/azure_foundry/token.py +182 -0
  43. code_puppy_core_plugins/azure_foundry/utils.py +375 -0
  44. code_puppy_core_plugins/btw/__init__.py +1 -0
  45. code_puppy_core_plugins/btw/inline_view.py +135 -0
  46. code_puppy_core_plugins/btw/register_callbacks.py +106 -0
  47. code_puppy_core_plugins/btw/side_query.py +91 -0
  48. code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
  49. code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
  50. code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
  51. code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
  52. code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
  53. code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
  54. code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
  55. code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
  56. code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
  57. code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
  58. code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
  59. code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
  60. code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
  61. code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
  62. code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
  63. code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
  64. code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
  65. code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
  66. code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
  67. code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
  68. code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
  69. code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
  70. code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
  71. code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
  72. code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
  73. code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
  74. code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
  75. code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
  76. code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
  77. code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
  78. code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
  79. code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
  80. code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
  81. code_puppy_core_plugins/computer_use/README.md +109 -0
  82. code_puppy_core_plugins/computer_use/__init__.py +1 -0
  83. code_puppy_core_plugins/computer_use/accessibility.py +163 -0
  84. code_puppy_core_plugins/computer_use/activation.py +75 -0
  85. code_puppy_core_plugins/computer_use/backend.py +584 -0
  86. code_puppy_core_plugins/computer_use/backend_types.py +5 -0
  87. code_puppy_core_plugins/computer_use/capture.py +110 -0
  88. code_puppy_core_plugins/computer_use/commands.py +62 -0
  89. code_puppy_core_plugins/computer_use/geometry.py +81 -0
  90. code_puppy_core_plugins/computer_use/inline_image.py +56 -0
  91. code_puppy_core_plugins/computer_use/keycodes.py +47 -0
  92. code_puppy_core_plugins/computer_use/policy.py +144 -0
  93. code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
  94. code_puppy_core_plugins/computer_use/safety.py +18 -0
  95. code_puppy_core_plugins/computer_use/settle.py +64 -0
  96. code_puppy_core_plugins/computer_use/state.py +86 -0
  97. code_puppy_core_plugins/computer_use/tools.py +469 -0
  98. code_puppy_core_plugins/context_indicator/__init__.py +14 -0
  99. code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
  100. code_puppy_core_plugins/context_indicator/usage.py +40 -0
  101. code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
  102. code_puppy_core_plugins/copilot_auth/config.py +93 -0
  103. code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
  104. code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
  105. code_puppy_core_plugins/copilot_auth/utils.py +587 -0
  106. code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
  107. code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
  108. code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
  109. code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
  110. code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
  111. code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
  112. code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
  113. code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
  114. code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
  115. code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
  116. code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
  117. code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
  118. code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
  119. code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
  120. code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
  121. code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
  122. code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
  123. code_puppy_core_plugins/emoji_filter/config.py +26 -0
  124. code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
  125. code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
  126. code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
  127. code_puppy_core_plugins/example_custom_command/README.md +328 -0
  128. code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
  129. code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
  130. code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
  131. code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
  132. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
  133. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
  134. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
  135. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
  136. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
  137. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
  138. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
  139. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
  140. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
  141. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
  142. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
  143. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
  144. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
  145. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
  146. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
  147. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
  148. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
  149. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
  150. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
  151. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
  152. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
  153. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
  154. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
  155. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
  156. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
  157. code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
  158. code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
  159. code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
  160. code_puppy_core_plugins/force_push_guard/detector.py +96 -0
  161. code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
  162. code_puppy_core_plugins/fork/__init__.py +7 -0
  163. code_puppy_core_plugins/fork/register_callbacks.py +445 -0
  164. code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
  165. code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
  166. code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
  167. code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
  168. code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
  169. code_puppy_core_plugins/grok_oauth/config.py +49 -0
  170. code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
  171. code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
  172. code_puppy_core_plugins/grok_oauth/utils.py +195 -0
  173. code_puppy_core_plugins/herdr/README.md +168 -0
  174. code_puppy_core_plugins/herdr/__init__.py +9 -0
  175. code_puppy_core_plugins/herdr/client.py +339 -0
  176. code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
  177. code_puppy_core_plugins/herdr/reporter.py +212 -0
  178. code_puppy_core_plugins/herdr/smoke.py +143 -0
  179. code_puppy_core_plugins/herdr/sources.py +133 -0
  180. code_puppy_core_plugins/hook_creator/__init__.py +1 -0
  181. code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
  182. code_puppy_core_plugins/hook_manager/__init__.py +1 -0
  183. code_puppy_core_plugins/hook_manager/config.py +290 -0
  184. code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
  185. code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
  186. code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
  187. code_puppy_core_plugins/meta_oauth/config.py +55 -0
  188. code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
  189. code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
  190. code_puppy_core_plugins/meta_oauth/utils.py +252 -0
  191. code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
  192. code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
  193. code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
  194. code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
  195. code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
  196. code_puppy_core_plugins/no_tools/__init__.py +0 -0
  197. code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
  198. code_puppy_core_plugins/oauth_pasteback.py +110 -0
  199. code_puppy_core_plugins/oauth_puppy_html.py +194 -0
  200. code_puppy_core_plugins/obsidian_agent/README.md +52 -0
  201. code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
  202. code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
  203. code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
  204. code_puppy_core_plugins/ollama/__init__.py +1 -0
  205. code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
  206. code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
  207. code_puppy_core_plugins/ollama_setup/completer.py +38 -0
  208. code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
  209. code_puppy_core_plugins/plugin_list/__init__.py +0 -0
  210. code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
  211. code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
  212. code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
  213. code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
  214. code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
  215. code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
  216. code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
  217. code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
  218. code_puppy_core_plugins/pop_command/__init__.py +1 -0
  219. code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
  220. code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
  221. code_puppy_core_plugins/prompt_newline/config.py +21 -0
  222. code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
  223. code_puppy_core_plugins/prune/__init__.py +1 -0
  224. code_puppy_core_plugins/prune/prune_menu.py +308 -0
  225. code_puppy_core_plugins/prune/prune_model.py +517 -0
  226. code_puppy_core_plugins/prune/prune_render.py +354 -0
  227. code_puppy_core_plugins/prune/register_callbacks.py +388 -0
  228. code_puppy_core_plugins/puppy_kennel/README.md +203 -0
  229. code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
  230. code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
  231. code_puppy_core_plugins/puppy_kennel/config.py +36 -0
  232. code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
  233. code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
  234. code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
  235. code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
  236. code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
  237. code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
  238. code_puppy_core_plugins/puppy_kennel/state.py +68 -0
  239. code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
  240. code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
  241. code_puppy_core_plugins/puppy_spinner/README.md +93 -0
  242. code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
  243. code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
  244. code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
  245. code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
  246. code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
  247. code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
  248. code_puppy_core_plugins/quick_resume/__init__.py +0 -0
  249. code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
  250. code_puppy_core_plugins/review_pr/__init__.py +1 -0
  251. code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
  252. code_puppy_core_plugins/shell_safety/__init__.py +6 -0
  253. code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
  254. code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
  255. code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
  256. code_puppy_core_plugins/stack_dump/__init__.py +1 -0
  257. code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
  258. code_puppy_core_plugins/statusline/__init__.py +14 -0
  259. code_puppy_core_plugins/statusline/config.py +68 -0
  260. code_puppy_core_plugins/statusline/payload.py +138 -0
  261. code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
  262. code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
  263. code_puppy_core_plugins/statusline/runner.py +122 -0
  264. code_puppy_core_plugins/statusline/statusline_command.py +197 -0
  265. code_puppy_core_plugins/steer_queue/__init__.py +10 -0
  266. code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
  267. code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
  268. code_puppy_core_plugins/subagent_panel/README.md +102 -0
  269. code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
  270. code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
  271. code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
  272. code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
  273. code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
  274. code_puppy_core_plugins/subagent_panel/state.py +231 -0
  275. code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
  276. code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
  277. code_puppy_core_plugins/theme/README.md +86 -0
  278. code_puppy_core_plugins/theme/__init__.py +14 -0
  279. code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
  280. code_puppy_core_plugins/theme/content_styles.py +146 -0
  281. code_puppy_core_plugins/theme/osc_palette.py +178 -0
  282. code_puppy_core_plugins/theme/picker.py +358 -0
  283. code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
  284. code_puppy_core_plugins/theme/register_callbacks.py +348 -0
  285. code_puppy_core_plugins/theme/rich_themes.py +202 -0
  286. code_puppy_core_plugins/theme/themes.py +720 -0
  287. code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
  288. code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
  289. code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
  290. code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
  291. code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
  292. code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
  293. code_puppy_core_plugins/universal_constructor/models.py +138 -0
  294. code_puppy_core_plugins/universal_constructor/provider.py +62 -0
  295. code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
  296. code_puppy_core_plugins/universal_constructor/registry.py +302 -0
  297. code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
  298. code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
  299. code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
  300. code_puppy_core_plugins/wiggum/__init__.py +1 -0
  301. code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
  302. code_puppy_core_plugins/wiggum/judge.py +299 -0
  303. code_puppy_core_plugins/wiggum/judge_config.py +249 -0
  304. code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
  305. code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
  306. code_puppy_core_plugins/wiggum/state.py +89 -0
  307. code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
  308. code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
  309. code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
  310. code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
  311. code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
  312. code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
  313. code_puppy_core_plugins-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,375 @@
1
+ """Utility functions for the Azure AI Foundry plugin.
2
+
3
+ This module provides helper functions for configuration management,
4
+ environment variable resolution, and model configuration.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import os
12
+ import re
13
+ from typing import Any
14
+
15
+ from .config import (
16
+ DEFAULT_CONTEXT_LENGTHS,
17
+ ENV_FOUNDRY_RESOURCE,
18
+ get_extra_models_path,
19
+ get_openai_context_length,
20
+ )
21
+ from code_puppy import atomic_io, atomic_json
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Context window suffix pattern: [<number><unit>] where unit is k or m (case-insensitive)
26
+ # Examples: [200k], [500k], [1m], [2m]
27
+ _CONTEXT_SUFFIX_PATTERN = re.compile(r"\[(\d+)([km])\]", re.IGNORECASE)
28
+
29
+ # Multipliers for context window units
30
+ _CONTEXT_MULTIPLIERS = {
31
+ "k": 1_000,
32
+ "m": 1_000_000,
33
+ }
34
+
35
+
36
+ def parse_context_window_suffix(name: str) -> tuple[str, int | None]:
37
+ """Parse and extract context window suffix from a model/deployment name.
38
+
39
+ Supports Claude Code format: [<number><unit>] where unit is 'k' (thousands)
40
+ or 'm' (millions). Examples: [200k], [500k], [1m], [2m]
41
+
42
+ Args:
43
+ name: The model or deployment name, possibly with context suffix.
44
+ e.g., "it-entra-claude-opus-4-6[1m]"
45
+
46
+ Returns:
47
+ A tuple of (stripped_name, context_length):
48
+ - stripped_name: The name with the suffix removed
49
+ - context_length: The parsed context length in tokens, or None if no suffix
50
+
51
+ Examples:
52
+ >>> parse_context_window_suffix("claude-opus-4-6[1m]")
53
+ ('claude-opus-4-6', 1000000)
54
+ >>> parse_context_window_suffix("claude-sonnet[200k]")
55
+ ('claude-sonnet', 200000)
56
+ >>> parse_context_window_suffix("claude-haiku-4-5")
57
+ ('claude-haiku-4-5', None)
58
+ """
59
+ match = _CONTEXT_SUFFIX_PATTERN.search(name)
60
+ if not match:
61
+ return name, None
62
+
63
+ number = int(match.group(1))
64
+ unit = match.group(2).lower()
65
+ multiplier = _CONTEXT_MULTIPLIERS[unit]
66
+ context_length = number * multiplier
67
+
68
+ # Remove the suffix from the name
69
+ stripped_name = _CONTEXT_SUFFIX_PATTERN.sub("", name).strip()
70
+
71
+ return stripped_name, context_length
72
+
73
+
74
+ def resolve_env_var(value: str) -> str:
75
+ """Resolve a value that may contain an environment variable reference.
76
+
77
+ If the value starts with '$', it's treated as an environment variable
78
+ reference and resolved. Otherwise, the value is returned as-is.
79
+
80
+ Args:
81
+ value: The value to resolve, possibly starting with '$'.
82
+
83
+ Returns:
84
+ The resolved value.
85
+
86
+ Example:
87
+ >>> os.environ["MY_VAR"] = "test"
88
+ >>> resolve_env_var("$MY_VAR")
89
+ 'test'
90
+ >>> resolve_env_var("literal")
91
+ 'literal'
92
+ """
93
+ if value and value.startswith("$"):
94
+ env_var = value[1:]
95
+ resolved = os.environ.get(env_var)
96
+ if resolved is None:
97
+ logger.warning(f"Environment variable '{env_var}' not set")
98
+ return ""
99
+ return resolved
100
+ return value
101
+
102
+
103
+ def load_extra_models() -> dict[str, Any]:
104
+ """Load the extra_models.json configuration file.
105
+
106
+ Bounded and lock-aware via :mod:`code_puppy.atomic_json` -- a large or
107
+ concurrently-being-written file can no longer balloon memory or read a
108
+ torn write.
109
+
110
+ Returns:
111
+ Dictionary containing model configurations, or empty dict if file
112
+ doesn't exist or is invalid.
113
+ """
114
+ extra_models_path = str(get_extra_models_path())
115
+ try:
116
+ return atomic_json.load_json(extra_models_path, default={})
117
+ except atomic_json.JsonFileCorrupt as e:
118
+ logger.error(f"Invalid JSON in extra_models.json: {e}")
119
+ return {}
120
+ except OSError as e:
121
+ logger.error(f"Error loading extra_models.json: {e}")
122
+ return {}
123
+
124
+
125
+ def save_extra_models(models: dict[str, Any]) -> bool:
126
+ """Save model configurations to extra_models.json.
127
+
128
+ Args:
129
+ models: Dictionary of model configurations to save.
130
+
131
+ Returns:
132
+ True if save succeeded, False otherwise.
133
+ """
134
+ extra_models_path = str(get_extra_models_path())
135
+
136
+ try:
137
+ with atomic_io.path_lock(extra_models_path):
138
+ atomic_io.atomic_write_bytes(
139
+ extra_models_path,
140
+ json.dumps(models, indent=2, ensure_ascii=False).encode("utf-8"),
141
+ )
142
+
143
+ logger.info(f"Saved {len(models)} models to extra_models.json")
144
+ return True
145
+
146
+ except Exception as e:
147
+ logger.error(f"Error saving extra_models.json: {e}")
148
+ return False
149
+
150
+
151
+ def build_foundry_model_config(
152
+ deployment_name: str,
153
+ model_tier: str,
154
+ foundry_resource: str | None = None,
155
+ context_length: int | None = None,
156
+ ) -> dict[str, Any]:
157
+ """Build a Code Puppy model configuration for an Azure Foundry deployment.
158
+
159
+ Args:
160
+ deployment_name: The deployment name in Azure Foundry.
161
+ model_tier: One of "opus", "sonnet", or "haiku".
162
+ foundry_resource: The Foundry resource name. If None, uses env var reference.
163
+ context_length: Override for context length. If None, uses default for tier.
164
+
165
+ Returns:
166
+ Dictionary containing the model configuration.
167
+ """
168
+ if context_length is None:
169
+ context_length = DEFAULT_CONTEXT_LENGTHS.get(model_tier.lower(), 200000)
170
+
171
+ resource_value = (
172
+ foundry_resource if foundry_resource else f"${ENV_FOUNDRY_RESOURCE}"
173
+ )
174
+
175
+ # All Anthropic models need extended_thinking + budget_tokens in the request body,
176
+ # plus interleaved_thinking for the beta header; Opus additionally adds effort.
177
+ supported_settings = [
178
+ "temperature",
179
+ "extended_thinking",
180
+ "budget_tokens",
181
+ "interleaved_thinking",
182
+ ]
183
+ if model_tier.lower() == "opus":
184
+ supported_settings.append("effort")
185
+
186
+ return {
187
+ "type": "azure_foundry",
188
+ "provider": "azure_foundry",
189
+ "name": deployment_name,
190
+ "foundry_resource": resource_value,
191
+ "context_length": context_length,
192
+ "supported_settings": supported_settings,
193
+ }
194
+
195
+
196
+ def add_foundry_models_to_config(
197
+ resource_name: str,
198
+ opus_deployment: str | None = None,
199
+ sonnet_deployment: str | None = None,
200
+ haiku_deployment: str | None = None,
201
+ ) -> list[str]:
202
+ """Add Azure Foundry model configurations to extra_models.json.
203
+
204
+ Args:
205
+ resource_name: The Azure Foundry resource name.
206
+ opus_deployment: Deployment name for Opus model (optional).
207
+ sonnet_deployment: Deployment name for Sonnet model (optional).
208
+ haiku_deployment: Deployment name for Haiku model (optional).
209
+
210
+ Returns:
211
+ List of model keys that were added.
212
+
213
+ Uses :func:`code_puppy.atomic_json.mutate_json` so the read and write
214
+ happen inside one locked transaction -- extra_models.json is also
215
+ touched by add_model_menu.py and the ollama_setup/aws_bedrock plugins,
216
+ so a read-then-write split across two unlocked calls could lose one of
217
+ those concurrent updates.
218
+ """
219
+ deployments = [
220
+ ("opus", opus_deployment, "foundry-claude-opus"),
221
+ ("sonnet", sonnet_deployment, "foundry-claude-sonnet"),
222
+ ("haiku", haiku_deployment, "foundry-claude-haiku"),
223
+ ]
224
+
225
+ entries: dict[str, dict[str, Any]] = {}
226
+ for tier, deployment, model_key in deployments:
227
+ if deployment:
228
+ # Parse context window suffix (Claude Code format: [200k], [1m], etc.)
229
+ actual_deployment, context_length = parse_context_window_suffix(deployment)
230
+ entries[model_key] = build_foundry_model_config(
231
+ deployment_name=actual_deployment,
232
+ model_tier=tier,
233
+ foundry_resource=resource_name,
234
+ context_length=context_length,
235
+ )
236
+
237
+ if not entries:
238
+ return []
239
+
240
+ def _mutate(models: dict[str, Any]) -> dict[str, Any]:
241
+ models.update(entries)
242
+ return models
243
+
244
+ extra_models_path = str(get_extra_models_path())
245
+ try:
246
+ atomic_json.mutate_json(extra_models_path, _mutate, default={})
247
+ except (atomic_json.JsonFileCorrupt, OSError) as e:
248
+ logger.error(f"Error saving extra_models.json: {e}")
249
+ return []
250
+
251
+ return list(entries.keys())
252
+
253
+
254
+ FOUNDRY_TYPES = {"azure_foundry", "azure_foundry_openai"}
255
+
256
+
257
+ _GPT5_SUPPORTED_SETTINGS = [
258
+ "reasoning_effort",
259
+ "summary",
260
+ "verbosity",
261
+ ]
262
+
263
+
264
+ def get_foundry_openai_supported_settings(model_name: str) -> list[str]:
265
+ """Return supported settings for an Azure Foundry OpenAI model.
266
+
267
+ Later GPT-5-family models support Code Puppy's reasoning/summary/verbosity
268
+ controls in addition to the baseline temperature setting.
269
+ """
270
+ supported_settings = ["temperature"]
271
+ if model_name.startswith("gpt-5"):
272
+ supported_settings.extend(_GPT5_SUPPORTED_SETTINGS)
273
+ return supported_settings
274
+
275
+
276
+ def add_discovered_models_to_config(
277
+ resource_name: str,
278
+ deployments: list,
279
+ ) -> list[str]:
280
+ """Add auto-discovered deployments to extra_models.json.
281
+
282
+ Classifies each deployment by model format (Anthropic vs OpenAI)
283
+ and creates the appropriate model config.
284
+
285
+ Uses :func:`code_puppy.atomic_json.mutate_json` so the read and write
286
+ happen inside one locked transaction (see :func:`add_foundry_models_to_config`).
287
+ """
288
+ from .discovery import AzureDeployment
289
+
290
+ entries: dict[str, dict[str, Any]] = {}
291
+
292
+ for d in deployments:
293
+ if not isinstance(d, AzureDeployment):
294
+ continue
295
+
296
+ key = f"foundry-{d.name}"
297
+
298
+ if d.model_format == "Anthropic":
299
+ tier = "haiku"
300
+ for t in ("opus", "sonnet"):
301
+ if t in d.model_name.lower():
302
+ tier = t
303
+ break
304
+ entries[key] = build_foundry_model_config(
305
+ deployment_name=d.name,
306
+ model_tier=tier,
307
+ foundry_resource=resource_name,
308
+ )
309
+
310
+ elif d.model_format == "OpenAI":
311
+ entries[key] = {
312
+ "type": "azure_foundry_openai",
313
+ "provider": "azure_foundry_openai",
314
+ "name": d.name,
315
+ "foundry_resource": resource_name,
316
+ "context_length": get_openai_context_length(d.model_name),
317
+ "supported_settings": get_foundry_openai_supported_settings(
318
+ d.model_name
319
+ ),
320
+ }
321
+
322
+ if not entries:
323
+ return []
324
+
325
+ def _mutate(models: dict[str, Any]) -> dict[str, Any]:
326
+ models.update(entries)
327
+ return models
328
+
329
+ extra_models_path = str(get_extra_models_path())
330
+ try:
331
+ atomic_json.mutate_json(extra_models_path, _mutate, default={})
332
+ except (atomic_json.JsonFileCorrupt, OSError) as e:
333
+ logger.error(f"Error saving extra_models.json: {e}")
334
+ return []
335
+
336
+ return list(entries.keys())
337
+
338
+
339
+ def remove_foundry_models_from_config() -> list[str]:
340
+ """Remove all Azure Foundry model configurations from extra_models.json.
341
+
342
+ Uses :func:`code_puppy.atomic_json.mutate_json` so the read and write
343
+ happen inside one locked transaction (see :func:`add_foundry_models_to_config`).
344
+ """
345
+ extra_models_path = str(get_extra_models_path())
346
+ removed_models: list[str] = []
347
+
348
+ def _mutate(models: dict[str, Any]) -> dict[str, Any]:
349
+ keys_to_remove = [
350
+ key
351
+ for key, config in models.items()
352
+ if isinstance(config, dict) and config.get("type") in FOUNDRY_TYPES
353
+ ]
354
+ for key in keys_to_remove:
355
+ del models[key]
356
+ removed_models.append(key)
357
+ return models
358
+
359
+ try:
360
+ atomic_json.mutate_json(extra_models_path, _mutate, default={})
361
+ except (atomic_json.JsonFileCorrupt, OSError) as e:
362
+ logger.error(f"Failed to persist model removal: {e}")
363
+ return []
364
+
365
+ return removed_models
366
+
367
+
368
+ def get_foundry_models_from_config() -> dict[str, Any]:
369
+ """Get all Azure Foundry model configurations from extra_models.json."""
370
+ models = load_extra_models()
371
+ return {
372
+ key: config
373
+ for key, config in models.items()
374
+ if isinstance(config, dict) and config.get("type") in FOUNDRY_TYPES
375
+ }
@@ -0,0 +1 @@
1
+ """/btw — quick side questions without polluting the main conversation."""
@@ -0,0 +1,135 @@
1
+ """Inline rendering for `/btw` answers (Claude Code style).
2
+
3
+ Why direct console writes instead of ``emit_info``: the `/btw` handler
4
+ blocks the main event loop while the side query runs, which starves the
5
+ async transcript renderer — queued messages would only paint *after*
6
+ the handler returns. Every slash command already executes under
7
+ ``suspended_run_ui`` (idle and mid-run), so the terminal is ours for
8
+ the duration; printing straight to it is the same trick the TUI menus
9
+ use.
10
+
11
+ The dismiss-wait is load-bearing mid-run: the agent stays parked at its
12
+ pause boundary until the user dismisses (or the timeout fires), so the
13
+ resuming stream can't immediately scroll the answer away.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import os
20
+ import sys
21
+ import time
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ DISMISS_TIMEOUT_S = 120.0
26
+ _DISMISS_KEYS = (b" ", b"\r", b"\n", b"\x1b")
27
+ _HINT = "Press Space, Enter, or Escape to dismiss"
28
+ _INDENT = (0, 0, 0, 2) # answer block: two-cell left inset
29
+
30
+
31
+ def is_tty() -> bool:
32
+ if os.getenv("CODE_PUPPY_NO_TUI") == "1":
33
+ return False
34
+ try:
35
+ return sys.stdin.isatty() and sys.stdout.isatty()
36
+ except Exception:
37
+ return False
38
+
39
+
40
+ def _console():
41
+ from rich.console import Console
42
+
43
+ return Console()
44
+
45
+
46
+ def show_asking(question: str, model_name: str) -> None:
47
+ """Echo the question and a progress line before the query blocks."""
48
+ from rich.markup import escape
49
+
50
+ console = _console()
51
+ console.print()
52
+ console.print(f"[bold yellow]/btw[/bold yellow] [dim]{escape(question)}[/dim]")
53
+ console.print(f" [dim]thinking ({escape(model_name)})...[/dim]")
54
+
55
+
56
+ def show_answer(answer: str) -> None:
57
+ """Render the answer inline as Markdown, plus the dismiss hint."""
58
+ from rich.markdown import Markdown
59
+ from rich.padding import Padding
60
+
61
+ console = _console()
62
+ console.print()
63
+ console.print(Padding(Markdown(answer), _INDENT))
64
+ console.print()
65
+ console.print(f" [dim]{_HINT}[/dim]")
66
+
67
+
68
+ def wait_for_dismiss(timeout_s: float = DISMISS_TIMEOUT_S) -> None:
69
+ """Block until Space / Enter / Esc, or auto-dismiss on timeout.
70
+
71
+ Never raises. Skips waiting entirely when a raw key can't be read
72
+ (no termios/msvcrt, weird stdin) — better to resume than to hang.
73
+ """
74
+ if not is_tty():
75
+ return
76
+ try:
77
+ _wait_key_posix(timeout_s)
78
+ return
79
+ except ImportError:
80
+ pass
81
+ except Exception:
82
+ logger.debug("btw: posix key wait failed", exc_info=True)
83
+ return
84
+ try:
85
+ _wait_key_windows(timeout_s)
86
+ except Exception:
87
+ logger.debug("btw: windows key wait failed", exc_info=True)
88
+
89
+
90
+ def _wait_key_posix(timeout_s: float) -> None:
91
+ import select
92
+ import termios
93
+ import tty
94
+
95
+ fd = sys.stdin.fileno()
96
+ old = termios.tcgetattr(fd)
97
+ try:
98
+ tty.setcbreak(fd)
99
+ deadline = time.monotonic() + timeout_s
100
+ while True:
101
+ remaining = deadline - time.monotonic()
102
+ if remaining <= 0:
103
+ return
104
+ ready, _, _ = select.select([fd], [], [], min(remaining, 0.5))
105
+ if ready and os.read(fd, 1) in _DISMISS_KEYS:
106
+ return
107
+ finally:
108
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
109
+
110
+
111
+ def _wait_key_windows(timeout_s: float) -> None:
112
+ import msvcrt
113
+
114
+ deadline = time.monotonic() + timeout_s
115
+ while time.monotonic() < deadline:
116
+ if msvcrt.kbhit() and msvcrt.getch() in _DISMISS_KEYS:
117
+ return
118
+ time.sleep(0.05)
119
+
120
+
121
+ def emit_fallback(question: str, answer: str) -> None:
122
+ """Non-TTY path: plain transcript message via the message queue."""
123
+ from code_puppy.messaging import emit_info
124
+
125
+ emit_info(f"/btw {question}\n\n{answer}")
126
+
127
+
128
+ __all__ = [
129
+ "DISMISS_TIMEOUT_S",
130
+ "emit_fallback",
131
+ "is_tty",
132
+ "show_answer",
133
+ "show_asking",
134
+ "wait_for_dismiss",
135
+ ]
@@ -0,0 +1,106 @@
1
+ """Plugin: `/btw` — ask a quick side question without derailing the task.
2
+
3
+ Runs an independent, single-turn model query (fresh throwaway agent, no
4
+ tools, no shared history) and shows the answer inline, Claude Code
5
+ style. The main conversation's context window never sees the exchange,
6
+ so minor "by the way..." curiosities stop costing you expensive tokens.
7
+
8
+ Mid-run behaviour comes free from ``run_ui._run_paused_commands``:
9
+ slash commands typed while the agent works are queued, the in-flight
10
+ stream finishes, the agent parks at its pause boundary, the command
11
+ runs with the terminal released, then the run resumes. `/btw` just has
12
+ to behave like any other blocking command inside that window — and the
13
+ dismiss-wait keeps the window open until the user has read the answer.
14
+
15
+ Usage:
16
+ /btw <question> One-shot side query, answer shown inline
17
+ /btw --help Show usage
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ from typing import List, Optional, Tuple
24
+
25
+ from code_puppy.callbacks import register_callback
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ COMMAND_NAME = "btw"
30
+
31
+ _USAGE = (
32
+ "Usage: /btw <question>\n"
33
+ " Runs an independent single-turn query and shows the answer in a\n"
34
+ " temporary overlay. Your main conversation history is untouched."
35
+ )
36
+
37
+
38
+ def _custom_help() -> List[Tuple[str, str]]:
39
+ return [
40
+ (
41
+ COMMAND_NAME,
42
+ "Quick side question, answered inline (single turn, history untouched)",
43
+ )
44
+ ]
45
+
46
+
47
+ def _parse_question(command: str) -> Optional[str]:
48
+ """Return the question text, or None if usage was shown instead."""
49
+ parts = command.split(maxsplit=1)
50
+ question = parts[1].strip() if len(parts) == 2 else ""
51
+ if not question or question in {"--help", "-h", "help"}:
52
+ from code_puppy.messaging import emit_info
53
+
54
+ emit_info(_USAGE)
55
+ return None
56
+ return question
57
+
58
+
59
+ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
60
+ if name != COMMAND_NAME:
61
+ return None
62
+
63
+ question = _parse_question(command)
64
+ if question is None:
65
+ return True
66
+
67
+ from code_puppy.messaging import emit_error, emit_info
68
+
69
+ from . import inline_view
70
+ from .side_query import ask_blocking, resolve_model_name
71
+
72
+ model_name = resolve_model_name()
73
+ if not model_name:
74
+ emit_error("/btw: could not resolve a model to ask. Check your model config.")
75
+ return True
76
+
77
+ tty = inline_view.is_tty()
78
+ if tty:
79
+ inline_view.show_asking(question, model_name)
80
+ else:
81
+ emit_info(f"btw: asking {model_name} (side query, history untouched)...")
82
+
83
+ try:
84
+ answer = ask_blocking(model_name, question)
85
+ except Exception as exc:
86
+ emit_error(f"/btw failed ({type(exc).__name__}): {exc}")
87
+ return True
88
+
89
+ if tty:
90
+ inline_view.show_answer(answer)
91
+ inline_view.wait_for_dismiss()
92
+ else:
93
+ inline_view.emit_fallback(question, answer)
94
+ return True
95
+
96
+
97
+ register_callback("custom_command", _handle_custom_command)
98
+ register_callback("custom_command_help", _custom_help)
99
+
100
+
101
+ __all__ = [
102
+ "COMMAND_NAME",
103
+ "_custom_help",
104
+ "_handle_custom_command",
105
+ "_parse_question",
106
+ ]
@@ -0,0 +1,91 @@
1
+ """One-shot, history-free model query backing the `/btw` command.
2
+
3
+ Deliberately minimal (YAGNI): a throwaway pydantic_ai Agent with no
4
+ tools, no message history, and a tiny system prompt. The main agent's
5
+ context window never sees any of this — that's the whole point.
6
+
7
+ Threading note: ``handle_command`` executes synchronously *inside* the
8
+ already-running main event loop, so we can't ``asyncio.run`` on the
9
+ main thread. Same escape hatch as the ``steer_queue`` menu: hop to a
10
+ worker thread and give it its own loop.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import concurrent.futures
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ QUERY_TIMEOUT_S = 180.0
22
+
23
+ _INSTRUCTIONS = (
24
+ "You are answering a quick side question ('/btw') a developer asked "
25
+ "mid-coding-session. Answer directly and concisely — a few sentences "
26
+ "or a short snippet is ideal. No preamble, no follow-up questions."
27
+ )
28
+
29
+
30
+ def resolve_model_name() -> str | None:
31
+ """Current agent's pinned model if any, else the global model."""
32
+ try:
33
+ from code_puppy.agents.agent_manager import get_current_agent
34
+
35
+ pinned = get_current_agent().get_model_name()
36
+ if pinned:
37
+ return pinned
38
+ except Exception:
39
+ logger.debug("btw: agent model lookup failed", exc_info=True)
40
+ try:
41
+ from code_puppy.config import get_global_model_name
42
+
43
+ return get_global_model_name()
44
+ except Exception:
45
+ logger.debug("btw: global model lookup failed", exc_info=True)
46
+ return None
47
+
48
+
49
+ async def _ask(model_name: str, question: str) -> str:
50
+ """Single-turn query against a fresh Agent. Raises on failure."""
51
+ from pydantic_ai import Agent, UsageLimits
52
+
53
+ from code_puppy.model_factory import ModelFactory, make_model_settings
54
+ from code_puppy.model_utils import prepare_prompt_for_model
55
+
56
+ models_config = ModelFactory.load_config()
57
+ if model_name not in models_config:
58
+ raise ValueError(f"model {model_name!r} not present in the model config")
59
+
60
+ model = ModelFactory.get_model(model_name, models_config)
61
+ prepared = prepare_prompt_for_model(
62
+ model_name,
63
+ _INSTRUCTIONS,
64
+ question,
65
+ prepend_system_to_user=True,
66
+ )
67
+ agent = Agent(
68
+ model=model,
69
+ instructions=prepared.instructions,
70
+ retries=1,
71
+ model_settings=make_model_settings(model_name),
72
+ )
73
+ result = await agent.run(
74
+ prepared.user_prompt,
75
+ usage_limits=UsageLimits(request_limit=3),
76
+ )
77
+ return str(result.output)
78
+
79
+
80
+ def ask_blocking(
81
+ model_name: str,
82
+ question: str,
83
+ timeout_s: float = QUERY_TIMEOUT_S,
84
+ ) -> str:
85
+ """Run the one-shot query on a worker thread; block until answered."""
86
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
87
+ future = executor.submit(lambda: asyncio.run(_ask(model_name, question)))
88
+ return future.result(timeout=timeout_s)
89
+
90
+
91
+ __all__ = ["QUERY_TIMEOUT_S", "ask_blocking", "resolve_model_name"]