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
ccproxy/api/bootstrap.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Application bootstrapping and dependency injection container setup.
|
|
3
|
+
|
|
4
|
+
This module is responsible for the initial setup of the application's core services,
|
|
5
|
+
including configuration loading and service container initialization. It acts as the
|
|
6
|
+
main entry point for assembling the application's components before the web server
|
|
7
|
+
starts.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from ccproxy.config.settings import Settings
|
|
11
|
+
from ccproxy.services.container import ServiceContainer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_service_container(settings: Settings | None = None) -> ServiceContainer:
|
|
15
|
+
"""
|
|
16
|
+
Create and configure the service container.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
settings: Optional pre-loaded settings instance. If not provided,
|
|
20
|
+
settings will be loaded from config files/environment.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
The initialized service container.
|
|
24
|
+
"""
|
|
25
|
+
if settings is None:
|
|
26
|
+
settings = Settings.from_config()
|
|
27
|
+
|
|
28
|
+
container = ServiceContainer(settings)
|
|
29
|
+
|
|
30
|
+
return container
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import ParamSpec, TypeVar
|
|
8
|
+
|
|
9
|
+
from fastapi import Request
|
|
10
|
+
|
|
11
|
+
from ccproxy.core.logging import get_logger as _get_logger
|
|
12
|
+
from ccproxy.core.request_context import RequestContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
P = ParamSpec("P")
|
|
16
|
+
R = TypeVar("R")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def format_chain(
|
|
20
|
+
*formats: str,
|
|
21
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
|
|
22
|
+
"""Existing simple decorator to attach a format chain to a route handler.
|
|
23
|
+
|
|
24
|
+
This attaches a __format_chain__ attribute used by validation and helpers.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def decorator(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
|
|
28
|
+
func.__format_chain__ = list(formats) # type: ignore[attr-defined]
|
|
29
|
+
|
|
30
|
+
@wraps(func)
|
|
31
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
32
|
+
return await func(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
return wrapper
|
|
35
|
+
|
|
36
|
+
return decorator
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def with_format_chain(
|
|
40
|
+
formats: list[str], *, endpoint: str | None = None
|
|
41
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
|
|
42
|
+
"""Decorator to set format chain and optional endpoint metadata on a route.
|
|
43
|
+
|
|
44
|
+
- Attaches __format_chain__ to the endpoint for upstream processing/validation
|
|
45
|
+
- Ensures request.state.context exists and sets context.format_chain
|
|
46
|
+
- Optionally sets context.metadata["endpoint"] to the provided upstream endpoint path
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def decorator(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
|
|
50
|
+
func.__format_chain__ = list(formats) # type: ignore[attr-defined]
|
|
51
|
+
|
|
52
|
+
@wraps(func)
|
|
53
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
54
|
+
# Find Request in args/kwargs
|
|
55
|
+
request: Request | None = None
|
|
56
|
+
for arg in args:
|
|
57
|
+
if isinstance(arg, Request):
|
|
58
|
+
request = arg
|
|
59
|
+
break
|
|
60
|
+
if request is None:
|
|
61
|
+
req = kwargs.get("request")
|
|
62
|
+
if isinstance(req, Request):
|
|
63
|
+
request = req
|
|
64
|
+
|
|
65
|
+
if request is not None:
|
|
66
|
+
# Ensure a context exists
|
|
67
|
+
if (
|
|
68
|
+
not hasattr(request.state, "context")
|
|
69
|
+
or request.state.context is None
|
|
70
|
+
):
|
|
71
|
+
request.state.context = RequestContext(
|
|
72
|
+
request_id=str(uuid.uuid4()),
|
|
73
|
+
start_time=time.perf_counter(),
|
|
74
|
+
logger=_get_logger(__name__),
|
|
75
|
+
)
|
|
76
|
+
# Set chain and endpoint metadata
|
|
77
|
+
request.state.context.format_chain = list(formats)
|
|
78
|
+
if endpoint:
|
|
79
|
+
request.state.context.metadata["endpoint"] = endpoint
|
|
80
|
+
|
|
81
|
+
return await func(*args, **kwargs)
|
|
82
|
+
|
|
83
|
+
return wrapper
|
|
84
|
+
|
|
85
|
+
return decorator
|
ccproxy/api/dependencies.py
CHANGED
|
@@ -2,214 +2,190 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import TYPE_CHECKING, Annotated, Any, TypeVar
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
from
|
|
8
|
+
import httpx
|
|
9
|
+
from fastapi import Depends, HTTPException, Request
|
|
9
10
|
|
|
10
|
-
from ccproxy.config.settings import Settings
|
|
11
|
-
from ccproxy.core.
|
|
12
|
-
from ccproxy.
|
|
13
|
-
from ccproxy.
|
|
14
|
-
from ccproxy.services.
|
|
15
|
-
from ccproxy.services.
|
|
16
|
-
from ccproxy.services.proxy_service import ProxyService
|
|
11
|
+
from ccproxy.config.settings import Settings
|
|
12
|
+
from ccproxy.core.logging import get_logger
|
|
13
|
+
from ccproxy.core.plugins import PluginRegistry, ProviderPluginRuntime
|
|
14
|
+
from ccproxy.core.plugins.hooks import HookManager
|
|
15
|
+
from ccproxy.services.adapters.base import BaseAdapter
|
|
16
|
+
from ccproxy.services.container import ServiceContainer
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
pass
|
|
21
|
+
|
|
19
22
|
logger = get_logger(__name__)
|
|
20
23
|
|
|
24
|
+
T = TypeVar("T")
|
|
21
25
|
|
|
22
|
-
def get_cached_settings(request: Request) -> Settings:
|
|
23
|
-
"""Get cached settings from app state.
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
def get_service(service_type: type[T]) -> Callable[[Request], T]:
|
|
28
|
+
"""Return a dependency callable that fetches a service from the container."""
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
def _get_service(request: Request) -> T:
|
|
31
|
+
"""Get a service from the container."""
|
|
32
|
+
container: ServiceContainer | None = getattr(
|
|
33
|
+
request.app.state, "service_container", None
|
|
34
|
+
)
|
|
35
|
+
if container is None:
|
|
36
|
+
logger.error(
|
|
37
|
+
"service_container_missing_on_app_state",
|
|
38
|
+
category="lifecycle",
|
|
39
|
+
)
|
|
40
|
+
raise HTTPException(
|
|
41
|
+
status_code=503, detail="Service container not initialized"
|
|
42
|
+
)
|
|
43
|
+
return container.get_service(service_type)
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
Settings instance from app state
|
|
45
|
+
return _get_service
|
|
33
46
|
|
|
34
|
-
Raises:
|
|
35
|
-
RuntimeError: If settings are not available in app state
|
|
36
|
-
"""
|
|
37
|
-
settings = getattr(request.app.state, "settings", None)
|
|
38
|
-
if settings is None:
|
|
39
|
-
# Fallback to get_settings() for safety, but this should not happen
|
|
40
|
-
# in normal operation after lifespan startup
|
|
41
|
-
logger.warning(
|
|
42
|
-
"Settings not found in app state, falling back to get_settings()"
|
|
43
|
-
)
|
|
44
|
-
settings = get_settings()
|
|
45
|
-
return settings
|
|
46
47
|
|
|
48
|
+
def _resolve_service_container(request: Request) -> ServiceContainer | None:
|
|
49
|
+
"""Resolve a service container from the request or global context."""
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
container: ServiceContainer | None = getattr(
|
|
52
|
+
request.app.state, "service_container", None
|
|
53
|
+
)
|
|
54
|
+
if container is not None:
|
|
55
|
+
return container
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
return ServiceContainer.get_current(strict=False)
|
|
59
|
+
except RuntimeError:
|
|
60
|
+
# Should not happen with strict=False but guard defensively
|
|
61
|
+
return None
|
|
50
62
|
|
|
51
|
-
This avoids recreating the ClaudeSDKService on every request by using the
|
|
52
|
-
service instance created during application startup.
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
def get_cached_settings(request: Request) -> Settings:
|
|
65
|
+
"""Get cached settings from app state.
|
|
66
|
+
|
|
67
|
+
Raises a 503 HTTPException if no service container is available,
|
|
68
|
+
preserving the existing behaviour for required dependencies.
|
|
69
|
+
"""
|
|
70
|
+
return get_service(Settings)(request)
|
|
71
|
+
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
def get_optional_settings(request: Request) -> Settings | None:
|
|
74
|
+
"""Best-effort retrieval of settings for optional dependencies.
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
Returns a Settings instance if a service container is available.
|
|
77
|
+
Falls back to a new Settings object (with defaults) when running in
|
|
78
|
+
lightweight test contexts where the container is not initialised.
|
|
62
79
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
|
|
81
|
+
container = _resolve_service_container(request)
|
|
82
|
+
if container is not None:
|
|
83
|
+
try:
|
|
84
|
+
return container.get_service(Settings)
|
|
85
|
+
except ValueError:
|
|
86
|
+
logger.debug(
|
|
87
|
+
"settings_not_registered_in_container",
|
|
88
|
+
category="config",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
return Settings()
|
|
93
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
67
94
|
logger.warning(
|
|
68
|
-
"
|
|
95
|
+
"optional_settings_initialization_failed",
|
|
96
|
+
error=str(exc),
|
|
97
|
+
category="config",
|
|
69
98
|
)
|
|
70
|
-
|
|
71
|
-
settings = get_cached_settings(request)
|
|
99
|
+
return None
|
|
72
100
|
|
|
73
|
-
claude_service = get_claude_service(settings)
|
|
74
|
-
return claude_service
|
|
75
101
|
|
|
102
|
+
async def get_http_client(request: Request) -> httpx.AsyncClient:
|
|
103
|
+
"""Get container-managed HTTP client from the service container."""
|
|
104
|
+
return get_service(httpx.AsyncClient)(request)
|
|
76
105
|
|
|
77
|
-
# Type aliases for dependency injection
|
|
78
|
-
SettingsDep = Annotated[Settings, Depends(get_cached_settings)]
|
|
79
106
|
|
|
107
|
+
def get_hook_manager(request: Request) -> HookManager:
|
|
108
|
+
"""Get HookManager from the service container.
|
|
80
109
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
This dependency is required; if the hook system has not been initialized
|
|
111
|
+
the request will fail with 503 to reflect misconfigured startup order.
|
|
112
|
+
"""
|
|
113
|
+
return get_service(HookManager)(request)
|
|
85
114
|
|
|
86
|
-
Args:
|
|
87
|
-
settings: Application settings dependency
|
|
88
115
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"""
|
|
92
|
-
logger.debug("Creating Claude SDK service instance")
|
|
93
|
-
# Get global metrics instance
|
|
94
|
-
metrics = get_metrics()
|
|
95
|
-
|
|
96
|
-
# Check if pooling should be enabled from configuration
|
|
97
|
-
use_pool = settings.claude.sdk_session_pool.enabled
|
|
98
|
-
session_manager = None
|
|
99
|
-
|
|
100
|
-
if use_pool:
|
|
101
|
-
logger.info(
|
|
102
|
-
"claude_sdk_pool_enabled",
|
|
103
|
-
message="Using Claude SDK client pooling for improved performance",
|
|
104
|
-
pool_size=settings.claude.sdk_session_pool.max_sessions,
|
|
105
|
-
max_pool_size=settings.claude.sdk_session_pool.max_sessions,
|
|
106
|
-
)
|
|
107
|
-
# Note: Session manager should be created in the lifespan function, not here
|
|
108
|
-
# This dependency function should not create stateful resources
|
|
116
|
+
def get_plugin_adapter(plugin_name: str) -> Any:
|
|
117
|
+
"""Create a dependency function for a specific plugin's adapter."""
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
def _get_adapter(request: Request) -> BaseAdapter:
|
|
120
|
+
"""Get adapter for the specified plugin."""
|
|
121
|
+
if not hasattr(request.app.state, "plugin_registry"):
|
|
122
|
+
raise HTTPException(
|
|
123
|
+
status_code=503, detail="Plugin registry not initialized"
|
|
124
|
+
)
|
|
115
125
|
|
|
126
|
+
registry: PluginRegistry = request.app.state.plugin_registry
|
|
127
|
+
runtime = registry.get_runtime(plugin_name)
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
if not runtime:
|
|
130
|
+
raise HTTPException(
|
|
131
|
+
status_code=503, detail=f"Plugin {plugin_name} not initialized"
|
|
132
|
+
)
|
|
121
133
|
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
if not isinstance(runtime, ProviderPluginRuntime):
|
|
135
|
+
raise HTTPException(
|
|
136
|
+
status_code=503, detail=f"Plugin {plugin_name} is not a provider plugin"
|
|
137
|
+
)
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return CredentialsManager(config=settings.auth)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def get_proxy_service(
|
|
133
|
-
request: Request,
|
|
134
|
-
settings: SettingsDep,
|
|
135
|
-
credentials_manager: Annotated[
|
|
136
|
-
CredentialsManager, Depends(get_credentials_manager)
|
|
137
|
-
],
|
|
138
|
-
) -> ProxyService:
|
|
139
|
-
"""Get proxy service instance.
|
|
140
|
-
|
|
141
|
-
Args:
|
|
142
|
-
request: FastAPI request object (for app state access)
|
|
143
|
-
settings: Application settings dependency
|
|
144
|
-
credentials_manager: Credentials manager dependency
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
Proxy service instance
|
|
148
|
-
"""
|
|
149
|
-
logger.debug("get_proxy_service")
|
|
150
|
-
# Create HTTP client for proxy
|
|
151
|
-
from ccproxy.core.http import HTTPXClient
|
|
152
|
-
|
|
153
|
-
http_client = HTTPXClient()
|
|
154
|
-
proxy_client = BaseProxyClient(http_client)
|
|
155
|
-
|
|
156
|
-
# Get global metrics instance
|
|
157
|
-
metrics = get_metrics()
|
|
158
|
-
|
|
159
|
-
return ProxyService(
|
|
160
|
-
proxy_client=proxy_client,
|
|
161
|
-
credentials_manager=credentials_manager,
|
|
162
|
-
settings=settings,
|
|
163
|
-
proxy_mode="full",
|
|
164
|
-
target_base_url=settings.reverse_proxy.target_url,
|
|
165
|
-
metrics=metrics,
|
|
166
|
-
app_state=request.app.state, # Pass app state for detection data access
|
|
167
|
-
)
|
|
139
|
+
if not runtime.adapter:
|
|
140
|
+
raise HTTPException(
|
|
141
|
+
status_code=503, detail=f"Plugin {plugin_name} adapter not available"
|
|
142
|
+
)
|
|
168
143
|
|
|
144
|
+
adapter: BaseAdapter = runtime.adapter
|
|
145
|
+
return adapter
|
|
169
146
|
|
|
170
|
-
|
|
171
|
-
"""Get observability metrics instance.
|
|
147
|
+
return _get_adapter
|
|
172
148
|
|
|
173
|
-
Returns:
|
|
174
|
-
PrometheusMetrics instance
|
|
175
|
-
"""
|
|
176
|
-
logger.debug("get_observability_metrics")
|
|
177
|
-
return get_metrics()
|
|
178
149
|
|
|
150
|
+
def get_provider_config_dependency(
|
|
151
|
+
plugin_name: str, config_type: type[T]
|
|
152
|
+
) -> Callable[[Request], T]:
|
|
153
|
+
"""Return a dependency that fetches a provider plugin's validated config."""
|
|
179
154
|
|
|
180
|
-
|
|
181
|
-
|
|
155
|
+
def _get_config(request: Request) -> T:
|
|
156
|
+
if not hasattr(request.app.state, "plugin_registry"):
|
|
157
|
+
raise HTTPException(
|
|
158
|
+
status_code=503, detail="Plugin registry not initialized"
|
|
159
|
+
)
|
|
182
160
|
|
|
183
|
-
|
|
184
|
-
|
|
161
|
+
registry: PluginRegistry = request.app.state.plugin_registry
|
|
162
|
+
runtime = registry.get_runtime(plugin_name)
|
|
185
163
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
164
|
+
if not runtime or not isinstance(runtime, ProviderPluginRuntime):
|
|
165
|
+
raise HTTPException(
|
|
166
|
+
status_code=503, detail=f"Plugin {plugin_name} not initialized"
|
|
167
|
+
)
|
|
190
168
|
|
|
169
|
+
context = getattr(runtime, "context", None)
|
|
170
|
+
if not context:
|
|
171
|
+
raise HTTPException(
|
|
172
|
+
status_code=503,
|
|
173
|
+
detail=f"Plugin {plugin_name} configuration unavailable",
|
|
174
|
+
)
|
|
191
175
|
|
|
192
|
-
|
|
193
|
-
|
|
176
|
+
try:
|
|
177
|
+
config = context.get(config_type)
|
|
178
|
+
return config # type: ignore[no-any-return]
|
|
179
|
+
except ValueError as exc: # pragma: no cover - defensive guard
|
|
180
|
+
raise HTTPException(
|
|
181
|
+
status_code=503,
|
|
182
|
+
detail=f"Plugin {plugin_name} configuration not loaded",
|
|
183
|
+
) from exc
|
|
194
184
|
|
|
195
|
-
|
|
196
|
-
request: FastAPI request object
|
|
185
|
+
return _get_config
|
|
197
186
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if storage is None:
|
|
204
|
-
storage = getattr(request.app.state, "duckdb_storage", None)
|
|
205
|
-
return storage
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
# Type aliases for service dependencies
|
|
209
|
-
ClaudeServiceDep = Annotated[ClaudeSDKService, Depends(get_cached_claude_service)]
|
|
210
|
-
ProxyServiceDep = Annotated[ProxyService, Depends(get_proxy_service)]
|
|
211
|
-
ObservabilityMetricsDep = Annotated[
|
|
212
|
-
PrometheusMetrics, Depends(get_observability_metrics)
|
|
213
|
-
]
|
|
214
|
-
LogStorageDep = Annotated[SimpleDuckDBStorage | None, Depends(get_log_storage)]
|
|
215
|
-
DuckDBStorageDep = Annotated[SimpleDuckDBStorage | None, Depends(get_duckdb_storage)]
|
|
187
|
+
|
|
188
|
+
SettingsDep = Annotated[Settings, Depends(get_cached_settings)]
|
|
189
|
+
OptionalSettingsDep = Annotated[Settings | None, Depends(get_optional_settings)]
|
|
190
|
+
HTTPClientDep = Annotated[httpx.AsyncClient, Depends(get_http_client)]
|
|
191
|
+
HookManagerDep = Annotated[HookManager, Depends(get_hook_manager)]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Utilities for validating format adapter chains during application startup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
|
|
7
|
+
from ccproxy.core.logging import TraceBoundLogger
|
|
8
|
+
from ccproxy.services.adapters.chain_validation import (
|
|
9
|
+
validate_chains,
|
|
10
|
+
validate_stream_pairs,
|
|
11
|
+
)
|
|
12
|
+
from ccproxy.services.adapters.format_registry import FormatRegistry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def collect_declared_format_chains(app: FastAPI) -> list[list[str]]:
|
|
16
|
+
"""Collect declared format chains from FastAPI routes."""
|
|
17
|
+
|
|
18
|
+
chains: list[list[str]] = []
|
|
19
|
+
for route in app.router.routes:
|
|
20
|
+
endpoint = getattr(route, "endpoint", None)
|
|
21
|
+
chain = getattr(endpoint, "__format_chain__", None)
|
|
22
|
+
if chain:
|
|
23
|
+
chains.append(chain)
|
|
24
|
+
return chains
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_route_format_chains(
|
|
28
|
+
*,
|
|
29
|
+
app: FastAPI,
|
|
30
|
+
registry: FormatRegistry,
|
|
31
|
+
logger: TraceBoundLogger,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Validate format chains declared on routes against the format registry."""
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
declared_chains = collect_declared_format_chains(app)
|
|
37
|
+
if not declared_chains:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
missing = validate_chains(registry=registry, chains=declared_chains)
|
|
41
|
+
missing_stream = validate_stream_pairs(
|
|
42
|
+
registry=registry, chains=declared_chains
|
|
43
|
+
)
|
|
44
|
+
if missing or missing_stream:
|
|
45
|
+
logger.error(
|
|
46
|
+
"format_chain_validation_failed",
|
|
47
|
+
missing_adapters=missing,
|
|
48
|
+
missing_stream_adapters=missing_stream,
|
|
49
|
+
)
|
|
50
|
+
except Exception as exc: # pragma: no cover - defensive logging path
|
|
51
|
+
logger.warning("format_registry_setup_skipped", error=str(exc))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ["validate_route_format_chains", "collect_declared_format_chains"]
|
ccproxy/api/middleware/cors.py
CHANGED
|
@@ -4,9 +4,9 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from fastapi import FastAPI
|
|
6
6
|
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
-
from structlog import get_logger
|
|
8
7
|
|
|
9
8
|
from ccproxy.config.settings import Settings
|
|
9
|
+
from ccproxy.core.logging import get_logger
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
logger = get_logger(__name__)
|
|
@@ -19,7 +19,6 @@ def setup_cors_middleware(app: FastAPI, settings: Settings) -> None:
|
|
|
19
19
|
app: FastAPI application instance
|
|
20
20
|
settings: Application settings containing CORS configuration
|
|
21
21
|
"""
|
|
22
|
-
logger.debug("cors_middleware_setup_start")
|
|
23
22
|
|
|
24
23
|
app.add_middleware(
|
|
25
24
|
CORSMiddleware,
|
|
@@ -32,7 +31,11 @@ def setup_cors_middleware(app: FastAPI, settings: Settings) -> None:
|
|
|
32
31
|
max_age=settings.cors.max_age,
|
|
33
32
|
)
|
|
34
33
|
|
|
35
|
-
logger.debug(
|
|
34
|
+
logger.debug(
|
|
35
|
+
"cors_middleware_configured",
|
|
36
|
+
origins=settings.cors.origins,
|
|
37
|
+
category="middleware",
|
|
38
|
+
)
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
def get_cors_config(settings: Settings) -> dict[str, Any]:
|