ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +434 -219
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +144 -168
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +388 -524
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +540 -19
- ccproxy/data/codex_headers_fallback.json +114 -7
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +61 -105
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +268 -276
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +68 -446
- ccproxy/utils/version_checker.py +273 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1251
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -243
- ccproxy/services/codex_detection_service.py +0 -252
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.7.dist-info/METADATA +0 -615
- ccproxy_api-0.1.7.dist-info/RECORD +0 -191
- ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Central registry for all hooks"""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
from sortedcontainers import SortedList # type: ignore[import-untyped]
|
|
8
|
+
|
|
9
|
+
from .base import Hook
|
|
10
|
+
from .events import HookEvent
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HookRegistry:
|
|
14
|
+
"""Central registry for all hooks with priority-based ordering."""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
# Use SortedList for automatic priority ordering
|
|
18
|
+
# Key function sorts by (priority, registration_order)
|
|
19
|
+
self._hooks: dict[HookEvent, Any] = defaultdict(
|
|
20
|
+
lambda: SortedList(
|
|
21
|
+
key=lambda h: (
|
|
22
|
+
getattr(h, "priority", 500),
|
|
23
|
+
self._registration_order.get(h, 0),
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
self._registration_order: dict[Hook, int] = {}
|
|
28
|
+
self._next_order = 0
|
|
29
|
+
self._logger = structlog.get_logger(__name__)
|
|
30
|
+
# Batch logging for registration/unregistration
|
|
31
|
+
self._pending_registrations: list[tuple[str, str, int]] = []
|
|
32
|
+
self._pending_unregistrations: list[tuple[str, str]] = []
|
|
33
|
+
|
|
34
|
+
def register(self, hook: Hook) -> None:
|
|
35
|
+
"""Register a hook for its events with priority ordering"""
|
|
36
|
+
priority = getattr(
|
|
37
|
+
hook, "priority", 500
|
|
38
|
+
) # Default priority for backward compatibility
|
|
39
|
+
|
|
40
|
+
# Track registration order for stable sorting
|
|
41
|
+
if hook not in self._registration_order:
|
|
42
|
+
self._registration_order[hook] = self._next_order
|
|
43
|
+
self._next_order += 1
|
|
44
|
+
|
|
45
|
+
events_registered = []
|
|
46
|
+
for event in hook.events:
|
|
47
|
+
self._hooks[event].add(hook)
|
|
48
|
+
event_name = event.value if hasattr(event, "value") else str(event)
|
|
49
|
+
events_registered.append(event_name)
|
|
50
|
+
# Log individual registrations at DEBUG level
|
|
51
|
+
# self._logger.debug(
|
|
52
|
+
# "hook_registered",
|
|
53
|
+
# name=hook.name,
|
|
54
|
+
# hook_event=event_name,
|
|
55
|
+
# priority=priority,
|
|
56
|
+
# )
|
|
57
|
+
|
|
58
|
+
# Log summary at DEBUG; a global summary will be logged elsewhere at INFO
|
|
59
|
+
if len(events_registered) > 0:
|
|
60
|
+
self._logger.debug(
|
|
61
|
+
"hook_registered",
|
|
62
|
+
name=hook.name,
|
|
63
|
+
events=events_registered,
|
|
64
|
+
event_count=len(events_registered),
|
|
65
|
+
priority=priority,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def unregister(self, hook: Hook) -> None:
|
|
69
|
+
"""Remove a hook from all events"""
|
|
70
|
+
events_unregistered = []
|
|
71
|
+
for event in hook.events:
|
|
72
|
+
try:
|
|
73
|
+
self._hooks[event].remove(hook)
|
|
74
|
+
event_name = event.value if hasattr(event, "value") else str(event)
|
|
75
|
+
events_unregistered.append(event_name)
|
|
76
|
+
# Log individual unregistrations at DEBUG level
|
|
77
|
+
# self._logger.debug(
|
|
78
|
+
# "hook_unregistered",
|
|
79
|
+
# name=hook.name,
|
|
80
|
+
# hook_event=event_name,
|
|
81
|
+
# )
|
|
82
|
+
except ValueError:
|
|
83
|
+
pass # Hook not in list, ignore
|
|
84
|
+
|
|
85
|
+
# Log summary at INFO level only if multiple events
|
|
86
|
+
if len(events_unregistered) > 1:
|
|
87
|
+
self._logger.info(
|
|
88
|
+
"hook_unregistered_summary",
|
|
89
|
+
name=hook.name,
|
|
90
|
+
events=events_unregistered,
|
|
91
|
+
event_count=len(events_unregistered),
|
|
92
|
+
)
|
|
93
|
+
elif events_unregistered:
|
|
94
|
+
# Single event - log at DEBUG level to reduce verbosity
|
|
95
|
+
self._logger.debug(
|
|
96
|
+
"hook_unregistered_single",
|
|
97
|
+
name=hook.name,
|
|
98
|
+
hook_event=events_unregistered[0],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Clean up registration order tracking
|
|
102
|
+
if hook in self._registration_order:
|
|
103
|
+
del self._registration_order[hook]
|
|
104
|
+
|
|
105
|
+
def get(self, event: HookEvent) -> list[Hook]:
|
|
106
|
+
"""Get all hooks for an event in priority order"""
|
|
107
|
+
return list(self._hooks.get(event, []))
|
|
108
|
+
|
|
109
|
+
def list(self) -> dict[str, list[dict[str, Any]]]:
|
|
110
|
+
"""Get summary of all registered hooks organized by event.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dictionary mapping event names to lists of hook info
|
|
114
|
+
"""
|
|
115
|
+
summary = {}
|
|
116
|
+
for event, hooks in self._hooks.items():
|
|
117
|
+
event_name = event.value if hasattr(event, "value") else str(event)
|
|
118
|
+
summary[event_name] = [
|
|
119
|
+
{
|
|
120
|
+
"name": hook.name,
|
|
121
|
+
"priority": getattr(hook, "priority", 500),
|
|
122
|
+
}
|
|
123
|
+
for hook in hooks
|
|
124
|
+
]
|
|
125
|
+
return summary
|
|
126
|
+
|
|
127
|
+
def has(self, event: HookEvent) -> bool:
|
|
128
|
+
"""Check if any hook is registered for the event."""
|
|
129
|
+
hooks = self._hooks.get(event)
|
|
130
|
+
return bool(hooks and len(hooks) > 0)
|
|
131
|
+
|
|
132
|
+
def clear(self) -> None:
|
|
133
|
+
"""Clear all registered hooks and reset ordering (testing or shutdown)."""
|
|
134
|
+
self._hooks.clear()
|
|
135
|
+
self._registration_order.clear()
|
|
136
|
+
self._next_order = 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Module-level accessor intentionally omitted.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Background thread manager for async hook execution."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import threading
|
|
5
|
+
import uuid
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
|
|
12
|
+
from .base import Hook, HookContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = structlog.get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class HookTask:
|
|
20
|
+
"""Represents a hook execution task."""
|
|
21
|
+
|
|
22
|
+
context: HookContext
|
|
23
|
+
task_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
24
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BackgroundHookThreadManager:
|
|
28
|
+
"""Manages a dedicated async thread for hook execution."""
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
"""Initialize the background thread manager."""
|
|
32
|
+
self._loop: asyncio.AbstractEventLoop | None = None
|
|
33
|
+
self._thread: threading.Thread | None = None
|
|
34
|
+
self._queue: asyncio.Queue[tuple[HookTask, Any]] | None = None
|
|
35
|
+
self._shutdown_event: asyncio.Event | None = None
|
|
36
|
+
self._running = False
|
|
37
|
+
self._logger = logger.bind(component="background_hook_thread")
|
|
38
|
+
# Signals when the background loop and its resources are ready
|
|
39
|
+
self._ready_event: threading.Event | None = None
|
|
40
|
+
|
|
41
|
+
def start(self) -> None:
|
|
42
|
+
"""Start the background thread with its own event loop."""
|
|
43
|
+
if self._running:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# Create readiness event so callers can safely enqueue without sleeps
|
|
47
|
+
self._ready_event = threading.Event()
|
|
48
|
+
|
|
49
|
+
self._thread = threading.Thread(
|
|
50
|
+
target=self._run_background_loop, name="hook-background-thread", daemon=True
|
|
51
|
+
)
|
|
52
|
+
self._thread.start()
|
|
53
|
+
|
|
54
|
+
# Block briefly until the background loop has initialized its resources
|
|
55
|
+
if self._ready_event and not self._ready_event.wait(timeout=1.0):
|
|
56
|
+
self._logger.warning("background_hook_thread_startup_timeout")
|
|
57
|
+
self._running = True
|
|
58
|
+
|
|
59
|
+
self._logger.debug("background_hook_thread_started")
|
|
60
|
+
|
|
61
|
+
def stop(self, timeout: float = 5.0) -> None:
|
|
62
|
+
"""Gracefully shutdown the background thread."""
|
|
63
|
+
if not self._running:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
self._logger.debug("stopping_background_hook_thread")
|
|
67
|
+
|
|
68
|
+
# Signal shutdown to the background loop
|
|
69
|
+
if self._loop and self._shutdown_event:
|
|
70
|
+
self._loop.call_soon_threadsafe(self._shutdown_event.set)
|
|
71
|
+
|
|
72
|
+
# Wait for thread to complete
|
|
73
|
+
if self._thread:
|
|
74
|
+
self._thread.join(timeout=timeout)
|
|
75
|
+
if self._thread.is_alive():
|
|
76
|
+
self._logger.warning("background_thread_shutdown_timeout")
|
|
77
|
+
|
|
78
|
+
self._running = False
|
|
79
|
+
self._loop = None
|
|
80
|
+
self._thread = None
|
|
81
|
+
self._queue = None
|
|
82
|
+
self._shutdown_event = None
|
|
83
|
+
self._ready_event = None
|
|
84
|
+
|
|
85
|
+
self._logger.debug("background_hook_thread_stopped")
|
|
86
|
+
|
|
87
|
+
def emit_async(self, context: HookContext, registry: Any) -> None:
|
|
88
|
+
"""Queue a hook task for background execution.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
context: Hook context to execute
|
|
92
|
+
registry: Hook registry to get hooks from
|
|
93
|
+
"""
|
|
94
|
+
if not self._running:
|
|
95
|
+
self.start()
|
|
96
|
+
|
|
97
|
+
if not self._loop or not self._queue:
|
|
98
|
+
self._logger.warning("background_thread_not_ready_dropping_task")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
task = HookTask(context=context)
|
|
102
|
+
|
|
103
|
+
# Add task to queue in a thread-safe way
|
|
104
|
+
try:
|
|
105
|
+
self._loop.call_soon_threadsafe(self._add_task_to_queue, task, registry)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
self._logger.error("failed_to_queue_hook_task", error=str(e))
|
|
108
|
+
|
|
109
|
+
def _add_task_to_queue(self, task: HookTask, registry: Any) -> None:
|
|
110
|
+
"""Add task to queue (called from background thread)."""
|
|
111
|
+
if self._queue:
|
|
112
|
+
try:
|
|
113
|
+
self._queue.put_nowait((task, registry))
|
|
114
|
+
except asyncio.QueueFull:
|
|
115
|
+
self._logger.warning("hook_task_queue_full_dropping_task")
|
|
116
|
+
|
|
117
|
+
def _run_background_loop(self) -> None:
|
|
118
|
+
"""Run the background event loop for hook processing."""
|
|
119
|
+
try:
|
|
120
|
+
# Create new event loop for this thread
|
|
121
|
+
self._loop = asyncio.new_event_loop()
|
|
122
|
+
asyncio.set_event_loop(self._loop)
|
|
123
|
+
|
|
124
|
+
# Create queue and shutdown event
|
|
125
|
+
self._queue = asyncio.Queue[tuple[HookTask, Any]](maxsize=1000)
|
|
126
|
+
self._shutdown_event = asyncio.Event()
|
|
127
|
+
|
|
128
|
+
# Signal to the starter that we're ready to accept tasks
|
|
129
|
+
if self._ready_event:
|
|
130
|
+
self._ready_event.set()
|
|
131
|
+
|
|
132
|
+
# Run the processing loop
|
|
133
|
+
self._loop.run_until_complete(self._process_tasks())
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error("background_hook_thread_error", error=str(e))
|
|
136
|
+
finally:
|
|
137
|
+
if self._loop:
|
|
138
|
+
self._loop.close()
|
|
139
|
+
|
|
140
|
+
async def _process_tasks(self) -> None:
|
|
141
|
+
"""Main task processing loop."""
|
|
142
|
+
self._logger.debug("background_hook_processor_started")
|
|
143
|
+
|
|
144
|
+
while self._shutdown_event and not self._shutdown_event.is_set():
|
|
145
|
+
try:
|
|
146
|
+
# Wait for either a task or shutdown signal
|
|
147
|
+
if not self._queue:
|
|
148
|
+
break
|
|
149
|
+
task_data = await asyncio.wait_for(self._queue.get(), timeout=0.1)
|
|
150
|
+
|
|
151
|
+
task, registry = task_data
|
|
152
|
+
await self._execute_task(task, registry)
|
|
153
|
+
|
|
154
|
+
except TimeoutError:
|
|
155
|
+
# Normal timeout, continue loop
|
|
156
|
+
continue
|
|
157
|
+
except Exception as e:
|
|
158
|
+
self._logger.error("hook_task_processing_error", error=str(e))
|
|
159
|
+
|
|
160
|
+
self._logger.debug("background_hook_processor_stopped")
|
|
161
|
+
|
|
162
|
+
async def _execute_task(self, task: HookTask, registry: Any) -> None:
|
|
163
|
+
"""Execute a single hook task.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
task: The hook task to execute
|
|
167
|
+
registry: Hook registry to get hooks from
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
hooks = registry.get(task.context.event)
|
|
171
|
+
if not hooks:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
# Execute all hooks for this event
|
|
175
|
+
for hook in hooks:
|
|
176
|
+
try:
|
|
177
|
+
await self._execute_hook(hook, task.context)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
self._logger.error(
|
|
180
|
+
"background_hook_execution_failed",
|
|
181
|
+
hook=hook.name,
|
|
182
|
+
event_type=task.context.event.value
|
|
183
|
+
if hasattr(task.context.event, "value")
|
|
184
|
+
else str(task.context.event),
|
|
185
|
+
error=str(e),
|
|
186
|
+
task_id=task.task_id,
|
|
187
|
+
)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self._logger.error(
|
|
190
|
+
"hook_task_execution_failed", error=str(e), task_id=task.task_id
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
async def _execute_hook(self, hook: Hook, context: HookContext) -> None:
|
|
194
|
+
"""Execute a single hook with proper async/sync handling.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
hook: The hook to execute
|
|
198
|
+
context: The context to pass to the hook
|
|
199
|
+
"""
|
|
200
|
+
result = hook(context)
|
|
201
|
+
if asyncio.iscoroutine(result):
|
|
202
|
+
await result
|
|
203
|
+
# If result is None, it was a sync hook and we're done
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Shared hook typing for headers to support dict or dict-like inputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HookHeaders(Protocol):
|
|
10
|
+
"""Protocol for header-like objects passed through hooks.
|
|
11
|
+
|
|
12
|
+
Implementations must preserve order when iterated. Plain dicts and
|
|
13
|
+
other dict-like objects can conform to this via duck typing.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def items(self) -> Iterable[tuple[str, str]]:
|
|
17
|
+
"""Return an iterable of (name, value) pairs in order."""
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> dict[str, str]: # pragma: no cover - protocol
|
|
21
|
+
"""Return a dict view (last occurrence wins per name)."""
|
|
22
|
+
...
|