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.
- code_muse/__init__.py +26 -0
- code_muse/__main__.py +10 -0
- code_muse/agents/__init__.py +31 -0
- code_muse/agents/_builder.py +214 -0
- code_muse/agents/_compaction.py +506 -0
- code_muse/agents/_diagnostics.py +171 -0
- code_muse/agents/_history.py +382 -0
- code_muse/agents/_key_listeners.py +148 -0
- code_muse/agents/_non_streaming_render.py +148 -0
- code_muse/agents/_runtime.py +596 -0
- code_muse/agents/agent_creator_agent.py +603 -0
- code_muse/agents/agent_helios.py +47 -0
- code_muse/agents/agent_manager.py +740 -0
- code_muse/agents/agent_muse.py +78 -0
- code_muse/agents/agent_planning.py +44 -0
- code_muse/agents/agent_qa_melpomene.py +207 -0
- code_muse/agents/base_agent.py +194 -0
- code_muse/agents/event_stream_handler.py +361 -0
- code_muse/agents/json_agent.py +201 -0
- code_muse/agents/prompt_v3.py +521 -0
- code_muse/agents/subagent_stream_handler.py +273 -0
- code_muse/callbacks.py +941 -0
- code_muse/chatgpt_codex_client.py +333 -0
- code_muse/claude_cache_client.py +853 -0
- code_muse/cli_runner/__init__.py +319 -0
- code_muse/cli_runner/args.py +63 -0
- code_muse/cli_runner/loop.py +510 -0
- code_muse/cli_runner/resume.py +72 -0
- code_muse/cli_runner/runner.py +161 -0
- code_muse/command_line/__init__.py +1 -0
- code_muse/command_line/add_model_menu.py +1331 -0
- code_muse/command_line/agent_menu.py +674 -0
- code_muse/command_line/attachments.py +397 -0
- code_muse/command_line/autosave_menu.py +709 -0
- code_muse/command_line/clipboard.py +528 -0
- code_muse/command_line/colors_menu.py +530 -0
- code_muse/command_line/command_handler.py +262 -0
- code_muse/command_line/command_registry.py +150 -0
- code_muse/command_line/config_commands.py +711 -0
- code_muse/command_line/core_commands.py +740 -0
- code_muse/command_line/diff_menu.py +865 -0
- code_muse/command_line/file_path_completion.py +73 -0
- code_muse/command_line/load_context_completion.py +57 -0
- code_muse/command_line/model_picker_completion.py +512 -0
- code_muse/command_line/model_settings_menu.py +983 -0
- code_muse/command_line/onboarding_slides.py +162 -0
- code_muse/command_line/onboarding_wizard.py +337 -0
- code_muse/command_line/pagination.py +41 -0
- code_muse/command_line/pin_command_completion.py +329 -0
- code_muse/command_line/prompt_toolkit_completion.py +886 -0
- code_muse/command_line/session_commands.py +304 -0
- code_muse/command_line/shell_passthrough.py +145 -0
- code_muse/command_line/skills_completion.py +158 -0
- code_muse/command_line/types.py +18 -0
- code_muse/command_line/uc_menu.py +908 -0
- code_muse/command_line/utils.py +105 -0
- code_muse/command_line/wiggum_state.py +77 -0
- code_muse/config.py +1138 -0
- code_muse/config_agent.py +168 -0
- code_muse/config_appearance.py +241 -0
- code_muse/config_model.py +357 -0
- code_muse/config_security.py +73 -0
- code_muse/error_logging.py +132 -0
- code_muse/evals/__init__.py +35 -0
- code_muse/evals/eval_helpers.py +81 -0
- code_muse/evals/eval_runner.py +299 -0
- code_muse/evals/sample_evals/__init__.py +1 -0
- code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
- code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
- code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
- code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
- code_muse/fs_scan_cache/__init__.py +31 -0
- code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
- code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
- code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
- code_muse/fs_scan_cache/tool_integration.py +309 -0
- code_muse/fs_scan_cache/ttl_policy.py +44 -0
- code_muse/gemini_code_assist.py +383 -0
- code_muse/gemini_model.py +838 -0
- code_muse/hook_engine/README.md +105 -0
- code_muse/hook_engine/__init__.py +21 -0
- code_muse/hook_engine/aliases.py +153 -0
- code_muse/hook_engine/engine.py +221 -0
- code_muse/hook_engine/executor.py +347 -0
- code_muse/hook_engine/matcher.py +154 -0
- code_muse/hook_engine/models.py +245 -0
- code_muse/hook_engine/registry.py +114 -0
- code_muse/hook_engine/trust.py +268 -0
- code_muse/hook_engine/validator.py +144 -0
- code_muse/http_utils.py +360 -0
- code_muse/keymap.py +128 -0
- code_muse/list_filtering.py +26 -0
- code_muse/main.py +10 -0
- code_muse/messaging/__init__.py +259 -0
- code_muse/messaging/bus.py +621 -0
- code_muse/messaging/commands.py +166 -0
- code_muse/messaging/markdown_patches.py +57 -0
- code_muse/messaging/message_queue.py +397 -0
- code_muse/messaging/messages.py +591 -0
- code_muse/messaging/queue_console.py +269 -0
- code_muse/messaging/renderers.py +308 -0
- code_muse/messaging/rich_renderer.py +1158 -0
- code_muse/messaging/shimmer.py +154 -0
- code_muse/messaging/spinner/__init__.py +87 -0
- code_muse/messaging/spinner/console_spinner.py +250 -0
- code_muse/messaging/spinner/spinner_base.py +82 -0
- code_muse/messaging/subagent_console.py +458 -0
- code_muse/model_factory.py +1203 -0
- code_muse/model_switching.py +59 -0
- code_muse/model_utils.py +156 -0
- code_muse/models.json +66 -0
- code_muse/models_cache/__init__.py +26 -0
- code_muse/models_cache/blocking_lru_cache.py +98 -0
- code_muse/models_cache/cache_writer.py +86 -0
- code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
- code_muse/models_cache/sha256_hash.pyx +34 -0
- code_muse/models_cache/startup_integration.py +75 -0
- code_muse/models_dev_api.json +1 -0
- code_muse/models_dev_parser.py +590 -0
- code_muse/motion.py +126 -0
- code_muse/plugins/__init__.py +471 -0
- code_muse/plugins/agent_skills/__init__.py +32 -0
- code_muse/plugins/agent_skills/config.py +176 -0
- code_muse/plugins/agent_skills/discovery.py +309 -0
- code_muse/plugins/agent_skills/downloader.py +389 -0
- code_muse/plugins/agent_skills/installer.py +19 -0
- code_muse/plugins/agent_skills/metadata.py +293 -0
- code_muse/plugins/agent_skills/prompt_builder.py +66 -0
- code_muse/plugins/agent_skills/register_callbacks.py +298 -0
- code_muse/plugins/agent_skills/remote_catalog.py +320 -0
- code_muse/plugins/agent_skills/skill_catalog.py +254 -0
- code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
- code_muse/plugins/agent_skills/skills_menu.py +791 -0
- code_muse/plugins/autonomous_memory/__init__.py +39 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
- code_muse/plugins/autonomous_memory/consolidation.py +82 -0
- code_muse/plugins/autonomous_memory/extraction.py +382 -0
- code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
- code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
- code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
- code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
- code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
- code_muse/plugins/aws_bedrock/__init__.py +14 -0
- code_muse/plugins/aws_bedrock/config.py +99 -0
- code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
- code_muse/plugins/aws_bedrock/utils.py +153 -0
- code_muse/plugins/azure_foundry/README.md +238 -0
- code_muse/plugins/azure_foundry/__init__.py +15 -0
- code_muse/plugins/azure_foundry/config.py +125 -0
- code_muse/plugins/azure_foundry/discovery.py +187 -0
- code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
- code_muse/plugins/azure_foundry/token.py +180 -0
- code_muse/plugins/azure_foundry/utils.py +345 -0
- code_muse/plugins/build_filter/__init__.py +1 -0
- code_muse/plugins/build_filter/register_callbacks.py +201 -0
- code_muse/plugins/build_filter/strategies/__init__.py +1 -0
- code_muse/plugins/build_filter/strategies/build.py +397 -0
- code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
- code_muse/plugins/chatgpt_oauth/config.py +52 -0
- code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
- code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
- code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_muse/plugins/chatgpt_oauth/utils.py +538 -0
- code_muse/plugins/checkpointing/__init__.py +29 -0
- code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
- code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
- code_muse/plugins/checkpointing/register_callbacks.py +51 -0
- code_muse/plugins/checkpointing/restore_command.py +263 -0
- code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
- code_muse/plugins/checkpointing/shadow_git.py +90 -0
- code_muse/plugins/claude_code_hooks/__init__.py +1 -0
- code_muse/plugins/claude_code_hooks/config.py +188 -0
- code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
- code_muse/plugins/claude_code_oauth/README.md +167 -0
- code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
- code_muse/plugins/claude_code_oauth/__init__.py +25 -0
- code_muse/plugins/claude_code_oauth/config.py +52 -0
- code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
- code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
- code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
- code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
- code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
- code_muse/plugins/claude_code_oauth/utils.py +664 -0
- code_muse/plugins/copilot_auth/__init__.py +11 -0
- code_muse/plugins/copilot_auth/config.py +91 -0
- code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
- code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
- code_muse/plugins/copilot_auth/utils.py +584 -0
- code_muse/plugins/custom_commands/__init__.py +14 -0
- code_muse/plugins/custom_commands/args_injection.py +82 -0
- code_muse/plugins/custom_commands/command_discovery.py +89 -0
- code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
- code_muse/plugins/custom_commands/register_callbacks.py +176 -0
- code_muse/plugins/customizable_commands/__init__.py +0 -0
- code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
- code_muse/plugins/destructive_command_guard/__init__.py +14 -0
- code_muse/plugins/destructive_command_guard/detector.py +375 -0
- code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
- code_muse/plugins/example_custom_command/README.md +280 -0
- code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
- code_muse/plugins/file_permission_handler/__init__.py +4 -0
- code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
- code_muse/plugins/filter_engine/__init__.py +30 -0
- code_muse/plugins/filter_engine/classifier.py +153 -0
- code_muse/plugins/filter_engine/content_detector.py +184 -0
- code_muse/plugins/filter_engine/dispatcher.py +244 -0
- code_muse/plugins/filter_engine/register_callbacks.py +188 -0
- code_muse/plugins/filter_engine/registry.py +279 -0
- code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
- code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
- code_muse/plugins/filter_engine/verbosity.py +63 -0
- code_muse/plugins/force_push_guard/__init__.py +5 -0
- code_muse/plugins/force_push_guard/detector.py +96 -0
- code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
- code_muse/plugins/force_push_guard/test_detector.py +143 -0
- code_muse/plugins/frontend_emitter/__init__.py +25 -0
- code_muse/plugins/frontend_emitter/emitter.py +121 -0
- code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
- code_muse/plugins/gac/__init__.py +4 -0
- code_muse/plugins/gac/git_ops.py +136 -0
- code_muse/plugins/gac/prompt.py +191 -0
- code_muse/plugins/gac/register_callbacks.py +82 -0
- code_muse/plugins/hook_creator/__init__.py +1 -0
- code_muse/plugins/hook_creator/register_callbacks.py +34 -0
- code_muse/plugins/hook_manager/__init__.py +1 -0
- code_muse/plugins/hook_manager/config.py +289 -0
- code_muse/plugins/hook_manager/hooks_menu.py +563 -0
- code_muse/plugins/hook_manager/register_callbacks.py +227 -0
- code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
- code_muse/plugins/mindpack/__init__.py +0 -0
- code_muse/plugins/mindpack/factory.py +930 -0
- code_muse/plugins/mindpack/judge.py +573 -0
- code_muse/plugins/mindpack/memory.py +100 -0
- code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
- code_muse/plugins/mindpack/orchestration.py +605 -0
- code_muse/plugins/mindpack/register_callbacks.py +175 -0
- code_muse/plugins/mindpack/schemas.py +358 -0
- code_muse/plugins/mindpack/tools.py +387 -0
- code_muse/plugins/oauth_muse_html.py +226 -0
- code_muse/plugins/ollama_setup/__init__.py +5 -0
- code_muse/plugins/ollama_setup/completer.py +36 -0
- code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
- code_muse/plugins/plan_command/__init__.py +0 -0
- code_muse/plugins/plan_command/register_callbacks.py +206 -0
- code_muse/plugins/plan_mode/__init__.py +37 -0
- code_muse/plugins/plan_mode/mode_cycling.py +40 -0
- code_muse/plugins/plan_mode/plan_generation.py +68 -0
- code_muse/plugins/plan_mode/plan_hooks.py +74 -0
- code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
- code_muse/plugins/plan_mode/register_callbacks.py +121 -0
- code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
- code_muse/plugins/policy_engine/__init__.py +46 -0
- code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
- code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
- code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
- code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
- code_muse/plugins/policy_engine/register_callbacks.py +112 -0
- code_muse/plugins/pop_command/__init__.py +1 -0
- code_muse/plugins/pop_command/register_callbacks.py +189 -0
- code_muse/plugins/prompt_newline/__init__.py +13 -0
- code_muse/plugins/prompt_newline/config.py +19 -0
- code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
- code_muse/plugins/safety_status/__init__.py +0 -0
- code_muse/plugins/safety_status/register_callbacks.py +113 -0
- code_muse/plugins/semantic_compression/__init__.py +6 -0
- code_muse/plugins/semantic_compression/compressor.py +295 -0
- code_muse/plugins/semantic_compression/config.py +123 -0
- code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
- code_muse/plugins/shell_minimizer/__init__.py +50 -0
- code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
- code_muse/plugins/shell_minimizer/pipeline.py +556 -0
- code_muse/plugins/shell_minimizer/primitives.py +482 -0
- code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
- code_muse/plugins/shell_safety/__init__.py +6 -0
- code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_muse/plugins/shell_safety/command_cache.py +149 -0
- code_muse/plugins/shell_safety/register_callbacks.py +202 -0
- code_muse/plugins/synthetic_status/__init__.py +1 -0
- code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
- code_muse/plugins/synthetic_status/status_api.py +145 -0
- code_muse/plugins/token_caching/__init__.py +21 -0
- code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
- code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
- code_muse/plugins/token_caching/register_callbacks.py +54 -0
- code_muse/plugins/token_caching/stats_display.py +35 -0
- code_muse/plugins/token_tracking/__init__.py +26 -0
- code_muse/plugins/token_tracking/database.py +381 -0
- code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
- code_muse/plugins/token_tracking/record.py +55 -0
- code_muse/plugins/token_tracking/register_callbacks.py +277 -0
- code_muse/plugins/token_tracking/reports.py +329 -0
- code_muse/plugins/universal_constructor/__init__.py +13 -0
- code_muse/plugins/universal_constructor/models.py +136 -0
- code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
- code_muse/plugins/universal_constructor/registry.py +390 -0
- code_muse/plugins/universal_constructor/runner.py +474 -0
- code_muse/plugins/universal_constructor/safety.py +440 -0
- code_muse/plugins/universal_constructor/sandbox.py +584 -0
- code_muse/provider_identity.py +105 -0
- code_muse/pydantic_patches.py +410 -0
- code_muse/reopenable_async_client.py +233 -0
- code_muse/round_robin_model.py +151 -0
- code_muse/secret_storage.py +74 -0
- code_muse/security/__init__.py +1 -0
- code_muse/security/redaction.cpython-314-darwin.so +0 -0
- code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/security/redaction.pyx +135 -0
- code_muse/session_storage.py +565 -0
- code_muse/status_display.py +261 -0
- code_muse/stream_parser/__init__.py +76 -0
- code_muse/stream_parser/assistant_text_parser.py +90 -0
- code_muse/stream_parser/citation_parser.py +76 -0
- code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
- code_muse/stream_parser/proposed_plan_parser.py +158 -0
- code_muse/stream_parser/stream_text_chunk.py +23 -0
- code_muse/stream_parser/stream_text_parser.py +27 -0
- code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/tagged_line_parser.pyx +251 -0
- code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
- code_muse/summarization_agent.py +308 -0
- code_muse/terminal_utils.cpython-314-darwin.so +0 -0
- code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/terminal_utils.pyx +483 -0
- code_muse/tools/__init__.py +459 -0
- code_muse/tools/agent_tools.py +613 -0
- code_muse/tools/ask_user_question/__init__.py +26 -0
- code_muse/tools/ask_user_question/constants.py +73 -0
- code_muse/tools/ask_user_question/demo_tui.py +55 -0
- code_muse/tools/ask_user_question/handler.py +232 -0
- code_muse/tools/ask_user_question/models.py +302 -0
- code_muse/tools/ask_user_question/registration.py +37 -0
- code_muse/tools/ask_user_question/renderers.py +336 -0
- code_muse/tools/ask_user_question/terminal_ui.py +327 -0
- code_muse/tools/ask_user_question/theme.py +156 -0
- code_muse/tools/ask_user_question/tui_loop.py +422 -0
- code_muse/tools/background_jobs.py +99 -0
- code_muse/tools/browser/__init__.py +37 -0
- code_muse/tools/browser/browser_control.py +289 -0
- code_muse/tools/browser/browser_interactions.py +545 -0
- code_muse/tools/browser/browser_locators.py +640 -0
- code_muse/tools/browser/browser_manager.py +376 -0
- code_muse/tools/browser/browser_navigation.py +251 -0
- code_muse/tools/browser/browser_screenshot.py +180 -0
- code_muse/tools/browser/browser_scripts.py +462 -0
- code_muse/tools/browser/browser_workflows.py +222 -0
- code_muse/tools/chrome_cdp/__init__.py +1070 -0
- code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
- code_muse/tools/command_runner.py +1401 -0
- code_muse/tools/common.py +1407 -0
- code_muse/tools/display.py +87 -0
- code_muse/tools/file_modifications.py +1099 -0
- code_muse/tools/file_operations.py +860 -0
- code_muse/tools/image_tools.py +185 -0
- code_muse/tools/meetin_proxy/__init__.py +243 -0
- code_muse/tools/meetin_proxy/capture_addon.py +82 -0
- code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
- code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
- code_muse/tools/path_policy.py +219 -0
- code_muse/tools/skills_tools.py +586 -0
- code_muse/tools/subagent_context.py +158 -0
- code_muse/tools/tools_content.py +50 -0
- code_muse/tools/universal_constructor.py +965 -0
- code_muse/uvx_detection.py +241 -0
- code_muse/version_checker.py +86 -0
- code_muse-0.0.1.data/data/code_muse/models.json +66 -0
- code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
- code_muse-0.0.1.dist-info/METADATA +845 -0
- code_muse-0.0.1.dist-info/RECORD +394 -0
- code_muse-0.0.1.dist-info/WHEEL +4 -0
- code_muse-0.0.1.dist-info/entry_points.txt +2 -0
- 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
|