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,91 @@
|
|
|
1
|
+
"""Configuration constants for the GitHub Copilot auth plugin."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from code_muse import config
|
|
7
|
+
|
|
8
|
+
# GitHub Copilot auth configuration
|
|
9
|
+
COPILOT_AUTH_CONFIG: dict[str, Any] = {
|
|
10
|
+
# Copilot session-token endpoints
|
|
11
|
+
"github_token_url": "https://api.github.com/copilot_internal/v2/token",
|
|
12
|
+
# GHE template: replace {host}
|
|
13
|
+
"ghe_token_url_template": "https://{host}/api/v3/copilot_internal/v2/token",
|
|
14
|
+
# OpenAI-compatible Copilot chat API (default; overridden per-host at runtime)
|
|
15
|
+
"api_base_url": "https://api.githubcopilot.com",
|
|
16
|
+
# Model prefix in Muse
|
|
17
|
+
"prefix": "copilot-",
|
|
18
|
+
"default_context_length": 128000,
|
|
19
|
+
# Headers expected by the Copilot API
|
|
20
|
+
"editor_version": "JetBrains-IU/2024.3",
|
|
21
|
+
"editor_plugin_version": "copilot/2.0.0",
|
|
22
|
+
"copilot_integration_id": "vscode-chat",
|
|
23
|
+
"openai_intent": "conversation-panel",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# GitHub Device Flow configuration — browser-based auth with no IDE required.
|
|
27
|
+
# Uses the VS Code Copilot extension's public client ID which supports Device Flow.
|
|
28
|
+
DEVICE_FLOW_CONFIG: dict[str, Any] = {
|
|
29
|
+
"client_id": "Iv1.b507a08c87ecfe98",
|
|
30
|
+
"scope": "read:user",
|
|
31
|
+
# Endpoint templates — {host} is replaced at runtime
|
|
32
|
+
"device_code_url": "https://{host}/login/device/code",
|
|
33
|
+
"access_token_url": "https://{host}/login/oauth/access_token",
|
|
34
|
+
# Polling defaults (server may override via response)
|
|
35
|
+
"default_poll_interval": 5,
|
|
36
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Models known to be available via Copilot (costs are $0 via Copilot).
|
|
40
|
+
# The /copilot-login flow will also try to fetch the live model list.
|
|
41
|
+
DEFAULT_COPILOT_MODELS = [
|
|
42
|
+
"gpt-4o",
|
|
43
|
+
"gpt-4.1",
|
|
44
|
+
"gpt-4.1-mini",
|
|
45
|
+
"o4-mini",
|
|
46
|
+
"o3",
|
|
47
|
+
"claude-opus-4.6",
|
|
48
|
+
"claude-opus-4",
|
|
49
|
+
"claude-sonnet-4",
|
|
50
|
+
"claude-sonnet-4.5",
|
|
51
|
+
"claude-haiku-4.5",
|
|
52
|
+
"gemini-2.5-pro",
|
|
53
|
+
"gemini-2.5-flash",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# Per-model context-length overrides (tokens).
|
|
57
|
+
COPILOT_MODEL_CONTEXT_LENGTHS: dict[str, int] = {
|
|
58
|
+
"gpt-4o": 64000,
|
|
59
|
+
"gpt-4.1": 128000,
|
|
60
|
+
"gpt-4.1-mini": 128000,
|
|
61
|
+
"o4-mini": 128000,
|
|
62
|
+
"o3": 128000,
|
|
63
|
+
"claude-opus-4.6": 200000,
|
|
64
|
+
"claude-opus-4": 200000,
|
|
65
|
+
"claude-sonnet-4": 128000,
|
|
66
|
+
"claude-sonnet-4.5": 128000,
|
|
67
|
+
"claude-haiku-4.5": 128000,
|
|
68
|
+
"gemini-2.5-pro": 128000,
|
|
69
|
+
"gemini-2.5-flash": 128000,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_copilot_models_path() -> Path:
|
|
74
|
+
"""Return path for persisted copilot_models.json (XDG_DATA_HOME)."""
|
|
75
|
+
data_dir = Path(config.DATA_DIR)
|
|
76
|
+
data_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
77
|
+
return data_dir / "copilot_models.json"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_session_cache_path() -> Path:
|
|
81
|
+
"""Return path for caching the short-lived Copilot session token."""
|
|
82
|
+
data_dir = Path(config.DATA_DIR)
|
|
83
|
+
data_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
84
|
+
return data_dir / "copilot_session.json"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_device_token_storage_path() -> Path:
|
|
88
|
+
"""Return path for storing tokens obtained via the GitHub Device Flow."""
|
|
89
|
+
data_dir = Path(config.DATA_DIR)
|
|
90
|
+
data_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
91
|
+
return data_dir / "copilot_device_tokens.json"
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""Reasoning-opaque round-trip interceptor for Copilot Claude models.
|
|
2
|
+
|
|
3
|
+
The Copilot API returns two fields on Claude assistant messages:
|
|
4
|
+
|
|
5
|
+
- ``reasoning_text``: the model's chain-of-thought (human-readable)
|
|
6
|
+
- ``reasoning_opaque``: encrypted round-trip blob (**must** be echoed back)
|
|
7
|
+
|
|
8
|
+
pydantic-ai's ``openai_chat_send_back_thinking_parts="field"`` mode preserves
|
|
9
|
+
``reasoning_text`` across tool calls, but it doesn't know about
|
|
10
|
+
``reasoning_opaque``. The Copilot API returns **400 Bad Request** if
|
|
11
|
+
``reasoning_text`` is sent without the accompanying ``reasoning_opaque``.
|
|
12
|
+
|
|
13
|
+
This module monkey-patches the httpx client to transparently:
|
|
14
|
+
|
|
15
|
+
1. Capture ``reasoning_opaque`` (keyed by ``reasoning_text``) from responses
|
|
16
|
+
2. Inject the matching ``reasoning_opaque`` into outgoing request messages
|
|
17
|
+
|
|
18
|
+
The approach mirrors the ``ChatGPTCodexAsyncClient`` pattern already in the codebase.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import httpx
|
|
27
|
+
from httpx import AsyncByteStream
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Cap the cache so it can't grow forever across long sessions.
|
|
32
|
+
_MAX_CACHE_ENTRIES = 256
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Helpers
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _text_key(text: str) -> str:
|
|
41
|
+
"""Short SHA-256 prefix — enough to avoid collisions in practice."""
|
|
42
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()[:32]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Stream wrapper — captures opaque data as SSE events flow through
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _OpaqueCapturingStream(AsyncByteStream):
|
|
51
|
+
"""Async byte-stream wrapper that taps ``reasoning_opaque`` from SSE.
|
|
52
|
+
|
|
53
|
+
Yields every byte unchanged so downstream consumers (the OpenAI SDK)
|
|
54
|
+
see the exact same data. Internally it buffers bytes, splits SSE
|
|
55
|
+
lines, parses JSON, and pairs ``reasoning_text`` chunks with
|
|
56
|
+
``reasoning_opaque`` chunks for storage in *opaque_cache*.
|
|
57
|
+
|
|
58
|
+
Inherits from ``httpx.AsyncByteStream`` so that
|
|
59
|
+
``httpx.Response.aclose()`` recognises us as an async stream and
|
|
60
|
+
doesn't blow up with *"Attempted to call an async close on an sync
|
|
61
|
+
stream"*. Ask me how I know.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
inner_stream: Any,
|
|
67
|
+
opaque_cache: dict[str, str],
|
|
68
|
+
thinking_field: str,
|
|
69
|
+
):
|
|
70
|
+
self._inner = inner_stream
|
|
71
|
+
self._opaque_cache = opaque_cache
|
|
72
|
+
self._thinking_field = thinking_field
|
|
73
|
+
self._buffer = ""
|
|
74
|
+
self._text_parts: list[str] = []
|
|
75
|
+
self._opaque_parts: list[str] = []
|
|
76
|
+
|
|
77
|
+
# -- async-iterator protocol ------------------------------------------
|
|
78
|
+
|
|
79
|
+
def __aiter__(self):
|
|
80
|
+
return self._iter_impl()
|
|
81
|
+
|
|
82
|
+
async def _iter_impl(self):
|
|
83
|
+
try:
|
|
84
|
+
async for chunk in self._inner:
|
|
85
|
+
self._feed(chunk)
|
|
86
|
+
yield chunk
|
|
87
|
+
finally:
|
|
88
|
+
self._flush()
|
|
89
|
+
|
|
90
|
+
async def aclose(self):
|
|
91
|
+
self._flush()
|
|
92
|
+
if hasattr(self._inner, "aclose"):
|
|
93
|
+
await self._inner.aclose()
|
|
94
|
+
|
|
95
|
+
# Fallback for any attributes we don't explicitly handle.
|
|
96
|
+
def __getattr__(self, name: str):
|
|
97
|
+
return getattr(self._inner, name)
|
|
98
|
+
|
|
99
|
+
# -- SSE parsing -------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _feed(self, chunk: bytes) -> None:
|
|
102
|
+
self._buffer += chunk.decode("utf-8", errors="replace")
|
|
103
|
+
self._process_buffer()
|
|
104
|
+
|
|
105
|
+
def _process_buffer(self) -> None:
|
|
106
|
+
while "\n" in self._buffer:
|
|
107
|
+
line, self._buffer = self._buffer.split("\n", 1)
|
|
108
|
+
line = line.strip()
|
|
109
|
+
if not line.startswith("data: "):
|
|
110
|
+
continue
|
|
111
|
+
data_str = line[6:]
|
|
112
|
+
if data_str == "[DONE]":
|
|
113
|
+
self._flush()
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
event = json.loads(data_str)
|
|
117
|
+
self._extract_from_event(event)
|
|
118
|
+
except json.JSONDecodeError, TypeError:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
def _extract_from_event(self, event: dict) -> None:
|
|
122
|
+
for choice in event.get("choices", []):
|
|
123
|
+
# Streaming deltas
|
|
124
|
+
delta = choice.get("delta") or {}
|
|
125
|
+
self._collect(delta)
|
|
126
|
+
# Non-streaming message (fallback)
|
|
127
|
+
message = choice.get("message") or {}
|
|
128
|
+
self._collect(message)
|
|
129
|
+
# Flush when the model signals it's done with this turn
|
|
130
|
+
if choice.get("finish_reason"):
|
|
131
|
+
self._flush()
|
|
132
|
+
|
|
133
|
+
def _collect(self, obj: dict) -> None:
|
|
134
|
+
text_chunk = obj.get(self._thinking_field)
|
|
135
|
+
if text_chunk:
|
|
136
|
+
self._text_parts.append(text_chunk)
|
|
137
|
+
opaque_chunk = obj.get("reasoning_opaque")
|
|
138
|
+
if opaque_chunk:
|
|
139
|
+
self._opaque_parts.append(opaque_chunk)
|
|
140
|
+
|
|
141
|
+
def _flush(self) -> None:
|
|
142
|
+
if self._opaque_parts and self._text_parts:
|
|
143
|
+
text = "".join(self._text_parts)
|
|
144
|
+
opaque = "".join(self._opaque_parts)
|
|
145
|
+
key = _text_key(text)
|
|
146
|
+
self._opaque_cache[key] = opaque
|
|
147
|
+
logger.debug(
|
|
148
|
+
"Captured reasoning_opaque (%d chars) for key %.8s…",
|
|
149
|
+
len(opaque),
|
|
150
|
+
key,
|
|
151
|
+
)
|
|
152
|
+
# Evict oldest entry if we exceed the cap.
|
|
153
|
+
while len(self._opaque_cache) > _MAX_CACHE_ENTRIES:
|
|
154
|
+
first_key = next(iter(self._opaque_cache))
|
|
155
|
+
del self._opaque_cache[first_key]
|
|
156
|
+
self._text_parts = []
|
|
157
|
+
self._opaque_parts = []
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Non-streaming capture (safety net)
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _capture_from_content(
|
|
166
|
+
content: bytes,
|
|
167
|
+
opaque_cache: dict[str, str],
|
|
168
|
+
thinking_field: str,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Extract ``reasoning_opaque`` from an already-read response body."""
|
|
171
|
+
try:
|
|
172
|
+
data = json.loads(content)
|
|
173
|
+
for choice in data.get("choices", []):
|
|
174
|
+
msg = choice.get("message") or {}
|
|
175
|
+
text = msg.get(thinking_field)
|
|
176
|
+
opaque = msg.get("reasoning_opaque")
|
|
177
|
+
if text and opaque:
|
|
178
|
+
opaque_cache[_text_key(text)] = opaque
|
|
179
|
+
except json.JSONDecodeError, TypeError, AttributeError:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# Request-side injection
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _rebuild_request_body(
|
|
189
|
+
request: httpx.Request,
|
|
190
|
+
body: dict,
|
|
191
|
+
client: httpx.AsyncClient,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Replace the request body in-place (mirrors ChatGPTCodexAsyncClient)."""
|
|
194
|
+
new_body = json.dumps(body).encode("utf-8")
|
|
195
|
+
rebuilt = client.build_request(
|
|
196
|
+
method=request.method,
|
|
197
|
+
url=request.url,
|
|
198
|
+
headers=request.headers,
|
|
199
|
+
content=new_body,
|
|
200
|
+
)
|
|
201
|
+
if hasattr(rebuilt, "_content"):
|
|
202
|
+
request._content = rebuilt._content # type: ignore[attr-defined]
|
|
203
|
+
if hasattr(rebuilt, "stream"):
|
|
204
|
+
request.stream = rebuilt.stream
|
|
205
|
+
if hasattr(rebuilt, "extensions"):
|
|
206
|
+
request.extensions = rebuilt.extensions
|
|
207
|
+
request.headers["Content-Length"] = str(len(new_body))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _inject_opaque_into_request(
|
|
211
|
+
request: httpx.Request,
|
|
212
|
+
opaque_cache: dict[str, str],
|
|
213
|
+
client: httpx.AsyncClient,
|
|
214
|
+
thinking_field: str,
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Add stored ``reasoning_opaque`` to assistant messages that need it.
|
|
217
|
+
|
|
218
|
+
**Prevention layer**: if we can't find a matching opaque for a
|
|
219
|
+
``reasoning_text``, we strip that field entirely rather than sending
|
|
220
|
+
it bare (which would trigger a 400).
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
body_bytes = request.content
|
|
224
|
+
if not body_bytes:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
body = json.loads(body_bytes)
|
|
228
|
+
if not isinstance(body, dict):
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
messages = body.get("messages")
|
|
232
|
+
if not messages:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
modified = False
|
|
236
|
+
injected = 0
|
|
237
|
+
stripped = 0
|
|
238
|
+
|
|
239
|
+
for msg in messages:
|
|
240
|
+
if not isinstance(msg, dict):
|
|
241
|
+
continue
|
|
242
|
+
if msg.get("role") != "assistant":
|
|
243
|
+
continue
|
|
244
|
+
if thinking_field not in msg:
|
|
245
|
+
continue
|
|
246
|
+
if "reasoning_opaque" in msg:
|
|
247
|
+
# Already has opaque — don't touch.
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
key = _text_key(msg[thinking_field])
|
|
251
|
+
opaque = opaque_cache.get(key)
|
|
252
|
+
if opaque:
|
|
253
|
+
msg["reasoning_opaque"] = opaque
|
|
254
|
+
modified = True
|
|
255
|
+
injected += 1
|
|
256
|
+
else:
|
|
257
|
+
# SAFETY: No matching opaque → strip reasoning_text to
|
|
258
|
+
# prevent the 400. Losing thinking context is better
|
|
259
|
+
# than crashing the conversation.
|
|
260
|
+
del msg[thinking_field]
|
|
261
|
+
modified = True
|
|
262
|
+
stripped += 1
|
|
263
|
+
logger.debug(
|
|
264
|
+
"Stripped orphaned %s (no opaque in cache)",
|
|
265
|
+
thinking_field,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if not modified:
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
_rebuild_request_body(request, body, client)
|
|
272
|
+
|
|
273
|
+
if injected:
|
|
274
|
+
logger.debug(
|
|
275
|
+
"Injected reasoning_opaque into %d assistant message(s)",
|
|
276
|
+
injected,
|
|
277
|
+
)
|
|
278
|
+
if stripped:
|
|
279
|
+
logger.debug(
|
|
280
|
+
"Stripped %d orphaned %s field(s) (cache miss)",
|
|
281
|
+
stripped,
|
|
282
|
+
thinking_field,
|
|
283
|
+
)
|
|
284
|
+
except Exception as exc:
|
|
285
|
+
# Never crash the real request.
|
|
286
|
+
logger.debug("Failed to inject reasoning_opaque: %s", exc)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _strip_all_reasoning_fields(
|
|
290
|
+
request: httpx.Request,
|
|
291
|
+
client: httpx.AsyncClient,
|
|
292
|
+
thinking_field: str,
|
|
293
|
+
) -> bool:
|
|
294
|
+
"""Remove ALL reasoning fields from the request body.
|
|
295
|
+
|
|
296
|
+
**Recovery layer**: called after a 400 to retry without any reasoning
|
|
297
|
+
context. Degrades gracefully to the old ``send_back_thinking_parts=False``
|
|
298
|
+
behaviour — thinking disappears after tool calls, but the conversation
|
|
299
|
+
keeps working.
|
|
300
|
+
|
|
301
|
+
Returns True if the body was modified.
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
body_bytes = request.content
|
|
305
|
+
if not body_bytes:
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
body = json.loads(body_bytes)
|
|
309
|
+
if not isinstance(body, dict):
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
messages = body.get("messages")
|
|
313
|
+
if not messages:
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
modified = False
|
|
317
|
+
for msg in messages:
|
|
318
|
+
if not isinstance(msg, dict):
|
|
319
|
+
continue
|
|
320
|
+
if msg.get("role") != "assistant":
|
|
321
|
+
continue
|
|
322
|
+
for field in (thinking_field, "reasoning_opaque"):
|
|
323
|
+
if field in msg:
|
|
324
|
+
del msg[field]
|
|
325
|
+
modified = True
|
|
326
|
+
|
|
327
|
+
if modified:
|
|
328
|
+
_rebuild_request_body(request, body, client)
|
|
329
|
+
|
|
330
|
+
return modified
|
|
331
|
+
except Exception as exc:
|
|
332
|
+
logger.debug("Failed to strip reasoning fields: %s", exc)
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ---------------------------------------------------------------------------
|
|
337
|
+
# Public API — single entry-point
|
|
338
|
+
# ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def patch_client_for_reasoning_opaque(
|
|
342
|
+
client: httpx.AsyncClient,
|
|
343
|
+
thinking_field: str = "reasoning_text",
|
|
344
|
+
) -> None:
|
|
345
|
+
"""Monkey-patch *client* to round-trip ``reasoning_opaque`` transparently.
|
|
346
|
+
|
|
347
|
+
Call **after** creating the client but **before** handing it to
|
|
348
|
+
``OpenAIProvider``. Works with plain ``httpx.AsyncClient`` and the
|
|
349
|
+
``RetryingAsyncClient`` subclass returned by ``create_async_client``.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
client: The httpx async client to patch.
|
|
353
|
+
thinking_field: JSON field name that carries the thinking content
|
|
354
|
+
(default ``"reasoning_text"`` — the Copilot Claude convention).
|
|
355
|
+
"""
|
|
356
|
+
opaque_cache: dict[str, str] = {}
|
|
357
|
+
original_send = client.send
|
|
358
|
+
|
|
359
|
+
async def _patched_send(
|
|
360
|
+
request: httpx.Request, *args: Any, **kwargs: Any
|
|
361
|
+
) -> httpx.Response:
|
|
362
|
+
# 1) Inject reasoning_opaque into outgoing POST bodies.
|
|
363
|
+
# Also strips orphaned reasoning_text with no cached opaque.
|
|
364
|
+
if request.method == "POST":
|
|
365
|
+
_inject_opaque_into_request(request, opaque_cache, client, thinking_field)
|
|
366
|
+
|
|
367
|
+
# 2) Let the real send (incl. auth + retries) run.
|
|
368
|
+
response = await original_send(request, *args, **kwargs)
|
|
369
|
+
|
|
370
|
+
# 3) RECOVERY: If the API still returns 400, it's likely because
|
|
371
|
+
# our reasoning fields are wrong/stale. Strip them all and
|
|
372
|
+
# retry once. This degrades to "no thinking after tool calls"
|
|
373
|
+
# but keeps the conversation alive.
|
|
374
|
+
if response.status_code == 400 and request.method == "POST":
|
|
375
|
+
# Read the error body for diagnostics before retrying.
|
|
376
|
+
try:
|
|
377
|
+
err_body = response.content.decode("utf-8", errors="replace")
|
|
378
|
+
except Exception:
|
|
379
|
+
err_body = "<unreadable>"
|
|
380
|
+
logger.warning(
|
|
381
|
+
"Copilot 400 — attempting recovery by stripping reasoning "
|
|
382
|
+
"fields and retrying. Error: %.300s",
|
|
383
|
+
err_body,
|
|
384
|
+
)
|
|
385
|
+
if _strip_all_reasoning_fields(request, client, thinking_field):
|
|
386
|
+
response = await original_send(request, *args, **kwargs)
|
|
387
|
+
if response.status_code == 200:
|
|
388
|
+
logger.info(
|
|
389
|
+
"Recovery succeeded — reasoning stripped, "
|
|
390
|
+
"conversation continues without thinking context."
|
|
391
|
+
)
|
|
392
|
+
else:
|
|
393
|
+
logger.warning(
|
|
394
|
+
"Recovery retry also failed (%d) — returning retry response.",
|
|
395
|
+
response.status_code,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# 4) Capture reasoning_opaque from successful responses.
|
|
399
|
+
if response.status_code == 200:
|
|
400
|
+
if response.is_stream_consumed:
|
|
401
|
+
_capture_from_content(response.content, opaque_cache, thinking_field)
|
|
402
|
+
elif hasattr(response, "stream") and response.stream is not None:
|
|
403
|
+
response.stream = _OpaqueCapturingStream(
|
|
404
|
+
response.stream, opaque_cache, thinking_field
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
return response
|
|
408
|
+
|
|
409
|
+
client.send = _patched_send # type: ignore[method-assign]
|