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
ccproxy/models/types.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
"""Common type aliases used across the ccproxy models."""
|
|
2
|
-
|
|
3
|
-
from typing import Literal, TypeAlias
|
|
4
|
-
|
|
5
|
-
from typing_extensions import TypedDict
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Message and content types
|
|
9
|
-
MessageRole: TypeAlias = Literal["user", "assistant", "system", "tool"]
|
|
10
|
-
OpenAIMessageRole: TypeAlias = Literal[
|
|
11
|
-
"system", "user", "assistant", "tool", "developer"
|
|
12
|
-
]
|
|
13
|
-
ContentBlockType: TypeAlias = Literal[
|
|
14
|
-
"text", "image", "image_url", "tool_use", "thinking"
|
|
15
|
-
]
|
|
16
|
-
OpenAIContentType: TypeAlias = Literal["text", "image_url"]
|
|
17
|
-
|
|
18
|
-
# Tool-related types
|
|
19
|
-
ToolChoiceType: TypeAlias = Literal["auto", "any", "tool", "none", "required"]
|
|
20
|
-
OpenAIToolChoiceType: TypeAlias = Literal["none", "auto", "required"]
|
|
21
|
-
ToolType: TypeAlias = Literal["function", "custom"]
|
|
22
|
-
|
|
23
|
-
# Response format types
|
|
24
|
-
ResponseFormatType: TypeAlias = Literal["text", "json_object", "json_schema"]
|
|
25
|
-
|
|
26
|
-
# Service tier types
|
|
27
|
-
ServiceTier: TypeAlias = Literal["auto", "standard_only"]
|
|
28
|
-
|
|
29
|
-
# Stop reasons (re-exported from messages for convenience)
|
|
30
|
-
StopReason: TypeAlias = Literal[
|
|
31
|
-
"end_turn",
|
|
32
|
-
"max_tokens",
|
|
33
|
-
"stop_sequence",
|
|
34
|
-
"tool_use",
|
|
35
|
-
"pause_turn",
|
|
36
|
-
"refusal",
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
# OpenAI finish reasons
|
|
40
|
-
OpenAIFinishReason: TypeAlias = Literal[
|
|
41
|
-
"stop", "length", "tool_calls", "content_filter"
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
# Error types
|
|
45
|
-
ErrorType: TypeAlias = Literal[
|
|
46
|
-
"error",
|
|
47
|
-
"rate_limit_error",
|
|
48
|
-
"invalid_request_error",
|
|
49
|
-
"authentication_error",
|
|
50
|
-
"not_found_error",
|
|
51
|
-
"overloaded_error",
|
|
52
|
-
"internal_server_error",
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
# Stream event types
|
|
56
|
-
StreamEventType: TypeAlias = Literal[
|
|
57
|
-
"message_start",
|
|
58
|
-
"message_delta",
|
|
59
|
-
"message_stop",
|
|
60
|
-
"content_block_start",
|
|
61
|
-
"content_block_delta",
|
|
62
|
-
"content_block_stop",
|
|
63
|
-
"ping",
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
# Image source types
|
|
67
|
-
ImageSourceType: TypeAlias = Literal["base64", "url"]
|
|
68
|
-
|
|
69
|
-
# Modality types
|
|
70
|
-
ModalityType: TypeAlias = Literal["text", "audio"]
|
|
71
|
-
|
|
72
|
-
# Reasoning effort types (OpenAI o1 models)
|
|
73
|
-
ReasoningEffort: TypeAlias = Literal["low", "medium", "high"]
|
|
74
|
-
|
|
75
|
-
# OpenAI object types
|
|
76
|
-
OpenAIObjectType: TypeAlias = Literal[
|
|
77
|
-
"chat.completion", "chat.completion.chunk", "model", "list"
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
# Permission behavior types
|
|
81
|
-
PermissionBehavior: TypeAlias = Literal["allow", "deny"]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# Usage and streaming related types
|
|
85
|
-
class UsageData(TypedDict, total=False):
|
|
86
|
-
"""Token usage data extracted from streaming or non-streaming responses."""
|
|
87
|
-
|
|
88
|
-
input_tokens: int | None
|
|
89
|
-
output_tokens: int | None
|
|
90
|
-
cache_read_input_tokens: int | None
|
|
91
|
-
cache_creation_input_tokens: int | None
|
|
92
|
-
event_type: StreamEventType | None
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class StreamingTokenMetrics(TypedDict, total=False):
|
|
96
|
-
"""Accumulated token metrics during streaming."""
|
|
97
|
-
|
|
98
|
-
tokens_input: int | None
|
|
99
|
-
tokens_output: int | None
|
|
100
|
-
cache_read_tokens: int | None
|
|
101
|
-
cache_write_tokens: int | None
|
|
102
|
-
cost_usd: float | None
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Observability module for the CCProxy API.
|
|
3
|
-
|
|
4
|
-
This module provides comprehensive observability capabilities including metrics collection,
|
|
5
|
-
structured logging, request context tracking, and observability pipeline management.
|
|
6
|
-
|
|
7
|
-
The observability system follows a hybrid architecture that combines:
|
|
8
|
-
- Real-time metrics collection and aggregation
|
|
9
|
-
- Structured logging with correlation IDs
|
|
10
|
-
- Request context propagation across service boundaries
|
|
11
|
-
- Pluggable pipeline for metrics export and alerting
|
|
12
|
-
|
|
13
|
-
Components:
|
|
14
|
-
- metrics: Core metrics collection, aggregation, and export functionality
|
|
15
|
-
- logging: Structured logging configuration and context-aware loggers
|
|
16
|
-
- context: Request context tracking and correlation across async operations
|
|
17
|
-
- pipeline: Observability data pipeline for metrics export and alerting
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from .context import (
|
|
21
|
-
RequestContext,
|
|
22
|
-
get_context_tracker,
|
|
23
|
-
request_context,
|
|
24
|
-
timed_operation,
|
|
25
|
-
tracked_request_context,
|
|
26
|
-
)
|
|
27
|
-
from .metrics import PrometheusMetrics, get_metrics, reset_metrics
|
|
28
|
-
from .pushgateway import (
|
|
29
|
-
PushgatewayClient,
|
|
30
|
-
get_pushgateway_client,
|
|
31
|
-
reset_pushgateway_client,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
__all__ = [
|
|
36
|
-
# Configuration
|
|
37
|
-
# Context management
|
|
38
|
-
"RequestContext",
|
|
39
|
-
"request_context",
|
|
40
|
-
"tracked_request_context",
|
|
41
|
-
"timed_operation",
|
|
42
|
-
"get_context_tracker",
|
|
43
|
-
# Prometheus metrics
|
|
44
|
-
"PrometheusMetrics",
|
|
45
|
-
"get_metrics",
|
|
46
|
-
"reset_metrics",
|
|
47
|
-
# Pushgateway
|
|
48
|
-
"PushgatewayClient",
|
|
49
|
-
"get_pushgateway_client",
|
|
50
|
-
"reset_pushgateway_client",
|
|
51
|
-
]
|
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
"""Unified access logging utilities for comprehensive request tracking.
|
|
2
|
-
|
|
3
|
-
This module provides centralized access logging functionality that can be used
|
|
4
|
-
across different parts of the application to generate consistent, comprehensive
|
|
5
|
-
access logs with complete request metadata including token usage and costs.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import time
|
|
11
|
-
from typing import TYPE_CHECKING, Any
|
|
12
|
-
|
|
13
|
-
import structlog
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from ccproxy.observability.context import RequestContext
|
|
18
|
-
from ccproxy.observability.metrics import PrometheusMetrics
|
|
19
|
-
from ccproxy.observability.storage.duckdb_simple import (
|
|
20
|
-
AccessLogPayload,
|
|
21
|
-
SimpleDuckDBStorage,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
logger = structlog.get_logger(__name__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
async def log_request_access(
|
|
29
|
-
context: RequestContext,
|
|
30
|
-
status_code: int | None = None,
|
|
31
|
-
client_ip: str | None = None,
|
|
32
|
-
user_agent: str | None = None,
|
|
33
|
-
method: str | None = None,
|
|
34
|
-
path: str | None = None,
|
|
35
|
-
query: str | None = None,
|
|
36
|
-
error_message: str | None = None,
|
|
37
|
-
storage: SimpleDuckDBStorage | None = None,
|
|
38
|
-
metrics: PrometheusMetrics | None = None,
|
|
39
|
-
**additional_metadata: Any,
|
|
40
|
-
) -> None:
|
|
41
|
-
"""Log comprehensive access information for a request.
|
|
42
|
-
|
|
43
|
-
This function generates a unified access log entry with complete request
|
|
44
|
-
metadata including timing, tokens, costs, and any additional context.
|
|
45
|
-
Also stores the access log in DuckDB if available and records Prometheus metrics.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
context: Request context with timing and metadata
|
|
49
|
-
status_code: HTTP status code
|
|
50
|
-
client_ip: Client IP address
|
|
51
|
-
user_agent: User agent string
|
|
52
|
-
method: HTTP method
|
|
53
|
-
path: Request path
|
|
54
|
-
query: Query parameters
|
|
55
|
-
error_message: Error message if applicable
|
|
56
|
-
storage: DuckDB storage instance (optional)
|
|
57
|
-
metrics: PrometheusMetrics instance for recording metrics (optional)
|
|
58
|
-
**additional_metadata: Any additional fields to include
|
|
59
|
-
"""
|
|
60
|
-
# Extract basic request info from context metadata if not provided
|
|
61
|
-
ctx_metadata = context.metadata
|
|
62
|
-
method = method or ctx_metadata.get("method")
|
|
63
|
-
path = path or ctx_metadata.get("path")
|
|
64
|
-
status_code = status_code or ctx_metadata.get("status_code")
|
|
65
|
-
|
|
66
|
-
# Prepare basic log data (always included)
|
|
67
|
-
log_data = {
|
|
68
|
-
"request_id": context.request_id,
|
|
69
|
-
"method": method,
|
|
70
|
-
"path": path,
|
|
71
|
-
"query": query,
|
|
72
|
-
"client_ip": client_ip,
|
|
73
|
-
"user_agent": user_agent,
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# Add response-specific fields (only for completed requests)
|
|
77
|
-
is_streaming = ctx_metadata.get("streaming", False)
|
|
78
|
-
is_streaming_complete = ctx_metadata.get("event_type", "") == "streaming_complete"
|
|
79
|
-
|
|
80
|
-
# Include response fields only if this is not a streaming start
|
|
81
|
-
if not is_streaming or is_streaming_complete or ctx_metadata.get("error"):
|
|
82
|
-
log_data.update(
|
|
83
|
-
{
|
|
84
|
-
"status_code": status_code,
|
|
85
|
-
"duration_ms": context.duration_ms,
|
|
86
|
-
"duration_seconds": context.duration_seconds,
|
|
87
|
-
"error_message": error_message,
|
|
88
|
-
}
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# Add token and cost metrics if available
|
|
92
|
-
token_fields = [
|
|
93
|
-
"tokens_input",
|
|
94
|
-
"tokens_output",
|
|
95
|
-
"cache_read_tokens",
|
|
96
|
-
"cache_write_tokens",
|
|
97
|
-
"cost_usd",
|
|
98
|
-
"cost_sdk_usd",
|
|
99
|
-
"num_turns",
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
for field in token_fields:
|
|
103
|
-
value = ctx_metadata.get(field)
|
|
104
|
-
if value is not None:
|
|
105
|
-
log_data[field] = value
|
|
106
|
-
|
|
107
|
-
# Add service and endpoint info
|
|
108
|
-
service_fields = ["endpoint", "model", "streaming", "service_type", "headers"]
|
|
109
|
-
|
|
110
|
-
for field in service_fields:
|
|
111
|
-
value = ctx_metadata.get(field)
|
|
112
|
-
if value is not None:
|
|
113
|
-
log_data[field] = value
|
|
114
|
-
|
|
115
|
-
# Add session context metadata if available
|
|
116
|
-
session_fields = [
|
|
117
|
-
"session_id",
|
|
118
|
-
"session_type", # "session_pool" or "direct"
|
|
119
|
-
"session_status", # active, idle, connecting, etc.
|
|
120
|
-
"session_age_seconds", # how long session has been alive
|
|
121
|
-
"session_message_count", # number of messages in session
|
|
122
|
-
"session_pool_enabled", # whether session pooling is enabled
|
|
123
|
-
"session_idle_seconds", # how long since last activity
|
|
124
|
-
"session_error_count", # number of errors in this session
|
|
125
|
-
"session_is_new", # whether this is a newly created session
|
|
126
|
-
]
|
|
127
|
-
|
|
128
|
-
for field in session_fields:
|
|
129
|
-
value = ctx_metadata.get(field)
|
|
130
|
-
if value is not None:
|
|
131
|
-
log_data[field] = value
|
|
132
|
-
|
|
133
|
-
# Add rate limit headers if available
|
|
134
|
-
rate_limit_fields = [
|
|
135
|
-
"x-ratelimit-limit",
|
|
136
|
-
"x-ratelimit-remaining",
|
|
137
|
-
"x-ratelimit-reset",
|
|
138
|
-
"anthropic-ratelimit-requests-limit",
|
|
139
|
-
"anthropic-ratelimit-requests-remaining",
|
|
140
|
-
"anthropic-ratelimit-requests-reset",
|
|
141
|
-
"anthropic-ratelimit-tokens-limit",
|
|
142
|
-
"anthropic-ratelimit-tokens-remaining",
|
|
143
|
-
"anthropic-ratelimit-tokens-reset",
|
|
144
|
-
"anthropic_request_id",
|
|
145
|
-
]
|
|
146
|
-
|
|
147
|
-
for field in rate_limit_fields:
|
|
148
|
-
value = ctx_metadata.get(field)
|
|
149
|
-
if value is not None:
|
|
150
|
-
log_data[field] = value
|
|
151
|
-
|
|
152
|
-
# Add any additional metadata provided
|
|
153
|
-
log_data.update(additional_metadata)
|
|
154
|
-
|
|
155
|
-
# Remove None values to keep log clean
|
|
156
|
-
log_data = {k: v for k, v in log_data.items() if v is not None}
|
|
157
|
-
|
|
158
|
-
logger = context.logger.bind(**log_data)
|
|
159
|
-
|
|
160
|
-
if context.metadata.get("error"):
|
|
161
|
-
logger.warn("access_log", exc_info=context.metadata.get("error"))
|
|
162
|
-
elif not is_streaming:
|
|
163
|
-
# Log as access_log event (structured logging)
|
|
164
|
-
logger.info("access_log")
|
|
165
|
-
elif is_streaming_complete:
|
|
166
|
-
logger.info("access_log")
|
|
167
|
-
else:
|
|
168
|
-
# if streaming is true, and not streaming_complete log as debug
|
|
169
|
-
# real access_log will come later
|
|
170
|
-
logger.info("access_log_streaming_start")
|
|
171
|
-
|
|
172
|
-
# Store in DuckDB if available
|
|
173
|
-
await _store_access_log(log_data, storage)
|
|
174
|
-
|
|
175
|
-
# Emit SSE event for real-time dashboard updates
|
|
176
|
-
await _emit_access_event("request_complete", log_data)
|
|
177
|
-
|
|
178
|
-
# Record Prometheus metrics if metrics instance is provided
|
|
179
|
-
if metrics and not error_message:
|
|
180
|
-
# Extract required values for metrics
|
|
181
|
-
endpoint = ctx_metadata.get("endpoint", path or "unknown")
|
|
182
|
-
model = ctx_metadata.get("model")
|
|
183
|
-
service_type = ctx_metadata.get("service_type")
|
|
184
|
-
|
|
185
|
-
# Record request count
|
|
186
|
-
if method and status_code:
|
|
187
|
-
metrics.record_request(
|
|
188
|
-
method=method,
|
|
189
|
-
endpoint=endpoint,
|
|
190
|
-
model=model,
|
|
191
|
-
status=status_code,
|
|
192
|
-
service_type=service_type,
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
# Record response time
|
|
196
|
-
if context.duration_seconds > 0:
|
|
197
|
-
metrics.record_response_time(
|
|
198
|
-
duration_seconds=context.duration_seconds,
|
|
199
|
-
model=model,
|
|
200
|
-
endpoint=endpoint,
|
|
201
|
-
service_type=service_type,
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
# Record token usage
|
|
205
|
-
tokens_input = ctx_metadata.get("tokens_input")
|
|
206
|
-
if tokens_input:
|
|
207
|
-
metrics.record_tokens(
|
|
208
|
-
token_count=tokens_input,
|
|
209
|
-
token_type="input",
|
|
210
|
-
model=model,
|
|
211
|
-
service_type=service_type,
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
tokens_output = ctx_metadata.get("tokens_output")
|
|
215
|
-
if tokens_output:
|
|
216
|
-
metrics.record_tokens(
|
|
217
|
-
token_count=tokens_output,
|
|
218
|
-
token_type="output",
|
|
219
|
-
model=model,
|
|
220
|
-
service_type=service_type,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
cache_read_tokens = ctx_metadata.get("cache_read_tokens")
|
|
224
|
-
if cache_read_tokens:
|
|
225
|
-
metrics.record_tokens(
|
|
226
|
-
token_count=cache_read_tokens,
|
|
227
|
-
token_type="cache_read",
|
|
228
|
-
model=model,
|
|
229
|
-
service_type=service_type,
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
cache_write_tokens = ctx_metadata.get("cache_write_tokens")
|
|
233
|
-
if cache_write_tokens:
|
|
234
|
-
metrics.record_tokens(
|
|
235
|
-
token_count=cache_write_tokens,
|
|
236
|
-
token_type="cache_write",
|
|
237
|
-
model=model,
|
|
238
|
-
service_type=service_type,
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
# Record cost
|
|
242
|
-
cost_usd = ctx_metadata.get("cost_usd")
|
|
243
|
-
if cost_usd:
|
|
244
|
-
metrics.record_cost(
|
|
245
|
-
cost_usd=cost_usd,
|
|
246
|
-
model=model,
|
|
247
|
-
cost_type="total",
|
|
248
|
-
service_type=service_type,
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
# Record error if there was one
|
|
252
|
-
if metrics and error_message:
|
|
253
|
-
endpoint = ctx_metadata.get("endpoint", path or "unknown")
|
|
254
|
-
model = ctx_metadata.get("model")
|
|
255
|
-
service_type = ctx_metadata.get("service_type")
|
|
256
|
-
|
|
257
|
-
# Extract error type from error message or use generic
|
|
258
|
-
error_type = additional_metadata.get(
|
|
259
|
-
"error_type",
|
|
260
|
-
type(error_message).__name__
|
|
261
|
-
if hasattr(error_message, "__class__")
|
|
262
|
-
else "unknown_error",
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
metrics.record_error(
|
|
266
|
-
error_type=error_type,
|
|
267
|
-
endpoint=endpoint,
|
|
268
|
-
model=model,
|
|
269
|
-
service_type=service_type,
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
async def _store_access_log(
|
|
274
|
-
log_data: dict[str, Any], storage: SimpleDuckDBStorage | None = None
|
|
275
|
-
) -> None:
|
|
276
|
-
"""Store access log in DuckDB storage if available.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
log_data: Log data to store
|
|
280
|
-
storage: DuckDB storage instance (optional)
|
|
281
|
-
"""
|
|
282
|
-
if not storage:
|
|
283
|
-
return
|
|
284
|
-
|
|
285
|
-
try:
|
|
286
|
-
# Prepare data for DuckDB storage
|
|
287
|
-
storage_data: AccessLogPayload = {
|
|
288
|
-
"timestamp": time.time(),
|
|
289
|
-
"request_id": log_data.get("request_id") or "",
|
|
290
|
-
"method": log_data.get("method", ""),
|
|
291
|
-
"endpoint": log_data.get("endpoint", log_data.get("path", "")),
|
|
292
|
-
"path": log_data.get("path", ""),
|
|
293
|
-
"query": log_data.get("query", ""),
|
|
294
|
-
"client_ip": log_data.get("client_ip", ""),
|
|
295
|
-
"user_agent": log_data.get("user_agent", ""),
|
|
296
|
-
"service_type": log_data.get("service_type", ""),
|
|
297
|
-
"model": log_data.get("model", ""),
|
|
298
|
-
"streaming": log_data.get("streaming", False),
|
|
299
|
-
"status_code": log_data.get("status_code", 200),
|
|
300
|
-
"duration_ms": log_data.get("duration_ms", 0.0),
|
|
301
|
-
"duration_seconds": log_data.get("duration_seconds", 0.0),
|
|
302
|
-
"tokens_input": log_data.get("tokens_input", 0),
|
|
303
|
-
"tokens_output": log_data.get("tokens_output", 0),
|
|
304
|
-
"cache_read_tokens": log_data.get("cache_read_tokens", 0),
|
|
305
|
-
"cache_write_tokens": log_data.get("cache_write_tokens", 0),
|
|
306
|
-
"cost_usd": log_data.get("cost_usd", 0.0),
|
|
307
|
-
"cost_sdk_usd": log_data.get("cost_sdk_usd", 0.0),
|
|
308
|
-
"num_turns": log_data.get("num_turns", 0),
|
|
309
|
-
# Session context metadata
|
|
310
|
-
"session_type": log_data.get("session_type", ""),
|
|
311
|
-
"session_status": log_data.get("session_status", ""),
|
|
312
|
-
"session_age_seconds": log_data.get("session_age_seconds", 0.0),
|
|
313
|
-
"session_message_count": log_data.get("session_message_count", 0),
|
|
314
|
-
"session_client_id": log_data.get("session_client_id", ""),
|
|
315
|
-
"session_pool_enabled": log_data.get("session_pool_enabled", False),
|
|
316
|
-
"session_idle_seconds": log_data.get("session_idle_seconds", 0.0),
|
|
317
|
-
"session_error_count": log_data.get("session_error_count", 0),
|
|
318
|
-
"session_is_new": log_data.get("session_is_new", True),
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
# Store asynchronously using queue-based DuckDB (prevents deadlocks)
|
|
322
|
-
if storage:
|
|
323
|
-
await storage.store_request(storage_data)
|
|
324
|
-
|
|
325
|
-
except Exception as e:
|
|
326
|
-
# Log error but don't fail the request
|
|
327
|
-
logger.error(
|
|
328
|
-
"access_log_duckdb_error",
|
|
329
|
-
error=str(e),
|
|
330
|
-
request_id=log_data.get("request_id"),
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
async def _write_to_storage(storage: Any, data: dict[str, Any]) -> None:
|
|
335
|
-
"""Write data to storage asynchronously."""
|
|
336
|
-
try:
|
|
337
|
-
await storage.store_request(data)
|
|
338
|
-
except Exception as e:
|
|
339
|
-
logger.error(
|
|
340
|
-
"duckdb_store_error",
|
|
341
|
-
error=str(e),
|
|
342
|
-
request_id=data.get("request_id"),
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
async def _emit_access_event(event_type: str, data: dict[str, Any]) -> None:
|
|
347
|
-
"""Emit SSE event for real-time dashboard updates."""
|
|
348
|
-
try:
|
|
349
|
-
from ccproxy.observability.sse_events import emit_sse_event
|
|
350
|
-
|
|
351
|
-
# Create event data for SSE (exclude internal fields)
|
|
352
|
-
sse_data = {
|
|
353
|
-
"request_id": data.get("request_id"),
|
|
354
|
-
"method": data.get("method"),
|
|
355
|
-
"path": data.get("path"),
|
|
356
|
-
"query": data.get("query"),
|
|
357
|
-
"status_code": data.get("status_code"),
|
|
358
|
-
"client_ip": data.get("client_ip"),
|
|
359
|
-
"user_agent": data.get("user_agent"),
|
|
360
|
-
"service_type": data.get("service_type"),
|
|
361
|
-
"model": data.get("model"),
|
|
362
|
-
"streaming": data.get("streaming"),
|
|
363
|
-
"duration_ms": data.get("duration_ms"),
|
|
364
|
-
"duration_seconds": data.get("duration_seconds"),
|
|
365
|
-
"tokens_input": data.get("tokens_input"),
|
|
366
|
-
"tokens_output": data.get("tokens_output"),
|
|
367
|
-
"cost_usd": data.get("cost_usd"),
|
|
368
|
-
"endpoint": data.get("endpoint"),
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
# Remove None values
|
|
372
|
-
sse_data = {k: v for k, v in sse_data.items() if v is not None}
|
|
373
|
-
|
|
374
|
-
await emit_sse_event(event_type, sse_data)
|
|
375
|
-
|
|
376
|
-
except Exception as e:
|
|
377
|
-
# Log error but don't fail the request
|
|
378
|
-
logger.debug(
|
|
379
|
-
"sse_emit_failed",
|
|
380
|
-
event_type=event_type,
|
|
381
|
-
error=str(e),
|
|
382
|
-
request_id=data.get("request_id"),
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
def log_request_start(
|
|
387
|
-
request_id: str,
|
|
388
|
-
method: str,
|
|
389
|
-
path: str,
|
|
390
|
-
client_ip: str | None = None,
|
|
391
|
-
user_agent: str | None = None,
|
|
392
|
-
query: str | None = None,
|
|
393
|
-
**additional_metadata: Any,
|
|
394
|
-
) -> None:
|
|
395
|
-
"""Log request start event with basic information.
|
|
396
|
-
|
|
397
|
-
This is used for early/middleware logging when full context isn't available yet.
|
|
398
|
-
|
|
399
|
-
Args:
|
|
400
|
-
request_id: Request identifier
|
|
401
|
-
method: HTTP method
|
|
402
|
-
path: Request path
|
|
403
|
-
client_ip: Client IP address
|
|
404
|
-
user_agent: User agent string
|
|
405
|
-
query: Query parameters
|
|
406
|
-
**additional_metadata: Any additional fields to include
|
|
407
|
-
"""
|
|
408
|
-
log_data = {
|
|
409
|
-
"request_id": request_id,
|
|
410
|
-
"method": method,
|
|
411
|
-
"path": path,
|
|
412
|
-
"client_ip": client_ip,
|
|
413
|
-
"user_agent": user_agent,
|
|
414
|
-
"query": query,
|
|
415
|
-
"event_type": "request_start",
|
|
416
|
-
"timestamp": time.time(),
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
# Add any additional metadata
|
|
420
|
-
log_data.update(additional_metadata)
|
|
421
|
-
|
|
422
|
-
# Remove None values
|
|
423
|
-
log_data = {k: v for k, v in log_data.items() if v is not None}
|
|
424
|
-
|
|
425
|
-
logger.debug("access_log_start", **log_data)
|
|
426
|
-
|
|
427
|
-
# Emit SSE event for real-time dashboard updates
|
|
428
|
-
# Note: This is a synchronous function, so we schedule the async emission
|
|
429
|
-
try:
|
|
430
|
-
import asyncio
|
|
431
|
-
|
|
432
|
-
from ccproxy.observability.sse_events import emit_sse_event
|
|
433
|
-
|
|
434
|
-
# Create event data for SSE
|
|
435
|
-
sse_data = {
|
|
436
|
-
"request_id": request_id,
|
|
437
|
-
"method": method,
|
|
438
|
-
"path": path,
|
|
439
|
-
"client_ip": client_ip,
|
|
440
|
-
"user_agent": user_agent,
|
|
441
|
-
"query": query,
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
# Remove None values
|
|
445
|
-
sse_data = {k: v for k, v in sse_data.items() if v is not None}
|
|
446
|
-
|
|
447
|
-
# Schedule async event emission
|
|
448
|
-
asyncio.create_task(emit_sse_event("request_start", sse_data))
|
|
449
|
-
|
|
450
|
-
except Exception as e:
|
|
451
|
-
# Log error but don't fail the request
|
|
452
|
-
logger.debug(
|
|
453
|
-
"sse_emit_failed",
|
|
454
|
-
event_type="request_start",
|
|
455
|
-
error=str(e),
|
|
456
|
-
request_id=request_id,
|
|
457
|
-
)
|