ccproxy-api 0.1.6__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 +439 -212
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +145 -176
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +402 -530
- 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 +558 -0
- ccproxy/data/codex_headers_fallback.json +121 -0
- 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 +63 -107
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +346 -314
- 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 +95 -342
- ccproxy/utils/version_checker.py +279 -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.6.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 -1231
- 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 -269
- ccproxy/services/codex_detection_service.py +0 -263
- 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.6.dist-info/METADATA +0 -615
- ccproxy_api-0.1.6.dist-info/RECORD +0 -189
- ccproxy_api-0.1.6.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.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Dependency injection container for all services.
|
|
2
|
+
|
|
3
|
+
This module provides a clean, testable dependency injection container that
|
|
4
|
+
manages service lifecycles and dependencies without singleton anti-patterns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from contextvars import ContextVar
|
|
12
|
+
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
import structlog
|
|
16
|
+
|
|
17
|
+
from ccproxy.config.settings import Settings
|
|
18
|
+
from ccproxy.core.plugins.hooks.registry import HookRegistry
|
|
19
|
+
from ccproxy.core.plugins.hooks.thread_manager import BackgroundHookThreadManager
|
|
20
|
+
from ccproxy.http.pool import HTTPPoolManager
|
|
21
|
+
from ccproxy.scheduler.registry import TaskRegistry
|
|
22
|
+
from ccproxy.services.adapters.format_registry import FormatRegistry
|
|
23
|
+
from ccproxy.services.auth_registry import AuthManagerRegistry
|
|
24
|
+
from ccproxy.services.cache import ResponseCache
|
|
25
|
+
from ccproxy.services.cli_detection import CLIDetectionService
|
|
26
|
+
from ccproxy.services.config import ProxyConfiguration
|
|
27
|
+
from ccproxy.services.factories import ConcreteServiceFactory
|
|
28
|
+
from ccproxy.services.interfaces import (
|
|
29
|
+
IRequestTracer,
|
|
30
|
+
NullMetricsCollector,
|
|
31
|
+
NullRequestTracer,
|
|
32
|
+
)
|
|
33
|
+
from ccproxy.services.mocking import MockResponseHandler
|
|
34
|
+
from ccproxy.streaming import StreamingHandler
|
|
35
|
+
from ccproxy.utils.binary_resolver import BinaryResolver
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from ccproxy.core.async_task_manager import AsyncTaskManager
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
logger = structlog.get_logger(__name__)
|
|
43
|
+
|
|
44
|
+
T = TypeVar("T")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ServiceContainer:
|
|
48
|
+
"""Dependency injection container for all services."""
|
|
49
|
+
|
|
50
|
+
_current_container: ClassVar[ContextVar[ServiceContainer | None]] = ContextVar(
|
|
51
|
+
"ccproxy_service_container",
|
|
52
|
+
default=None,
|
|
53
|
+
)
|
|
54
|
+
_default_container: ClassVar[ServiceContainer | None] = None
|
|
55
|
+
|
|
56
|
+
def __init__(self, settings: Settings) -> None:
|
|
57
|
+
"""Initialize the service container."""
|
|
58
|
+
self.settings = settings
|
|
59
|
+
self._services: dict[object, Any] = {}
|
|
60
|
+
self._factories: dict[object, Callable[[], Any]] = {}
|
|
61
|
+
|
|
62
|
+
self.register_service(Settings, self.settings)
|
|
63
|
+
self.register_service(ServiceContainer, self)
|
|
64
|
+
|
|
65
|
+
factory = ConcreteServiceFactory(self)
|
|
66
|
+
factory.register_services()
|
|
67
|
+
|
|
68
|
+
# Ensure a request tracer is always available for early consumers
|
|
69
|
+
# Plugins may override this with a real tracer at runtime
|
|
70
|
+
# Register a default tracer using the protocol as key
|
|
71
|
+
self.register_service(IRequestTracer, instance=NullRequestTracer())
|
|
72
|
+
|
|
73
|
+
# Make this container available for modules that resolve services globally
|
|
74
|
+
self.activate()
|
|
75
|
+
|
|
76
|
+
def activate(self, *, set_default: bool = True) -> None:
|
|
77
|
+
"""Mark this container as the current active container."""
|
|
78
|
+
self.__class__._current_container.set(self)
|
|
79
|
+
if set_default:
|
|
80
|
+
self.__class__._default_container = self
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def get_current(cls, *, strict: bool = True) -> ServiceContainer | None:
|
|
84
|
+
"""Return the currently active container.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
strict: When True, raise an error if no container is active.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Active service container or None.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
container = cls._current_container.get()
|
|
94
|
+
if container is None:
|
|
95
|
+
container = cls._default_container
|
|
96
|
+
if container is None and strict:
|
|
97
|
+
raise RuntimeError("ServiceContainer is not available")
|
|
98
|
+
return container
|
|
99
|
+
|
|
100
|
+
def register_service(
|
|
101
|
+
self,
|
|
102
|
+
service_type: object,
|
|
103
|
+
instance: Any | None = None,
|
|
104
|
+
factory: Callable[[], Any] | None = None,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Register a service instance or factory."""
|
|
107
|
+
if instance is not None:
|
|
108
|
+
self._services[service_type] = instance
|
|
109
|
+
elif factory is not None:
|
|
110
|
+
self._factories[service_type] = factory
|
|
111
|
+
else:
|
|
112
|
+
raise ValueError("Either instance or factory must be provided")
|
|
113
|
+
|
|
114
|
+
def get_service(self, service_type: type[T]) -> T:
|
|
115
|
+
"""Get a service instance by key (type or protocol)."""
|
|
116
|
+
if service_type not in self._services:
|
|
117
|
+
if service_type in self._factories:
|
|
118
|
+
self._services[service_type] = self._factories[service_type]()
|
|
119
|
+
else:
|
|
120
|
+
# Best-effort name for error messages
|
|
121
|
+
type_name = getattr(service_type, "__name__", str(service_type))
|
|
122
|
+
raise ValueError(f"Service {type_name} not registered")
|
|
123
|
+
return cast(T, self._services[service_type])
|
|
124
|
+
|
|
125
|
+
def get_request_tracer(self) -> IRequestTracer:
|
|
126
|
+
"""Get request tracer service instance."""
|
|
127
|
+
service = self._services.get(IRequestTracer)
|
|
128
|
+
if service is None:
|
|
129
|
+
raise ValueError("Service IRequestTracer not registered")
|
|
130
|
+
return cast(IRequestTracer, service)
|
|
131
|
+
|
|
132
|
+
def set_request_tracer(self, tracer: IRequestTracer) -> None:
|
|
133
|
+
"""Set the request tracer (called by plugin)."""
|
|
134
|
+
self.register_service(IRequestTracer, instance=tracer)
|
|
135
|
+
|
|
136
|
+
def get_mock_handler(self) -> MockResponseHandler:
|
|
137
|
+
"""Get mock handler service instance."""
|
|
138
|
+
return self.get_service(MockResponseHandler)
|
|
139
|
+
|
|
140
|
+
def get_streaming_handler(self) -> StreamingHandler:
|
|
141
|
+
"""Get streaming handler service instance."""
|
|
142
|
+
return self.get_service(StreamingHandler)
|
|
143
|
+
|
|
144
|
+
def get_binary_resolver(self) -> BinaryResolver:
|
|
145
|
+
"""Get binary resolver service instance."""
|
|
146
|
+
return self.get_service(BinaryResolver)
|
|
147
|
+
|
|
148
|
+
def get_cli_detection_service(self) -> CLIDetectionService:
|
|
149
|
+
"""Get CLI detection service instance."""
|
|
150
|
+
return self.get_service(CLIDetectionService)
|
|
151
|
+
|
|
152
|
+
def get_proxy_config(self) -> ProxyConfiguration:
|
|
153
|
+
"""Get proxy configuration service instance."""
|
|
154
|
+
return self.get_service(ProxyConfiguration)
|
|
155
|
+
|
|
156
|
+
def get_http_client(self) -> httpx.AsyncClient:
|
|
157
|
+
"""Get container-managed HTTP client instance."""
|
|
158
|
+
return self.get_service(httpx.AsyncClient)
|
|
159
|
+
|
|
160
|
+
def get_pool_manager(self) -> HTTPPoolManager:
|
|
161
|
+
"""Get HTTP connection pool manager instance."""
|
|
162
|
+
return self.get_service(HTTPPoolManager)
|
|
163
|
+
|
|
164
|
+
def get_response_cache(self) -> ResponseCache:
|
|
165
|
+
"""Get response cache service instance."""
|
|
166
|
+
return self.get_service(ResponseCache)
|
|
167
|
+
|
|
168
|
+
# Use HTTPPoolManager for pooling
|
|
169
|
+
|
|
170
|
+
def get_format_registry(self) -> FormatRegistry:
|
|
171
|
+
"""Get format adapter registry service instance."""
|
|
172
|
+
return self.get_service(FormatRegistry)
|
|
173
|
+
|
|
174
|
+
# FormatterRegistry removed; use FormatRegistry exclusively.
|
|
175
|
+
|
|
176
|
+
def get_oauth_registry(self) -> Any:
|
|
177
|
+
"""Get OAuth provider registry instance."""
|
|
178
|
+
# Import lazily to avoid circular imports through auth package
|
|
179
|
+
from ccproxy.auth.oauth.registry import OAuthRegistry
|
|
180
|
+
|
|
181
|
+
return self.get_service(OAuthRegistry)
|
|
182
|
+
|
|
183
|
+
def get_hook_registry(self) -> HookRegistry:
|
|
184
|
+
"""Get hook registry instance."""
|
|
185
|
+
return self.get_service(HookRegistry)
|
|
186
|
+
|
|
187
|
+
def get_task_registry(self) -> TaskRegistry:
|
|
188
|
+
"""Get scheduled task registry instance."""
|
|
189
|
+
return self.get_service(TaskRegistry)
|
|
190
|
+
|
|
191
|
+
def get_auth_manager_registry(self) -> AuthManagerRegistry:
|
|
192
|
+
"""Get auth manager registry instance."""
|
|
193
|
+
return self.get_service(AuthManagerRegistry)
|
|
194
|
+
|
|
195
|
+
def get_background_hook_thread_manager(self) -> BackgroundHookThreadManager:
|
|
196
|
+
"""Get background hook thread manager instance."""
|
|
197
|
+
return self.get_service(BackgroundHookThreadManager)
|
|
198
|
+
|
|
199
|
+
def get_async_task_manager(self) -> AsyncTaskManager:
|
|
200
|
+
"""Get async task manager instance."""
|
|
201
|
+
from ccproxy.core.async_task_manager import AsyncTaskManager
|
|
202
|
+
|
|
203
|
+
return self.get_service(AsyncTaskManager)
|
|
204
|
+
|
|
205
|
+
def get_adapter_dependencies(self, metrics: Any | None = None) -> dict[str, Any]:
|
|
206
|
+
"""Get all services an adapter might need."""
|
|
207
|
+
return {
|
|
208
|
+
"http_client": self.get_http_client(),
|
|
209
|
+
"request_tracer": self.get_request_tracer(),
|
|
210
|
+
"metrics": metrics or NullMetricsCollector(),
|
|
211
|
+
"streaming_handler": self.get_streaming_handler(),
|
|
212
|
+
"logger": structlog.get_logger(),
|
|
213
|
+
"config": self.get_proxy_config(),
|
|
214
|
+
"cli_detection_service": self.get_cli_detection_service(),
|
|
215
|
+
"format_registry": self.get_format_registry(),
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async def close(self) -> None:
|
|
219
|
+
"""Close all managed resources during shutdown."""
|
|
220
|
+
for service in list(self._services.values()):
|
|
221
|
+
# Avoid recursive self-close
|
|
222
|
+
if service is self:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
# Prefer aclose() if available (e.g., httpx.AsyncClient)
|
|
227
|
+
if hasattr(service, "aclose") and callable(service.aclose):
|
|
228
|
+
maybe_coro = service.aclose()
|
|
229
|
+
if inspect.isawaitable(maybe_coro):
|
|
230
|
+
await maybe_coro
|
|
231
|
+
elif hasattr(service, "close") and callable(service.close):
|
|
232
|
+
maybe_coro = service.close()
|
|
233
|
+
if inspect.isawaitable(maybe_coro):
|
|
234
|
+
await maybe_coro
|
|
235
|
+
elif hasattr(service, "stop") and callable(service.stop):
|
|
236
|
+
stop_result = service.stop()
|
|
237
|
+
if inspect.isawaitable(stop_result):
|
|
238
|
+
await stop_result
|
|
239
|
+
# else: nothing to close
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(
|
|
242
|
+
"service_close_failed",
|
|
243
|
+
service=type(service).__name__,
|
|
244
|
+
error=str(e),
|
|
245
|
+
exc_info=e,
|
|
246
|
+
category="lifecycle",
|
|
247
|
+
)
|
|
248
|
+
self._services.clear()
|
|
249
|
+
logger.debug("service_container_resources_closed", category="lifecycle")
|
|
250
|
+
|
|
251
|
+
async def shutdown(self) -> None:
|
|
252
|
+
"""Shutdown all services in the container."""
|
|
253
|
+
await self.close()
|
|
254
|
+
if self.__class__._default_container is self:
|
|
255
|
+
self.__class__._default_container = None
|
|
256
|
+
self.__class__._current_container.set(None)
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""Concrete service factory implementations.
|
|
2
|
+
|
|
3
|
+
This module provides concrete implementations of service factories that
|
|
4
|
+
create and configure service instances according to their interfaces.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
import structlog
|
|
13
|
+
|
|
14
|
+
from ccproxy.auth.oauth import registry as oauth_registry_module
|
|
15
|
+
from ccproxy.config.settings import Settings
|
|
16
|
+
from ccproxy.core.plugins.hooks import HookManager
|
|
17
|
+
from ccproxy.core.plugins.hooks.registry import HookRegistry
|
|
18
|
+
from ccproxy.core.plugins.hooks.thread_manager import BackgroundHookThreadManager
|
|
19
|
+
from ccproxy.http.client import HTTPClientFactory
|
|
20
|
+
from ccproxy.http.pool import HTTPPoolManager
|
|
21
|
+
from ccproxy.scheduler.registry import TaskRegistry
|
|
22
|
+
from ccproxy.services.adapters.format_adapter import DictFormatAdapter
|
|
23
|
+
from ccproxy.services.adapters.format_registry import FormatRegistry
|
|
24
|
+
from ccproxy.services.adapters.simple_converters import (
|
|
25
|
+
convert_anthropic_to_openai_response,
|
|
26
|
+
)
|
|
27
|
+
from ccproxy.services.auth_registry import AuthManagerRegistry
|
|
28
|
+
from ccproxy.services.cache import ResponseCache
|
|
29
|
+
from ccproxy.services.cli_detection import CLIDetectionService
|
|
30
|
+
from ccproxy.services.config import ProxyConfiguration
|
|
31
|
+
from ccproxy.services.mocking import MockResponseHandler
|
|
32
|
+
from ccproxy.streaming import StreamingHandler
|
|
33
|
+
from ccproxy.testing import RealisticMockResponseGenerator
|
|
34
|
+
from ccproxy.utils.binary_resolver import BinaryResolver
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from ccproxy.core.async_task_manager import AsyncTaskManager
|
|
39
|
+
from ccproxy.services.container import ServiceContainer
|
|
40
|
+
|
|
41
|
+
logger = structlog.get_logger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _CoreAdapterSpec(TypedDict):
|
|
45
|
+
"""Type definition for core adapter specification dictionary."""
|
|
46
|
+
|
|
47
|
+
from_format: str
|
|
48
|
+
to_format: str
|
|
49
|
+
request: Any # Format converter function
|
|
50
|
+
response: Any # Format converter function
|
|
51
|
+
stream: Any # Format converter function
|
|
52
|
+
error: Any # Error converter function
|
|
53
|
+
name: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ConcreteServiceFactory:
|
|
57
|
+
"""Concrete implementation of service factory."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, container: ServiceContainer) -> None:
|
|
60
|
+
"""Initialize the service factory."""
|
|
61
|
+
self._container = container
|
|
62
|
+
|
|
63
|
+
def register_services(self) -> None:
|
|
64
|
+
"""Register all services with the container."""
|
|
65
|
+
self._container.register_service(
|
|
66
|
+
MockResponseHandler, factory=self.create_mock_handler
|
|
67
|
+
)
|
|
68
|
+
self._container.register_service(
|
|
69
|
+
StreamingHandler, factory=self.create_streaming_handler
|
|
70
|
+
)
|
|
71
|
+
self._container.register_service(
|
|
72
|
+
ProxyConfiguration, factory=self.create_proxy_config
|
|
73
|
+
)
|
|
74
|
+
self._container.register_service(
|
|
75
|
+
httpx.AsyncClient, factory=self.create_http_client
|
|
76
|
+
)
|
|
77
|
+
self._container.register_service(
|
|
78
|
+
CLIDetectionService, factory=self.create_cli_detection_service
|
|
79
|
+
)
|
|
80
|
+
self._container.register_service(
|
|
81
|
+
HTTPPoolManager, factory=self.create_http_pool_manager
|
|
82
|
+
)
|
|
83
|
+
self._container.register_service(
|
|
84
|
+
ResponseCache, factory=self.create_response_cache
|
|
85
|
+
)
|
|
86
|
+
self._container.register_service(
|
|
87
|
+
BinaryResolver, factory=self.create_binary_resolver
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self._container.register_service(
|
|
91
|
+
FormatRegistry, factory=self.create_format_registry
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Registries
|
|
95
|
+
self._container.register_service(
|
|
96
|
+
HookRegistry, factory=self.create_hook_registry
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self._container.register_service(
|
|
100
|
+
oauth_registry_module.OAuthRegistry, factory=self.create_oauth_registry
|
|
101
|
+
)
|
|
102
|
+
self._container.register_service(
|
|
103
|
+
TaskRegistry, factory=self.create_task_registry
|
|
104
|
+
)
|
|
105
|
+
self._container.register_service(
|
|
106
|
+
AuthManagerRegistry, factory=self.create_auth_manager_registry
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Register background thread manager for hooks
|
|
110
|
+
self._container.register_service(
|
|
111
|
+
BackgroundHookThreadManager,
|
|
112
|
+
factory=self.create_background_hook_thread_manager,
|
|
113
|
+
)
|
|
114
|
+
from ccproxy.core.async_task_manager import AsyncTaskManager
|
|
115
|
+
|
|
116
|
+
self._container.register_service(
|
|
117
|
+
AsyncTaskManager,
|
|
118
|
+
factory=self.create_async_task_manager,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def create_mock_handler(self) -> MockResponseHandler:
|
|
122
|
+
"""Create mock handler instance."""
|
|
123
|
+
mock_generator = RealisticMockResponseGenerator()
|
|
124
|
+
settings = self._container.get_service(Settings)
|
|
125
|
+
# Create simple format adapter for anthropic->openai conversion (for mock responses)
|
|
126
|
+
openai_adapter = DictFormatAdapter(
|
|
127
|
+
response=convert_anthropic_to_openai_response,
|
|
128
|
+
name="mock_anthropic_to_openai",
|
|
129
|
+
)
|
|
130
|
+
# Configure streaming settings if needed
|
|
131
|
+
openai_thinking_xml = getattr(
|
|
132
|
+
getattr(settings, "llm", object()), "openai_thinking_xml", True
|
|
133
|
+
)
|
|
134
|
+
if hasattr(openai_adapter, "configure_streaming"):
|
|
135
|
+
openai_adapter.configure_streaming(openai_thinking_xml=openai_thinking_xml)
|
|
136
|
+
|
|
137
|
+
handler = MockResponseHandler(
|
|
138
|
+
mock_generator=mock_generator,
|
|
139
|
+
openai_adapter=openai_adapter,
|
|
140
|
+
error_rate=0.05,
|
|
141
|
+
latency_range=(0.5, 2.0),
|
|
142
|
+
)
|
|
143
|
+
return handler
|
|
144
|
+
|
|
145
|
+
def create_streaming_handler(self) -> StreamingHandler:
|
|
146
|
+
"""Create streaming handler instance.
|
|
147
|
+
|
|
148
|
+
Requires HookManager to be registered before resolution to avoid
|
|
149
|
+
post-hoc patching of the handler.
|
|
150
|
+
"""
|
|
151
|
+
hook_manager = self._container.get_service(HookManager)
|
|
152
|
+
handler = StreamingHandler(hook_manager=hook_manager)
|
|
153
|
+
return handler
|
|
154
|
+
|
|
155
|
+
def create_proxy_config(self) -> ProxyConfiguration:
|
|
156
|
+
"""Create proxy configuration instance."""
|
|
157
|
+
config = ProxyConfiguration()
|
|
158
|
+
return config
|
|
159
|
+
|
|
160
|
+
def create_http_client(self) -> httpx.AsyncClient:
|
|
161
|
+
"""Create HTTP client instance."""
|
|
162
|
+
settings = self._container.get_service(Settings)
|
|
163
|
+
hook_manager = self._container.get_service(HookManager)
|
|
164
|
+
client = HTTPClientFactory.create_client(
|
|
165
|
+
settings=settings, hook_manager=hook_manager
|
|
166
|
+
)
|
|
167
|
+
logger.debug("http_client_created", category="lifecycle")
|
|
168
|
+
return client
|
|
169
|
+
|
|
170
|
+
def create_cli_detection_service(self) -> CLIDetectionService:
|
|
171
|
+
"""Create CLI detection service instance."""
|
|
172
|
+
settings = self._container.get_service(Settings)
|
|
173
|
+
return CLIDetectionService(settings)
|
|
174
|
+
|
|
175
|
+
def create_http_pool_manager(self) -> HTTPPoolManager:
|
|
176
|
+
"""Create HTTP pool manager instance."""
|
|
177
|
+
settings = self._container.get_service(Settings)
|
|
178
|
+
hook_manager = self._container.get_service(HookManager)
|
|
179
|
+
logger.debug(
|
|
180
|
+
"http_pool_manager_created",
|
|
181
|
+
has_hook_manager=hook_manager is not None,
|
|
182
|
+
hook_manager_type=type(hook_manager).__name__ if hook_manager else "None",
|
|
183
|
+
category="lifecycle",
|
|
184
|
+
)
|
|
185
|
+
return HTTPPoolManager(settings, hook_manager)
|
|
186
|
+
|
|
187
|
+
def create_response_cache(self) -> ResponseCache:
|
|
188
|
+
"""Create response cache instance."""
|
|
189
|
+
return ResponseCache()
|
|
190
|
+
|
|
191
|
+
# ConnectionPoolManager is no longer used; HTTPPoolManager only
|
|
192
|
+
|
|
193
|
+
def create_binary_resolver(self) -> BinaryResolver:
|
|
194
|
+
"""Create a BinaryResolver from settings."""
|
|
195
|
+
settings = self._container.get_service(Settings)
|
|
196
|
+
return BinaryResolver.from_settings(settings)
|
|
197
|
+
|
|
198
|
+
def create_format_registry(self) -> FormatRegistry:
|
|
199
|
+
"""Create format adapter registry with core adapters pre-registered.
|
|
200
|
+
|
|
201
|
+
Pre-registers common format conversions to prevent plugin conflicts.
|
|
202
|
+
Plugins can still register their own plugin-specific adapters.
|
|
203
|
+
"""
|
|
204
|
+
settings = self._container.get_service(Settings)
|
|
205
|
+
|
|
206
|
+
# Always use priority mode (latest behavior)
|
|
207
|
+
registry = FormatRegistry()
|
|
208
|
+
|
|
209
|
+
# Pre-register core format adapters
|
|
210
|
+
self._register_core_format_adapters(registry, settings)
|
|
211
|
+
registry.flush_all_logs()
|
|
212
|
+
|
|
213
|
+
logger.debug(
|
|
214
|
+
"format_registry_created",
|
|
215
|
+
category="format",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return registry
|
|
219
|
+
|
|
220
|
+
def create_hook_registry(self) -> HookRegistry:
|
|
221
|
+
"""Create a HookRegistry instance."""
|
|
222
|
+
return HookRegistry()
|
|
223
|
+
|
|
224
|
+
def create_oauth_registry(self) -> Any:
|
|
225
|
+
"""Create an OAuthRegistry instance (imported lazily to avoid cycles)."""
|
|
226
|
+
from ccproxy.auth.oauth.registry import OAuthRegistry
|
|
227
|
+
|
|
228
|
+
return OAuthRegistry()
|
|
229
|
+
|
|
230
|
+
def create_task_registry(self) -> TaskRegistry:
|
|
231
|
+
"""Create a TaskRegistry instance."""
|
|
232
|
+
return TaskRegistry()
|
|
233
|
+
|
|
234
|
+
def create_auth_manager_registry(self) -> AuthManagerRegistry:
|
|
235
|
+
"""Create an AuthManagerRegistry instance.
|
|
236
|
+
|
|
237
|
+
Note: Auth managers are registered by their respective plugins during initialization.
|
|
238
|
+
"""
|
|
239
|
+
return AuthManagerRegistry()
|
|
240
|
+
|
|
241
|
+
def _register_core_format_adapters(
|
|
242
|
+
self, registry: FormatRegistry, settings: Settings | None = None
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Register essential format adapters provided by core.
|
|
245
|
+
|
|
246
|
+
Registers commonly-needed format conversions to prevent plugin duplication
|
|
247
|
+
and ensure required adapters are available for plugin dependencies.
|
|
248
|
+
"""
|
|
249
|
+
from ccproxy.core.constants import (
|
|
250
|
+
FORMAT_ANTHROPIC_MESSAGES,
|
|
251
|
+
FORMAT_OPENAI_CHAT,
|
|
252
|
+
FORMAT_OPENAI_RESPONSES,
|
|
253
|
+
)
|
|
254
|
+
from ccproxy.services.adapters.simple_converters import (
|
|
255
|
+
convert_anthropic_to_openai_error,
|
|
256
|
+
convert_anthropic_to_openai_request,
|
|
257
|
+
convert_anthropic_to_openai_response,
|
|
258
|
+
convert_anthropic_to_openai_responses_error,
|
|
259
|
+
convert_anthropic_to_openai_responses_request,
|
|
260
|
+
convert_anthropic_to_openai_responses_response,
|
|
261
|
+
convert_anthropic_to_openai_responses_stream,
|
|
262
|
+
convert_anthropic_to_openai_stream,
|
|
263
|
+
convert_openai_chat_to_openai_responses_error,
|
|
264
|
+
convert_openai_chat_to_openai_responses_request,
|
|
265
|
+
convert_openai_chat_to_openai_responses_response,
|
|
266
|
+
convert_openai_chat_to_openai_responses_stream,
|
|
267
|
+
convert_openai_responses_to_anthropic_error,
|
|
268
|
+
convert_openai_responses_to_anthropic_request,
|
|
269
|
+
convert_openai_responses_to_anthropic_response,
|
|
270
|
+
convert_openai_responses_to_anthropic_stream,
|
|
271
|
+
convert_openai_responses_to_openai_chat_error,
|
|
272
|
+
convert_openai_responses_to_openai_chat_request,
|
|
273
|
+
convert_openai_responses_to_openai_chat_response,
|
|
274
|
+
convert_openai_responses_to_openai_chat_stream,
|
|
275
|
+
convert_openai_to_anthropic_error,
|
|
276
|
+
convert_openai_to_anthropic_request,
|
|
277
|
+
convert_openai_to_anthropic_response,
|
|
278
|
+
convert_openai_to_anthropic_stream,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Define core format adapter specifications
|
|
282
|
+
core_adapter_specs: list[_CoreAdapterSpec] = [
|
|
283
|
+
# Most commonly required: Anthropic ↔ OpenAI Responses
|
|
284
|
+
{
|
|
285
|
+
"from_format": FORMAT_ANTHROPIC_MESSAGES,
|
|
286
|
+
"to_format": FORMAT_OPENAI_RESPONSES,
|
|
287
|
+
"request": convert_anthropic_to_openai_responses_request,
|
|
288
|
+
"response": convert_anthropic_to_openai_responses_response,
|
|
289
|
+
"stream": convert_anthropic_to_openai_responses_stream,
|
|
290
|
+
"error": convert_anthropic_to_openai_responses_error,
|
|
291
|
+
"name": "core_anthropic_to_openai_responses",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"from_format": FORMAT_OPENAI_RESPONSES,
|
|
295
|
+
"to_format": FORMAT_ANTHROPIC_MESSAGES,
|
|
296
|
+
"request": convert_openai_responses_to_anthropic_request,
|
|
297
|
+
"response": convert_openai_responses_to_anthropic_response,
|
|
298
|
+
"stream": convert_openai_responses_to_anthropic_stream,
|
|
299
|
+
"error": convert_openai_responses_to_anthropic_error,
|
|
300
|
+
"name": "core_openai_responses_to_anthropic",
|
|
301
|
+
},
|
|
302
|
+
# OpenAI Chat ↔ Responses (needed by Codex plugin)
|
|
303
|
+
{
|
|
304
|
+
"from_format": FORMAT_OPENAI_CHAT,
|
|
305
|
+
"to_format": FORMAT_OPENAI_RESPONSES,
|
|
306
|
+
"request": convert_openai_chat_to_openai_responses_request,
|
|
307
|
+
"response": convert_openai_chat_to_openai_responses_response,
|
|
308
|
+
"stream": convert_openai_chat_to_openai_responses_stream,
|
|
309
|
+
"error": convert_openai_chat_to_openai_responses_error,
|
|
310
|
+
"name": "core_openai_chat_to_responses",
|
|
311
|
+
},
|
|
312
|
+
# Reverse: OpenAI Responses -> OpenAI Chat
|
|
313
|
+
{
|
|
314
|
+
"from_format": FORMAT_OPENAI_RESPONSES,
|
|
315
|
+
"to_format": FORMAT_OPENAI_CHAT,
|
|
316
|
+
"request": convert_openai_responses_to_openai_chat_request,
|
|
317
|
+
"response": convert_openai_responses_to_openai_chat_response,
|
|
318
|
+
"stream": convert_openai_responses_to_openai_chat_stream,
|
|
319
|
+
"error": convert_openai_responses_to_openai_chat_error,
|
|
320
|
+
"name": "core_openai_responses_to_chat",
|
|
321
|
+
},
|
|
322
|
+
# Anthropic ↔ OpenAI Chat (commonly needed for proxying)
|
|
323
|
+
{
|
|
324
|
+
"from_format": FORMAT_ANTHROPIC_MESSAGES,
|
|
325
|
+
"to_format": FORMAT_OPENAI_CHAT,
|
|
326
|
+
"request": convert_anthropic_to_openai_request,
|
|
327
|
+
"response": convert_anthropic_to_openai_response,
|
|
328
|
+
"stream": convert_anthropic_to_openai_stream,
|
|
329
|
+
"error": convert_anthropic_to_openai_error,
|
|
330
|
+
"name": "core_anthropic_to_openai_chat",
|
|
331
|
+
},
|
|
332
|
+
# Reverse: OpenAI Chat -> Anthropic
|
|
333
|
+
{
|
|
334
|
+
"from_format": FORMAT_OPENAI_CHAT,
|
|
335
|
+
"to_format": FORMAT_ANTHROPIC_MESSAGES,
|
|
336
|
+
"request": convert_openai_to_anthropic_request,
|
|
337
|
+
"response": convert_openai_to_anthropic_response,
|
|
338
|
+
"stream": convert_openai_to_anthropic_stream,
|
|
339
|
+
"error": convert_openai_to_anthropic_error,
|
|
340
|
+
"name": "core_openai_chat_to_anthropic",
|
|
341
|
+
},
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
# Register each core adapter
|
|
345
|
+
for spec in core_adapter_specs:
|
|
346
|
+
adapter = DictFormatAdapter(
|
|
347
|
+
request=spec["request"],
|
|
348
|
+
response=spec["response"],
|
|
349
|
+
stream=spec["stream"],
|
|
350
|
+
error=spec["error"],
|
|
351
|
+
name=spec["name"],
|
|
352
|
+
)
|
|
353
|
+
registry.register(
|
|
354
|
+
from_format=spec["from_format"],
|
|
355
|
+
to_format=spec["to_format"],
|
|
356
|
+
adapter=adapter,
|
|
357
|
+
plugin_name="core",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
logger.debug(
|
|
361
|
+
"core_format_adapters_registered",
|
|
362
|
+
count=len(core_adapter_specs),
|
|
363
|
+
adapters=[
|
|
364
|
+
f"{spec['from_format']}->{spec['to_format']}"
|
|
365
|
+
for spec in core_adapter_specs
|
|
366
|
+
],
|
|
367
|
+
category="format",
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def create_background_hook_thread_manager(self) -> BackgroundHookThreadManager:
|
|
371
|
+
"""Create background hook thread manager instance."""
|
|
372
|
+
manager = BackgroundHookThreadManager()
|
|
373
|
+
logger.debug("background_hook_thread_manager_created", category="lifecycle")
|
|
374
|
+
return manager
|
|
375
|
+
|
|
376
|
+
def create_async_task_manager(self) -> AsyncTaskManager:
|
|
377
|
+
"""Create async task manager instance."""
|
|
378
|
+
from ccproxy.core.async_task_manager import AsyncTaskManager
|
|
379
|
+
|
|
380
|
+
return AsyncTaskManager()
|