my-claude-code 7.13.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.
- my_claude_code/__init__.py +1 -0
- my_claude_code/api/__init__.py +1 -0
- my_claude_code/api/admin_cache.py +49 -0
- my_claude_code/api/admin_claude_config_routes.py +212 -0
- my_claude_code/api/admin_custom_routes.py +610 -0
- my_claude_code/api/admin_desktop_app_routes.py +472 -0
- my_claude_code/api/admin_export_routes.py +455 -0
- my_claude_code/api/admin_harness_routes.py +621 -0
- my_claude_code/api/admin_routes.py +3517 -0
- my_claude_code/api/admin_server_routes.py +183 -0
- my_claude_code/api/admin_static/admin.css +7774 -0
- my_claude_code/api/admin_static/admin.js +18659 -0
- my_claude_code/api/admin_static/img/admin-analytics-cost.png +0 -0
- my_claude_code/api/admin_static/img/admin-analytics-harness.png +0 -0
- my_claude_code/api/admin_static/img/admin-analytics.png +0 -0
- my_claude_code/api/admin_static/img/admin-catalogue-refresh.png +0 -0
- my_claude_code/api/admin_static/img/admin-coding-agent-card.png +0 -0
- my_claude_code/api/admin_static/img/admin-coding-agents.png +0 -0
- my_claude_code/api/admin_static/img/admin-credential-health.png +0 -0
- my_claude_code/api/admin_static/img/admin-custom-provider.png +0 -0
- my_claude_code/api/admin_static/img/admin-desktop-apps.png +0 -0
- my_claude_code/api/admin_static/img/admin-get-started.png +0 -0
- my_claude_code/api/admin_static/img/admin-key-performance.png +0 -0
- my_claude_code/api/admin_static/img/admin-limits-calculator.png +0 -0
- my_claude_code/api/admin_static/img/admin-limits.png +0 -0
- my_claude_code/api/admin_static/img/admin-messaging.png +0 -0
- my_claude_code/api/admin_static/img/admin-model-config.png +0 -0
- my_claude_code/api/admin_static/img/admin-models-billed-est.png +0 -0
- my_claude_code/api/admin_static/img/admin-models-bulk.png +0 -0
- my_claude_code/api/admin_static/img/admin-models-learned.png +0 -0
- my_claude_code/api/admin_static/img/admin-models.png +0 -0
- my_claude_code/api/admin_static/img/admin-page.png +0 -0
- my_claude_code/api/admin_static/img/admin-providers.png +0 -0
- my_claude_code/api/admin_static/img/admin-request-contradiction.png +0 -0
- my_claude_code/api/admin_static/img/admin-request-detail.png +0 -0
- my_claude_code/api/admin_static/img/admin-requests.png +0 -0
- my_claude_code/api/admin_static/img/admin-version.png +0 -0
- my_claude_code/api/admin_static/img/admin-vision-adapter-mode.png +0 -0
- my_claude_code/api/admin_static/img/admin-websearch-analytics.png +0 -0
- my_claude_code/api/admin_static/img/admin-websearch.png +0 -0
- my_claude_code/api/admin_static/img/app-icon-96.png +0 -0
- my_claude_code/api/admin_static/img/cc-model-picker.png +0 -0
- my_claude_code/api/admin_static/img/claude-desktop-developer-menu.png +0 -0
- my_claude_code/api/admin_static/img/claude-desktop-gateway-config.png +0 -0
- my_claude_code/api/admin_static/img/codex-model-picker.png +0 -0
- my_claude_code/api/admin_static/index.html +4842 -0
- my_claude_code/api/admin_websearch_routes.py +121 -0
- my_claude_code/api/app.py +171 -0
- my_claude_code/api/dependencies.py +140 -0
- my_claude_code/api/detection.py +121 -0
- my_claude_code/api/docs_content.py +290 -0
- my_claude_code/api/docs_render.py +278 -0
- my_claude_code/api/gemini_model_catalog.py +71 -0
- my_claude_code/api/gemini_routes.py +245 -0
- my_claude_code/api/handlers/__init__.py +15 -0
- my_claude_code/api/handlers/chat_completions.py +335 -0
- my_claude_code/api/handlers/gemini.py +350 -0
- my_claude_code/api/handlers/messages.py +688 -0
- my_claude_code/api/handlers/responses.py +239 -0
- my_claude_code/api/handlers/token_count.py +85 -0
- my_claude_code/api/model_admin.py +1349 -0
- my_claude_code/api/model_catalog.py +264 -0
- my_claude_code/api/models_page_cache.py +166 -0
- my_claude_code/api/optimization_handlers.py +223 -0
- my_claude_code/api/ports.py +62 -0
- my_claude_code/api/request_capture.py +1397 -0
- my_claude_code/api/request_errors.py +112 -0
- my_claude_code/api/request_ids.py +100 -0
- my_claude_code/api/request_pricing.py +209 -0
- my_claude_code/api/response_streams.py +470 -0
- my_claude_code/api/routes.py +310 -0
- my_claude_code/api/validation_log.py +46 -0
- my_claude_code/api/web_tools/__init__.py +51 -0
- my_claude_code/api/web_tools/constants.py +17 -0
- my_claude_code/api/web_tools/egress.py +105 -0
- my_claude_code/api/web_tools/outbound.py +692 -0
- my_claude_code/api/web_tools/parsers.py +102 -0
- my_claude_code/api/web_tools/request.py +132 -0
- my_claude_code/api/web_tools/search_providers.py +56 -0
- my_claude_code/api/web_tools/streaming.py +315 -0
- my_claude_code/api/wire_surfaces.py +66 -0
- my_claude_code/application/__init__.py +1 -0
- my_claude_code/application/catalogue_model.py +512 -0
- my_claude_code/application/catalogues/__init__.py +166 -0
- my_claude_code/application/catalogues/aider.py +249 -0
- my_claude_code/application/catalogues/base.py +265 -0
- my_claude_code/application/catalogues/claude_desktop.py +80 -0
- my_claude_code/application/catalogues/cline.py +211 -0
- my_claude_code/application/catalogues/codex.py +294 -0
- my_claude_code/application/catalogues/commandcode.py +303 -0
- my_claude_code/application/catalogues/crush.py +394 -0
- my_claude_code/application/catalogues/droid.py +184 -0
- my_claude_code/application/catalogues/gemini_cli.py +237 -0
- my_claude_code/application/catalogues/goose.py +254 -0
- my_claude_code/application/catalogues/kilo.py +69 -0
- my_claude_code/application/catalogues/kimi.py +245 -0
- my_claude_code/application/catalogues/opencode.py +365 -0
- my_claude_code/application/catalogues/pi.py +131 -0
- my_claude_code/application/catalogues/qwen.py +262 -0
- my_claude_code/application/catalogues/vscode.py +147 -0
- my_claude_code/application/cost.py +323 -0
- my_claude_code/application/deadline_hints.py +98 -0
- my_claude_code/application/derived_payloads.py +224 -0
- my_claude_code/application/desktop_documents.py +379 -0
- my_claude_code/application/errors.py +80 -0
- my_claude_code/application/execution.py +2238 -0
- my_claude_code/application/model_metadata.py +565 -0
- my_claude_code/application/output_tokens.py +335 -0
- my_claude_code/application/ports.py +165 -0
- my_claude_code/application/reasoning.py +112 -0
- my_claude_code/application/reasoning_budget.py +229 -0
- my_claude_code/application/reasoning_gating.py +719 -0
- my_claude_code/application/release_updates.py +1485 -0
- my_claude_code/application/route_health.py +441 -0
- my_claude_code/application/routing.py +1282 -0
- my_claude_code/application/tier_chains.py +175 -0
- my_claude_code/application/vision_describe.py +529 -0
- my_claude_code/assets/app-icon.icns +0 -0
- my_claude_code/assets/app-icon.ico +0 -0
- my_claude_code/assets/app-icon.png +0 -0
- my_claude_code/assets/tray-icon.png +0 -0
- my_claude_code/cli/__init__.py +5 -0
- my_claude_code/cli/apps_command.py +274 -0
- my_claude_code/cli/claude_env.py +118 -0
- my_claude_code/cli/commands.py +827 -0
- my_claude_code/cli/desktop.py +1239 -0
- my_claude_code/cli/desktop_assets.py +153 -0
- my_claude_code/cli/desktop_entrypoint.py +232 -0
- my_claude_code/cli/desktop_status.py +360 -0
- my_claude_code/cli/desktop_tray.py +249 -0
- my_claude_code/cli/desktop_window.py +635 -0
- my_claude_code/cli/entrypoints.py +217 -0
- my_claude_code/cli/first_start.py +205 -0
- my_claude_code/cli/harnesses/__init__.py +6 -0
- my_claude_code/cli/harnesses/catalogue_client.py +243 -0
- my_claude_code/cli/harnesses/catalogue_documents.py +110 -0
- my_claude_code/cli/harnesses/registry.py +53 -0
- my_claude_code/cli/installer_support.py +467 -0
- my_claude_code/cli/launchers/__init__.py +1 -0
- my_claude_code/cli/launchers/aider.py +268 -0
- my_claude_code/cli/launchers/claude.py +126 -0
- my_claude_code/cli/launchers/cline.py +270 -0
- my_claude_code/cli/launchers/codex.py +235 -0
- my_claude_code/cli/launchers/commandcode.py +243 -0
- my_claude_code/cli/launchers/common.py +183 -0
- my_claude_code/cli/launchers/crush.py +231 -0
- my_claude_code/cli/launchers/droid.py +226 -0
- my_claude_code/cli/launchers/gemini.py +322 -0
- my_claude_code/cli/launchers/goose.py +269 -0
- my_claude_code/cli/launchers/kimi.py +214 -0
- my_claude_code/cli/launchers/opencode.py +203 -0
- my_claude_code/cli/launchers/pi.py +158 -0
- my_claude_code/cli/launchers/pi_extension.ts +216 -0
- my_claude_code/cli/launchers/qwen.py +266 -0
- my_claude_code/cli/legacy_stubs.py +85 -0
- my_claude_code/cli/managed/__init__.py +6 -0
- my_claude_code/cli/managed/claude.py +215 -0
- my_claude_code/cli/managed/diagnostics.py +47 -0
- my_claude_code/cli/managed/manager.py +207 -0
- my_claude_code/cli/managed/session.py +293 -0
- my_claude_code/cli/migrate_config_dir.py +479 -0
- my_claude_code/cli/port_diagnostics.py +142 -0
- my_claude_code/cli/port_takeover.py +308 -0
- my_claude_code/cli/process_registry.py +85 -0
- my_claude_code/cli/rtk_commands.py +117 -0
- my_claude_code/cli/tool_paths.py +58 -0
- my_claude_code/config/__init__.py +5 -0
- my_claude_code/config/admin/__init__.py +1 -0
- my_claude_code/config/admin/manifest.py +2485 -0
- my_claude_code/config/admin/persistence.py +425 -0
- my_claude_code/config/admin/provider_manifest.py +749 -0
- my_claude_code/config/admin/route_refs.py +119 -0
- my_claude_code/config/admin/sources.py +91 -0
- my_claude_code/config/admin/spec.py +71 -0
- my_claude_code/config/admin/status.py +129 -0
- my_claude_code/config/admin/validation.py +81 -0
- my_claude_code/config/admin/values.py +256 -0
- my_claude_code/config/admin/websearch_manifest.py +280 -0
- my_claude_code/config/atomic_json.py +69 -0
- my_claude_code/config/claude_code_catalog.py +242 -0
- my_claude_code/config/claude_config_editor.py +443 -0
- my_claude_code/config/claude_discovery.py +225 -0
- my_claude_code/config/claude_settings.py +426 -0
- my_claude_code/config/constants.py +669 -0
- my_claude_code/config/credentials.py +21 -0
- my_claude_code/config/data/__init__.py +1 -0
- my_claude_code/config/data/claude_code_config_catalog.json +6105 -0
- my_claude_code/config/desktop.py +792 -0
- my_claude_code/config/desktop_apply.py +1810 -0
- my_claude_code/config/desktop_apps.py +1873 -0
- my_claude_code/config/desktop_shell.py +1032 -0
- my_claude_code/config/document_codecs.py +923 -0
- my_claude_code/config/env.example +1584 -0
- my_claude_code/config/env_files.py +192 -0
- my_claude_code/config/env_migrations.py +234 -0
- my_claude_code/config/env_template.py +76 -0
- my_claude_code/config/harness_attribution.py +42 -0
- my_claude_code/config/harness_base_url.py +104 -0
- my_claude_code/config/harness_cline.py +159 -0
- my_claude_code/config/harness_config_merge.py +326 -0
- my_claude_code/config/harness_tiers.py +386 -0
- my_claude_code/config/harness_toml.py +227 -0
- my_claude_code/config/harnesses.py +1702 -0
- my_claude_code/config/legacy_env_rewrite.py +158 -0
- my_claude_code/config/limits.py +318 -0
- my_claude_code/config/logging_config.py +372 -0
- my_claude_code/config/model_overrides.py +383 -0
- my_claude_code/config/model_refs.py +120 -0
- my_claude_code/config/nim.py +119 -0
- my_claude_code/config/onboarding.py +458 -0
- my_claude_code/config/paths.py +724 -0
- my_claude_code/config/provider_catalog.py +912 -0
- my_claude_code/config/provider_registry.py +539 -0
- my_claude_code/config/proxy_auth.py +73 -0
- my_claude_code/config/reasoning.py +25 -0
- my_claude_code/config/reasoning_enum.py +146 -0
- my_claude_code/config/restore_record.py +420 -0
- my_claude_code/config/rtk.py +563 -0
- my_claude_code/config/server_urls.py +32 -0
- my_claude_code/config/settings.py +2260 -0
- my_claude_code/config/update_progress.py +492 -0
- my_claude_code/config/websearch_catalog.py +925 -0
- my_claude_code/core/__init__.py +15 -0
- my_claude_code/core/anthropic/__init__.py +128 -0
- my_claude_code/core/anthropic/content.py +31 -0
- my_claude_code/core/anthropic/conversion.py +968 -0
- my_claude_code/core/anthropic/errors.py +93 -0
- my_claude_code/core/anthropic/image_downscale.py +185 -0
- my_claude_code/core/anthropic/image_tokens.py +349 -0
- my_claude_code/core/anthropic/models.py +183 -0
- my_claude_code/core/anthropic/openai_tool_names.py +124 -0
- my_claude_code/core/anthropic/request_modalities.py +130 -0
- my_claude_code/core/anthropic/request_serialization.py +57 -0
- my_claude_code/core/anthropic/request_snapshot.py +35 -0
- my_claude_code/core/anthropic/server_tool_sse.py +12 -0
- my_claude_code/core/anthropic/sse_aggregation.py +128 -0
- my_claude_code/core/anthropic/stream_contracts.py +343 -0
- my_claude_code/core/anthropic/streaming/__init__.py +39 -0
- my_claude_code/core/anthropic/streaming/emitter.py +62 -0
- my_claude_code/core/anthropic/streaming/ledger.py +550 -0
- my_claude_code/core/anthropic/streaming/recovery.py +212 -0
- my_claude_code/core/anthropic/streaming/splice.py +542 -0
- my_claude_code/core/anthropic/streaming/truncation.py +297 -0
- my_claude_code/core/anthropic/thinking.py +140 -0
- my_claude_code/core/anthropic/tokens.py +169 -0
- my_claude_code/core/anthropic/tool_result_media.py +524 -0
- my_claude_code/core/anthropic/tool_result_trimming.py +487 -0
- my_claude_code/core/anthropic/tool_schema.py +94 -0
- my_claude_code/core/anthropic/tools.py +462 -0
- my_claude_code/core/anthropic/upstream_errors.py +55 -0
- my_claude_code/core/anthropic/utils.py +9 -0
- my_claude_code/core/async_iterators.py +26 -0
- my_claude_code/core/catalogue_refs.py +84 -0
- my_claude_code/core/client_fingerprint.py +415 -0
- my_claude_code/core/credential_attribution.py +73 -0
- my_claude_code/core/credential_rotation.py +582 -0
- my_claude_code/core/derived_cache.py +172 -0
- my_claude_code/core/diagnostics.py +330 -0
- my_claude_code/core/export.py +1334 -0
- my_claude_code/core/failures.py +147 -0
- my_claude_code/core/gateway_model_ids.py +51 -0
- my_claude_code/core/gemini_api/__init__.py +59 -0
- my_claude_code/core/gemini_api/adapter.py +69 -0
- my_claude_code/core/gemini_api/assembler.py +258 -0
- my_claude_code/core/gemini_api/catalog.py +58 -0
- my_claude_code/core/gemini_api/errors.py +131 -0
- my_claude_code/core/gemini_api/events.py +51 -0
- my_claude_code/core/gemini_api/input.py +558 -0
- my_claude_code/core/gemini_api/models.py +186 -0
- my_claude_code/core/gemini_api/paths.py +77 -0
- my_claude_code/core/gemini_api/response.py +74 -0
- my_claude_code/core/gemini_api/stream.py +97 -0
- my_claude_code/core/gemini_api/tools.py +203 -0
- my_claude_code/core/gemini_api/usage.py +92 -0
- my_claude_code/core/identity.py +161 -0
- my_claude_code/core/image_geometry.py +200 -0
- my_claude_code/core/interprocess_lock.py +90 -0
- my_claude_code/core/mcc_processes.py +687 -0
- my_claude_code/core/model_ids.py +224 -0
- my_claude_code/core/model_visibility.py +215 -0
- my_claude_code/core/openai_chat_completions/__init__.py +25 -0
- my_claude_code/core/openai_chat_completions/adapter.py +72 -0
- my_claude_code/core/openai_chat_completions/assembler.py +250 -0
- my_claude_code/core/openai_chat_completions/completion.py +99 -0
- my_claude_code/core/openai_chat_completions/errors.py +34 -0
- my_claude_code/core/openai_chat_completions/events.py +46 -0
- my_claude_code/core/openai_chat_completions/ids.py +12 -0
- my_claude_code/core/openai_chat_completions/input.py +403 -0
- my_claude_code/core/openai_chat_completions/models.py +69 -0
- my_claude_code/core/openai_chat_completions/stream.py +97 -0
- my_claude_code/core/openai_chat_completions/tools.py +139 -0
- my_claude_code/core/openai_chat_completions/usage.py +88 -0
- my_claude_code/core/openai_common/__init__.py +29 -0
- my_claude_code/core/openai_common/anthropic_sse.py +75 -0
- my_claude_code/core/openai_common/errors.py +74 -0
- my_claude_code/core/openai_common/usage.py +26 -0
- my_claude_code/core/openai_responses/__init__.py +17 -0
- my_claude_code/core/openai_responses/adapter.py +39 -0
- my_claude_code/core/openai_responses/anthropic_sse.py +15 -0
- my_claude_code/core/openai_responses/errors.py +28 -0
- my_claude_code/core/openai_responses/events.py +17 -0
- my_claude_code/core/openai_responses/ids.py +19 -0
- my_claude_code/core/openai_responses/input.py +388 -0
- my_claude_code/core/openai_responses/items.py +35 -0
- my_claude_code/core/openai_responses/models.py +26 -0
- my_claude_code/core/openai_responses/reasoning.py +54 -0
- my_claude_code/core/openai_responses/stream.py +92 -0
- my_claude_code/core/openai_responses/streaming/__init__.py +5 -0
- my_claude_code/core/openai_responses/streaming/assembler.py +356 -0
- my_claude_code/core/openai_responses/streaming/blocks.py +36 -0
- my_claude_code/core/openai_responses/streaming/completion.py +154 -0
- my_claude_code/core/openai_responses/streaming/error_mapping.py +28 -0
- my_claude_code/core/openai_responses/streaming/event_builders.py +182 -0
- my_claude_code/core/openai_responses/streaming/ledger.py +105 -0
- my_claude_code/core/openai_responses/tools.py +375 -0
- my_claude_code/core/openai_responses/usage.py +11 -0
- my_claude_code/core/optimization_discovery.py +314 -0
- my_claude_code/core/process_handoff.py +24 -0
- my_claude_code/core/rate_limit.py +321 -0
- my_claude_code/core/reasoning.py +490 -0
- my_claude_code/core/reported_cost.py +199 -0
- my_claude_code/core/request_headers.py +153 -0
- my_claude_code/core/request_images.py +164 -0
- my_claude_code/core/request_log.py +7447 -0
- my_claude_code/core/server_inventory.py +431 -0
- my_claude_code/core/startup_state.py +229 -0
- my_claude_code/core/stop_deadline.py +228 -0
- my_claude_code/core/tier_refs.py +394 -0
- my_claude_code/core/token_encoder.py +36 -0
- my_claude_code/core/trace.py +197 -0
- my_claude_code/core/upstream_ladder.py +668 -0
- my_claude_code/core/version.py +39 -0
- my_claude_code/core/waiting_clock.py +71 -0
- my_claude_code/core/websearch/__init__.py +8 -0
- my_claude_code/core/websearch/models.py +30 -0
- my_claude_code/core/wire_capture.py +855 -0
- my_claude_code/docs_bundle/ANTHROPIC-SUBSCRIPTION.md +378 -0
- my_claude_code/docs_bundle/ARCHITECTURE.md +2661 -0
- my_claude_code/docs_bundle/CLAUDE-CODE-CONFIG.md +982 -0
- my_claude_code/docs_bundle/CLIENTS.md +143 -0
- my_claude_code/docs_bundle/CONTRIBUTING.md +64 -0
- my_claude_code/docs_bundle/DOCS-README.md +21 -0
- my_claude_code/docs_bundle/MESSAGING.md +90 -0
- my_claude_code/docs_bundle/OAUTH-PROVIDERS.md +98 -0
- my_claude_code/docs_bundle/README.md +378 -0
- my_claude_code/docs_bundle/ROUTING-REFERENCE.md +310 -0
- my_claude_code/docs_bundle/USAGE.md +4484 -0
- my_claude_code/docs_bundle/WEB-SEARCH.md +209 -0
- my_claude_code/installers/install.ps1 +4675 -0
- my_claude_code/installers/install.sh +2644 -0
- my_claude_code/messaging/__init__.py +16 -0
- my_claude_code/messaging/cli_event_constants.py +67 -0
- my_claude_code/messaging/command_context.py +99 -0
- my_claude_code/messaging/command_dispatcher.py +31 -0
- my_claude_code/messaging/commands.py +182 -0
- my_claude_code/messaging/event_parser.py +184 -0
- my_claude_code/messaging/limiter.py +342 -0
- my_claude_code/messaging/managed_protocols.py +35 -0
- my_claude_code/messaging/models.py +57 -0
- my_claude_code/messaging/node_event_pipeline.py +120 -0
- my_claude_code/messaging/node_runner.py +408 -0
- my_claude_code/messaging/platforms/__init__.py +20 -0
- my_claude_code/messaging/platforms/discord.py +319 -0
- my_claude_code/messaging/platforms/discord_inbound.py +112 -0
- my_claude_code/messaging/platforms/discord_io.py +167 -0
- my_claude_code/messaging/platforms/factory.py +109 -0
- my_claude_code/messaging/platforms/outbox.py +143 -0
- my_claude_code/messaging/platforms/ports.py +99 -0
- my_claude_code/messaging/platforms/telegram.py +300 -0
- my_claude_code/messaging/platforms/telegram_inbound.py +133 -0
- my_claude_code/messaging/platforms/telegram_io.py +287 -0
- my_claude_code/messaging/platforms/voice_flow.py +388 -0
- my_claude_code/messaging/rendering/__init__.py +3 -0
- my_claude_code/messaging/rendering/discord_markdown.py +318 -0
- my_claude_code/messaging/rendering/markdown_tables.py +47 -0
- my_claude_code/messaging/rendering/profiles.py +53 -0
- my_claude_code/messaging/rendering/telegram_markdown.py +327 -0
- my_claude_code/messaging/safe_diagnostics.py +15 -0
- my_claude_code/messaging/session/__init__.py +5 -0
- my_claude_code/messaging/session/managed_message_log.py +135 -0
- my_claude_code/messaging/session/persistence.py +161 -0
- my_claude_code/messaging/session/store.py +162 -0
- my_claude_code/messaging/transcript/__init__.py +6 -0
- my_claude_code/messaging/transcript/buffer.py +220 -0
- my_claude_code/messaging/transcript/context.py +18 -0
- my_claude_code/messaging/transcript/renderer.py +65 -0
- my_claude_code/messaging/transcript/segments.py +176 -0
- my_claude_code/messaging/transcript/subagents.py +108 -0
- my_claude_code/messaging/transcription.py +132 -0
- my_claude_code/messaging/trees/__init__.py +41 -0
- my_claude_code/messaging/trees/graph.py +250 -0
- my_claude_code/messaging/trees/identity.py +16 -0
- my_claude_code/messaging/trees/manager.py +587 -0
- my_claude_code/messaging/trees/node.py +56 -0
- my_claude_code/messaging/trees/processor.py +272 -0
- my_claude_code/messaging/trees/queue.py +47 -0
- my_claude_code/messaging/trees/repository.py +135 -0
- my_claude_code/messaging/trees/runtime.py +487 -0
- my_claude_code/messaging/trees/snapshot.py +221 -0
- my_claude_code/messaging/trees/transitions.py +174 -0
- my_claude_code/messaging/turn_intake.py +241 -0
- my_claude_code/messaging/ui_updates.py +99 -0
- my_claude_code/messaging/voice.py +376 -0
- my_claude_code/messaging/workflow.py +714 -0
- my_claude_code/providers/__init__.py +12 -0
- my_claude_code/providers/anthropic/__init__.py +10 -0
- my_claude_code/providers/anthropic/models.py +55 -0
- my_claude_code/providers/anthropic/provider.py +160 -0
- my_claude_code/providers/anthropic_messages/__init__.py +21 -0
- my_claude_code/providers/anthropic_messages/auth.py +55 -0
- my_claude_code/providers/anthropic_messages/probe.py +19 -0
- my_claude_code/providers/anthropic_messages/provider.py +527 -0
- my_claude_code/providers/anthropic_messages/request.py +175 -0
- my_claude_code/providers/anthropic_messages/streaming.py +130 -0
- my_claude_code/providers/anthropic_oauth/__init__.py +74 -0
- my_claude_code/providers/anthropic_oauth/auth.py +266 -0
- my_claude_code/providers/anthropic_oauth/betas.py +48 -0
- my_claude_code/providers/anthropic_oauth/cli.py +242 -0
- my_claude_code/providers/anthropic_oauth/constants.py +270 -0
- my_claude_code/providers/anthropic_oauth/credentials.py +729 -0
- my_claude_code/providers/anthropic_oauth/entrypoint.py +109 -0
- my_claude_code/providers/anthropic_oauth/loopback.py +311 -0
- my_claude_code/providers/anthropic_oauth/oauth_login.py +222 -0
- my_claude_code/providers/anthropic_oauth/provider.py +341 -0
- my_claude_code/providers/anthropic_oauth/rate_limit_headers.py +137 -0
- my_claude_code/providers/base.py +206 -0
- my_claude_code/providers/chatgpt_oauth/__init__.py +6 -0
- my_claude_code/providers/chatgpt_oauth/browser_login.py +546 -0
- my_claude_code/providers/chatgpt_oauth/codex_catalogue.py +551 -0
- my_claude_code/providers/chatgpt_oauth/conversion.py +67 -0
- my_claude_code/providers/chatgpt_oauth/credentials.py +600 -0
- my_claude_code/providers/chatgpt_oauth/oauth_login.py +316 -0
- my_claude_code/providers/chatgpt_oauth/provider.py +827 -0
- my_claude_code/providers/chatgpt_oauth/response_headers.py +128 -0
- my_claude_code/providers/chatgpt_oauth/streaming.py +25 -0
- my_claude_code/providers/cloudflare/__init__.py +8 -0
- my_claude_code/providers/cloudflare/client.py +150 -0
- my_claude_code/providers/commandcode/__init__.py +10 -0
- my_claude_code/providers/commandcode/client.py +175 -0
- my_claude_code/providers/commandcode/models.py +67 -0
- my_claude_code/providers/credential_rotation.py +546 -0
- my_claude_code/providers/deepseek/__init__.py +5 -0
- my_claude_code/providers/deepseek/client.py +129 -0
- my_claude_code/providers/deepseek/compat.py +376 -0
- my_claude_code/providers/deepseek/tool_choice.py +72 -0
- my_claude_code/providers/failure_policy.py +842 -0
- my_claude_code/providers/gemini/__init__.py +5 -0
- my_claude_code/providers/gemini/client.py +91 -0
- my_claude_code/providers/gemini/quirks.py +161 -0
- my_claude_code/providers/github_models/__init__.py +5 -0
- my_claude_code/providers/github_models/client.py +142 -0
- my_claude_code/providers/google_openai/__init__.py +15 -0
- my_claude_code/providers/google_openai/provider.py +80 -0
- my_claude_code/providers/google_openai/reasoning.py +123 -0
- my_claude_code/providers/google_openai/thought_signatures.py +117 -0
- my_claude_code/providers/http.py +51 -0
- my_claude_code/providers/kilo/__init__.py +5 -0
- my_claude_code/providers/kilo/client.py +27 -0
- my_claude_code/providers/lmstudio/__init__.py +5 -0
- my_claude_code/providers/lmstudio/client.py +132 -0
- my_claude_code/providers/mistral/__init__.py +5 -0
- my_claude_code/providers/mistral/client.py +110 -0
- my_claude_code/providers/mistral/reasoning.py +404 -0
- my_claude_code/providers/model_listing.py +589 -0
- my_claude_code/providers/nous_portal/__init__.py +5 -0
- my_claude_code/providers/nous_portal/client.py +65 -0
- my_claude_code/providers/nvidia_nim/__init__.py +5 -0
- my_claude_code/providers/nvidia_nim/client.py +240 -0
- my_claude_code/providers/nvidia_nim/request_options.py +151 -0
- my_claude_code/providers/nvidia_nim/retry.py +112 -0
- my_claude_code/providers/nvidia_nim/tool_schema.py +255 -0
- my_claude_code/providers/nvidia_nim/voice.py +113 -0
- my_claude_code/providers/open_router/__init__.py +5 -0
- my_claude_code/providers/open_router/client.py +22 -0
- my_claude_code/providers/openai_chat/__init__.py +113 -0
- my_claude_code/providers/openai_chat/base_url.py +7 -0
- my_claude_code/providers/openai_chat/chunks.py +357 -0
- my_claude_code/providers/openai_chat/client_identity.py +262 -0
- my_claude_code/providers/openai_chat/complaint.py +26 -0
- my_claude_code/providers/openai_chat/extra_body.py +60 -0
- my_claude_code/providers/openai_chat/identity_enforcement.py +169 -0
- my_claude_code/providers/openai_chat/learned_dialect.py +164 -0
- my_claude_code/providers/openai_chat/messages_transport.py +201 -0
- my_claude_code/providers/openai_chat/opencode_identity.py +342 -0
- my_claude_code/providers/openai_chat/output_cap.py +17 -0
- my_claude_code/providers/openai_chat/profiles.py +954 -0
- my_claude_code/providers/openai_chat/provider.py +1644 -0
- my_claude_code/providers/openai_chat/reasoning.py +337 -0
- my_claude_code/providers/openai_chat/reasoning_details.py +174 -0
- my_claude_code/providers/openai_chat/reasoning_reject.py +16 -0
- my_claude_code/providers/openai_chat/request_policy.py +231 -0
- my_claude_code/providers/openai_chat/response_surface.py +292 -0
- my_claude_code/providers/openai_chat/responses_transport.py +329 -0
- my_claude_code/providers/openai_chat/tool_calls.py +295 -0
- my_claude_code/providers/openai_chat/usage.py +114 -0
- my_claude_code/providers/openai_responses/__init__.py +32 -0
- my_claude_code/providers/openai_responses/conversion.py +382 -0
- my_claude_code/providers/openai_responses/streaming.py +426 -0
- my_claude_code/providers/openrouter_gateway.py +218 -0
- my_claude_code/providers/rate_limit.py +400 -0
- my_claude_code/providers/recovery/__init__.py +141 -0
- my_claude_code/providers/recovery/complaint.py +190 -0
- my_claude_code/providers/recovery/facts.py +386 -0
- my_claude_code/providers/recovery/ladder.py +264 -0
- my_claude_code/providers/recovery/memory.py +161 -0
- my_claude_code/providers/recovery/output_cap.py +118 -0
- my_claude_code/providers/recovery/reasoning_reject.py +146 -0
- my_claude_code/providers/recovery/store.py +569 -0
- my_claude_code/providers/recovery/surface.py +107 -0
- my_claude_code/providers/runtime/__init__.py +11 -0
- my_claude_code/providers/runtime/capability_probes.py +359 -0
- my_claude_code/providers/runtime/config.py +178 -0
- my_claude_code/providers/runtime/discovery.py +313 -0
- my_claude_code/providers/runtime/factory.py +355 -0
- my_claude_code/providers/runtime/identity_probe.py +229 -0
- my_claude_code/providers/runtime/litellm_prices.py +411 -0
- my_claude_code/providers/runtime/model_cache.py +237 -0
- my_claude_code/providers/runtime/models_dev.py +2206 -0
- my_claude_code/providers/runtime/reasoning_probe.py +155 -0
- my_claude_code/providers/runtime/rotating.py +420 -0
- my_claude_code/providers/runtime/runtime.py +48 -0
- my_claude_code/providers/runtime/served_models.py +137 -0
- my_claude_code/providers/runtime/validation.py +132 -0
- my_claude_code/providers/stream_recovery.py +364 -0
- my_claude_code/providers/vertex/__init__.py +5 -0
- my_claude_code/providers/vertex/auth.py +105 -0
- my_claude_code/providers/vertex/client.py +134 -0
- my_claude_code/providers/vertex/endpoint.py +50 -0
- my_claude_code/providers/vertex/models.py +52 -0
- my_claude_code/runtime/__init__.py +1 -0
- my_claude_code/runtime/application.py +1050 -0
- my_claude_code/runtime/asgi.py +525 -0
- my_claude_code/runtime/bootstrap.py +72 -0
- my_claude_code/runtime/catalogue_store.py +203 -0
- my_claude_code/runtime/discovery_timer.py +239 -0
- my_claude_code/runtime/harness_catalogues.py +345 -0
- my_claude_code/runtime/provider_manager.py +982 -0
- my_claude_code/runtime/warmup.py +149 -0
- my_claude_code/websearch/__init__.py +52 -0
- my_claude_code/websearch/adapters/__init__.py +37 -0
- my_claude_code/websearch/adapters/brave.py +160 -0
- my_claude_code/websearch/adapters/ddgs.py +88 -0
- my_claude_code/websearch/adapters/exa.py +133 -0
- my_claude_code/websearch/adapters/firecrawl.py +138 -0
- my_claude_code/websearch/adapters/http.py +154 -0
- my_claude_code/websearch/adapters/jina.py +91 -0
- my_claude_code/websearch/adapters/linkup.py +109 -0
- my_claude_code/websearch/adapters/ollama.py +79 -0
- my_claude_code/websearch/adapters/parallel.py +111 -0
- my_claude_code/websearch/adapters/perplexity.py +99 -0
- my_claude_code/websearch/adapters/searchapi.py +119 -0
- my_claude_code/websearch/adapters/searxng.py +97 -0
- my_claude_code/websearch/adapters/serpapi.py +119 -0
- my_claude_code/websearch/adapters/serper.py +124 -0
- my_claude_code/websearch/adapters/tavily.py +113 -0
- my_claude_code/websearch/analytics.py +1407 -0
- my_claude_code/websearch/base.py +157 -0
- my_claude_code/websearch/errors.py +79 -0
- my_claude_code/websearch/options.py +69 -0
- my_claude_code/websearch/registry.py +534 -0
- my_claude_code/websearch/rotation.py +253 -0
- my_claude_code-7.13.1.dist-info/METADATA +423 -0
- my_claude_code-7.13.1.dist-info/RECORD +567 -0
- my_claude_code-7.13.1.dist-info/WHEEL +4 -0
- my_claude_code-7.13.1.dist-info/entry_points.txt +44 -0
- my_claude_code-7.13.1.dist-info/licenses/COMMERCIAL-LICENSE.md +55 -0
- my_claude_code-7.13.1.dist-info/licenses/LICENSE +748 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""DeepSeek Anthropic-to-OpenAI chat request policy."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from my_claude_code.application.errors import InvalidRequestError
|
|
9
|
+
from my_claude_code.config.settings import configured_default_max_output_tokens
|
|
10
|
+
from my_claude_code.core.anthropic import (
|
|
11
|
+
ReasoningReplayMode,
|
|
12
|
+
dump_messages_request,
|
|
13
|
+
serialize_tool_result_content,
|
|
14
|
+
)
|
|
15
|
+
from my_claude_code.core.anthropic.models import MessagesRequest
|
|
16
|
+
from my_claude_code.core.reasoning import (
|
|
17
|
+
ReasoningControl,
|
|
18
|
+
ReasoningPolicy,
|
|
19
|
+
)
|
|
20
|
+
from my_claude_code.providers.openai_chat import (
|
|
21
|
+
OpenAIChatRequestPolicy,
|
|
22
|
+
build_openai_chat_request_body,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
DEEPSEEK_REQUEST_POLICY = OpenAIChatRequestPolicy(
|
|
26
|
+
provider_name="DEEPSEEK",
|
|
27
|
+
reasoning_replay=ReasoningReplayMode.REASONING_CONTENT,
|
|
28
|
+
include_extra_body=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Server-tool blocks DeepSeek's native surface cannot represent at all. Images
|
|
32
|
+
# and documents are deliberately *not* here any more: since 6.49.0 the router
|
|
33
|
+
# replaces a visual block with a placeholder for any model published as not
|
|
34
|
+
# accepting images -- which DeepSeek's chat models are -- so by the time a body
|
|
35
|
+
# is built there is nothing to strip, and the general rule covers every other
|
|
36
|
+
# provider too. A DeepSeek model that does publish image support now gets the
|
|
37
|
+
# picture through the ordinary hoist rather than a provider-specific refusal.
|
|
38
|
+
_UNSUPPORTED_MESSAGE_BLOCK_TYPES = frozenset(
|
|
39
|
+
{
|
|
40
|
+
"server_tool_use",
|
|
41
|
+
"web_search_tool_result",
|
|
42
|
+
"web_fetch_tool_result",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_deepseek_request_body(
|
|
48
|
+
request_data: MessagesRequest,
|
|
49
|
+
*,
|
|
50
|
+
reasoning: ReasoningPolicy,
|
|
51
|
+
provider_id: str = "",
|
|
52
|
+
) -> dict:
|
|
53
|
+
"""Build a DeepSeek Chat Completions body from an Anthropic request."""
|
|
54
|
+
logger.debug(
|
|
55
|
+
"DEEPSEEK_REQUEST: chat build model={} msgs={}",
|
|
56
|
+
request_data.model,
|
|
57
|
+
len(request_data.messages),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
data = dump_messages_request(request_data)
|
|
61
|
+
_validate_deepseek_request_dict(data)
|
|
62
|
+
|
|
63
|
+
has_tool_history = _has_tool_history(data)
|
|
64
|
+
has_replayable_tool_thinking = _all_tool_calls_have_replayable_thinking(data)
|
|
65
|
+
unsafe_tool_followup = has_tool_history and not has_replayable_tool_thinking
|
|
66
|
+
effective_reasoning = reasoning
|
|
67
|
+
if reasoning.control is not ReasoningControl.OFF:
|
|
68
|
+
if unsafe_tool_followup:
|
|
69
|
+
logger.debug(
|
|
70
|
+
"DEEPSEEK_REQUEST: disabling thinking for tool follow-up without "
|
|
71
|
+
"replayable thinking model={} msgs={} tools={}",
|
|
72
|
+
data.get("model"),
|
|
73
|
+
len(data.get("messages", [])),
|
|
74
|
+
len(data.get("tools", [])),
|
|
75
|
+
)
|
|
76
|
+
_remove_deepseek_thinking_hints(data)
|
|
77
|
+
effective_reasoning = ReasoningPolicy.off()
|
|
78
|
+
elif has_tool_history:
|
|
79
|
+
logger.debug(
|
|
80
|
+
"DEEPSEEK_REQUEST: keeping thinking for tool follow-up with "
|
|
81
|
+
"replayable thinking model={} msgs={} tools={}",
|
|
82
|
+
data.get("model"),
|
|
83
|
+
len(data.get("messages", [])),
|
|
84
|
+
len(data.get("tools", [])),
|
|
85
|
+
)
|
|
86
|
+
elif data.get("tools") or data.get("tool_choice"):
|
|
87
|
+
logger.debug(
|
|
88
|
+
"DEEPSEEK_REQUEST: keeping thinking for initial tool request "
|
|
89
|
+
"model={} msgs={} tools={}",
|
|
90
|
+
data.get("model"),
|
|
91
|
+
len(data.get("messages", [])),
|
|
92
|
+
len(data.get("tools", [])),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if "messages" in data:
|
|
96
|
+
data["messages"] = _normalize_tool_result_content(
|
|
97
|
+
sanitize_deepseek_messages_for_openai(data["messages"])
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
sanitized_request = MessagesRequest.model_validate(data)
|
|
101
|
+
body = build_openai_chat_request_body(
|
|
102
|
+
sanitized_request,
|
|
103
|
+
reasoning=effective_reasoning,
|
|
104
|
+
policy=DEEPSEEK_REQUEST_POLICY,
|
|
105
|
+
postprocessors=(_apply_deepseek_chat_extras,),
|
|
106
|
+
provider_id=provider_id,
|
|
107
|
+
)
|
|
108
|
+
if body.get("max_tokens") is None:
|
|
109
|
+
configured = configured_default_max_output_tokens()
|
|
110
|
+
if configured is None:
|
|
111
|
+
body.pop("max_tokens", None)
|
|
112
|
+
else:
|
|
113
|
+
body["max_tokens"] = configured
|
|
114
|
+
|
|
115
|
+
logger.debug(
|
|
116
|
+
"DEEPSEEK_REQUEST: build done model={} msgs={} tools={}",
|
|
117
|
+
body.get("model"),
|
|
118
|
+
len(body.get("messages", [])),
|
|
119
|
+
len(body.get("tools", [])),
|
|
120
|
+
)
|
|
121
|
+
return body
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def sanitize_deepseek_messages_for_openai(messages: Any) -> Any:
|
|
125
|
+
"""Keep only DeepSeek-required reasoning history on assistant tool calls."""
|
|
126
|
+
if not isinstance(messages, list):
|
|
127
|
+
return messages
|
|
128
|
+
|
|
129
|
+
sanitized: list[Any] = []
|
|
130
|
+
for message in messages:
|
|
131
|
+
if not isinstance(message, dict):
|
|
132
|
+
sanitized.append(message)
|
|
133
|
+
continue
|
|
134
|
+
if message.get("role") != "assistant":
|
|
135
|
+
sanitized.append(message)
|
|
136
|
+
continue
|
|
137
|
+
replay_tool_reasoning = _assistant_has_tool_use(message)
|
|
138
|
+
new_msg = dict(message)
|
|
139
|
+
if not replay_tool_reasoning:
|
|
140
|
+
new_msg.pop("reasoning_content", None)
|
|
141
|
+
content = message.get("content")
|
|
142
|
+
if not isinstance(content, list):
|
|
143
|
+
sanitized.append(new_msg)
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
filtered = [
|
|
147
|
+
block
|
|
148
|
+
for block in content
|
|
149
|
+
if not (
|
|
150
|
+
isinstance(block, dict)
|
|
151
|
+
and (
|
|
152
|
+
block.get("type") == "redacted_thinking"
|
|
153
|
+
or (block.get("type") == "thinking" and not replay_tool_reasoning)
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
]
|
|
157
|
+
new_msg["content"] = filtered or ""
|
|
158
|
+
sanitized.append(new_msg)
|
|
159
|
+
return sanitized
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _is_server_listed_tool(tool: Mapping[str, Any]) -> bool:
|
|
163
|
+
name = (tool.get("name") or "").strip()
|
|
164
|
+
if name in ("web_search", "web_fetch"):
|
|
165
|
+
return True
|
|
166
|
+
typ = tool.get("type")
|
|
167
|
+
if isinstance(typ, str):
|
|
168
|
+
return typ.startswith("web_search") or typ.startswith("web_fetch")
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _walk_block_list_for_unsupported(blocks: Any, *, where: str) -> None:
|
|
173
|
+
if not isinstance(blocks, list):
|
|
174
|
+
return
|
|
175
|
+
for block in blocks:
|
|
176
|
+
if not isinstance(block, dict):
|
|
177
|
+
continue
|
|
178
|
+
btype = block.get("type")
|
|
179
|
+
if btype in _UNSUPPORTED_MESSAGE_BLOCK_TYPES:
|
|
180
|
+
raise InvalidRequestError(
|
|
181
|
+
f"DeepSeek native does not support {btype!r} blocks ({where})."
|
|
182
|
+
)
|
|
183
|
+
if btype == "tool_result" and "content" in block:
|
|
184
|
+
_walk_block_list_for_unsupported(
|
|
185
|
+
block["content"], where=f"{where} (tool_result content)"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _validate_deepseek_request_dict(data: dict[str, Any]) -> None:
|
|
190
|
+
mcp = data.get("mcp_servers")
|
|
191
|
+
if mcp:
|
|
192
|
+
raise InvalidRequestError("DeepSeek does not support mcp_servers on requests.")
|
|
193
|
+
|
|
194
|
+
for tool in data.get("tools") or ():
|
|
195
|
+
if not isinstance(tool, dict):
|
|
196
|
+
continue
|
|
197
|
+
if _is_server_listed_tool(tool):
|
|
198
|
+
raise InvalidRequestError(
|
|
199
|
+
"DeepSeek does not support listed Anthropic server tools "
|
|
200
|
+
"(web_search / web_fetch). Remove them or use a different provider."
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
for i, message in enumerate(data.get("messages") or ()):
|
|
204
|
+
if not isinstance(message, dict):
|
|
205
|
+
continue
|
|
206
|
+
content = message.get("content")
|
|
207
|
+
if isinstance(content, list):
|
|
208
|
+
_walk_block_list_for_unsupported(content, where=f"messages[{i}].content")
|
|
209
|
+
|
|
210
|
+
system = data.get("system")
|
|
211
|
+
if isinstance(system, list):
|
|
212
|
+
_walk_block_list_for_unsupported(system, where="system")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _has_tool_history_blocks(message: Mapping[str, Any]) -> bool:
|
|
216
|
+
role = message.get("role")
|
|
217
|
+
content = message.get("content")
|
|
218
|
+
if not isinstance(content, list):
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
for block in content:
|
|
222
|
+
if not isinstance(block, dict):
|
|
223
|
+
continue
|
|
224
|
+
btype = block.get("type")
|
|
225
|
+
if role == "assistant" and btype == "tool_use":
|
|
226
|
+
return True
|
|
227
|
+
if role == "user" and btype == "tool_result":
|
|
228
|
+
return True
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _has_replayable_thinking_before_tool_use(message: Mapping[str, Any]) -> bool:
|
|
233
|
+
if message.get("role") != "assistant":
|
|
234
|
+
return False
|
|
235
|
+
content = message.get("content")
|
|
236
|
+
if not isinstance(content, list):
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
has_thinking = isinstance(message.get("reasoning_content"), str)
|
|
240
|
+
for block in content:
|
|
241
|
+
if not isinstance(block, dict):
|
|
242
|
+
continue
|
|
243
|
+
btype = block.get("type")
|
|
244
|
+
if btype == "thinking" and isinstance(block.get("thinking"), str):
|
|
245
|
+
has_thinking = True
|
|
246
|
+
continue
|
|
247
|
+
if btype == "tool_use":
|
|
248
|
+
return has_thinking
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _assistant_has_tool_use(message: Mapping[str, Any]) -> bool:
|
|
253
|
+
if message.get("role") != "assistant":
|
|
254
|
+
return False
|
|
255
|
+
content = message.get("content")
|
|
256
|
+
return isinstance(content, list) and any(
|
|
257
|
+
isinstance(block, dict) and block.get("type") == "tool_use" for block in content
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _has_tool_history(data: dict[str, Any]) -> bool:
|
|
262
|
+
for message in data.get("messages") or ():
|
|
263
|
+
if isinstance(message, Mapping) and _has_tool_history_blocks(message):
|
|
264
|
+
return True
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _all_tool_calls_have_replayable_thinking(data: dict[str, Any]) -> bool:
|
|
269
|
+
found_tool_call = False
|
|
270
|
+
for message in data.get("messages") or ():
|
|
271
|
+
if not isinstance(message, Mapping) or not _assistant_has_tool_use(message):
|
|
272
|
+
continue
|
|
273
|
+
found_tool_call = True
|
|
274
|
+
if not _has_replayable_thinking_before_tool_use(message):
|
|
275
|
+
return False
|
|
276
|
+
return found_tool_call
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _remove_deepseek_thinking_hints(data: dict[str, Any]) -> None:
|
|
280
|
+
output_config = data.get("output_config")
|
|
281
|
+
if isinstance(output_config, dict) and "effort" in output_config:
|
|
282
|
+
cleaned_output_config = dict(output_config)
|
|
283
|
+
cleaned_output_config.pop("effort", None)
|
|
284
|
+
if cleaned_output_config:
|
|
285
|
+
data["output_config"] = cleaned_output_config
|
|
286
|
+
else:
|
|
287
|
+
data.pop("output_config", None)
|
|
288
|
+
|
|
289
|
+
context_management = data.get("context_management")
|
|
290
|
+
if not isinstance(context_management, dict):
|
|
291
|
+
return
|
|
292
|
+
edits = context_management.get("edits")
|
|
293
|
+
if not isinstance(edits, list):
|
|
294
|
+
return
|
|
295
|
+
filtered_edits = [
|
|
296
|
+
edit
|
|
297
|
+
for edit in edits
|
|
298
|
+
if not (
|
|
299
|
+
isinstance(edit, dict)
|
|
300
|
+
and isinstance(edit.get("type"), str)
|
|
301
|
+
and edit["type"].startswith("clear_thinking_")
|
|
302
|
+
)
|
|
303
|
+
]
|
|
304
|
+
if len(filtered_edits) == len(edits):
|
|
305
|
+
return
|
|
306
|
+
cleaned_context_management = dict(context_management)
|
|
307
|
+
if filtered_edits:
|
|
308
|
+
cleaned_context_management["edits"] = filtered_edits
|
|
309
|
+
data["context_management"] = cleaned_context_management
|
|
310
|
+
else:
|
|
311
|
+
cleaned_context_management.pop("edits", None)
|
|
312
|
+
if cleaned_context_management:
|
|
313
|
+
data["context_management"] = cleaned_context_management
|
|
314
|
+
else:
|
|
315
|
+
data.pop("context_management", None)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _normalize_tool_result_content(messages: Any) -> Any:
|
|
319
|
+
if not isinstance(messages, list):
|
|
320
|
+
return messages
|
|
321
|
+
|
|
322
|
+
normalized: list[Any] = []
|
|
323
|
+
for message in messages:
|
|
324
|
+
if not isinstance(message, dict):
|
|
325
|
+
normalized.append(message)
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
content = message.get("content")
|
|
329
|
+
if not isinstance(content, list):
|
|
330
|
+
normalized.append(message)
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
new_content: list[Any] = []
|
|
334
|
+
for block in content:
|
|
335
|
+
if not isinstance(block, dict):
|
|
336
|
+
new_content.append(block)
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
if block.get("type") == "tool_result":
|
|
340
|
+
normalized_block = dict(block)
|
|
341
|
+
normalized_block["content"] = serialize_tool_result_content(
|
|
342
|
+
block.get("content")
|
|
343
|
+
)
|
|
344
|
+
new_content.append(normalized_block)
|
|
345
|
+
else:
|
|
346
|
+
new_content.append(block)
|
|
347
|
+
|
|
348
|
+
new_msg = dict(message)
|
|
349
|
+
new_msg["content"] = new_content
|
|
350
|
+
normalized.append(new_msg)
|
|
351
|
+
|
|
352
|
+
return normalized
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _apply_deepseek_chat_extras(
|
|
356
|
+
body: dict[str, Any], _request_data: MessagesRequest, policy: ReasoningPolicy
|
|
357
|
+
) -> None:
|
|
358
|
+
extra_body = body.setdefault("extra_body", {})
|
|
359
|
+
if not isinstance(extra_body, dict):
|
|
360
|
+
return
|
|
361
|
+
if policy.control is ReasoningControl.OFF:
|
|
362
|
+
extra_body["thinking"] = {"type": "disabled"}
|
|
363
|
+
return
|
|
364
|
+
# 2026-08-29: send the effort the caller actually asked for. This used to
|
|
365
|
+
# collapse the whole ladder onto two rungs -- "max" for XHIGH/MAX and "high"
|
|
366
|
+
# for everything else -- so a request for MINIMAL bought HIGH's latency and
|
|
367
|
+
# tokens, and MEDIUM and HIGH were indistinguishable. DeepSeek names its own
|
|
368
|
+
# enum when it rejects an invalid one: "reasoning_effort: unknown variant
|
|
369
|
+
# `bogus_value`, expected one of `none`, `minimal`, `low`, `medium`, `high`,
|
|
370
|
+
# `xhigh`, `max`". That is FCC's :class:`ReasoningEffort` ladder exactly,
|
|
371
|
+
# value for value, so the translation is the identity and no per-model
|
|
372
|
+
# branching is needed to pick it.
|
|
373
|
+
if policy.effort is not None:
|
|
374
|
+
body["reasoning_effort"] = policy.effort.value
|
|
375
|
+
elif policy.requests_reasoning:
|
|
376
|
+
extra_body["thinking"] = {"type": "enabled"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""DeepSeek forced tool_choice rejection recovery.
|
|
2
|
+
|
|
3
|
+
DeepSeek's documented Chat Completions API accepts a forced named
|
|
4
|
+
``tool_choice`` (``{"type": "function", "function": {"name": ...}}``), but
|
|
5
|
+
some DeepSeek reasoning models reject it at request time with an HTTP 400
|
|
6
|
+
even though the shape is spec-compliant. This module detects that specific
|
|
7
|
+
rejection reactively (from the upstream error, never from the model name --
|
|
8
|
+
DeepSeek's model ids have already shifted once and are not a stable signal)
|
|
9
|
+
and produces a retry body that keeps the "must call some tool" half of the
|
|
10
|
+
caller's intent by downgrading to ``"required"``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from copy import deepcopy
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from my_claude_code.providers.recovery import is_bad_request, upstream_complaint
|
|
17
|
+
|
|
18
|
+
_REJECTION_HINTS = (
|
|
19
|
+
"tool_choice",
|
|
20
|
+
"tool choice",
|
|
21
|
+
)
|
|
22
|
+
_UNSUPPORTED_WORDS = (
|
|
23
|
+
"not support",
|
|
24
|
+
"unsupported",
|
|
25
|
+
"does not support",
|
|
26
|
+
"invalid",
|
|
27
|
+
"not allowed",
|
|
28
|
+
"not permitted",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_deepseek_tool_choice_rejection(error: Exception) -> bool:
|
|
33
|
+
"""Return whether an upstream error rejects a forced ``tool_choice``.
|
|
34
|
+
|
|
35
|
+
Matches narrowly: the error must be a request-validation rejection AND the
|
|
36
|
+
provider's own words must mention ``tool_choice`` (or "tool choice")
|
|
37
|
+
together with an unsupported/rejection word (e.g. "does not support",
|
|
38
|
+
"invalid", "not allowed"). Requiring both keeps this from firing on
|
|
39
|
+
unrelated 400s such as context-length-exceeded or bad-schema errors, which
|
|
40
|
+
never mention tool_choice at all.
|
|
41
|
+
|
|
42
|
+
Echo-safe since 6.33.0: the words come from the fleet-wide matcher, which
|
|
43
|
+
prunes the keys under which a validation error echoes the submitted request
|
|
44
|
+
back. Reading that echo made every forced-tool request its own evidence --
|
|
45
|
+
the body it sent contains ``tool_choice`` by definition, and one unrelated
|
|
46
|
+
"invalid" anywhere in the same 400 was enough to complete the pair.
|
|
47
|
+
"""
|
|
48
|
+
if not is_bad_request(error):
|
|
49
|
+
return False
|
|
50
|
+
complaint = upstream_complaint(error)
|
|
51
|
+
mentions_tool_choice = any(hint in complaint for hint in _REJECTION_HINTS)
|
|
52
|
+
mentions_rejection = any(word in complaint for word in _UNSUPPORTED_WORDS)
|
|
53
|
+
return mentions_tool_choice and mentions_rejection
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def clone_body_with_required_tool_choice(body: dict[str, Any]) -> dict[str, Any] | None:
|
|
57
|
+
"""Return a body clone with a forced named ``tool_choice`` downgraded.
|
|
58
|
+
|
|
59
|
+
Downgrades OpenAI-shaped ``{"type": "function", "function": {"name": ...}}``
|
|
60
|
+
to the string ``"required"`` -- the caller demanded a tool call, and
|
|
61
|
+
``"required"`` preserves that half even when the *which tool* half
|
|
62
|
+
cannot be honoured. Returns ``None`` if there is nothing to downgrade
|
|
63
|
+
(i.e. ``tool_choice`` was not a forced named choice), so the caller can
|
|
64
|
+
tell "no retry available" apart from "retry with an unchanged body".
|
|
65
|
+
"""
|
|
66
|
+
tool_choice = body.get("tool_choice")
|
|
67
|
+
if not isinstance(tool_choice, dict) or tool_choice.get("type") != "function":
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
cloned = deepcopy(body)
|
|
71
|
+
cloned["tool_choice"] = "required"
|
|
72
|
+
return cloned
|