ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0__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.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +434 -219
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +144 -168
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +388 -524
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +540 -19
- ccproxy/data/codex_headers_fallback.json +114 -7
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +61 -105
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +268 -276
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +68 -446
- ccproxy/utils/version_checker.py +273 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1251
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -243
- ccproxy/services/codex_detection_service.py +0 -252
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.7.dist-info/METADATA +0 -615
- ccproxy_api-0.1.7.dist-info/RECORD +0 -191
- ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"""Binary resolution with package manager fallback support."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
7
|
+
|
|
8
|
+
from typing_extensions import TypedDict
|
|
9
|
+
|
|
10
|
+
from ccproxy.core.logging import TraceBoundLogger, get_logger
|
|
11
|
+
from ccproxy.utils.caching import ttl_cache
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ccproxy.config.settings import Settings
|
|
16
|
+
|
|
17
|
+
logger: TraceBoundLogger = get_logger()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BinaryCommand(NamedTuple):
|
|
21
|
+
"""Represents a resolved binary command."""
|
|
22
|
+
|
|
23
|
+
command: list[str]
|
|
24
|
+
is_direct: bool
|
|
25
|
+
is_in_path: bool
|
|
26
|
+
package_manager: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PackageManagerConfig(TypedDict, total=False):
|
|
30
|
+
"""Configuration for a package manager."""
|
|
31
|
+
|
|
32
|
+
check_cmd: list[str]
|
|
33
|
+
priority: int
|
|
34
|
+
exec_cmd: str # Optional field
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CLIInfo(TypedDict):
|
|
38
|
+
"""Common structure for CLI information."""
|
|
39
|
+
|
|
40
|
+
name: str # CLI name (e.g., "claude", "codex")
|
|
41
|
+
version: str | None # Version string
|
|
42
|
+
source: str # "path" | "package_manager" | "unknown"
|
|
43
|
+
path: str | None # Direct path if available
|
|
44
|
+
command: list[str] # Full command to execute
|
|
45
|
+
package_manager: str | None # Package manager used (if applicable)
|
|
46
|
+
is_available: bool # Whether the CLI is accessible
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BinaryResolver:
|
|
50
|
+
"""Resolves binaries with fallback to package managers."""
|
|
51
|
+
|
|
52
|
+
PACKAGE_MANAGERS: dict[str, PackageManagerConfig] = {
|
|
53
|
+
"bunx": {"check_cmd": ["bun", "--version"], "priority": 1},
|
|
54
|
+
"pnpm": {"check_cmd": ["pnpm", "--version"], "exec_cmd": "dlx", "priority": 2},
|
|
55
|
+
"npx": {"check_cmd": ["npx", "--version"], "priority": 3},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
KNOWN_PACKAGES = {
|
|
59
|
+
"claude": "@anthropic-ai/claude-code",
|
|
60
|
+
"codex": "@openai/codex",
|
|
61
|
+
"gemini": "@google/gemini-cli",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
fallback_enabled: bool = True,
|
|
67
|
+
package_manager_only: bool = False,
|
|
68
|
+
preferred_package_manager: str | None = None,
|
|
69
|
+
package_manager_priority: list[str] | None = None,
|
|
70
|
+
):
|
|
71
|
+
"""Initialize the binary resolver.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
fallback_enabled: Whether to use package manager fallback
|
|
75
|
+
package_manager_only: Skip direct binary lookup and use package managers exclusively
|
|
76
|
+
preferred_package_manager: Preferred package manager (bunx, pnpm, npx)
|
|
77
|
+
package_manager_priority: Custom priority order for package managers
|
|
78
|
+
"""
|
|
79
|
+
self.fallback_enabled = fallback_enabled
|
|
80
|
+
self.package_manager_only = package_manager_only
|
|
81
|
+
self.preferred_package_manager = preferred_package_manager
|
|
82
|
+
self.package_manager_priority = package_manager_priority or [
|
|
83
|
+
"bunx",
|
|
84
|
+
"pnpm",
|
|
85
|
+
"npx",
|
|
86
|
+
]
|
|
87
|
+
self._available_managers: dict[str, bool] | None = None
|
|
88
|
+
|
|
89
|
+
@ttl_cache(maxsize=32, ttl=300.0)
|
|
90
|
+
def find_binary(
|
|
91
|
+
self,
|
|
92
|
+
binary_name: str,
|
|
93
|
+
package_name: str | None = None,
|
|
94
|
+
package_manager_only: bool | None = None,
|
|
95
|
+
fallback_enabled: bool | None = None,
|
|
96
|
+
) -> BinaryCommand | None:
|
|
97
|
+
"""Find a binary with optional package manager fallback.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
binary_name: Name of the binary to find. Can be:
|
|
101
|
+
- Simple binary name (e.g., "claude")
|
|
102
|
+
- Full package name (e.g., "@anthropic-ai/claude-code")
|
|
103
|
+
package_name: NPM package name if different from binary name
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
BinaryCommand with resolved command or None if not found
|
|
107
|
+
"""
|
|
108
|
+
if package_manager_only is None:
|
|
109
|
+
package_manager_only = self.package_manager_only
|
|
110
|
+
if fallback_enabled is None:
|
|
111
|
+
fallback_enabled = self.fallback_enabled
|
|
112
|
+
|
|
113
|
+
# Determine if binary_name is a full package name (contains @ or /)
|
|
114
|
+
is_full_package = "@" in binary_name or "/" in binary_name
|
|
115
|
+
|
|
116
|
+
if is_full_package and package_name is None:
|
|
117
|
+
# If binary_name is a full package name, use it as the package
|
|
118
|
+
# and extract the binary name from it
|
|
119
|
+
package_name = binary_name
|
|
120
|
+
# Extract binary name from package (last part after /)
|
|
121
|
+
binary_name = binary_name.split("/")[-1]
|
|
122
|
+
|
|
123
|
+
# If package_manager_only mode, skip direct binary lookup
|
|
124
|
+
if package_manager_only:
|
|
125
|
+
package_name = package_name or self.KNOWN_PACKAGES.get(
|
|
126
|
+
binary_name, binary_name
|
|
127
|
+
)
|
|
128
|
+
result = self._find_via_package_manager(binary_name, package_name)
|
|
129
|
+
if result:
|
|
130
|
+
logger.trace(
|
|
131
|
+
"binary_resolved",
|
|
132
|
+
binary=binary_name,
|
|
133
|
+
manager=result.package_manager,
|
|
134
|
+
command=result.command,
|
|
135
|
+
source="package_manager",
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
logger.trace(
|
|
139
|
+
"binary_resolution_failed",
|
|
140
|
+
binary=binary_name,
|
|
141
|
+
source="package_manager",
|
|
142
|
+
)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
# First, try direct binary lookup in PATH
|
|
146
|
+
direct_path = shutil.which(binary_name)
|
|
147
|
+
if direct_path:
|
|
148
|
+
return BinaryCommand(command=[direct_path], is_direct=True, is_in_path=True)
|
|
149
|
+
|
|
150
|
+
# Check common installation locations
|
|
151
|
+
common_paths = self._get_common_paths(binary_name)
|
|
152
|
+
for path in common_paths:
|
|
153
|
+
if path.exists() and path.is_file():
|
|
154
|
+
logger.debug(
|
|
155
|
+
"binary_found_in_common_path", binary=binary_name, path=str(path)
|
|
156
|
+
)
|
|
157
|
+
return BinaryCommand(
|
|
158
|
+
command=[str(path)], is_direct=True, is_in_path=False
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# If fallback is disabled, stop here
|
|
162
|
+
if not fallback_enabled:
|
|
163
|
+
logger.debug("binary_fallback_disabled", binary=binary_name)
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
# Try package manager fallback
|
|
167
|
+
package_name = package_name or self.KNOWN_PACKAGES.get(binary_name, binary_name)
|
|
168
|
+
return self._find_via_package_manager(binary_name, package_name)
|
|
169
|
+
|
|
170
|
+
def _find_via_package_manager(
|
|
171
|
+
self, binary_name: str, package_name: str
|
|
172
|
+
) -> BinaryCommand | None:
|
|
173
|
+
"""Find binary via package manager execution.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
binary_name: Name of the binary
|
|
177
|
+
package_name: NPM package name
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
BinaryCommand with package manager command or None
|
|
181
|
+
"""
|
|
182
|
+
# Get available package managers
|
|
183
|
+
available = self._get_available_managers()
|
|
184
|
+
|
|
185
|
+
# If preferred manager is set and available, try it first
|
|
186
|
+
if (
|
|
187
|
+
self.preferred_package_manager
|
|
188
|
+
and self.preferred_package_manager in available
|
|
189
|
+
):
|
|
190
|
+
cmd = self._build_package_manager_command(
|
|
191
|
+
self.preferred_package_manager, package_name
|
|
192
|
+
)
|
|
193
|
+
if cmd:
|
|
194
|
+
logger.debug(
|
|
195
|
+
"binary_using_preferred_manager",
|
|
196
|
+
binary=binary_name,
|
|
197
|
+
manager=self.preferred_package_manager,
|
|
198
|
+
command=cmd,
|
|
199
|
+
)
|
|
200
|
+
return BinaryCommand(
|
|
201
|
+
command=cmd,
|
|
202
|
+
is_direct=False,
|
|
203
|
+
is_in_path=False,
|
|
204
|
+
package_manager=self.preferred_package_manager,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Try package managers in priority order
|
|
208
|
+
for manager_name in self.package_manager_priority:
|
|
209
|
+
if manager_name not in available or not available[manager_name]:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
cmd = self._build_package_manager_command(manager_name, package_name)
|
|
213
|
+
if cmd:
|
|
214
|
+
return BinaryCommand(
|
|
215
|
+
command=cmd,
|
|
216
|
+
is_direct=False,
|
|
217
|
+
is_in_path=False,
|
|
218
|
+
package_manager=manager_name,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
logger.debug(
|
|
222
|
+
"binary_not_found_with_fallback",
|
|
223
|
+
binary=binary_name,
|
|
224
|
+
package=package_name,
|
|
225
|
+
available_managers=list(available.keys()),
|
|
226
|
+
)
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
def _build_package_manager_command(
|
|
230
|
+
self, manager_name: str, package_name: str
|
|
231
|
+
) -> list[str] | None:
|
|
232
|
+
"""Build command for executing via package manager.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
manager_name: Name of the package manager
|
|
236
|
+
package_name: Package to execute
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Command list or None if manager not configured
|
|
240
|
+
"""
|
|
241
|
+
commands = {
|
|
242
|
+
"bunx": ["bunx", package_name],
|
|
243
|
+
"pnpm": ["pnpm", "dlx", package_name],
|
|
244
|
+
"npx": ["npx", "--yes", package_name],
|
|
245
|
+
}
|
|
246
|
+
return commands.get(manager_name)
|
|
247
|
+
|
|
248
|
+
def _get_common_paths(self, binary_name: str) -> list[Path]:
|
|
249
|
+
"""Get common installation paths for a binary.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
binary_name: Name of the binary
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
List of paths to check
|
|
256
|
+
"""
|
|
257
|
+
paths = [
|
|
258
|
+
# User-specific locations
|
|
259
|
+
Path.home() / ".cache" / ".bun" / "bin" / binary_name,
|
|
260
|
+
Path.home() / ".local" / "bin" / binary_name,
|
|
261
|
+
Path.home() / ".local" / "share" / "nvim" / "mason" / "bin" / binary_name,
|
|
262
|
+
Path.home() / ".npm-global" / "bin" / binary_name,
|
|
263
|
+
Path.home() / "bin" / binary_name,
|
|
264
|
+
# System locations
|
|
265
|
+
Path("/usr/local/bin") / binary_name,
|
|
266
|
+
Path("/usr/bin") / binary_name,
|
|
267
|
+
Path("/opt/homebrew/bin") / binary_name, # macOS ARM
|
|
268
|
+
# Node/npm locations
|
|
269
|
+
Path.home()
|
|
270
|
+
/ ".nvm"
|
|
271
|
+
/ "versions"
|
|
272
|
+
/ "node"
|
|
273
|
+
/ "default"
|
|
274
|
+
/ "bin"
|
|
275
|
+
/ binary_name,
|
|
276
|
+
Path.home() / ".volta" / "bin" / binary_name,
|
|
277
|
+
]
|
|
278
|
+
return paths
|
|
279
|
+
|
|
280
|
+
def _get_available_managers(self) -> dict[str, bool]:
|
|
281
|
+
"""Get available package managers on the system.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Dictionary of manager names to availability status
|
|
285
|
+
"""
|
|
286
|
+
if self._available_managers is not None:
|
|
287
|
+
return self._available_managers
|
|
288
|
+
|
|
289
|
+
self._available_managers = {}
|
|
290
|
+
manager_info = {}
|
|
291
|
+
|
|
292
|
+
for manager_name, config in self.PACKAGE_MANAGERS.items():
|
|
293
|
+
check_cmd = config["check_cmd"]
|
|
294
|
+
try:
|
|
295
|
+
# Use subprocess.run with capture to check availability
|
|
296
|
+
result = subprocess.run(
|
|
297
|
+
check_cmd,
|
|
298
|
+
capture_output=True,
|
|
299
|
+
text=True,
|
|
300
|
+
timeout=2,
|
|
301
|
+
check=False,
|
|
302
|
+
)
|
|
303
|
+
available = result.returncode == 0
|
|
304
|
+
self._available_managers[manager_name] = available
|
|
305
|
+
if available:
|
|
306
|
+
version = result.stdout.strip() if result.stdout else "unknown"
|
|
307
|
+
manager_info[manager_name] = version
|
|
308
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
309
|
+
self._available_managers[manager_name] = False
|
|
310
|
+
|
|
311
|
+
# Log all available managers in one consolidated message
|
|
312
|
+
if manager_info:
|
|
313
|
+
logger.debug(
|
|
314
|
+
"package_managers_detected",
|
|
315
|
+
managers=manager_info,
|
|
316
|
+
count=len(manager_info),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return self._available_managers
|
|
320
|
+
|
|
321
|
+
def get_available_package_managers(self) -> list[str]:
|
|
322
|
+
"""Get list of available package managers on the system.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of package manager names that are available (e.g., ['bunx', 'pnpm'])
|
|
326
|
+
"""
|
|
327
|
+
available = self._get_available_managers()
|
|
328
|
+
return [name for name, is_available in available.items() if is_available]
|
|
329
|
+
|
|
330
|
+
def get_package_manager_info(self) -> dict[str, dict[str, str | bool | int]]:
|
|
331
|
+
"""Get detailed information about package managers.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Dictionary with package manager info including availability and priority
|
|
335
|
+
"""
|
|
336
|
+
available = self._get_available_managers()
|
|
337
|
+
info: dict[str, dict[str, str | bool | int]] = {}
|
|
338
|
+
|
|
339
|
+
for name, config in self.PACKAGE_MANAGERS.items():
|
|
340
|
+
exec_cmd = config.get("exec_cmd", name)
|
|
341
|
+
info[name] = {
|
|
342
|
+
"available": bool(available.get(name, False)),
|
|
343
|
+
"priority": int(config["priority"]),
|
|
344
|
+
"check_command": str(" ".join(config["check_cmd"])),
|
|
345
|
+
"exec_command": str(exec_cmd if exec_cmd is not None else name),
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return info
|
|
349
|
+
|
|
350
|
+
def get_cli_info(
|
|
351
|
+
self,
|
|
352
|
+
binary_name: str,
|
|
353
|
+
package_name: str | None = None,
|
|
354
|
+
version: str | None = None,
|
|
355
|
+
) -> CLIInfo:
|
|
356
|
+
"""Get comprehensive CLI information in common format.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
binary_name: Name of the binary to find
|
|
360
|
+
package_name: NPM package name if different from binary name
|
|
361
|
+
version: Optional version string (if known)
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
CLIInfo dictionary with structured information
|
|
365
|
+
"""
|
|
366
|
+
result = self.find_binary(binary_name, package_name)
|
|
367
|
+
|
|
368
|
+
if not result:
|
|
369
|
+
return CLIInfo(
|
|
370
|
+
name=binary_name,
|
|
371
|
+
version=version,
|
|
372
|
+
source="unknown",
|
|
373
|
+
path=None,
|
|
374
|
+
command=[],
|
|
375
|
+
package_manager=None,
|
|
376
|
+
is_available=False,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Determine source and path
|
|
380
|
+
if result.is_direct:
|
|
381
|
+
source = "path"
|
|
382
|
+
path = result.command[0] if result.command else None
|
|
383
|
+
else:
|
|
384
|
+
source = "package_manager"
|
|
385
|
+
path = None
|
|
386
|
+
|
|
387
|
+
return CLIInfo(
|
|
388
|
+
name=binary_name,
|
|
389
|
+
version=version,
|
|
390
|
+
source=source,
|
|
391
|
+
path=path,
|
|
392
|
+
command=result.command,
|
|
393
|
+
package_manager=result.package_manager,
|
|
394
|
+
is_available=True,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def clear_cache(self) -> None:
|
|
398
|
+
"""Clear all caches."""
|
|
399
|
+
# Reset the available managers cache
|
|
400
|
+
self._available_managers = None
|
|
401
|
+
|
|
402
|
+
@classmethod
|
|
403
|
+
def from_settings(cls, settings: "Settings") -> "BinaryResolver":
|
|
404
|
+
"""Create a BinaryResolver from application settings.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
settings: Application settings
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Configured BinaryResolver instance
|
|
411
|
+
"""
|
|
412
|
+
return cls(
|
|
413
|
+
fallback_enabled=settings.binary.fallback_enabled,
|
|
414
|
+
package_manager_only=settings.binary.package_manager_only,
|
|
415
|
+
preferred_package_manager=settings.binary.preferred_package_manager,
|
|
416
|
+
package_manager_priority=settings.binary.package_manager_priority,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# Global instance for convenience
|
|
421
|
+
_default_resolver = BinaryResolver()
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def find_binary_with_fallback(
|
|
425
|
+
binary_name: str,
|
|
426
|
+
package_name: str | None = None,
|
|
427
|
+
fallback_enabled: bool = True,
|
|
428
|
+
) -> list[str] | None:
|
|
429
|
+
"""Convenience function to find a binary with package manager fallback.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
binary_name: Name of the binary to find. Can be:
|
|
433
|
+
- Simple binary name (e.g., "claude")
|
|
434
|
+
- Full package name (e.g., "@anthropic-ai/claude-code")
|
|
435
|
+
package_name: NPM package name if different from binary name
|
|
436
|
+
fallback_enabled: Whether to use package manager fallback
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Command list to execute the binary, or None if not found
|
|
440
|
+
"""
|
|
441
|
+
resolver = BinaryResolver(fallback_enabled=fallback_enabled)
|
|
442
|
+
result = resolver.find_binary(binary_name, package_name)
|
|
443
|
+
return result.command if result else None
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def is_package_manager_command(command: list[str]) -> bool:
|
|
447
|
+
"""Check if a command uses a package manager.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
command: Command list to check
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
True if command uses a package manager
|
|
454
|
+
"""
|
|
455
|
+
if not command:
|
|
456
|
+
return False
|
|
457
|
+
first_cmd = Path(command[0]).name
|
|
458
|
+
return first_cmd in ["npx", "bunx", "pnpm"]
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def get_available_package_managers() -> list[str]:
|
|
462
|
+
"""Convenience function to get available package managers using default resolver.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
List of package manager names that are available
|
|
466
|
+
"""
|
|
467
|
+
return _default_resolver.get_available_package_managers()
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def get_package_manager_info() -> dict[str, dict[str, str | bool | int]]:
|
|
471
|
+
"""Convenience function to get package manager info using default resolver.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Dictionary with package manager info including availability and priority
|
|
475
|
+
"""
|
|
476
|
+
return _default_resolver.get_package_manager_info()
|