ccproxy-api 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +439 -212
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +145 -176
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +402 -530
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +558 -0
- ccproxy/data/codex_headers_fallback.json +121 -0
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +63 -107
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +346 -314
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +95 -342
- ccproxy/utils/version_checker.py +279 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1231
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -269
- ccproxy/services/codex_detection_service.py +0 -263
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.6.dist-info/METADATA +0 -615
- ccproxy_api-0.1.6.dist-info/RECORD +0 -189
- ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
"""Pricing updater for managing periodic refresh of pricing data."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import ValidationError
|
|
7
9
|
|
|
8
|
-
from ccproxy.
|
|
10
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
9
11
|
|
|
10
12
|
from .cache import PricingCache
|
|
13
|
+
from .config import PricingConfig
|
|
11
14
|
from .loader import PricingLoader
|
|
12
15
|
from .models import PricingData
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
logger =
|
|
18
|
+
logger = get_plugin_logger(__name__)
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class PricingUpdater:
|
|
@@ -21,7 +24,7 @@ class PricingUpdater:
|
|
|
21
24
|
def __init__(
|
|
22
25
|
self,
|
|
23
26
|
cache: PricingCache,
|
|
24
|
-
settings:
|
|
27
|
+
settings: PricingConfig,
|
|
25
28
|
) -> None:
|
|
26
29
|
"""Initialize pricing updater.
|
|
27
30
|
|
|
@@ -47,8 +50,6 @@ class PricingUpdater:
|
|
|
47
50
|
Returns:
|
|
48
51
|
Current pricing data as PricingData model
|
|
49
52
|
"""
|
|
50
|
-
import time
|
|
51
|
-
|
|
52
53
|
current_time = time.time()
|
|
53
54
|
|
|
54
55
|
# Return cached pricing if recent and not forced
|
|
@@ -76,7 +77,7 @@ class PricingUpdater:
|
|
|
76
77
|
)
|
|
77
78
|
|
|
78
79
|
if should_refresh:
|
|
79
|
-
logger.
|
|
80
|
+
logger.debug("pricing_refresh_start")
|
|
80
81
|
await self._refresh_pricing()
|
|
81
82
|
|
|
82
83
|
# Load pricing data
|
|
@@ -115,7 +116,7 @@ class PricingUpdater:
|
|
|
115
116
|
True if refresh was successful
|
|
116
117
|
"""
|
|
117
118
|
try:
|
|
118
|
-
logger.
|
|
119
|
+
logger.debug("pricing_refresh_start")
|
|
119
120
|
|
|
120
121
|
# Download fresh data
|
|
121
122
|
raw_data = await self.cache.download_pricing_data()
|
|
@@ -128,11 +129,26 @@ class PricingUpdater:
|
|
|
128
129
|
logger.error("cache_save_failed")
|
|
129
130
|
return False
|
|
130
131
|
|
|
131
|
-
logger.
|
|
132
|
+
logger.debug("pricing_refresh_completed")
|
|
132
133
|
return True
|
|
133
134
|
|
|
135
|
+
except httpx.TimeoutException as e:
|
|
136
|
+
logger.error("pricing_refresh_timeout", error=str(e), exc_info=e)
|
|
137
|
+
return False
|
|
138
|
+
except httpx.HTTPError as e:
|
|
139
|
+
logger.error("pricing_refresh_http_error", error=str(e), exc_info=e)
|
|
140
|
+
return False
|
|
141
|
+
except json.JSONDecodeError as e:
|
|
142
|
+
logger.error("pricing_refresh_json_error", error=str(e), exc_info=e)
|
|
143
|
+
return False
|
|
144
|
+
except ValidationError as e:
|
|
145
|
+
logger.error("pricing_refresh_validation_error", error=str(e), exc_info=e)
|
|
146
|
+
return False
|
|
147
|
+
except OSError as e:
|
|
148
|
+
logger.error("pricing_refresh_io_error", error=str(e), exc_info=e)
|
|
149
|
+
return False
|
|
134
150
|
except Exception as e:
|
|
135
|
-
logger.error("pricing_refresh_failed", error=str(e))
|
|
151
|
+
logger.error("pricing_refresh_failed", error=str(e), exc_info=e)
|
|
136
152
|
return False
|
|
137
153
|
|
|
138
154
|
async def _load_pricing_data(self) -> PricingData | None:
|
|
@@ -146,7 +162,13 @@ class PricingUpdater:
|
|
|
146
162
|
|
|
147
163
|
if raw_data is not None:
|
|
148
164
|
# Load and validate pricing data using Pydantic
|
|
149
|
-
|
|
165
|
+
# Use the configured provider setting (defaults to "all")
|
|
166
|
+
pricing_data = PricingLoader.load_pricing_from_data(
|
|
167
|
+
raw_data,
|
|
168
|
+
provider=self.settings.pricing_provider,
|
|
169
|
+
map_to_claude=False, # Don't map OpenAI models to Claude
|
|
170
|
+
verbose=False,
|
|
171
|
+
)
|
|
150
172
|
|
|
151
173
|
if pricing_data:
|
|
152
174
|
# Get cache info to display age
|
|
@@ -154,69 +176,21 @@ class PricingUpdater:
|
|
|
154
176
|
age_hours = cache_info.get("age_hours")
|
|
155
177
|
|
|
156
178
|
if age_hours is not None:
|
|
157
|
-
logger.
|
|
179
|
+
logger.debug(
|
|
158
180
|
"pricing_loaded_from_external",
|
|
159
181
|
model_count=len(pricing_data),
|
|
160
182
|
cache_age_hours=round(age_hours, 2),
|
|
161
183
|
)
|
|
162
184
|
else:
|
|
163
|
-
logger.
|
|
185
|
+
logger.debug(
|
|
164
186
|
"pricing_loaded_from_external", model_count=len(pricing_data)
|
|
165
187
|
)
|
|
166
188
|
return pricing_data
|
|
167
189
|
else:
|
|
168
190
|
logger.warning("external_pricing_validation_failed")
|
|
169
191
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
logger.info("using_embedded_pricing_fallback")
|
|
173
|
-
return self._get_embedded_pricing()
|
|
174
|
-
else:
|
|
175
|
-
logger.error("pricing_unavailable_no_fallback")
|
|
176
|
-
return None
|
|
177
|
-
|
|
178
|
-
def _get_embedded_pricing(self) -> PricingData:
|
|
179
|
-
"""Get embedded (hardcoded) pricing as fallback.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
Embedded pricing data as PricingData model
|
|
183
|
-
"""
|
|
184
|
-
# This is the current hardcoded pricing from CostCalculator
|
|
185
|
-
embedded_data = {
|
|
186
|
-
"claude-3-5-sonnet-20241022": {
|
|
187
|
-
"input": Decimal("3.00"),
|
|
188
|
-
"output": Decimal("15.00"),
|
|
189
|
-
"cache_read": Decimal("0.30"),
|
|
190
|
-
"cache_write": Decimal("3.75"),
|
|
191
|
-
},
|
|
192
|
-
"claude-3-5-haiku-20241022": {
|
|
193
|
-
"input": Decimal("0.25"),
|
|
194
|
-
"output": Decimal("1.25"),
|
|
195
|
-
"cache_read": Decimal("0.03"),
|
|
196
|
-
"cache_write": Decimal("0.30"),
|
|
197
|
-
},
|
|
198
|
-
"claude-3-opus-20240229": {
|
|
199
|
-
"input": Decimal("15.00"),
|
|
200
|
-
"output": Decimal("75.00"),
|
|
201
|
-
"cache_read": Decimal("1.50"),
|
|
202
|
-
"cache_write": Decimal("18.75"),
|
|
203
|
-
},
|
|
204
|
-
"claude-3-sonnet-20240229": {
|
|
205
|
-
"input": Decimal("3.00"),
|
|
206
|
-
"output": Decimal("15.00"),
|
|
207
|
-
"cache_read": Decimal("0.30"),
|
|
208
|
-
"cache_write": Decimal("3.75"),
|
|
209
|
-
},
|
|
210
|
-
"claude-3-haiku-20240307": {
|
|
211
|
-
"input": Decimal("0.25"),
|
|
212
|
-
"output": Decimal("1.25"),
|
|
213
|
-
"cache_read": Decimal("0.03"),
|
|
214
|
-
"cache_write": Decimal("0.30"),
|
|
215
|
-
},
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
# Create PricingData from embedded data
|
|
219
|
-
return PricingData.from_dict(embedded_data)
|
|
192
|
+
logger.error("pricing_unavailable_no_fallback")
|
|
193
|
+
return None
|
|
220
194
|
|
|
221
195
|
async def force_refresh(self) -> bool:
|
|
222
196
|
"""Force a refresh of pricing data.
|
|
@@ -268,7 +242,6 @@ class PricingUpdater:
|
|
|
268
242
|
"models_loaded": len(pricing_data) if pricing_data else 0,
|
|
269
243
|
"model_names": pricing_data.model_names() if pricing_data else [],
|
|
270
244
|
"auto_update": self.settings.auto_update,
|
|
271
|
-
"fallback_to_embedded": self.settings.fallback_to_embedded,
|
|
272
245
|
"has_cached_pricing": self._cached_pricing is not None,
|
|
273
246
|
}
|
|
274
247
|
|
|
@@ -286,14 +259,30 @@ class PricingUpdater:
|
|
|
286
259
|
if raw_data is None:
|
|
287
260
|
return False
|
|
288
261
|
|
|
289
|
-
# Try to
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
262
|
+
# Try to extract models based on configured provider
|
|
263
|
+
if self.settings.pricing_provider == "claude":
|
|
264
|
+
models = PricingLoader.extract_claude_models(raw_data)
|
|
265
|
+
if not models:
|
|
266
|
+
logger.warning("claude_models_not_found_in_external")
|
|
267
|
+
return False
|
|
268
|
+
else:
|
|
269
|
+
models = PricingLoader.extract_models_by_provider(
|
|
270
|
+
raw_data, provider=self.settings.pricing_provider
|
|
271
|
+
)
|
|
272
|
+
if not models:
|
|
273
|
+
logger.warning(
|
|
274
|
+
"models_not_found_in_external",
|
|
275
|
+
provider=self.settings.pricing_provider,
|
|
276
|
+
)
|
|
277
|
+
return False
|
|
294
278
|
|
|
295
279
|
# Try to load and validate using Pydantic
|
|
296
|
-
pricing_data = PricingLoader.load_pricing_from_data(
|
|
280
|
+
pricing_data = PricingLoader.load_pricing_from_data(
|
|
281
|
+
raw_data,
|
|
282
|
+
provider=self.settings.pricing_provider,
|
|
283
|
+
map_to_claude=False,
|
|
284
|
+
verbose=False,
|
|
285
|
+
)
|
|
297
286
|
if not pricing_data:
|
|
298
287
|
logger.warning("external_pricing_load_failed")
|
|
299
288
|
return False
|
|
@@ -303,6 +292,31 @@ class PricingUpdater:
|
|
|
303
292
|
)
|
|
304
293
|
return True
|
|
305
294
|
|
|
295
|
+
except httpx.TimeoutException as e:
|
|
296
|
+
logger.error(
|
|
297
|
+
"external_pricing_validation_timeout", error=str(e), exc_info=e
|
|
298
|
+
)
|
|
299
|
+
return False
|
|
300
|
+
except httpx.HTTPError as e:
|
|
301
|
+
logger.error(
|
|
302
|
+
"external_pricing_validation_http_error", error=str(e), exc_info=e
|
|
303
|
+
)
|
|
304
|
+
return False
|
|
305
|
+
except json.JSONDecodeError as e:
|
|
306
|
+
logger.error(
|
|
307
|
+
"external_pricing_validation_json_error", error=str(e), exc_info=e
|
|
308
|
+
)
|
|
309
|
+
return False
|
|
310
|
+
except ValidationError as e:
|
|
311
|
+
logger.error(
|
|
312
|
+
"external_pricing_validation_validation_error", error=str(e), exc_info=e
|
|
313
|
+
)
|
|
314
|
+
return False
|
|
315
|
+
except OSError as e:
|
|
316
|
+
logger.error(
|
|
317
|
+
"external_pricing_validation_io_error", error=str(e), exc_info=e
|
|
318
|
+
)
|
|
319
|
+
return False
|
|
306
320
|
except Exception as e:
|
|
307
|
-
logger.error("external_pricing_validation_failed", error=str(e))
|
|
321
|
+
logger.error("external_pricing_validation_failed", error=str(e), exc_info=e)
|
|
308
322
|
return False
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Cost calculation utilities for token-based pricing (plugin-owned).
|
|
2
|
+
|
|
3
|
+
These helpers live inside the pricing plugin to avoid coupling core to
|
|
4
|
+
pricing logic. They accept an optional PricingService instance for callers
|
|
5
|
+
that already have one; otherwise they create a default service on demand.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .config import PricingConfig
|
|
11
|
+
from .service import PricingService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def calculate_token_cost(
|
|
15
|
+
tokens_input: int | None,
|
|
16
|
+
tokens_output: int | None,
|
|
17
|
+
model: str | None,
|
|
18
|
+
cache_read_tokens: int | None = None,
|
|
19
|
+
cache_write_tokens: int | None = None,
|
|
20
|
+
pricing_service: PricingService | None = None,
|
|
21
|
+
) -> float | None:
|
|
22
|
+
"""Calculate total cost in USD for the given token usage.
|
|
23
|
+
|
|
24
|
+
If no pricing_service is provided, a default PricingService is created
|
|
25
|
+
using PricingConfig(). Returns None if model or tokens are missing or if
|
|
26
|
+
pricing information is unavailable.
|
|
27
|
+
"""
|
|
28
|
+
if not model or (
|
|
29
|
+
not tokens_input
|
|
30
|
+
and not tokens_output
|
|
31
|
+
and not cache_read_tokens
|
|
32
|
+
and not cache_write_tokens
|
|
33
|
+
):
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
service = pricing_service or PricingService(PricingConfig())
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
cost_decimal = await service.calculate_cost(
|
|
40
|
+
model_name=model,
|
|
41
|
+
input_tokens=tokens_input or 0,
|
|
42
|
+
output_tokens=tokens_output or 0,
|
|
43
|
+
cache_read_tokens=cache_read_tokens or 0,
|
|
44
|
+
cache_write_tokens=cache_write_tokens or 0,
|
|
45
|
+
)
|
|
46
|
+
return float(cost_decimal) if cost_decimal is not None else None
|
|
47
|
+
except Exception:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def calculate_cost_breakdown(
|
|
52
|
+
tokens_input: int | None,
|
|
53
|
+
tokens_output: int | None,
|
|
54
|
+
model: str | None,
|
|
55
|
+
cache_read_tokens: int | None = None,
|
|
56
|
+
cache_write_tokens: int | None = None,
|
|
57
|
+
pricing_service: PricingService | None = None,
|
|
58
|
+
) -> dict[str, float | str] | None:
|
|
59
|
+
"""Return a detailed cost breakdown using current pricing data.
|
|
60
|
+
|
|
61
|
+
If no pricing_service is provided, a default PricingService is created.
|
|
62
|
+
Returns None if inputs are insufficient or model pricing is unavailable.
|
|
63
|
+
"""
|
|
64
|
+
if not model or (
|
|
65
|
+
not tokens_input
|
|
66
|
+
and not tokens_output
|
|
67
|
+
and not cache_read_tokens
|
|
68
|
+
and not cache_write_tokens
|
|
69
|
+
):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
service = pricing_service or PricingService(PricingConfig())
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
model_pricing = await service.get_model_pricing(model)
|
|
76
|
+
if not model_pricing:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
input_cost = ((tokens_input or 0) / 1_000_000) * float(model_pricing.input)
|
|
80
|
+
output_cost = ((tokens_output or 0) / 1_000_000) * float(model_pricing.output)
|
|
81
|
+
cache_read_cost = ((cache_read_tokens or 0) / 1_000_000) * float(
|
|
82
|
+
model_pricing.cache_read
|
|
83
|
+
)
|
|
84
|
+
cache_write_cost = ((cache_write_tokens or 0) / 1_000_000) * float(
|
|
85
|
+
model_pricing.cache_write
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
total_cost = input_cost + output_cost + cache_read_cost + cache_write_cost
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"input_cost": input_cost,
|
|
92
|
+
"output_cost": output_cost,
|
|
93
|
+
"cache_read_cost": cache_read_cost,
|
|
94
|
+
"cache_write_cost": cache_write_cost,
|
|
95
|
+
"total_cost": total_cost,
|
|
96
|
+
"model": model,
|
|
97
|
+
}
|
|
98
|
+
except Exception:
|
|
99
|
+
return None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Request Tracer Plugin
|
|
2
|
+
|
|
3
|
+
Captures detailed request and response traces for troubleshooting.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
- Registers both the core HTTP tracer and contextual request hook handlers
|
|
7
|
+
- Writes JSON or raw HTTP logs with size and path filtering controls
|
|
8
|
+
- Validates configuration and reports issues through structured logs
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
- `RequestTracerConfig` toggles enablement, formats, filters, and log targets
|
|
12
|
+
- Hook registration occurs only when a hook registry is available
|
|
13
|
+
- Generate defaults with `python3 scripts/generate_config_from_model.py \
|
|
14
|
+
--format toml --plugin request_tracer --config-class RequestTracerConfig`
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[plugins.request_tracer]
|
|
18
|
+
# enabled = true
|
|
19
|
+
# verbose_api = true
|
|
20
|
+
# json_logs_enabled = true
|
|
21
|
+
# raw_http_enabled = true
|
|
22
|
+
# trace_oauth = true
|
|
23
|
+
# log_dir = "/tmp/ccproxy/traces"
|
|
24
|
+
# exclude_paths = ["/health", "/metrics", "/readyz", "/livez"]
|
|
25
|
+
# include_paths = []
|
|
26
|
+
# exclude_headers = ["authorization", "x-api-key", "cookie", "x-auth-token"]
|
|
27
|
+
# redact_sensitive = true
|
|
28
|
+
# max_body_size = 10485760
|
|
29
|
+
# truncate_body_preview = 1024
|
|
30
|
+
# log_client_request = true
|
|
31
|
+
# log_client_response = true
|
|
32
|
+
# log_provider_request = true
|
|
33
|
+
# log_provider_response = true
|
|
34
|
+
# log_streaming_chunks = false
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Related Components
|
|
38
|
+
- `hook.py`: handles request lifecycle events and formatting
|
|
39
|
+
- `plugin.py`: runtime validation and hook wiring
|
|
40
|
+
- `config.py`: settings model for log output options
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Configuration for the RequestTracer plugin."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RequestTracerConfig(BaseModel):
|
|
7
|
+
"""Unified configuration for request tracing.
|
|
8
|
+
|
|
9
|
+
Combines structured JSON tracing (from core_tracer) and raw HTTP logging
|
|
10
|
+
(from raw_http_logger) into a single configuration.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Enable/disable entire plugin
|
|
14
|
+
enabled: bool = Field(
|
|
15
|
+
default=True, description="Enable or disable the request tracer plugin"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Structured tracing (from core_tracer)
|
|
19
|
+
verbose_api: bool = Field(
|
|
20
|
+
default=True,
|
|
21
|
+
description="Enable verbose API logging with structured JSON output",
|
|
22
|
+
)
|
|
23
|
+
json_logs_enabled: bool = Field(
|
|
24
|
+
default=True, description="Enable structured JSON logging to files"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Raw HTTP logging (from raw_http_logger)
|
|
28
|
+
raw_http_enabled: bool = Field(
|
|
29
|
+
default=True, description="Enable raw HTTP protocol logging"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# OAuth tracing
|
|
33
|
+
trace_oauth: bool = Field(
|
|
34
|
+
default=True,
|
|
35
|
+
description="Enable OAuth request/response tracing for CLI operations",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Directory configuration
|
|
39
|
+
log_dir: str = Field(
|
|
40
|
+
default="/tmp/ccproxy/traces", description="Base directory for all trace logs"
|
|
41
|
+
)
|
|
42
|
+
request_log_dir: str | None = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Override directory for structured JSON logs (defaults to log_dir)",
|
|
45
|
+
)
|
|
46
|
+
raw_log_dir: str | None = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
description="Override directory for raw HTTP logs (defaults to log_dir/raw)",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Request filtering
|
|
52
|
+
exclude_paths: list[str] = Field(
|
|
53
|
+
default_factory=lambda: ["/health", "/metrics", "/readyz", "/livez"],
|
|
54
|
+
description="Request paths to exclude from tracing",
|
|
55
|
+
)
|
|
56
|
+
include_paths: list[str] = Field(
|
|
57
|
+
default_factory=list, description="If specified, only trace these paths"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Privacy & security
|
|
61
|
+
exclude_headers: list[str] = Field(
|
|
62
|
+
default_factory=lambda: [
|
|
63
|
+
"authorization",
|
|
64
|
+
"x-api-key",
|
|
65
|
+
"cookie",
|
|
66
|
+
"x-auth-token",
|
|
67
|
+
],
|
|
68
|
+
description="Headers to redact in raw logs",
|
|
69
|
+
)
|
|
70
|
+
redact_sensitive: bool = Field(
|
|
71
|
+
default=True, description="Redact sensitive data in structured logs"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Performance settings
|
|
75
|
+
max_body_size: int = Field(
|
|
76
|
+
default=10485760, # 10MB
|
|
77
|
+
description="Maximum body size to log (bytes)",
|
|
78
|
+
)
|
|
79
|
+
truncate_body_preview: int = Field(
|
|
80
|
+
default=1024,
|
|
81
|
+
description="Maximum body preview size for structured logs (chars)",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Granular control
|
|
85
|
+
log_client_request: bool = Field(default=True, description="Log client requests")
|
|
86
|
+
log_client_response: bool = Field(default=True, description="Log client responses")
|
|
87
|
+
log_provider_request: bool = Field(
|
|
88
|
+
default=True, description="Log provider requests"
|
|
89
|
+
)
|
|
90
|
+
log_provider_response: bool = Field(
|
|
91
|
+
default=True, description="Log provider responses"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Streaming configuration
|
|
95
|
+
log_streaming_chunks: bool = Field(
|
|
96
|
+
default=False, description="Log individual streaming chunks (verbose)"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
model_config = ConfigDict()
|
|
100
|
+
|
|
101
|
+
def get_json_log_dir(self) -> str:
|
|
102
|
+
"""Get directory for structured JSON logs."""
|
|
103
|
+
return self.request_log_dir or self.log_dir
|
|
104
|
+
|
|
105
|
+
def get_raw_log_dir(self) -> str:
|
|
106
|
+
"""Get directory for raw HTTP logs."""
|
|
107
|
+
return self.raw_log_dir or self.log_dir
|
|
108
|
+
|
|
109
|
+
def should_trace_path(self, path: str) -> bool:
|
|
110
|
+
"""Check if a path should be traced based on include/exclude rules."""
|
|
111
|
+
# First check exclude_paths (takes precedence)
|
|
112
|
+
if any(path.startswith(exclude) for exclude in self.exclude_paths):
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
# Then check include_paths (if specified, only log included paths)
|
|
116
|
+
if self.include_paths:
|
|
117
|
+
return any(path.startswith(include) for include in self.include_paths)
|
|
118
|
+
|
|
119
|
+
# Default: trace all paths not explicitly excluded
|
|
120
|
+
return True
|