ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0a4__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.0a4.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0a4.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.0a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,25 +9,34 @@ Key features:
|
|
|
9
9
|
- Accurate timing measurement using time.perf_counter()
|
|
10
10
|
- Request correlation with unique IDs
|
|
11
11
|
- Structured logging integration
|
|
12
|
-
- Async-safe context management
|
|
12
|
+
- Async-safe context management with contextvars
|
|
13
13
|
- Exception handling and error tracking
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import asyncio
|
|
19
|
+
import json
|
|
19
20
|
import time
|
|
20
21
|
import uuid
|
|
21
22
|
from collections.abc import AsyncGenerator
|
|
22
23
|
from contextlib import asynccontextmanager
|
|
24
|
+
from contextvars import ContextVar, Token
|
|
23
25
|
from dataclasses import dataclass, field
|
|
24
26
|
from datetime import UTC, datetime
|
|
25
27
|
from typing import Any
|
|
26
28
|
|
|
27
29
|
import structlog
|
|
28
30
|
|
|
31
|
+
from ccproxy.core.logging import TraceBoundLogger, get_logger
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
|
|
36
|
+
# Context variable for async-safe request context propagation
|
|
37
|
+
request_context_var: ContextVar[RequestContext | None] = ContextVar(
|
|
38
|
+
"request_context", default=None
|
|
39
|
+
)
|
|
31
40
|
|
|
32
41
|
|
|
33
42
|
@dataclass
|
|
@@ -41,10 +50,12 @@ class RequestContext:
|
|
|
41
50
|
|
|
42
51
|
request_id: str
|
|
43
52
|
start_time: float
|
|
44
|
-
logger: structlog.BoundLogger
|
|
53
|
+
logger: structlog.stdlib.BoundLogger | TraceBoundLogger
|
|
45
54
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
46
55
|
storage: Any | None = None # Optional DuckDB storage instance
|
|
47
56
|
log_timestamp: datetime | None = None # Datetime for consistent logging filenames
|
|
57
|
+
metrics: dict[str, Any] = field(default_factory=dict) # Request metrics storage
|
|
58
|
+
format_chain: list[str] | None = None # Format conversion chain
|
|
48
59
|
|
|
49
60
|
@property
|
|
50
61
|
def duration_ms(self) -> float:
|
|
@@ -80,6 +91,92 @@ class RequestContext:
|
|
|
80
91
|
# Fallback to current time if not set
|
|
81
92
|
return datetime.now(UTC).strftime("%Y%m%d%H%M%S")
|
|
82
93
|
|
|
94
|
+
def set_current(self) -> Token[RequestContext | None]:
|
|
95
|
+
"""Set this context as the current request context.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Token that can be used to restore the previous context
|
|
99
|
+
"""
|
|
100
|
+
return request_context_var.set(self)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def get_current() -> RequestContext | None:
|
|
104
|
+
"""Get the current request context from async context.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The current RequestContext or None if not set
|
|
108
|
+
"""
|
|
109
|
+
return request_context_var.get()
|
|
110
|
+
|
|
111
|
+
def clear_current(self, token: Token[RequestContext | None]) -> None:
|
|
112
|
+
"""Clear the current context and restore the previous one.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
token: The token returned by set_current()
|
|
116
|
+
"""
|
|
117
|
+
request_context_var.reset(token)
|
|
118
|
+
|
|
119
|
+
def to_dict(self) -> dict[str, Any]:
|
|
120
|
+
"""Serialize the context to a dictionary for JSON logging.
|
|
121
|
+
|
|
122
|
+
Returns all context data including:
|
|
123
|
+
- Request ID and timing information
|
|
124
|
+
- All metadata (costs, tokens, model, etc.)
|
|
125
|
+
- All metrics
|
|
126
|
+
- Computed properties (duration_ms, duration_seconds)
|
|
127
|
+
|
|
128
|
+
Excludes non-serializable fields like logger and storage.
|
|
129
|
+
"""
|
|
130
|
+
# Start with basic fields
|
|
131
|
+
data = {
|
|
132
|
+
"request_id": self.request_id,
|
|
133
|
+
"start_time": self.start_time,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Add computed timing properties
|
|
137
|
+
try:
|
|
138
|
+
data["duration_ms"] = self.duration_ms
|
|
139
|
+
data["duration_seconds"] = self.duration_seconds
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
# Add log timestamp if present
|
|
144
|
+
if self.log_timestamp:
|
|
145
|
+
try:
|
|
146
|
+
data["log_timestamp"] = self.log_timestamp.isoformat()
|
|
147
|
+
except Exception:
|
|
148
|
+
data["log_timestamp"] = str(self.log_timestamp)
|
|
149
|
+
|
|
150
|
+
# Add all metadata (includes costs, tokens, model info, etc.)
|
|
151
|
+
if self.metadata:
|
|
152
|
+
# Try to deep copy metadata to avoid reference issues
|
|
153
|
+
try:
|
|
154
|
+
# Ensure metadata is JSON serializable
|
|
155
|
+
data["metadata"] = json.loads(json.dumps(self.metadata, default=str))
|
|
156
|
+
except Exception:
|
|
157
|
+
data["metadata"] = self.metadata
|
|
158
|
+
|
|
159
|
+
# Add all metrics
|
|
160
|
+
if self.metrics:
|
|
161
|
+
try:
|
|
162
|
+
# Ensure metrics is JSON serializable
|
|
163
|
+
data["metrics"] = json.loads(json.dumps(self.metrics, default=str))
|
|
164
|
+
except Exception:
|
|
165
|
+
data["metrics"] = self.metrics
|
|
166
|
+
|
|
167
|
+
return data
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def get_request_event_stream() -> AsyncGenerator[dict[str, Any], None]:
|
|
171
|
+
"""Async generator for request events used by analytics streaming.
|
|
172
|
+
|
|
173
|
+
This is a lightweight stub for type-checking and optional runtime use.
|
|
174
|
+
Integrations can replace or wrap this to provide actual event streams.
|
|
175
|
+
"""
|
|
176
|
+
# Empty async generator
|
|
177
|
+
for _ in ():
|
|
178
|
+
yield {}
|
|
179
|
+
|
|
83
180
|
|
|
84
181
|
@asynccontextmanager
|
|
85
182
|
async def request_context(
|
|
@@ -125,8 +222,7 @@ async def request_context(
|
|
|
125
222
|
"request_start", request_id=request_id, timestamp=time.time(), **initial_context
|
|
126
223
|
)
|
|
127
224
|
|
|
128
|
-
#
|
|
129
|
-
await _emit_request_start_event(request_id, initial_context)
|
|
225
|
+
# SSE events removed - functionality moved to plugins
|
|
130
226
|
|
|
131
227
|
# Increment active requests if metrics provided
|
|
132
228
|
if metrics:
|
|
@@ -142,31 +238,31 @@ async def request_context(
|
|
|
142
238
|
log_timestamp=log_timestamp,
|
|
143
239
|
)
|
|
144
240
|
|
|
241
|
+
# Set as current context for async propagation
|
|
242
|
+
token = ctx.set_current()
|
|
243
|
+
|
|
145
244
|
try:
|
|
146
245
|
yield ctx
|
|
147
246
|
|
|
148
247
|
# Log successful completion with comprehensive access log
|
|
149
248
|
duration_ms = ctx.duration_ms
|
|
150
249
|
|
|
151
|
-
#
|
|
152
|
-
|
|
250
|
+
# Also keep the original request_success event for debugging
|
|
251
|
+
# Merge metadata, avoiding duplicates
|
|
252
|
+
success_log_data = {
|
|
253
|
+
"request_id": request_id,
|
|
254
|
+
"duration_ms": duration_ms,
|
|
255
|
+
"duration_seconds": ctx.duration_seconds,
|
|
256
|
+
}
|
|
153
257
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
user_agent=ctx.metadata.get("user_agent"),
|
|
159
|
-
query=ctx.metadata.get("query"),
|
|
160
|
-
storage=ctx.storage, # Pass storage from context
|
|
161
|
-
)
|
|
258
|
+
# Add metadata, avoiding duplicates
|
|
259
|
+
for key, value in ctx.metadata.items():
|
|
260
|
+
if key not in ("duration_ms", "duration_seconds", "request_id"):
|
|
261
|
+
success_log_data[key] = value
|
|
162
262
|
|
|
163
|
-
# Also keep the original request_success event for debugging
|
|
164
263
|
request_logger.debug(
|
|
165
264
|
"request_success",
|
|
166
|
-
|
|
167
|
-
duration_ms=duration_ms,
|
|
168
|
-
duration_seconds=ctx.duration_seconds,
|
|
169
|
-
**ctx.metadata,
|
|
265
|
+
**success_log_data,
|
|
170
266
|
)
|
|
171
267
|
|
|
172
268
|
except Exception as e:
|
|
@@ -174,22 +270,34 @@ async def request_context(
|
|
|
174
270
|
duration_ms = ctx.duration_ms
|
|
175
271
|
error_type = type(e).__name__
|
|
176
272
|
|
|
273
|
+
# Merge metadata but ensure no duplicate duration fields
|
|
274
|
+
log_data = {
|
|
275
|
+
"request_id": request_id,
|
|
276
|
+
"duration_ms": duration_ms,
|
|
277
|
+
"duration_seconds": ctx.duration_seconds,
|
|
278
|
+
"error_type": error_type,
|
|
279
|
+
"error_message": str(e),
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Add metadata, avoiding duplicates
|
|
283
|
+
for key, value in ctx.metadata.items():
|
|
284
|
+
if key not in ("duration_ms", "duration_seconds"):
|
|
285
|
+
log_data[key] = value
|
|
286
|
+
|
|
177
287
|
request_logger.error(
|
|
178
288
|
"request_error",
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
duration_seconds=ctx.duration_seconds,
|
|
182
|
-
error_type=error_type,
|
|
183
|
-
error_message=str(e),
|
|
184
|
-
**ctx.metadata,
|
|
289
|
+
exc_info=e,
|
|
290
|
+
**log_data,
|
|
185
291
|
)
|
|
186
292
|
|
|
187
|
-
#
|
|
188
|
-
await _emit_request_error_event(request_id, error_type, str(e), ctx.metadata)
|
|
293
|
+
# SSE events removed - functionality moved to plugins
|
|
189
294
|
|
|
190
295
|
# Re-raise the exception
|
|
191
296
|
raise
|
|
192
297
|
finally:
|
|
298
|
+
# Clear the current context
|
|
299
|
+
ctx.clear_current(token)
|
|
300
|
+
|
|
193
301
|
# Decrement active requests if metrics provided
|
|
194
302
|
if metrics:
|
|
195
303
|
metrics.dec_active_requests()
|
|
@@ -273,6 +381,7 @@ async def timed_operation(
|
|
|
273
381
|
duration_ms=duration_ms,
|
|
274
382
|
error_type=error_type,
|
|
275
383
|
error_message=str(e),
|
|
384
|
+
exc_info=e,
|
|
276
385
|
**{
|
|
277
386
|
k: v for k, v in op_context.items() if k not in ("logger", "start_time")
|
|
278
387
|
},
|
|
@@ -395,69 +504,3 @@ async def tracked_request_context(
|
|
|
395
504
|
finally:
|
|
396
505
|
# Remove from tracker
|
|
397
506
|
await tracker.remove_context(ctx.request_id)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
async def _emit_request_start_event(
|
|
401
|
-
request_id: str, initial_context: dict[str, Any]
|
|
402
|
-
) -> None:
|
|
403
|
-
"""Emit SSE event for request start."""
|
|
404
|
-
try:
|
|
405
|
-
from ccproxy.observability.sse_events import emit_sse_event
|
|
406
|
-
|
|
407
|
-
# Create event data for SSE
|
|
408
|
-
sse_data = {
|
|
409
|
-
"request_id": request_id,
|
|
410
|
-
"method": initial_context.get("method"),
|
|
411
|
-
"path": initial_context.get("path"),
|
|
412
|
-
"client_ip": initial_context.get("client_ip"),
|
|
413
|
-
"user_agent": initial_context.get("user_agent"),
|
|
414
|
-
"query": initial_context.get("query"),
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
# Remove None values
|
|
418
|
-
sse_data = {k: v for k, v in sse_data.items() if v is not None}
|
|
419
|
-
|
|
420
|
-
await emit_sse_event("request_start", sse_data)
|
|
421
|
-
|
|
422
|
-
except Exception as e:
|
|
423
|
-
# Log error but don't fail the request
|
|
424
|
-
logger.debug(
|
|
425
|
-
"sse_emit_failed",
|
|
426
|
-
event_type="request_start",
|
|
427
|
-
error=str(e),
|
|
428
|
-
request_id=request_id,
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
async def _emit_request_error_event(
|
|
433
|
-
request_id: str, error_type: str, error_message: str, metadata: dict[str, Any]
|
|
434
|
-
) -> None:
|
|
435
|
-
"""Emit SSE event for request error."""
|
|
436
|
-
try:
|
|
437
|
-
from ccproxy.observability.sse_events import emit_sse_event
|
|
438
|
-
|
|
439
|
-
# Create event data for SSE
|
|
440
|
-
sse_data = {
|
|
441
|
-
"request_id": request_id,
|
|
442
|
-
"error_type": error_type,
|
|
443
|
-
"error_message": error_message,
|
|
444
|
-
"method": metadata.get("method"),
|
|
445
|
-
"path": metadata.get("path"),
|
|
446
|
-
"client_ip": metadata.get("client_ip"),
|
|
447
|
-
"user_agent": metadata.get("user_agent"),
|
|
448
|
-
"query": metadata.get("query"),
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
# Remove None values
|
|
452
|
-
sse_data = {k: v for k, v in sse_data.items() if v is not None}
|
|
453
|
-
|
|
454
|
-
await emit_sse_event("request_error", sse_data)
|
|
455
|
-
|
|
456
|
-
except Exception as e:
|
|
457
|
-
# Log error but don't fail the request
|
|
458
|
-
logger.debug(
|
|
459
|
-
"sse_emit_failed",
|
|
460
|
-
event_type="request_error",
|
|
461
|
-
error=str(e),
|
|
462
|
-
request_id=request_id,
|
|
463
|
-
)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Helpers for collecting status information used across interfaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from ccproxy.config.settings import Settings
|
|
11
|
+
from ccproxy.core.logging import get_logger
|
|
12
|
+
from ccproxy.core.plugins.discovery import discover_and_load_plugins
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class DirectoryStatus:
|
|
20
|
+
"""Represents availability of a directory."""
|
|
21
|
+
|
|
22
|
+
path: Path
|
|
23
|
+
exists: bool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class SystemSnapshot:
|
|
28
|
+
"""Summary of system-level settings."""
|
|
29
|
+
|
|
30
|
+
host: str
|
|
31
|
+
port: int
|
|
32
|
+
log_level: str
|
|
33
|
+
auth_token_configured: bool
|
|
34
|
+
plugins_enabled: bool
|
|
35
|
+
plugin_directories: tuple[DirectoryStatus, ...]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class ConfigSource:
|
|
40
|
+
"""Represents a potential configuration file location."""
|
|
41
|
+
|
|
42
|
+
path: Path
|
|
43
|
+
exists: bool
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class ConfigSnapshot:
|
|
48
|
+
"""Collected configuration source information."""
|
|
49
|
+
|
|
50
|
+
sources: tuple[ConfigSource, ...]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class PluginInfo:
|
|
55
|
+
"""Represents the status of an individual plugin."""
|
|
56
|
+
|
|
57
|
+
name: str
|
|
58
|
+
state: Literal["enabled", "error"]
|
|
59
|
+
version: str | None
|
|
60
|
+
description: str | None
|
|
61
|
+
error: str | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class PluginSnapshot:
|
|
66
|
+
"""Collection of plugin discovery results."""
|
|
67
|
+
|
|
68
|
+
plugin_system_enabled: bool
|
|
69
|
+
enabled_plugins: tuple[PluginInfo, ...]
|
|
70
|
+
disabled_plugins: tuple[str, ...]
|
|
71
|
+
configuration_notes: tuple[str, ...]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def enabled_count(self) -> int:
|
|
75
|
+
return len(self.enabled_plugins)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def disabled_count(self) -> int:
|
|
79
|
+
return len(self.disabled_plugins)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def total_count(self) -> int:
|
|
83
|
+
return self.enabled_count + self.disabled_count
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def collect_system_snapshot(settings: Settings) -> SystemSnapshot:
|
|
87
|
+
"""Build a system snapshot from settings."""
|
|
88
|
+
directories: list[DirectoryStatus] = []
|
|
89
|
+
for directory in settings.plugin_discovery.directories:
|
|
90
|
+
dir_path = Path(directory)
|
|
91
|
+
directories.append(DirectoryStatus(path=dir_path, exists=dir_path.exists()))
|
|
92
|
+
|
|
93
|
+
return SystemSnapshot(
|
|
94
|
+
host=str(settings.server.host),
|
|
95
|
+
port=int(settings.server.port),
|
|
96
|
+
log_level=settings.logging.level.upper(),
|
|
97
|
+
auth_token_configured=bool(settings.security.auth_token),
|
|
98
|
+
plugins_enabled=bool(settings.enable_plugins),
|
|
99
|
+
plugin_directories=tuple(directories),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def collect_config_snapshot(*, cwd: Path | None = None) -> ConfigSnapshot:
|
|
104
|
+
"""Inspect common configuration locations relative to the current working directory."""
|
|
105
|
+
effective_cwd = cwd or Path.cwd()
|
|
106
|
+
candidates: Iterable[Path] = (
|
|
107
|
+
effective_cwd / ".ccproxy.toml",
|
|
108
|
+
effective_cwd / "ccproxy.toml",
|
|
109
|
+
Path.home() / ".ccproxy" / "config.toml",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
sources = tuple(
|
|
113
|
+
ConfigSource(path=path, exists=path.exists()) for path in candidates
|
|
114
|
+
)
|
|
115
|
+
return ConfigSnapshot(sources=sources)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def collect_plugin_snapshot(settings: Settings) -> PluginSnapshot:
|
|
119
|
+
"""Discover plugins and report basic status information."""
|
|
120
|
+
if not settings.enable_plugins:
|
|
121
|
+
notes = _collect_plugin_configuration_notes(settings)
|
|
122
|
+
return PluginSnapshot(
|
|
123
|
+
plugin_system_enabled=False,
|
|
124
|
+
enabled_plugins=(),
|
|
125
|
+
disabled_plugins=(),
|
|
126
|
+
configuration_notes=notes,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
plugin_infos: list[PluginInfo] = []
|
|
130
|
+
try:
|
|
131
|
+
plugin_factories = discover_and_load_plugins(settings)
|
|
132
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
133
|
+
logger.error("plugin_discovery_failed", error=str(exc), exc_info=exc)
|
|
134
|
+
return PluginSnapshot(
|
|
135
|
+
plugin_system_enabled=True,
|
|
136
|
+
enabled_plugins=(),
|
|
137
|
+
disabled_plugins=(),
|
|
138
|
+
configuration_notes=_collect_plugin_configuration_notes(settings),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for name, factory in sorted(plugin_factories.items()):
|
|
142
|
+
try:
|
|
143
|
+
manifest = factory.get_manifest()
|
|
144
|
+
plugin_infos.append(
|
|
145
|
+
PluginInfo(
|
|
146
|
+
name=name,
|
|
147
|
+
state="enabled",
|
|
148
|
+
version=manifest.version,
|
|
149
|
+
description=manifest.description,
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
except Exception as exc:
|
|
153
|
+
error_text = str(exc)
|
|
154
|
+
logger.error(
|
|
155
|
+
"plugin_manifest_failed", plugin=name, error=error_text, exc_info=exc
|
|
156
|
+
)
|
|
157
|
+
plugin_infos.append(
|
|
158
|
+
PluginInfo(
|
|
159
|
+
name=name,
|
|
160
|
+
state="error",
|
|
161
|
+
version=None,
|
|
162
|
+
description=None,
|
|
163
|
+
error=error_text,
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
disabled_plugins = tuple(
|
|
168
|
+
sorted(_find_disabled_plugins(settings, set(plugin_factories.keys())))
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return PluginSnapshot(
|
|
172
|
+
plugin_system_enabled=True,
|
|
173
|
+
enabled_plugins=tuple(plugin_infos),
|
|
174
|
+
disabled_plugins=disabled_plugins,
|
|
175
|
+
configuration_notes=_collect_plugin_configuration_notes(settings),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _collect_plugin_configuration_notes(settings: Settings) -> tuple[str, ...]:
|
|
180
|
+
notes: list[str] = []
|
|
181
|
+
if settings.plugins_disable_local_discovery:
|
|
182
|
+
notes.append("Local discovery disabled")
|
|
183
|
+
if settings.disabled_plugins:
|
|
184
|
+
notes.append(f"Explicitly disabled: {len(settings.disabled_plugins)}")
|
|
185
|
+
if settings.enabled_plugins:
|
|
186
|
+
notes.append(f"Allow-list active: {len(settings.enabled_plugins)} allowed")
|
|
187
|
+
return tuple(notes)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _find_disabled_plugins(settings: Settings, enabled_plugins: set[str]) -> set[str]:
|
|
191
|
+
"""Find plugins that exist but are disabled in the configuration."""
|
|
192
|
+
disabled: set[str] = set()
|
|
193
|
+
|
|
194
|
+
if settings.plugins_disable_local_discovery:
|
|
195
|
+
return disabled
|
|
196
|
+
|
|
197
|
+
for plugin_dir_path in settings.plugin_discovery.directories:
|
|
198
|
+
plugin_dir = Path(plugin_dir_path)
|
|
199
|
+
if not plugin_dir.exists():
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
for item in plugin_dir.iterdir():
|
|
203
|
+
if not item.is_dir() or item.name.startswith("_"):
|
|
204
|
+
continue
|
|
205
|
+
plugin_file = item / "plugin.py"
|
|
206
|
+
if not plugin_file.exists():
|
|
207
|
+
continue
|
|
208
|
+
if item.name not in enabled_plugins:
|
|
209
|
+
disabled.add(item.name)
|
|
210
|
+
|
|
211
|
+
return disabled
|
ccproxy/core/transformers.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Core transformer abstractions for request/response transformation."""
|
|
2
2
|
|
|
3
|
+
import time
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, runtime_checkable
|
|
5
6
|
|
|
6
|
-
from
|
|
7
|
-
|
|
7
|
+
from ccproxy.core.logging import get_logger
|
|
8
8
|
from ccproxy.core.types import ProxyRequest, ProxyResponse, TransformContext
|
|
9
9
|
|
|
10
10
|
|
|
@@ -65,8 +65,8 @@ class BaseTransformer(ABC):
|
|
|
65
65
|
|
|
66
66
|
try:
|
|
67
67
|
# Calculate data sizes
|
|
68
|
-
input_size = self._calculate_data_size(input_data)
|
|
69
|
-
output_size = self._calculate_data_size(output_data) if output_data else 0
|
|
68
|
+
# input_size = self._calculate_data_size(input_data)
|
|
69
|
+
# output_size = self._calculate_data_size(output_data) if output_data else 0
|
|
70
70
|
|
|
71
71
|
# Create a unique request ID for this transformation
|
|
72
72
|
request_id = (
|
|
@@ -80,6 +80,15 @@ class BaseTransformer(ABC):
|
|
|
80
80
|
processing_time=duration_ms,
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
+
except (AttributeError, TypeError) as e:
|
|
84
|
+
# Don't let metrics collection fail the transformation
|
|
85
|
+
logger = get_logger(__name__)
|
|
86
|
+
# logger = logging.getLogger(__name__)
|
|
87
|
+
logger.debug(
|
|
88
|
+
"transformation_metrics_attribute_error",
|
|
89
|
+
error=str(e),
|
|
90
|
+
exc_info=e,
|
|
91
|
+
)
|
|
83
92
|
except Exception as e:
|
|
84
93
|
# Don't let metrics collection fail the transformation
|
|
85
94
|
logger = get_logger(__name__)
|
|
@@ -131,8 +140,6 @@ class RequestTransformer(BaseTransformer):
|
|
|
131
140
|
Returns:
|
|
132
141
|
The transformed request
|
|
133
142
|
"""
|
|
134
|
-
import time
|
|
135
|
-
|
|
136
143
|
start_time = time.perf_counter()
|
|
137
144
|
error_msg = None
|
|
138
145
|
result = None
|
|
@@ -186,8 +193,6 @@ class ResponseTransformer(BaseTransformer):
|
|
|
186
193
|
Returns:
|
|
187
194
|
The transformed response
|
|
188
195
|
"""
|
|
189
|
-
import time
|
|
190
|
-
|
|
191
196
|
start_time = time.perf_counter()
|
|
192
197
|
error_msg = None
|
|
193
198
|
result = None
|