code-muse 0.0.1__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 (394) hide show
  1. code_muse/__init__.py +26 -0
  2. code_muse/__main__.py +10 -0
  3. code_muse/agents/__init__.py +31 -0
  4. code_muse/agents/_builder.py +214 -0
  5. code_muse/agents/_compaction.py +506 -0
  6. code_muse/agents/_diagnostics.py +171 -0
  7. code_muse/agents/_history.py +382 -0
  8. code_muse/agents/_key_listeners.py +148 -0
  9. code_muse/agents/_non_streaming_render.py +148 -0
  10. code_muse/agents/_runtime.py +596 -0
  11. code_muse/agents/agent_creator_agent.py +603 -0
  12. code_muse/agents/agent_helios.py +47 -0
  13. code_muse/agents/agent_manager.py +740 -0
  14. code_muse/agents/agent_muse.py +78 -0
  15. code_muse/agents/agent_planning.py +44 -0
  16. code_muse/agents/agent_qa_melpomene.py +207 -0
  17. code_muse/agents/base_agent.py +194 -0
  18. code_muse/agents/event_stream_handler.py +361 -0
  19. code_muse/agents/json_agent.py +201 -0
  20. code_muse/agents/prompt_v3.py +521 -0
  21. code_muse/agents/subagent_stream_handler.py +273 -0
  22. code_muse/callbacks.py +941 -0
  23. code_muse/chatgpt_codex_client.py +333 -0
  24. code_muse/claude_cache_client.py +853 -0
  25. code_muse/cli_runner/__init__.py +319 -0
  26. code_muse/cli_runner/args.py +63 -0
  27. code_muse/cli_runner/loop.py +510 -0
  28. code_muse/cli_runner/resume.py +72 -0
  29. code_muse/cli_runner/runner.py +161 -0
  30. code_muse/command_line/__init__.py +1 -0
  31. code_muse/command_line/add_model_menu.py +1331 -0
  32. code_muse/command_line/agent_menu.py +674 -0
  33. code_muse/command_line/attachments.py +397 -0
  34. code_muse/command_line/autosave_menu.py +709 -0
  35. code_muse/command_line/clipboard.py +528 -0
  36. code_muse/command_line/colors_menu.py +530 -0
  37. code_muse/command_line/command_handler.py +262 -0
  38. code_muse/command_line/command_registry.py +150 -0
  39. code_muse/command_line/config_commands.py +711 -0
  40. code_muse/command_line/core_commands.py +740 -0
  41. code_muse/command_line/diff_menu.py +865 -0
  42. code_muse/command_line/file_path_completion.py +73 -0
  43. code_muse/command_line/load_context_completion.py +57 -0
  44. code_muse/command_line/model_picker_completion.py +512 -0
  45. code_muse/command_line/model_settings_menu.py +983 -0
  46. code_muse/command_line/onboarding_slides.py +162 -0
  47. code_muse/command_line/onboarding_wizard.py +337 -0
  48. code_muse/command_line/pagination.py +41 -0
  49. code_muse/command_line/pin_command_completion.py +329 -0
  50. code_muse/command_line/prompt_toolkit_completion.py +886 -0
  51. code_muse/command_line/session_commands.py +304 -0
  52. code_muse/command_line/shell_passthrough.py +145 -0
  53. code_muse/command_line/skills_completion.py +158 -0
  54. code_muse/command_line/types.py +18 -0
  55. code_muse/command_line/uc_menu.py +908 -0
  56. code_muse/command_line/utils.py +105 -0
  57. code_muse/command_line/wiggum_state.py +77 -0
  58. code_muse/config.py +1138 -0
  59. code_muse/config_agent.py +168 -0
  60. code_muse/config_appearance.py +241 -0
  61. code_muse/config_model.py +357 -0
  62. code_muse/config_security.py +73 -0
  63. code_muse/error_logging.py +132 -0
  64. code_muse/evals/__init__.py +35 -0
  65. code_muse/evals/eval_helpers.py +81 -0
  66. code_muse/evals/eval_runner.py +299 -0
  67. code_muse/evals/sample_evals/__init__.py +1 -0
  68. code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
  69. code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
  70. code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
  71. code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
  72. code_muse/fs_scan_cache/__init__.py +31 -0
  73. code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
  74. code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
  75. code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
  76. code_muse/fs_scan_cache/tool_integration.py +309 -0
  77. code_muse/fs_scan_cache/ttl_policy.py +44 -0
  78. code_muse/gemini_code_assist.py +383 -0
  79. code_muse/gemini_model.py +838 -0
  80. code_muse/hook_engine/README.md +105 -0
  81. code_muse/hook_engine/__init__.py +21 -0
  82. code_muse/hook_engine/aliases.py +153 -0
  83. code_muse/hook_engine/engine.py +221 -0
  84. code_muse/hook_engine/executor.py +347 -0
  85. code_muse/hook_engine/matcher.py +154 -0
  86. code_muse/hook_engine/models.py +245 -0
  87. code_muse/hook_engine/registry.py +114 -0
  88. code_muse/hook_engine/trust.py +268 -0
  89. code_muse/hook_engine/validator.py +144 -0
  90. code_muse/http_utils.py +360 -0
  91. code_muse/keymap.py +128 -0
  92. code_muse/list_filtering.py +26 -0
  93. code_muse/main.py +10 -0
  94. code_muse/messaging/__init__.py +259 -0
  95. code_muse/messaging/bus.py +621 -0
  96. code_muse/messaging/commands.py +166 -0
  97. code_muse/messaging/markdown_patches.py +57 -0
  98. code_muse/messaging/message_queue.py +397 -0
  99. code_muse/messaging/messages.py +591 -0
  100. code_muse/messaging/queue_console.py +269 -0
  101. code_muse/messaging/renderers.py +308 -0
  102. code_muse/messaging/rich_renderer.py +1158 -0
  103. code_muse/messaging/shimmer.py +154 -0
  104. code_muse/messaging/spinner/__init__.py +87 -0
  105. code_muse/messaging/spinner/console_spinner.py +250 -0
  106. code_muse/messaging/spinner/spinner_base.py +82 -0
  107. code_muse/messaging/subagent_console.py +458 -0
  108. code_muse/model_factory.py +1203 -0
  109. code_muse/model_switching.py +59 -0
  110. code_muse/model_utils.py +156 -0
  111. code_muse/models.json +66 -0
  112. code_muse/models_cache/__init__.py +26 -0
  113. code_muse/models_cache/blocking_lru_cache.py +98 -0
  114. code_muse/models_cache/cache_writer.py +86 -0
  115. code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
  116. code_muse/models_cache/sha256_hash.pyx +34 -0
  117. code_muse/models_cache/startup_integration.py +75 -0
  118. code_muse/models_dev_api.json +1 -0
  119. code_muse/models_dev_parser.py +590 -0
  120. code_muse/motion.py +126 -0
  121. code_muse/plugins/__init__.py +471 -0
  122. code_muse/plugins/agent_skills/__init__.py +32 -0
  123. code_muse/plugins/agent_skills/config.py +176 -0
  124. code_muse/plugins/agent_skills/discovery.py +309 -0
  125. code_muse/plugins/agent_skills/downloader.py +389 -0
  126. code_muse/plugins/agent_skills/installer.py +19 -0
  127. code_muse/plugins/agent_skills/metadata.py +293 -0
  128. code_muse/plugins/agent_skills/prompt_builder.py +66 -0
  129. code_muse/plugins/agent_skills/register_callbacks.py +298 -0
  130. code_muse/plugins/agent_skills/remote_catalog.py +320 -0
  131. code_muse/plugins/agent_skills/skill_catalog.py +254 -0
  132. code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
  133. code_muse/plugins/agent_skills/skills_menu.py +791 -0
  134. code_muse/plugins/autonomous_memory/__init__.py +39 -0
  135. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
  136. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
  137. code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
  138. code_muse/plugins/autonomous_memory/consolidation.py +82 -0
  139. code_muse/plugins/autonomous_memory/extraction.py +382 -0
  140. code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
  141. code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
  142. code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
  143. code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
  144. code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
  145. code_muse/plugins/aws_bedrock/__init__.py +14 -0
  146. code_muse/plugins/aws_bedrock/config.py +99 -0
  147. code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
  148. code_muse/plugins/aws_bedrock/utils.py +153 -0
  149. code_muse/plugins/azure_foundry/README.md +238 -0
  150. code_muse/plugins/azure_foundry/__init__.py +15 -0
  151. code_muse/plugins/azure_foundry/config.py +125 -0
  152. code_muse/plugins/azure_foundry/discovery.py +187 -0
  153. code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
  154. code_muse/plugins/azure_foundry/token.py +180 -0
  155. code_muse/plugins/azure_foundry/utils.py +345 -0
  156. code_muse/plugins/build_filter/__init__.py +1 -0
  157. code_muse/plugins/build_filter/register_callbacks.py +201 -0
  158. code_muse/plugins/build_filter/strategies/__init__.py +1 -0
  159. code_muse/plugins/build_filter/strategies/build.py +397 -0
  160. code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
  161. code_muse/plugins/chatgpt_oauth/config.py +52 -0
  162. code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
  163. code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
  164. code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
  165. code_muse/plugins/chatgpt_oauth/utils.py +538 -0
  166. code_muse/plugins/checkpointing/__init__.py +29 -0
  167. code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
  168. code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
  169. code_muse/plugins/checkpointing/register_callbacks.py +51 -0
  170. code_muse/plugins/checkpointing/restore_command.py +263 -0
  171. code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
  172. code_muse/plugins/checkpointing/shadow_git.py +90 -0
  173. code_muse/plugins/claude_code_hooks/__init__.py +1 -0
  174. code_muse/plugins/claude_code_hooks/config.py +188 -0
  175. code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
  176. code_muse/plugins/claude_code_oauth/README.md +167 -0
  177. code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
  178. code_muse/plugins/claude_code_oauth/__init__.py +25 -0
  179. code_muse/plugins/claude_code_oauth/config.py +52 -0
  180. code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
  181. code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
  182. code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
  183. code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
  184. code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
  185. code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
  186. code_muse/plugins/claude_code_oauth/utils.py +664 -0
  187. code_muse/plugins/copilot_auth/__init__.py +11 -0
  188. code_muse/plugins/copilot_auth/config.py +91 -0
  189. code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
  190. code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
  191. code_muse/plugins/copilot_auth/utils.py +584 -0
  192. code_muse/plugins/custom_commands/__init__.py +14 -0
  193. code_muse/plugins/custom_commands/args_injection.py +82 -0
  194. code_muse/plugins/custom_commands/command_discovery.py +89 -0
  195. code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
  196. code_muse/plugins/custom_commands/register_callbacks.py +176 -0
  197. code_muse/plugins/customizable_commands/__init__.py +0 -0
  198. code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
  199. code_muse/plugins/destructive_command_guard/__init__.py +14 -0
  200. code_muse/plugins/destructive_command_guard/detector.py +375 -0
  201. code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
  202. code_muse/plugins/example_custom_command/README.md +280 -0
  203. code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
  204. code_muse/plugins/file_permission_handler/__init__.py +4 -0
  205. code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
  206. code_muse/plugins/filter_engine/__init__.py +30 -0
  207. code_muse/plugins/filter_engine/classifier.py +153 -0
  208. code_muse/plugins/filter_engine/content_detector.py +184 -0
  209. code_muse/plugins/filter_engine/dispatcher.py +244 -0
  210. code_muse/plugins/filter_engine/register_callbacks.py +188 -0
  211. code_muse/plugins/filter_engine/registry.py +279 -0
  212. code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
  213. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
  214. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
  215. code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
  216. code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
  217. code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
  218. code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
  219. code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
  220. code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
  221. code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
  222. code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
  223. code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
  224. code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
  225. code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
  226. code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
  227. code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
  228. code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
  229. code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
  230. code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
  231. code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
  232. code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
  233. code_muse/plugins/filter_engine/verbosity.py +63 -0
  234. code_muse/plugins/force_push_guard/__init__.py +5 -0
  235. code_muse/plugins/force_push_guard/detector.py +96 -0
  236. code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
  237. code_muse/plugins/force_push_guard/test_detector.py +143 -0
  238. code_muse/plugins/frontend_emitter/__init__.py +25 -0
  239. code_muse/plugins/frontend_emitter/emitter.py +121 -0
  240. code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
  241. code_muse/plugins/gac/__init__.py +4 -0
  242. code_muse/plugins/gac/git_ops.py +136 -0
  243. code_muse/plugins/gac/prompt.py +191 -0
  244. code_muse/plugins/gac/register_callbacks.py +82 -0
  245. code_muse/plugins/hook_creator/__init__.py +1 -0
  246. code_muse/plugins/hook_creator/register_callbacks.py +34 -0
  247. code_muse/plugins/hook_manager/__init__.py +1 -0
  248. code_muse/plugins/hook_manager/config.py +289 -0
  249. code_muse/plugins/hook_manager/hooks_menu.py +563 -0
  250. code_muse/plugins/hook_manager/register_callbacks.py +227 -0
  251. code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
  252. code_muse/plugins/mindpack/__init__.py +0 -0
  253. code_muse/plugins/mindpack/factory.py +930 -0
  254. code_muse/plugins/mindpack/judge.py +573 -0
  255. code_muse/plugins/mindpack/memory.py +100 -0
  256. code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
  257. code_muse/plugins/mindpack/orchestration.py +605 -0
  258. code_muse/plugins/mindpack/register_callbacks.py +175 -0
  259. code_muse/plugins/mindpack/schemas.py +358 -0
  260. code_muse/plugins/mindpack/tools.py +387 -0
  261. code_muse/plugins/oauth_muse_html.py +226 -0
  262. code_muse/plugins/ollama_setup/__init__.py +5 -0
  263. code_muse/plugins/ollama_setup/completer.py +36 -0
  264. code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
  265. code_muse/plugins/plan_command/__init__.py +0 -0
  266. code_muse/plugins/plan_command/register_callbacks.py +206 -0
  267. code_muse/plugins/plan_mode/__init__.py +37 -0
  268. code_muse/plugins/plan_mode/mode_cycling.py +40 -0
  269. code_muse/plugins/plan_mode/plan_generation.py +68 -0
  270. code_muse/plugins/plan_mode/plan_hooks.py +74 -0
  271. code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
  272. code_muse/plugins/plan_mode/register_callbacks.py +121 -0
  273. code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
  274. code_muse/plugins/policy_engine/__init__.py +46 -0
  275. code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
  276. code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
  277. code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
  278. code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
  279. code_muse/plugins/policy_engine/register_callbacks.py +112 -0
  280. code_muse/plugins/pop_command/__init__.py +1 -0
  281. code_muse/plugins/pop_command/register_callbacks.py +189 -0
  282. code_muse/plugins/prompt_newline/__init__.py +13 -0
  283. code_muse/plugins/prompt_newline/config.py +19 -0
  284. code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
  285. code_muse/plugins/safety_status/__init__.py +0 -0
  286. code_muse/plugins/safety_status/register_callbacks.py +113 -0
  287. code_muse/plugins/semantic_compression/__init__.py +6 -0
  288. code_muse/plugins/semantic_compression/compressor.py +295 -0
  289. code_muse/plugins/semantic_compression/config.py +123 -0
  290. code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
  291. code_muse/plugins/shell_minimizer/__init__.py +50 -0
  292. code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
  293. code_muse/plugins/shell_minimizer/pipeline.py +556 -0
  294. code_muse/plugins/shell_minimizer/primitives.py +482 -0
  295. code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
  296. code_muse/plugins/shell_safety/__init__.py +6 -0
  297. code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
  298. code_muse/plugins/shell_safety/command_cache.py +149 -0
  299. code_muse/plugins/shell_safety/register_callbacks.py +202 -0
  300. code_muse/plugins/synthetic_status/__init__.py +1 -0
  301. code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
  302. code_muse/plugins/synthetic_status/status_api.py +145 -0
  303. code_muse/plugins/token_caching/__init__.py +21 -0
  304. code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
  305. code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
  306. code_muse/plugins/token_caching/register_callbacks.py +54 -0
  307. code_muse/plugins/token_caching/stats_display.py +35 -0
  308. code_muse/plugins/token_tracking/__init__.py +26 -0
  309. code_muse/plugins/token_tracking/database.py +381 -0
  310. code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
  311. code_muse/plugins/token_tracking/record.py +55 -0
  312. code_muse/plugins/token_tracking/register_callbacks.py +277 -0
  313. code_muse/plugins/token_tracking/reports.py +329 -0
  314. code_muse/plugins/universal_constructor/__init__.py +13 -0
  315. code_muse/plugins/universal_constructor/models.py +136 -0
  316. code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
  317. code_muse/plugins/universal_constructor/registry.py +390 -0
  318. code_muse/plugins/universal_constructor/runner.py +474 -0
  319. code_muse/plugins/universal_constructor/safety.py +440 -0
  320. code_muse/plugins/universal_constructor/sandbox.py +584 -0
  321. code_muse/provider_identity.py +105 -0
  322. code_muse/pydantic_patches.py +410 -0
  323. code_muse/reopenable_async_client.py +233 -0
  324. code_muse/round_robin_model.py +151 -0
  325. code_muse/secret_storage.py +74 -0
  326. code_muse/security/__init__.py +1 -0
  327. code_muse/security/redaction.cpython-314-darwin.so +0 -0
  328. code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
  329. code_muse/security/redaction.pyx +135 -0
  330. code_muse/session_storage.py +565 -0
  331. code_muse/status_display.py +261 -0
  332. code_muse/stream_parser/__init__.py +76 -0
  333. code_muse/stream_parser/assistant_text_parser.py +90 -0
  334. code_muse/stream_parser/citation_parser.py +76 -0
  335. code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
  336. code_muse/stream_parser/proposed_plan_parser.py +158 -0
  337. code_muse/stream_parser/stream_text_chunk.py +23 -0
  338. code_muse/stream_parser/stream_text_parser.py +27 -0
  339. code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
  340. code_muse/stream_parser/tagged_line_parser.pyx +251 -0
  341. code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
  342. code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
  343. code_muse/summarization_agent.py +308 -0
  344. code_muse/terminal_utils.cpython-314-darwin.so +0 -0
  345. code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
  346. code_muse/terminal_utils.pyx +483 -0
  347. code_muse/tools/__init__.py +459 -0
  348. code_muse/tools/agent_tools.py +613 -0
  349. code_muse/tools/ask_user_question/__init__.py +26 -0
  350. code_muse/tools/ask_user_question/constants.py +73 -0
  351. code_muse/tools/ask_user_question/demo_tui.py +55 -0
  352. code_muse/tools/ask_user_question/handler.py +232 -0
  353. code_muse/tools/ask_user_question/models.py +302 -0
  354. code_muse/tools/ask_user_question/registration.py +37 -0
  355. code_muse/tools/ask_user_question/renderers.py +336 -0
  356. code_muse/tools/ask_user_question/terminal_ui.py +327 -0
  357. code_muse/tools/ask_user_question/theme.py +156 -0
  358. code_muse/tools/ask_user_question/tui_loop.py +422 -0
  359. code_muse/tools/background_jobs.py +99 -0
  360. code_muse/tools/browser/__init__.py +37 -0
  361. code_muse/tools/browser/browser_control.py +289 -0
  362. code_muse/tools/browser/browser_interactions.py +545 -0
  363. code_muse/tools/browser/browser_locators.py +640 -0
  364. code_muse/tools/browser/browser_manager.py +376 -0
  365. code_muse/tools/browser/browser_navigation.py +251 -0
  366. code_muse/tools/browser/browser_screenshot.py +180 -0
  367. code_muse/tools/browser/browser_scripts.py +462 -0
  368. code_muse/tools/browser/browser_workflows.py +222 -0
  369. code_muse/tools/chrome_cdp/__init__.py +1070 -0
  370. code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
  371. code_muse/tools/command_runner.py +1401 -0
  372. code_muse/tools/common.py +1407 -0
  373. code_muse/tools/display.py +87 -0
  374. code_muse/tools/file_modifications.py +1099 -0
  375. code_muse/tools/file_operations.py +860 -0
  376. code_muse/tools/image_tools.py +185 -0
  377. code_muse/tools/meetin_proxy/__init__.py +243 -0
  378. code_muse/tools/meetin_proxy/capture_addon.py +82 -0
  379. code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
  380. code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
  381. code_muse/tools/path_policy.py +219 -0
  382. code_muse/tools/skills_tools.py +586 -0
  383. code_muse/tools/subagent_context.py +158 -0
  384. code_muse/tools/tools_content.py +50 -0
  385. code_muse/tools/universal_constructor.py +965 -0
  386. code_muse/uvx_detection.py +241 -0
  387. code_muse/version_checker.py +86 -0
  388. code_muse-0.0.1.data/data/code_muse/models.json +66 -0
  389. code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
  390. code_muse-0.0.1.dist-info/METADATA +845 -0
  391. code_muse-0.0.1.dist-info/RECORD +394 -0
  392. code_muse-0.0.1.dist-info/WHEEL +4 -0
  393. code_muse-0.0.1.dist-info/entry_points.txt +2 -0
  394. code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,495 @@
