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,506 @@
1
+ """Azure AI Foundry Plugin callbacks for Code Puppy CLI.
2
+
3
+ This plugin enables Code Puppy to use Anthropic Claude models hosted on
4
+ Microsoft Azure AI Foundry with Azure AD (Entra ID) authentication.
5
+
6
+ The plugin uses credentials from `az login` to authenticate, eliminating
7
+ the need for API keys.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import sys
14
+ from typing import Any
15
+
16
+ from code_puppy.callbacks import register_callback
17
+ from code_puppy.command_line.utils import safe_input
18
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
19
+ from code_puppy.tools.command_runner import set_awaiting_user_input
20
+
21
+ from .config import (
22
+ DEFAULT_DEPLOYMENT_NAMES,
23
+ ENV_FOUNDRY_RESOURCE,
24
+ get_foundry_resource,
25
+ )
26
+ from .discovery import find_account, list_deployments
27
+ from .token import get_token_provider
28
+ from .utils import (
29
+ add_discovered_models_to_config,
30
+ add_foundry_models_to_config,
31
+ get_foundry_models_from_config,
32
+ remove_foundry_models_from_config,
33
+ resolve_env_var,
34
+ )
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ # ============================================================================
40
+ # Slash Command Handlers
41
+ # ============================================================================
42
+
43
+
44
+ def _handle_foundry_status() -> None:
45
+ """Handle the /foundry-status command.
46
+
47
+ Displays the current Azure AD authentication status and configured
48
+ Foundry models.
49
+ """
50
+ emit_info("")
51
+ emit_info("Azure AI Foundry Status")
52
+ emit_info("=" * 40)
53
+
54
+ # Check Azure AD authentication
55
+ token_provider = get_token_provider()
56
+ is_auth, status_msg, user_info = token_provider.check_auth_status()
57
+
58
+ if is_auth:
59
+ emit_success(f"Authentication: {status_msg}")
60
+ if user_info:
61
+ emit_info(f" Logged in as: {user_info}")
62
+ else:
63
+ emit_warning(f"Authentication: {status_msg}")
64
+
65
+ # List configured models and check resource
66
+ foundry_models = get_foundry_models_from_config()
67
+
68
+ # Check resource - from env var or from configured models
69
+ resource = get_foundry_resource()
70
+ if not resource and foundry_models:
71
+ # Get resource from first configured model
72
+ first_model = next(iter(foundry_models.values()))
73
+ resource = first_model.get("foundry_resource", "")
74
+ if resource.startswith("$"):
75
+ resource = None # It's an unresolved env var reference
76
+
77
+ emit_info("")
78
+ if resource:
79
+ emit_info(f"Foundry Resource: {resource}")
80
+ else:
81
+ emit_warning(f"Foundry Resource: Not set (set {ENV_FOUNDRY_RESOURCE})")
82
+
83
+ emit_info("")
84
+ if foundry_models:
85
+ emit_info(f"Configured Models ({len(foundry_models)}):")
86
+ for model_key, config in foundry_models.items():
87
+ deployment = config.get("name", "unknown")
88
+ emit_info(f" - {model_key}: {deployment}")
89
+ else:
90
+ emit_info("Configured Models: None")
91
+ emit_info(" Run /foundry-setup to configure models")
92
+
93
+ emit_info("")
94
+
95
+
96
+ def _handle_foundry_setup() -> None:
97
+ """Handle the /foundry-setup command.
98
+
99
+ Interactive wizard to configure Azure Foundry models.
100
+ Uses print() for synchronous output to avoid message bus buffering issues.
101
+ """
102
+
103
+ def _print(msg: str = "") -> None:
104
+ """Print with immediate flush."""
105
+ print(msg, flush=True)
106
+
107
+ _print()
108
+ _print("Azure AI Foundry Setup")
109
+ _print("=" * 40)
110
+ _print()
111
+
112
+ # Check Azure CLI authentication first
113
+ _print("Step 1: Checking Azure CLI authentication...")
114
+ token_provider = get_token_provider()
115
+ is_auth, status_msg, user_info = token_provider.check_auth_status()
116
+
117
+ if not is_auth:
118
+ _print(f" ERROR: {status_msg}")
119
+ _print()
120
+ _print("Please run 'az login' first, then try again.")
121
+ return
122
+
123
+ _print(f" OK: {status_msg}")
124
+ if user_info:
125
+ _print(f" User: {user_info}")
126
+ _print()
127
+
128
+ # Get resource name
129
+ _print("Step 2: Azure Resource Name")
130
+ current_resource = get_foundry_resource()
131
+ if current_resource:
132
+ _print(f" Current: {current_resource}")
133
+
134
+ resource_prompt = " Enter resource name"
135
+ if current_resource:
136
+ resource_prompt += f" [{current_resource}]"
137
+ resource_prompt += ": "
138
+
139
+ set_awaiting_user_input(True)
140
+ try:
141
+ sys.stdout.flush()
142
+ resource_input = safe_input(resource_prompt).strip()
143
+ resource_name = resource_input if resource_input else current_resource
144
+
145
+ if not resource_name:
146
+ _print(" ERROR: Resource name is required.")
147
+ return
148
+
149
+ _print()
150
+
151
+ # Step 3: Try auto-discovery, fall back to manual
152
+ _print("Step 3: Discovering deployments...")
153
+ account = find_account(resource_name)
154
+
155
+ if account:
156
+ _print(f" Found: {account.name} ({account.location})")
157
+ _print(f" RG: {account.resource_group}")
158
+ _print()
159
+
160
+ deployments = list_deployments(account)
161
+ succeeded = [d for d in deployments if d.provisioning_state == "Succeeded"]
162
+
163
+ if succeeded:
164
+ _print(f" {len(succeeded)} active deployment(s):")
165
+ for d in succeeded:
166
+ _print(f" - {d.name} ({d.model_format}: {d.model_name})")
167
+ _print()
168
+
169
+ sys.stdout.flush()
170
+ confirm = safe_input(" Configure these? [Y/n]: ").strip().lower()
171
+ if confirm not in ("", "y", "yes"):
172
+ _print(" Skipped.")
173
+ return
174
+ else:
175
+ _print(" No active deployments found.")
176
+ return
177
+ else:
178
+ _print(" Discovery failed — falling back to manual entry.")
179
+ _print()
180
+ succeeded = None
181
+
182
+ except (KeyboardInterrupt, EOFError):
183
+ _print()
184
+ _print("Setup cancelled.")
185
+ return
186
+ finally:
187
+ set_awaiting_user_input(False)
188
+
189
+ _print()
190
+
191
+ # Step 4: Save configuration
192
+ _print("Step 4: Saving configuration...")
193
+
194
+ if not get_foundry_resource():
195
+ _print(
196
+ f" Tip: Set {ENV_FOUNDRY_RESOURCE}={resource_name} in your environment"
197
+ )
198
+
199
+ if succeeded is not None:
200
+ # Auto-discovered — configure all succeeded deployments
201
+ added_models = add_discovered_models_to_config(resource_name, succeeded)
202
+ else:
203
+ # Manual fallback — use hardcoded Anthropic defaults
204
+ added_models = add_foundry_models_to_config(
205
+ resource_name=resource_name,
206
+ opus_deployment=DEFAULT_DEPLOYMENT_NAMES["opus"],
207
+ sonnet_deployment=DEFAULT_DEPLOYMENT_NAMES["sonnet"],
208
+ haiku_deployment=DEFAULT_DEPLOYMENT_NAMES["haiku"],
209
+ )
210
+
211
+ _print()
212
+ if added_models:
213
+ _print(f"OK: Configured {len(added_models)} model(s):")
214
+ for model_key in added_models:
215
+ _print(f" - {model_key}")
216
+ _print()
217
+ _print(f"Use '/model {added_models[0]}' to switch to a Foundry model.")
218
+ else:
219
+ _print("WARNING: No models were added. Check the configuration.")
220
+
221
+ _print()
222
+
223
+
224
+ def _handle_foundry_remove() -> None:
225
+ """Handle the /foundry-remove command.
226
+
227
+ Removes all Azure Foundry model configurations.
228
+ """
229
+ removed = remove_foundry_models_from_config()
230
+ if removed:
231
+ emit_success(f"Removed {len(removed)} Foundry model(s):")
232
+ for model_key in removed:
233
+ emit_info(f" - {model_key}")
234
+ else:
235
+ emit_info("No Foundry models found in configuration.")
236
+
237
+
238
+ # ============================================================================
239
+ # Custom Command Registration
240
+ # ============================================================================
241
+
242
+
243
+ def _custom_help() -> list[tuple[str, str]]:
244
+ """Return help entries for custom commands."""
245
+ return [
246
+ (
247
+ "foundry-status",
248
+ "Check Azure AI Foundry authentication and configuration status",
249
+ ),
250
+ ("foundry-setup", "Interactive wizard to configure Azure Foundry models"),
251
+ ("foundry-remove", "Remove all Azure Foundry model configurations"),
252
+ ]
253
+
254
+
255
+ def _handle_custom_command(command: str, name: str) -> bool | None:
256
+ """Handle custom slash commands for the Azure Foundry plugin.
257
+
258
+ Dispatches to handlers for "foundry-status", "foundry-setup", and
259
+ "foundry-remove" commands.
260
+
261
+ Args:
262
+ command: The full command string.
263
+ name: The command name (without slash).
264
+
265
+ Returns:
266
+ True if the command was handled successfully.
267
+ False if the command was recognized but handler() raised an exception.
268
+ None if the command is not handled by this plugin.
269
+ """
270
+ handlers = {
271
+ "foundry-status": _handle_foundry_status,
272
+ "foundry-setup": _handle_foundry_setup,
273
+ "foundry-remove": _handle_foundry_remove,
274
+ }
275
+
276
+ handler = handlers.get(name)
277
+ if handler is None:
278
+ return None
279
+
280
+ try:
281
+ handler()
282
+ return True
283
+ except Exception as e:
284
+ logger.exception("Error handling /%s command: %s", name, e)
285
+ emit_error(f"Command /{name} failed: {e}")
286
+ return False
287
+
288
+
289
+ # ============================================================================
290
+ # Model Type Handler
291
+ # ============================================================================
292
+
293
+
294
+ def _create_azure_foundry_model(
295
+ model_name: str, model_config: dict, config: dict
296
+ ) -> Any:
297
+ """Create an Azure Foundry model instance.
298
+
299
+ This handler is registered via the 'register_model_type' callback to handle
300
+ models with type='azure_foundry'.
301
+
302
+ Args:
303
+ model_name: The model key name (e.g., 'foundry-claude-opus').
304
+ model_config: The model configuration dictionary.
305
+ config: The full models configuration.
306
+
307
+ Returns:
308
+ An AnthropicModel instance configured for Azure Foundry, or None on error.
309
+ """
310
+ try:
311
+ from anthropic import AsyncAnthropicFoundry
312
+ from pydantic_ai.models.anthropic import AnthropicModel
313
+ except ImportError as e:
314
+ emit_error(
315
+ f"Failed to create Azure Foundry model '{model_name}': "
316
+ f"Missing dependency - {e}"
317
+ )
318
+ return None
319
+
320
+ from code_puppy.claude_cache_client import patch_anthropic_client_messages
321
+ from code_puppy.config import get_effective_model_settings
322
+ from code_puppy.model_factory import CONTEXT_1M_BETA
323
+ from code_puppy.provider_identity import (
324
+ make_anthropic_provider,
325
+ resolve_provider_identity,
326
+ )
327
+
328
+ # Get the Foundry resource name
329
+ resource_config = model_config.get("foundry_resource", f"${ENV_FOUNDRY_RESOURCE}")
330
+ resource_name = resolve_env_var(resource_config)
331
+
332
+ if not resource_name:
333
+ emit_warning(
334
+ f"Azure Foundry resource not configured for model '{model_name}'. "
335
+ f"Set {ENV_FOUNDRY_RESOURCE} or run /foundry-setup."
336
+ )
337
+ return None
338
+
339
+ # Get the deployment name (model name in Azure)
340
+ deployment_name = model_config.get("name")
341
+ if not deployment_name:
342
+ emit_warning(f"Deployment name not specified for model '{model_name}'.")
343
+ return None
344
+
345
+ # Get the token provider
346
+ token_provider = get_token_provider()
347
+
348
+ # Check authentication status
349
+ is_auth, status_msg, _ = token_provider.check_auth_status()
350
+ if not is_auth:
351
+ emit_warning(
352
+ f"Azure AD authentication failed for model '{model_name}': {status_msg}"
353
+ )
354
+ return None
355
+
356
+ try:
357
+ # Check for interleaved thinking setting (default True for Foundry models)
358
+ effective_settings = get_effective_model_settings(model_name)
359
+ interleaved_thinking = effective_settings.get("interleaved_thinking", True)
360
+
361
+ # Build anthropic-beta header if needed
362
+ beta_parts: list[str] = []
363
+ if interleaved_thinking:
364
+ beta_parts.append("interleaved-thinking-2025-05-14")
365
+
366
+ # Add 1M context beta header for long-context models
367
+ context_length = model_config.get("context_length", 200000)
368
+ if context_length >= 1_000_000:
369
+ beta_parts.append(CONTEXT_1M_BETA)
370
+
371
+ # Build default headers dict if we have beta features
372
+ default_headers: dict[str, str] | None = None
373
+ if beta_parts:
374
+ default_headers = {"anthropic-beta": ",".join(beta_parts)}
375
+
376
+ # Create the client with token provider. Pass default_headers directly:
377
+ # .with_options() copy() passes auth_token, not a valid __init__ param (SDK bug).
378
+ anthropic_client = AsyncAnthropicFoundry(
379
+ resource=resource_name,
380
+ azure_ad_token_provider=token_provider.get_token,
381
+ default_headers=default_headers,
382
+ )
383
+
384
+ # Patch for cache control injection
385
+ patch_anthropic_client_messages(anthropic_client)
386
+
387
+ # Create the pydantic-ai provider and model
388
+ provider_identity = resolve_provider_identity(model_name, model_config)
389
+ provider = make_anthropic_provider(
390
+ provider_identity,
391
+ anthropic_client=anthropic_client,
392
+ )
393
+
394
+ model = AnthropicModel(model_name=deployment_name, provider=provider)
395
+ logger.info(
396
+ "Created Azure Foundry model: %s -> %s @ %s",
397
+ model_name,
398
+ deployment_name,
399
+ resource_name,
400
+ )
401
+ return model
402
+
403
+ except Exception as e:
404
+ emit_error(f"Failed to create Azure Foundry model '{model_name}': {e}")
405
+ logger.exception(f"Error creating Azure Foundry model: {e}")
406
+ return None
407
+
408
+
409
+ def _create_azure_foundry_openai_model(
410
+ model_name: str, model_config: dict, config: dict
411
+ ) -> Any:
412
+ """Create an Azure Foundry OpenAI model instance.
413
+
414
+ Handles models with type='azure_foundry_openai' — OpenAI models on
415
+ Azure AI Services using Azure AD token auth (no API keys).
416
+ """
417
+ try:
418
+ from openai import AsyncAzureOpenAI
419
+ from pydantic_ai.models.openai import (
420
+ OpenAIChatModel,
421
+ OpenAIResponsesModel,
422
+ OpenAIResponsesModelSettings,
423
+ )
424
+ except ImportError as e:
425
+ emit_error(f"Failed to create Azure Foundry OpenAI model '{model_name}': {e}")
426
+ return None
427
+
428
+ from code_puppy.provider_identity import (
429
+ make_openai_provider,
430
+ resolve_provider_identity,
431
+ )
432
+
433
+ resource_config = model_config.get("foundry_resource", f"${ENV_FOUNDRY_RESOURCE}")
434
+ resource_name = resolve_env_var(resource_config)
435
+
436
+ if not resource_name:
437
+ emit_warning(
438
+ f"Azure Foundry resource not configured for model '{model_name}'. "
439
+ f"Set {ENV_FOUNDRY_RESOURCE} or run /foundry-setup."
440
+ )
441
+ return None
442
+
443
+ deployment_name = model_config.get("name")
444
+ if not deployment_name:
445
+ emit_warning(f"Deployment name not specified for model '{model_name}'.")
446
+ return None
447
+
448
+ token_provider = get_token_provider()
449
+ is_auth, status_msg, _ = token_provider.check_auth_status()
450
+ if not is_auth:
451
+ emit_warning(f"Azure AD auth failed for model '{model_name}': {status_msg}")
452
+ return None
453
+
454
+ try:
455
+ api_version = model_config.get("api_version", "2025-04-01-preview")
456
+ azure_endpoint = f"https://{resource_name}.openai.azure.com"
457
+
458
+ azure_client = AsyncAzureOpenAI(
459
+ azure_endpoint=azure_endpoint,
460
+ api_version=api_version,
461
+ azure_ad_token_provider=token_provider.get_token,
462
+ )
463
+
464
+ provider_identity = resolve_provider_identity(model_name, model_config)
465
+ provider = make_openai_provider(provider_identity, openai_client=azure_client)
466
+
467
+ if deployment_name.startswith("gpt-5"):
468
+ # Azure reasoning items lack encrypted_content, so pydantic-ai drops them but
469
+ # still replays the function_call by ID -> HTTP 400. openai_send_reasoning_ids=False
470
+ # disables ID replay; thinking goes as plain assistant text instead.
471
+ settings = OpenAIResponsesModelSettings(openai_send_reasoning_ids=False)
472
+ model = OpenAIResponsesModel(
473
+ model_name=deployment_name, provider=provider, settings=settings
474
+ )
475
+ else:
476
+ model = OpenAIChatModel(model_name=deployment_name, provider=provider)
477
+ logger.info(
478
+ "Created Azure Foundry OpenAI model: %s -> %s @ %s",
479
+ model_name,
480
+ deployment_name,
481
+ resource_name,
482
+ )
483
+ return model
484
+
485
+ except Exception as e:
486
+ emit_error(f"Failed to create Azure Foundry OpenAI model '{model_name}': {e}")
487
+ logger.exception("Error creating Azure Foundry OpenAI model: %s", e)
488
+ return None
489
+
490
+
491
+ def _register_model_types() -> list[dict[str, Any]]:
492
+ """Register azure_foundry and azure_foundry_openai model type handlers."""
493
+ return [
494
+ {"type": "azure_foundry", "handler": _create_azure_foundry_model},
495
+ {"type": "azure_foundry_openai", "handler": _create_azure_foundry_openai_model},
496
+ ]
497
+
498
+
499
+ # ============================================================================
500
+ # Callback Registration
501
+ # ============================================================================
502
+
503
+ # Register all callbacks when this module is imported
504
+ register_callback("custom_command_help", _custom_help)
505
+ register_callback("custom_command", _handle_custom_command)
506
+ register_callback("register_model_type", _register_model_types)
@@ -0,0 +1,182 @@
1
+ """Azure AD token provider for Azure AI Foundry authentication.
2
+
3
+ This module provides token management for authenticating with Azure AI Foundry
4
+ using credentials from the Azure CLI (`az login`).
5
+
6
+ The token provider uses `AzureCliCredential` from the `azure-identity` library
7
+ to obtain tokens without requiring API keys.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import time
14
+ from collections.abc import Callable
15
+
16
+ from .config import AZURE_COGNITIVE_SCOPE, TOKEN_REFRESH_BUFFER
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Singleton instance of the token provider
21
+ _token_provider_instance: "AzureFoundryTokenProvider | None" = None
22
+
23
+
24
+ class AzureFoundryTokenProvider:
25
+ """Provides Azure AD tokens for Anthropic Foundry using az login credentials.
26
+
27
+ This class wraps the Azure CLI credential to provide tokens for
28
+ authenticating with Azure AI Foundry. It handles token caching and
29
+ provides status checking functionality.
30
+
31
+ Example:
32
+ >>> provider = AzureFoundryTokenProvider()
33
+ >>> token = provider.get_token()
34
+ >>> # Use token for API calls
35
+ """
36
+
37
+ def __init__(self, scope: str = AZURE_COGNITIVE_SCOPE):
38
+ """Initialize the token provider.
39
+
40
+ Args:
41
+ scope: The Azure AD scope for token acquisition.
42
+ Defaults to the Cognitive Services scope.
43
+ """
44
+ self._scope = scope
45
+ self._credential = None
46
+ self._token_provider_func: Callable[[], str] | None = None
47
+ self._initialized = False
48
+ self._init_error: str | None = None
49
+
50
+ def _ensure_initialized(self) -> bool:
51
+ """Lazily initialize the Azure credential.
52
+
53
+ Returns:
54
+ True if initialization succeeded, False otherwise.
55
+ """
56
+ if self._initialized:
57
+ return self._init_error is None
58
+
59
+ try:
60
+ from azure.identity import AzureCliCredential, get_bearer_token_provider
61
+
62
+ self._credential = AzureCliCredential()
63
+ self._token_provider_func = get_bearer_token_provider(
64
+ self._credential, self._scope
65
+ )
66
+ self._initialized = True
67
+ self._init_error = None
68
+ logger.debug("Azure CLI credential initialized successfully")
69
+ return True
70
+
71
+ except ImportError as e:
72
+ self._initialized = True
73
+ self._init_error = f"azure-identity package not installed: {e}"
74
+ logger.error(self._init_error)
75
+ return False
76
+
77
+ except Exception as e:
78
+ self._initialized = True
79
+ self._init_error = f"Failed to initialize Azure credential: {e}"
80
+ logger.error(self._init_error)
81
+ return False
82
+
83
+ def get_token(self) -> str:
84
+ """Get a valid access token for Azure AI Foundry.
85
+
86
+ The token is obtained from the Azure CLI credential and is
87
+ automatically refreshed when needed by the underlying provider.
88
+
89
+ Returns:
90
+ A valid access token string.
91
+
92
+ Raises:
93
+ RuntimeError: If the token provider is not initialized or
94
+ if token acquisition fails.
95
+ """
96
+ if not self._ensure_initialized():
97
+ raise RuntimeError(self._init_error or "Token provider not initialized")
98
+
99
+ if self._token_provider_func is None:
100
+ raise RuntimeError("Token provider function not available")
101
+
102
+ try:
103
+ return self._token_provider_func()
104
+ except Exception as e:
105
+ logger.error(f"Failed to acquire token: {e}")
106
+ raise RuntimeError(f"Failed to acquire Azure AD token: {e}") from e
107
+
108
+ def check_auth_status(self) -> tuple[bool, str, str | None]:
109
+ """Check if Azure CLI authentication is valid.
110
+
111
+ Returns:
112
+ A tuple of (is_authenticated, status_message, user_info):
113
+ - is_authenticated: True if auth is valid, False otherwise
114
+ - status_message: Human-readable status description
115
+ - user_info: Email/UPN of logged-in user if available
116
+ """
117
+ if not self._ensure_initialized():
118
+ return False, self._init_error or "Not initialized", None
119
+
120
+ try:
121
+ # Try to get a token to verify authentication
122
+ token = self._credential.get_token(self._scope)
123
+ expires_in = int(token.expires_on - time.time())
124
+
125
+ # Try to get user info from the token
126
+ user_info = None
127
+ try:
128
+ # The token is a JWT, we can decode the payload to get user info
129
+ import base64
130
+ import json
131
+
132
+ # Split the JWT and decode the payload (second part)
133
+ parts = token.token.split(".")
134
+ if len(parts) >= 2:
135
+ # Add padding if needed
136
+ payload = parts[1]
137
+ padding = 4 - len(payload) % 4
138
+ if padding != 4:
139
+ payload += "=" * padding
140
+ decoded = base64.urlsafe_b64decode(payload)
141
+ claims = json.loads(decoded)
142
+ user_info = (
143
+ claims.get("upn")
144
+ or claims.get("email")
145
+ or claims.get("preferred_username")
146
+ )
147
+ except Exception:
148
+ # Ignore errors in user info extraction
149
+ pass
150
+
151
+ if expires_in > TOKEN_REFRESH_BUFFER:
152
+ return True, f"Valid (expires in {expires_in // 60} minutes)", user_info
153
+ else:
154
+ return (
155
+ True,
156
+ f"Valid but expiring soon ({expires_in} seconds)",
157
+ user_info,
158
+ )
159
+
160
+ except Exception as e:
161
+ error_name = type(e).__name__
162
+ if "CredentialUnavailableError" in error_name:
163
+ return False, "Not authenticated - run 'az login'", None
164
+ return False, f"Authentication error: {e}", None
165
+
166
+
167
+ def get_token_provider() -> AzureFoundryTokenProvider:
168
+ """Get the singleton token provider instance.
169
+
170
+ Returns:
171
+ The global AzureFoundryTokenProvider instance.
172
+ """
173
+ global _token_provider_instance
174
+ if _token_provider_instance is None:
175
+ _token_provider_instance = AzureFoundryTokenProvider()
176
+ return _token_provider_instance
177
+
178
+
179
+ def reset_token_provider() -> None:
180
+ """Reset the singleton token provider (useful for testing)."""
181
+ global _token_provider_instance
182
+ _token_provider_instance = None