1
+ """Azure AI Foundry Plugin callbacks for Muse CLI.
2
+
3
+ This plugin enables Muse 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
+ import logging
11
+ import sys
12
+ from typing import Any
13
+
14
+ from code_muse.callbacks import register_callback
15
+ from code_muse.command_line.utils import safe_input
16
+ from code_muse.messaging import emit_error, emit_info, emit_success, emit_warning
17
+ from code_muse.tools.command_runner import set_awaiting_user_input
18
+
19
+ from .config import (
20
+ DEFAULT_DEPLOYMENT_NAMES,
21
+ ENV_FOUNDRY_RESOURCE,
22
+ get_foundry_resource,
23
+ )
24
+ from .discovery import find_account, list_deployments
25
+ from .token import get_token_provider
26
+ from .utils import (
27
+ add_discovered_models_to_config,
28
+ add_foundry_models_to_config,
29
+ get_foundry_models_from_config,
30
+ remove_foundry_models_from_config,
31
+ resolve_env_var,
32
+ )
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ # ============================================================================
38
+ # Slash Command Handlers
39
+ # ============================================================================
40
+
41
+
42
+ def _handle_foundry_status() -> None:
43
+ """Handle the /foundry-status command.
44
+
45
+ Displays the current Azure AD authentication status and configured
46
+ Foundry models.
47
+ """
48
+ emit_info("")
49
+ emit_info("Azure AI Foundry Status")
50
+ emit_info("=" * 40)
51
+
52
+ # Check Azure AD authentication
53
+ token_provider = get_token_provider()
54
+ is_auth, status_msg, user_info = token_provider.check_auth_status()
55
+
56
+ if is_auth:
57
+ emit_success(f"Authentication: {status_msg}")
58
+ if user_info:
59
+ emit_info(f" Logged in as: {user_info}")
60
+ else:
61
+ emit_warning(f"Authentication: {status_msg}")
62
+
63
+ # List configured models and check resource
64
+ foundry_models = get_foundry_models_from_config()
65
+
66
+ # Check resource - from env var or from configured models
67
+ resource = get_foundry_resource()
68
+ if not resource and foundry_models:
69
+ # Get resource from first configured model
70
+ first_model = next(iter(foundry_models.values()))
71
+ resource = first_model.get("foundry_resource", "")
72
+ if resource.startswith("$"):
73
+ resource = None # It's an unresolved env var reference
74
+
75
+ emit_info("")
76
+ if resource:
77
+ emit_info(f"Foundry Resource: {resource}")
78
+ else:
79
+ emit_warning(f"Foundry Resource: Not set (set {ENV_FOUNDRY_RESOURCE})")
80
+
81
+ emit_info("")
82
+ if foundry_models:
83
+ emit_info(f"Configured Models ({len(foundry_models)}):")
84
+ for model_key, config in foundry_models.items():
85
+ deployment = config.get("name", "unknown")
86
+ emit_info(f" - {model_key}: {deployment}")
87
+ else:
88
+ emit_info("Configured Models: None")
89
+ emit_info(" Run /foundry-setup to configure models")
90
+
91
+ emit_info("")
92
+
93
+
94
+ def _handle_foundry_setup() -> None:
95
+ """Handle the /foundry-setup command.
96
+
97
+ Interactive wizard to configure Azure Foundry models.
98
+ Uses print() for synchronous output to avoid message bus buffering issues.
99
+ """
100
+
101
+ def _print(msg: str = "") -> None:
102
+ """Print with immediate flush."""
103
+ print(msg, flush=True)
104
+
105
+ _print()
106
+ _print("Azure AI Foundry Setup")
107
+ _print("=" * 40)
108
+ _print()
109
+
110
+ # Check Azure CLI authentication first
111
+ _print("Step 1: Checking Azure CLI authentication...")
112
+ token_provider = get_token_provider()
113
+ is_auth, status_msg, user_info = token_provider.check_auth_status()
114
+
115
+ if not is_auth:
116
+ _print(f" ERROR: {status_msg}")
117
+ _print()
118
+ _print("Please run 'az login' first, then try again.")
119
+ return
120
+
121
+ _print(f" OK: {status_msg}")
122
+ if user_info:
123
+ _print(f" User: {user_info}")
124
+ _print()
125
+
126
+ # Get resource name
127
+ _print("Step 2: Azure Resource Name")
128
+ current_resource = get_foundry_resource()
129
+ if current_resource:
130
+ _print(f" Current: {current_resource}")
131
+
132
+ resource_prompt = " Enter resource name"
133
+ if current_resource:
134
+ resource_prompt += f" [{current_resource}]"
135
+ resource_prompt += ": "
136
+
137
+ set_awaiting_user_input(True)
138
+ try:
139
+ sys.stdout.flush()
140
+ resource_input = safe_input(resource_prompt).strip()
141
+ resource_name = resource_input if resource_input else current_resource
142
+
143
+ if not resource_name:
144
+ _print(" ERROR: Resource name is required.")
145
+ return
146
+
147
+ _print()
148
+
149
+ # Step 3: Try auto-discovery, fall back to manual
150
+ _print("Step 3: Discovering deployments...")
151
+ account = find_account(resource_name)
152
+
153
+ if account:
154
+ _print(f" Found: {account.name} ({account.location})")
155
+ _print(f" RG: {account.resource_group}")
156
+ _print()
157
+
158
+ deployments = list_deployments(account)
159
+ succeeded = [d for d in deployments if d.provisioning_state == "Succeeded"]
160
+
161
+ if succeeded:
162
+ _print(f" {len(succeeded)} active deployment(s):")
163
+ for d in succeeded:
164
+ _print(f" - {d.name} ({d.model_format}: {d.model_name})")
165
+ _print()
166
+
167
+ sys.stdout.flush()
168
+ confirm = safe_input(" Configure these? [Y/n]: ").strip().lower()
169
+ if confirm not in ("", "y", "yes"):
170
+ _print(" Skipped.")
171
+ return
172
+ else:
173
+ _print(" No active deployments found.")
174
+ return
175
+ else:
176
+ _print(" Discovery failed — falling back to manual entry.")
177
+ _print()
178
+ succeeded = None
179
+
180
+ except KeyboardInterrupt, EOFError:
181
+ _print()
182
+ _print("Setup cancelled.")
183
+ return
184
+ finally:
185
+ set_awaiting_user_input(False)
186
+
187
+ _print()
188
+
189
+ # Step 4: Save configuration
190
+ _print("Step 4: Saving configuration...")
191
+
192
+ if not get_foundry_resource():
193
+ _print(
194
+ f" Tip: Set {ENV_FOUNDRY_RESOURCE}={resource_name} in your environment"
195
+ )
196
+
197
+ if succeeded is not None:
198
+ # Auto-discovered — configure all succeeded deployments
199
+ added_models = add_discovered_models_to_config(resource_name, succeeded)
200
+ else:
201
+ # Manual fallback — use hardcoded Anthropic defaults
202
+ added_models = add_foundry_models_to_config(
203
+ resource_name=resource_name,
204
+ opus_deployment=DEFAULT_DEPLOYMENT_NAMES["opus"],
205
+ sonnet_deployment=DEFAULT_DEPLOYMENT_NAMES["sonnet"],
206
+ haiku_deployment=DEFAULT_DEPLOYMENT_NAMES["haiku"],
207
+ )
208
+
209
+ _print()
210
+ if added_models:
211
+ _print(f"OK: Configured {len(added_models)} model(s):")
212
+ for model_key in added_models:
213
+ _print(f" - {model_key}")
214
+ _print()
215
+ _print(f"Use '/model {added_models[0]}' to switch to a Foundry model.")
216
+ else:
217
+ _print("WARNING: No models were added. Check the configuration.")
218
+
219
+ _print()
220
+
221
+
222
+ def _handle_foundry_remove() -> None:
223
+ """Handle the /foundry-remove command.
224
+
225
+ Removes all Azure Foundry model configurations.
226
+ """
227
+ removed = remove_foundry_models_from_config()
228
+ if removed:
229
+ emit_success(f"Removed {len(removed)} Foundry model(s):")
230
+ for model_key in removed:
231
+ emit_info(f" - {model_key}")
232
+ else:
233
+ emit_info("No Foundry models found in configuration.")
234
+
235
+
236
+ # ============================================================================
237
+ # Custom Command Registration
238
+ # ============================================================================
239
+
240
+
241
+ def _custom_help() -> list[tuple[str, str]]:
242
+ """Return help entries for custom commands."""
243
+ return [
244
+ (
245
+ "foundry-status",
246
+ "Check Azure AI Foundry authentication and configuration status",
247
+ ),
248
+ ("foundry-setup", "Interactive wizard to configure Azure Foundry models"),
249
+ ("foundry-remove", "Remove all Azure Foundry model configurations"),
250
+ ]
251
+
252
+
253
+ def _handle_custom_command(command: str, name: str) -> bool | None:
254
+ """Handle custom slash commands for the Azure Foundry plugin.
255
+
256
+ Dispatches to handlers for "foundry-status", "foundry-setup", and
257
+ "foundry-remove" commands.
258
+
259
+ Args:
260
+ command: The full command string.
261
+ name: The command name (without slash).
262
+
263
+ Returns:
264
+ True if the command was handled successfully.
265
+ False if the command was recognized but handler() raised an exception.
266
+ None if the command is not handled by this plugin.
267
+ """
268
+ handlers = {
269
+ "foundry-status": _handle_foundry_status,
270
+ "foundry-setup": _handle_foundry_setup,
271
+ "foundry-remove": _handle_foundry_remove,
272
+ }
273
+
274
+ handler = handlers.get(name)
275
+ if handler is None:
276
+ return None
277
+
278
+ try:
279
+ handler()
280
+ return True
281
+ except Exception as e:
282
+ logger.exception("Error handling /%s command: %s", name, e)
283
+ emit_error(f"Command /{name} failed: {e}")
284
+ return False
285
+
286
+
287
+ # ============================================================================
288
+ # Model Type Handler
289
+ # ============================================================================
290
+
291
+
292
+ def _create_azure_foundry_model(
293
+ model_name: str, model_config: dict, config: dict
294
+ ) -> Any:
295
+ """Create an Azure Foundry model instance.
296
+
297
+ This handler is registered via the 'register_model_type' callback to handle
298
+ models with type='azure_foundry'.
299
+
300
+ Args:
301
+ model_name: The model key name (e.g., 'foundry-claude-opus').
302
+ model_config: The model configuration dictionary.
303
+ config: The full models configuration.
304
+
305
+ Returns:
306
+ An AnthropicModel instance configured for Azure Foundry, or None on error.
307
+ """
308
+ try:
309
+ from anthropic import AsyncAnthropicFoundry
310
+ from pydantic_ai.models.anthropic import AnthropicModel
311
+ except ImportError as e:
312
+ emit_error(
313
+ f"Failed to create Azure Foundry model '{model_name}': "
314
+ f"Missing dependency - {e}"
315
+ )
316
+ return None
317
+
318
+ from code_muse.claude_cache_client import patch_anthropic_client_messages
319
+ from code_muse.config import get_effective_model_settings
320
+ from code_muse.model_factory import CONTEXT_1M_BETA
321
+ from code_muse.provider_identity import (
322
+ make_anthropic_provider,
323
+ resolve_provider_identity,
324
+ )
325
+
326
+ # Get the Foundry resource name
327
+ resource_config = model_config.get("foundry_resource", f"${ENV_FOUNDRY_RESOURCE}")
328
+ resource_name = resolve_env_var(resource_config)
329
+
330
+ if not resource_name:
331
+ emit_warning(
332
+ f"Azure Foundry resource not configured for model '{model_name}'. "
333
+ f"Set {ENV_FOUNDRY_RESOURCE} or run /foundry-setup."
334
+ )
335
+ return None
336
+
337
+ # Get the deployment name (model name in Azure)
338
+ deployment_name = model_config.get("name")
339
+ if not deployment_name:
340
+ emit_warning(f"Deployment name not specified for model '{model_name}'.")
341
+ return None
342
+
343
+ # Get the token provider
344
+ token_provider = get_token_provider()
345
+
346
+ # Check authentication status
347
+ is_auth, status_msg, _ = token_provider.check_auth_status()
348
+ if not is_auth:
349
+ emit_warning(
350
+ f"Azure AD authentication failed for model '{model_name}': {status_msg}"
351
+ )
352
+ return None
353
+
354
+ try:
355
+ # Check for interleaved thinking setting (default True for Foundry models)
356
+ effective_settings = get_effective_model_settings(model_name)
357
+ interleaved_thinking = effective_settings.get("interleaved_thinking", True)
358
+
359
+ # Build anthropic-beta header if needed
360
+ beta_parts: list[str] = []
361
+ if interleaved_thinking:
362
+ beta_parts.append("interleaved-thinking-2025-05-14")
363
+
364
+ # Add 1M context beta header for long-context models
365
+ context_length = model_config.get("context_length", 200000)
366
+ if context_length >= 1_000_000:
367
+ beta_parts.append(CONTEXT_1M_BETA)
368
+
369
+ # Build default headers dict if we have beta features
370
+ default_headers: dict[str, str] | None = None
371
+ if beta_parts:
372
+ default_headers = {"anthropic-beta": ",".join(beta_parts)}
373
+
374
+ # Create the Azure Foundry Anthropic client with token provider
375
+ # Note: We pass default_headers here because AsyncAnthropicFoundry.with_options()
376
+ # has a bug where copy() passes auth_token which isn't a valid __init__ param
377
+ anthropic_client = AsyncAnthropicFoundry(
378
+ resource=resource_name,
379
+ azure_ad_token_provider=token_provider.get_token,
380
+ default_headers=default_headers,
381
+ )
382
+
383
+ # Patch for cache control injection
384
+ patch_anthropic_client_messages(anthropic_client)
385
+
386
+ # Create the pydantic-ai provider and model
387
+ provider_identity = resolve_provider_identity(model_name, model_config)
388
+ provider = make_anthropic_provider(
389
+ provider_identity,
390
+ anthropic_client=anthropic_client,
391
+ )
392
+
393
+ model = AnthropicModel(model_name=deployment_name, provider=provider)
394
+ logger.info(
395
+ "Created Azure Foundry model: %s -> %s @ %s",
396
+ model_name,
397
+ deployment_name,
398
+ resource_name,
399
+ )
400
+ return model
401
+
402
+ except Exception as e:
403
+ emit_error(f"Failed to create Azure Foundry model '{model_name}': {e}")
404
+ logger.exception(f"Error creating Azure Foundry model: {e}")
405
+ return None
406
+
407
+
408
+ def _create_azure_foundry_openai_model(
409
+ model_name: str, model_config: dict, config: dict
410
+ ) -> Any:
411
+ """Create an Azure Foundry OpenAI model instance.
412
+
413
+ Handles models with type='azure_foundry_openai' — OpenAI models on
414
+ Azure AI Services using Azure AD token auth (no API keys).
415
+ """
416
+ try:
417
+ from openai import AsyncAzureOpenAI
418
+ from pydantic_ai.models.openai import OpenAIChatModel, OpenAIResponsesModel
419
+ except ImportError as e:
420
+ emit_error(f"Failed to create Azure Foundry OpenAI model '{model_name}': {e}")
421
+ return None
422
+
423
+ from code_muse.provider_identity import (
424
+ make_openai_provider,
425
+ resolve_provider_identity,
426
+ )
427
+
428
+ resource_config = model_config.get("foundry_resource", f"${ENV_FOUNDRY_RESOURCE}")
429
+ resource_name = resolve_env_var(resource_config)
430
+
431
+ if not resource_name:
432
+ emit_warning(
433
+ f"Azure Foundry resource not configured for model '{model_name}'. "
434
+ f"Set {ENV_FOUNDRY_RESOURCE} or run /foundry-setup."
435
+ )
436
+ return None
437
+
438
+ deployment_name = model_config.get("name")
439
+ if not deployment_name:
440
+ emit_warning(f"Deployment name not specified for model '{model_name}'.")
441
+ return None
442
+
443
+ token_provider = get_token_provider()
444
+ is_auth, status_msg, _ = token_provider.check_auth_status()
445
+ if not is_auth:
446
+ emit_warning(f"Azure AD auth failed for model '{model_name}': {status_msg}")
447
+ return None
448
+
449
+ try:
450
+ api_version = model_config.get("api_version", "2025-04-01-preview")
451
+ azure_endpoint = f"https://{resource_name}.openai.azure.com"
452
+
453
+ azure_client = AsyncAzureOpenAI(
454
+ azure_endpoint=azure_endpoint,
455
+ api_version=api_version,
456
+ azure_ad_token_provider=token_provider.get_token,
457
+ )
458
+
459
+ provider_identity = resolve_provider_identity(model_name, model_config)
460
+ provider = make_openai_provider(provider_identity, openai_client=azure_client)
461
+
462
+ if deployment_name.startswith("gpt-5"):
463
+ model = OpenAIResponsesModel(model_name=deployment_name, provider=provider)
464
+ else:
465
+ model = OpenAIChatModel(model_name=deployment_name, provider=provider)
466
+ logger.info(
467
+ "Created Azure Foundry OpenAI model: %s -> %s @ %s",
468
+ model_name,
469
+ deployment_name,
470
+ resource_name,
471
+ )
472
+ return model
473
+
474
+ except Exception as e:
475
+ emit_error(f"Failed to create Azure Foundry OpenAI model '{model_name}': {e}")
476
+ logger.exception("Error creating Azure Foundry OpenAI model: %s", e)
477
+ return None
478
+
479
+
480
+ def _register_model_types() -> list[dict[str, Any]]:
481
+ """Register azure_foundry and azure_foundry_openai model type handlers."""
482
+ return [
483
+ {"type": "azure_foundry", "handler": _create_azure_foundry_model},
484
+ {"type": "azure_foundry_openai", "handler": _create_azure_foundry_openai_model},
485
+ ]
486
+
487
+
488
+ # ============================================================================
489
+ # Callback Registration
490
+ # ============================================================================
491
+
492
+ # Register all callbacks when this module is imported
493
+ register_callback("custom_command_help", _custom_help)
494
+ register_callback("custom_command", _handle_custom_command)
495
+ register_callback("register_model_type", _register_model_types)
@@ -0,0 +1,180 @@
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
+ import logging
11
+ import time
12
+ from collections.abc import Callable
13
+
14
+ from .config import AZURE_COGNITIVE_SCOPE, TOKEN_REFRESH_BUFFER
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Singleton instance of the token provider
19
+ _token_provider_instance: AzureFoundryTokenProvider | None = None
20
+
21
+
22
+ class AzureFoundryTokenProvider:
23
+ """Provides Azure AD tokens for Anthropic Foundry using az login credentials.
24
+
25
+ This class wraps the Azure CLI credential to provide tokens for
26
+ authenticating with Azure AI Foundry. It handles token caching and
27
+ provides status checking functionality.
28
+
29
+ Example:
30
+ >>> provider = AzureFoundryTokenProvider()
31
+ >>> token = provider.get_token()
32
+ >>> # Use token for API calls
33
+ """
34
+
35
+ def __init__(self, scope: str = AZURE_COGNITIVE_SCOPE):
36
+ """Initialize the token provider.
37
+
38
+ Args:
39
+ scope: The Azure AD scope for token acquisition.
40
+ Defaults to the Cognitive Services scope.
41
+ """
42
+ self._scope = scope
43
+ self._credential = None
44
+ self._token_provider_func: Callable[[], str] | None = None
45
+ self._initialized = False
46
+ self._init_error: str | None = None
47
+
48
+ def _ensure_initialized(self) -> bool:
49
+ """Lazily initialize the Azure credential.
50
+
51
+ Returns:
52
+ True if initialization succeeded, False otherwise.
53
+ """
54
+ if self._initialized:
55
+ return self._init_error is None
56
+
57
+ try:
58
+ from azure.identity import AzureCliCredential, get_bearer_token_provider
59
+
60
+ self._credential = AzureCliCredential()
61
+ self._token_provider_func = get_bearer_token_provider(
62
+ self._credential, self._scope
63
+ )
64
+ self._initialized = True
65
+ self._init_error = None
66
+ logger.debug("Azure CLI credential initialized successfully")
67
+ return True
68
+
69
+ except ImportError as e:
70
+ self._initialized = True
71
+ self._init_error = f"azure-identity package not installed: {e}"
72
+ logger.error(self._init_error)
73
+ return False
74
+
75
+ except Exception as e:
76
+ self._initialized = True
77
+ self._init_error = f"Failed to initialize Azure credential: {e}"
78
+ logger.error(self._init_error)
79
+ return False
80
+
81
+ def get_token(self) -> str:
82
+ """Get a valid access token for Azure AI Foundry.
83
+
84
+ The token is obtained from the Azure CLI credential and is
85
+ automatically refreshed when needed by the underlying provider.
86
+
87
+ Returns:
88
+ A valid access token string.
89
+
90
+ Raises:
91
+ RuntimeError: If the token provider is not initialized or
92
+ if token acquisition fails.
93
+ """
94
+ if not self._ensure_initialized():
95
+ raise RuntimeError(self._init_error or "Token provider not initialized")
96
+
97
+ if self._token_provider_func is None:
98
+ raise RuntimeError("Token provider function not available")
99
+
100
+ try:
101
+ return self._token_provider_func()
102
+ except Exception as e:
103
+ logger.error(f"Failed to acquire token: {e}")
104
+ raise RuntimeError(f"Failed to acquire Azure AD token: {e}") from e
105
+
106
+ def check_auth_status(self) -> tuple[bool, str, str | None]:
107
+ """Check if Azure CLI authentication is valid.
108
+
109
+ Returns:
110
+ A tuple of (is_authenticated, status_message, user_info):
111
+ - is_authenticated: True if auth is valid, False otherwise
112
+ - status_message: Human-readable status description
113
+ - user_info: Email/UPN of logged-in user if available
114
+ """
115
+ if not self._ensure_initialized():
116
+ return False, self._init_error or "Not initialized", None
117
+
118
+ try:
119
+ # Try to get a token to verify authentication
120
+ token = self._credential.get_token(self._scope)
121
+ expires_in = int(token.expires_on - time.time())
122
+
123
+ # Try to get user info from the token
124
+ user_info = None
125
+ try:
126
+ # The token is a JWT, we can decode the payload to get user info
127
+ import base64
128
+ import json
129
+
130
+ # Split the JWT and decode the payload (second part)
131
+ parts = token.token.split(".")
132
+ if len(parts) >= 2:
133
+ # Add padding if needed
134
+ payload = parts[1]
135
+ padding = 4 - len(payload) % 4
136
+ if padding != 4:
137
+ payload += "=" * padding
138
+ decoded = base64.urlsafe_b64decode(payload)
139
+ claims = json.loads(decoded)
140
+ user_info = (
141
+ claims.get("upn")
142
+ or claims.get("email")
143
+ or claims.get("preferred_username")
144
+ )
145
+ except Exception:
146
+ # Ignore errors in user info extraction
147
+ pass
148
+
149
+ if expires_in > TOKEN_REFRESH_BUFFER:
150
+ return True, f"Valid (expires in {expires_in // 60} minutes)", user_info
151
+ else:
152
+ return (
153
+ True,
154
+ f"Valid but expiring soon ({expires_in} seconds)",
155
+ user_info,
156
+ )
157
+
158
+ except Exception as e:
159
+ error_name = type(e).__name__
160
+ if "CredentialUnavailableError" in error_name:
161
+ return False, "Not authenticated - run 'az login'", None
162
+ return False, f"Authentication error: {e}", None
163
+
164
+
165
+ def get_token_provider() -> AzureFoundryTokenProvider:
166
+ """Get the singleton token provider instance.
167
+
168
+ Returns:
169
+ The global AzureFoundryTokenProvider instance.
170
+ """
171
+ global _token_provider_instance
172
+ if _token_provider_instance is None:
173
+ _token_provider_instance = AzureFoundryTokenProvider()
174
+ return _token_provider_instance
175
+
176
+
177
+ def reset_token_provider() -> None:
178
+ """Reset the singleton token provider (useful for testing)."""
179
+ global _token_provider_instance
180
+ _token_provider_instance = None