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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Service for managing token limits and max_tokens modifications."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
8
|
+
|
|
9
|
+
from .config import MaxTokensConfig
|
|
10
|
+
from .models import MaxTokensModification, ModelTokenLimits, TokenLimitsData
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = get_plugin_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TokenLimitsService:
|
|
17
|
+
"""Service for managing model token limits and max_tokens modifications."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: MaxTokensConfig):
|
|
20
|
+
"""Initialize token limits service."""
|
|
21
|
+
self.config = config
|
|
22
|
+
self.token_limits_data = TokenLimitsData()
|
|
23
|
+
self._pricing_cache_path = (
|
|
24
|
+
Path.home() / ".cache" / "ccproxy" / "model_pricing.json"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if self.config.prioritize_local_file:
|
|
28
|
+
# Load local file first (takes precedence)
|
|
29
|
+
self._load_limits_from_local_file()
|
|
30
|
+
self._load_limits_from_pricing_cache()
|
|
31
|
+
else:
|
|
32
|
+
# Load pricing cache first, local file as fallback
|
|
33
|
+
self._load_limits_from_pricing_cache()
|
|
34
|
+
self._load_limits_from_local_file()
|
|
35
|
+
|
|
36
|
+
def _load_limits_from_pricing_cache(self) -> None:
|
|
37
|
+
"""Load token limits from pricing plugin cache."""
|
|
38
|
+
logger.debug(
|
|
39
|
+
"loading_token_limits_from_pricing_cache",
|
|
40
|
+
cache_path=str(self._pricing_cache_path),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if not self._pricing_cache_path.exists():
|
|
44
|
+
logger.warning(
|
|
45
|
+
"pricing_cache_not_found_plugin_will_not_modify_requests",
|
|
46
|
+
cache_path=str(self._pricing_cache_path),
|
|
47
|
+
message="max_tokens plugin requires pricing cache to operate",
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
with self._pricing_cache_path.open("r", encoding="utf-8") as f:
|
|
53
|
+
pricing_data = json.load(f)
|
|
54
|
+
|
|
55
|
+
loaded_count = 0
|
|
56
|
+
for model_name, model_data in pricing_data.items():
|
|
57
|
+
# Skip non-dict entries (like headers or metadata)
|
|
58
|
+
if not isinstance(model_data, dict):
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Skip image generation models and other non-text models
|
|
62
|
+
if model_data.get("mode") == "image_generation":
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Extract max_output_tokens (prefer this over max_tokens)
|
|
66
|
+
max_output = model_data.get("max_output_tokens") or model_data.get(
|
|
67
|
+
"max_tokens"
|
|
68
|
+
)
|
|
69
|
+
max_input = model_data.get("max_input_tokens")
|
|
70
|
+
|
|
71
|
+
# Skip if values are not integers (e.g., documentation strings)
|
|
72
|
+
if not isinstance(max_output, int):
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
if max_output:
|
|
76
|
+
self.token_limits_data.models[model_name] = ModelTokenLimits(
|
|
77
|
+
max_output_tokens=max_output,
|
|
78
|
+
max_input_tokens=max_input
|
|
79
|
+
if isinstance(max_input, int)
|
|
80
|
+
else None,
|
|
81
|
+
)
|
|
82
|
+
loaded_count += 1
|
|
83
|
+
|
|
84
|
+
logger.debug(
|
|
85
|
+
"token_limits_loaded_from_pricing_cache",
|
|
86
|
+
model_count=loaded_count,
|
|
87
|
+
cache_path=str(self._pricing_cache_path),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(
|
|
92
|
+
"failed_to_load_pricing_cache_plugin_will_not_modify_requests",
|
|
93
|
+
cache_path=str(self._pricing_cache_path),
|
|
94
|
+
error=str(e),
|
|
95
|
+
exc_info=e,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _load_limits_from_local_file(self) -> None:
|
|
99
|
+
"""Load token limits from local token_limits.json file."""
|
|
100
|
+
local_file_path = Path(self.config.default_token_limits_file)
|
|
101
|
+
|
|
102
|
+
logger.debug(
|
|
103
|
+
"loading_token_limits_from_local_file",
|
|
104
|
+
file_path=str(local_file_path),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not local_file_path.exists():
|
|
108
|
+
logger.debug(
|
|
109
|
+
"local_token_limits_file_not_found",
|
|
110
|
+
file_path=str(local_file_path),
|
|
111
|
+
)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
with local_file_path.open("r", encoding="utf-8") as f:
|
|
116
|
+
local_data = json.load(f)
|
|
117
|
+
|
|
118
|
+
# Handle flat structure like pricing cache (no nested "models" object)
|
|
119
|
+
models_data = {}
|
|
120
|
+
if "models" in local_data:
|
|
121
|
+
# Old format with nested models
|
|
122
|
+
models_data = local_data.get("models", {})
|
|
123
|
+
if not isinstance(models_data, dict):
|
|
124
|
+
logger.warning(
|
|
125
|
+
"invalid_local_token_limits_format",
|
|
126
|
+
file_path=str(local_file_path),
|
|
127
|
+
reason="models section is not a dictionary",
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
else:
|
|
131
|
+
# New flat format like pricing cache
|
|
132
|
+
models_data = {
|
|
133
|
+
k: v
|
|
134
|
+
for k, v in local_data.items()
|
|
135
|
+
if not k.startswith("_") and isinstance(v, dict)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
loaded_count = 0
|
|
139
|
+
for model_name, model_limits in models_data.items():
|
|
140
|
+
if not isinstance(model_limits, dict):
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
max_output = model_limits.get("max_output_tokens")
|
|
144
|
+
max_input = model_limits.get("max_input_tokens")
|
|
145
|
+
|
|
146
|
+
if isinstance(max_output, int) and max_output > 0:
|
|
147
|
+
if self.config.prioritize_local_file:
|
|
148
|
+
# Local file values take precedence over pricing cache
|
|
149
|
+
self.token_limits_data.models[model_name] = ModelTokenLimits(
|
|
150
|
+
max_output_tokens=max_output,
|
|
151
|
+
max_input_tokens=max_input
|
|
152
|
+
if isinstance(max_input, int) and max_input > 0
|
|
153
|
+
else None,
|
|
154
|
+
)
|
|
155
|
+
loaded_count += 1
|
|
156
|
+
else:
|
|
157
|
+
# Local file is fallback - only add if model doesn't exist
|
|
158
|
+
if model_name not in self.token_limits_data.models:
|
|
159
|
+
self.token_limits_data.models[model_name] = (
|
|
160
|
+
ModelTokenLimits(
|
|
161
|
+
max_output_tokens=max_output,
|
|
162
|
+
max_input_tokens=max_input
|
|
163
|
+
if isinstance(max_input, int) and max_input > 0
|
|
164
|
+
else None,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
loaded_count += 1
|
|
168
|
+
|
|
169
|
+
logger.debug(
|
|
170
|
+
"token_limits_loaded_from_local_file",
|
|
171
|
+
file_path=str(local_file_path),
|
|
172
|
+
model_count=loaded_count,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(
|
|
177
|
+
"failed_to_load_local_token_limits_file",
|
|
178
|
+
file_path=str(local_file_path),
|
|
179
|
+
error=str(e),
|
|
180
|
+
exc_info=e,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def get_max_output_tokens(self, model_name: str) -> int | None:
|
|
184
|
+
"""Get maximum output tokens for a model."""
|
|
185
|
+
return self.token_limits_data.get_max_output_tokens(model_name)
|
|
186
|
+
|
|
187
|
+
def should_modify_max_tokens(
|
|
188
|
+
self, request_data: dict[str, Any], model: str
|
|
189
|
+
) -> tuple[bool, str]:
|
|
190
|
+
"""Determine if max_tokens should be modified for the request."""
|
|
191
|
+
current_max_tokens = request_data.get("max_tokens")
|
|
192
|
+
|
|
193
|
+
# Enforce mode: always modify to set max_tokens to model limit
|
|
194
|
+
if self.config.enforce_mode:
|
|
195
|
+
return True, "enforced"
|
|
196
|
+
|
|
197
|
+
# Case 1: No max_tokens provided
|
|
198
|
+
if current_max_tokens is None:
|
|
199
|
+
return True, "missing"
|
|
200
|
+
|
|
201
|
+
# Case 2: Invalid max_tokens (not a positive integer)
|
|
202
|
+
if not isinstance(current_max_tokens, int) or current_max_tokens <= 0:
|
|
203
|
+
return True, "invalid"
|
|
204
|
+
|
|
205
|
+
# Case 3: Max tokens exceeds model limit
|
|
206
|
+
model_limit = self.get_max_output_tokens(model)
|
|
207
|
+
if model_limit and current_max_tokens > model_limit:
|
|
208
|
+
return True, "exceeded"
|
|
209
|
+
|
|
210
|
+
# No modification needed
|
|
211
|
+
return False, "none"
|
|
212
|
+
|
|
213
|
+
def modify_max_tokens(
|
|
214
|
+
self, request_data: dict[str, Any], model: str, provider: str | None = None
|
|
215
|
+
) -> tuple[dict[str, Any], MaxTokensModification | None]:
|
|
216
|
+
"""Modify max_tokens in request data if needed."""
|
|
217
|
+
should_modify, reason_type = self.should_modify_max_tokens(request_data, model)
|
|
218
|
+
|
|
219
|
+
if not should_modify:
|
|
220
|
+
return request_data, None
|
|
221
|
+
|
|
222
|
+
original_max_tokens = request_data.get("max_tokens")
|
|
223
|
+
|
|
224
|
+
# Determine the appropriate max_tokens value
|
|
225
|
+
model_limit = self.get_max_output_tokens(model)
|
|
226
|
+
|
|
227
|
+
if model_limit:
|
|
228
|
+
new_max_tokens = model_limit
|
|
229
|
+
else:
|
|
230
|
+
# Use fallback when model limit is unknown
|
|
231
|
+
new_max_tokens = self.config.fallback_max_tokens
|
|
232
|
+
logger.debug(
|
|
233
|
+
"using_fallback_max_tokens",
|
|
234
|
+
model=model,
|
|
235
|
+
fallback=self.config.fallback_max_tokens,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Create modification info
|
|
239
|
+
modification = MaxTokensModification(
|
|
240
|
+
original_max_tokens=original_max_tokens,
|
|
241
|
+
new_max_tokens=new_max_tokens,
|
|
242
|
+
model=model,
|
|
243
|
+
reason=self.config.get_modification_reason(reason_type),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Create modified request data
|
|
247
|
+
modified_data = request_data.copy()
|
|
248
|
+
modified_data["max_tokens"] = new_max_tokens
|
|
249
|
+
|
|
250
|
+
if self.config.log_modifications:
|
|
251
|
+
logger.info(
|
|
252
|
+
"max_tokens_modified",
|
|
253
|
+
model=model,
|
|
254
|
+
provider=provider,
|
|
255
|
+
original=original_max_tokens,
|
|
256
|
+
new=new_max_tokens,
|
|
257
|
+
reason=modification.reason,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return modified_data, modification
|
|
261
|
+
|
|
262
|
+
def initialize(self) -> None:
|
|
263
|
+
"""Initialize the service."""
|
|
264
|
+
logger.debug(
|
|
265
|
+
"token_limits_service_initialized",
|
|
266
|
+
models_count=len(self.token_limits_data.models),
|
|
267
|
+
pricing_cache=str(self._pricing_cache_path),
|
|
268
|
+
fallback=self.config.fallback_max_tokens,
|
|
269
|
+
enforce_mode=self.config.enforce_mode,
|
|
270
|
+
prioritize_local_file=self.config.prioritize_local_file,
|
|
271
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claude-opus-4-1-20250805": {
|
|
3
|
+
"max_output_tokens": 32000,
|
|
4
|
+
"max_input_tokens": 200000
|
|
5
|
+
},
|
|
6
|
+
"claude-opus-4-20250514": {
|
|
7
|
+
"max_output_tokens": 32000,
|
|
8
|
+
"max_input_tokens": 200000
|
|
9
|
+
},
|
|
10
|
+
"claude-sonnet-4-20250514": {
|
|
11
|
+
"max_output_tokens": 64000,
|
|
12
|
+
"max_input_tokens": 1000000
|
|
13
|
+
},
|
|
14
|
+
"claude-3-7-sonnet-20250219": {
|
|
15
|
+
"max_output_tokens": 8192,
|
|
16
|
+
"max_input_tokens": 200000
|
|
17
|
+
},
|
|
18
|
+
"claude-3-5-sonnet-20241022": {
|
|
19
|
+
"max_output_tokens": 8192,
|
|
20
|
+
"max_input_tokens": 200000
|
|
21
|
+
},
|
|
22
|
+
"claude-3-5-haiku-20241022": {
|
|
23
|
+
"max_output_tokens": 8192,
|
|
24
|
+
"max_input_tokens": 200000
|
|
25
|
+
},
|
|
26
|
+
"claude-3-opus-20240229": {
|
|
27
|
+
"max_output_tokens": 4096,
|
|
28
|
+
"max_input_tokens": 200000
|
|
29
|
+
},
|
|
30
|
+
"claude-3-sonnet-20240229": {
|
|
31
|
+
"max_output_tokens": 4096,
|
|
32
|
+
"max_input_tokens": 200000
|
|
33
|
+
},
|
|
34
|
+
"claude-3-haiku-20240307": {
|
|
35
|
+
"max_output_tokens": 4096,
|
|
36
|
+
"max_input_tokens": 200000
|
|
37
|
+
},
|
|
38
|
+
"gpt-5": {
|
|
39
|
+
"max_output_tokens": 128000,
|
|
40
|
+
"max_input_tokens": 272000
|
|
41
|
+
},
|
|
42
|
+
"gpt-5-codex": {
|
|
43
|
+
"max_output_tokens": 128000,
|
|
44
|
+
"max_input_tokens": 272000
|
|
45
|
+
},
|
|
46
|
+
"_metadata": {
|
|
47
|
+
"source": "generated from ~/.cache/ccproxy/model_pricing.json",
|
|
48
|
+
"claude_models_count": 9,
|
|
49
|
+
"codex_models_count": 2,
|
|
50
|
+
"total_models": 11,
|
|
51
|
+
"generated_for": "max_tokens plugin enforce mode support",
|
|
52
|
+
"note": "Flat structure format, uses simple model names for compatibility with request handling"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Metrics Plugin
|
|
2
|
+
|
|
3
|
+
Collects Prometheus-style metrics for CCProxy events and optionally pushes them.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
- Registers a metrics hook to observe request, token, error, and pool events
|
|
7
|
+
- Exposes a `/metrics` endpoint for scraping when enabled
|
|
8
|
+
- Can schedule a Pushgateway task for off-box metrics delivery
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
- `MetricsConfig` toggles collection, namespace, endpoint, and pushgateway options
|
|
12
|
+
- Scheduler integration is automatic when push mode is enabled
|
|
13
|
+
- Generate defaults with `python3 scripts/generate_config_from_model.py \
|
|
14
|
+
--format toml --plugin metrics --config-class MetricsConfig`
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[plugins.metrics]
|
|
18
|
+
# enabled = true
|
|
19
|
+
# namespace = "ccproxy"
|
|
20
|
+
# metrics_endpoint_enabled = true
|
|
21
|
+
# pushgateway_enabled = false
|
|
22
|
+
# pushgateway_job = "ccproxy"
|
|
23
|
+
# pushgateway_push_interval = 60
|
|
24
|
+
# collect_request_metrics = true
|
|
25
|
+
# collect_token_metrics = true
|
|
26
|
+
# collect_cost_metrics = true
|
|
27
|
+
# collect_error_metrics = true
|
|
28
|
+
# collect_pool_metrics = true
|
|
29
|
+
# histogram_buckets = [0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 25.0]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Related Components
|
|
33
|
+
- `hook.py`: implements the metrics collector and event handling
|
|
34
|
+
- `routes.py`: builds the FastAPI router for Prometheus scrape format
|
|
35
|
+
- `tasks.py`: Pushgateway task invoked by the scheduler
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Prometheus metrics for
|
|
2
|
+
Prometheus metrics collector for the metrics plugin.
|
|
3
3
|
|
|
4
4
|
This module provides direct prometheus_client integration for fast operational metrics
|
|
5
5
|
like request counts, response times, and resource usage. These metrics are optimized
|
|
@@ -10,7 +10,7 @@ Key features:
|
|
|
10
10
|
- Minimal overhead for high-frequency operations
|
|
11
11
|
- Standard Prometheus metric types (Counter, Histogram, Gauge)
|
|
12
12
|
- Automatic label management and validation
|
|
13
|
-
-
|
|
13
|
+
- Integration with hook events for metric updates
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
@@ -84,10 +84,10 @@ except ImportError:
|
|
|
84
84
|
CollectorRegistry = _DummyCollectorRegistry # type: ignore[misc,assignment]
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
from
|
|
87
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
logger =
|
|
90
|
+
logger = get_plugin_logger(__name__)
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
class PrometheusMetrics:
|
|
@@ -102,7 +102,7 @@ class PrometheusMetrics:
|
|
|
102
102
|
self,
|
|
103
103
|
namespace: str = "ccproxy",
|
|
104
104
|
registry: CollectorRegistry | None = None,
|
|
105
|
-
|
|
105
|
+
histogram_buckets: list[float] | None = None,
|
|
106
106
|
):
|
|
107
107
|
"""
|
|
108
108
|
Initialize Prometheus metrics.
|
|
@@ -110,7 +110,7 @@ class PrometheusMetrics:
|
|
|
110
110
|
Args:
|
|
111
111
|
namespace: Metric name prefix
|
|
112
112
|
registry: Custom Prometheus registry (uses default if None)
|
|
113
|
-
|
|
113
|
+
histogram_buckets: Custom histogram bucket boundaries
|
|
114
114
|
"""
|
|
115
115
|
if not PROMETHEUS_AVAILABLE:
|
|
116
116
|
logger.warning(
|
|
@@ -127,13 +127,21 @@ class PrometheusMetrics:
|
|
|
127
127
|
else:
|
|
128
128
|
self.registry = registry
|
|
129
129
|
self._enabled = PROMETHEUS_AVAILABLE
|
|
130
|
-
self.
|
|
130
|
+
self._histogram_buckets = histogram_buckets or [
|
|
131
|
+
0.01,
|
|
132
|
+
0.05,
|
|
133
|
+
0.1,
|
|
134
|
+
0.25,
|
|
135
|
+
0.5,
|
|
136
|
+
1.0,
|
|
137
|
+
2.5,
|
|
138
|
+
5.0,
|
|
139
|
+
10.0,
|
|
140
|
+
25.0,
|
|
141
|
+
]
|
|
131
142
|
|
|
132
143
|
if self._enabled:
|
|
133
144
|
self._init_metrics()
|
|
134
|
-
# Initialize pushgateway client if not provided via DI
|
|
135
|
-
if self._pushgateway_client is None:
|
|
136
|
-
self._init_pushgateway()
|
|
137
145
|
|
|
138
146
|
def _init_metrics(self) -> None:
|
|
139
147
|
"""Initialize all Prometheus metric objects."""
|
|
@@ -149,7 +157,7 @@ class PrometheusMetrics:
|
|
|
149
157
|
f"{self.namespace}_response_duration_seconds",
|
|
150
158
|
"Response time in seconds",
|
|
151
159
|
labelnames=["model", "endpoint", "service_type"],
|
|
152
|
-
buckets=
|
|
160
|
+
buckets=self._histogram_buckets,
|
|
153
161
|
registry=self.registry,
|
|
154
162
|
)
|
|
155
163
|
|
|
@@ -263,7 +271,7 @@ class PrometheusMetrics:
|
|
|
263
271
|
|
|
264
272
|
# Set initial system info
|
|
265
273
|
try:
|
|
266
|
-
from ccproxy import __version__
|
|
274
|
+
from ccproxy.core import __version__
|
|
267
275
|
|
|
268
276
|
version = __version__
|
|
269
277
|
except ImportError:
|
|
@@ -279,28 +287,6 @@ class PrometheusMetrics:
|
|
|
279
287
|
# Set service as up
|
|
280
288
|
self.up.labels(job="ccproxy").set(1)
|
|
281
289
|
|
|
282
|
-
def _init_pushgateway(self) -> None:
|
|
283
|
-
"""Initialize Pushgateway client if configured (fallback for non-DI usage)."""
|
|
284
|
-
try:
|
|
285
|
-
# Import here to avoid circular imports
|
|
286
|
-
from ccproxy.config.settings import get_settings
|
|
287
|
-
|
|
288
|
-
from .pushgateway import PushgatewayClient
|
|
289
|
-
|
|
290
|
-
settings = get_settings()
|
|
291
|
-
|
|
292
|
-
self._pushgateway_client = PushgatewayClient(settings.observability)
|
|
293
|
-
|
|
294
|
-
if self._pushgateway_client.is_enabled():
|
|
295
|
-
logger.info(
|
|
296
|
-
"pushgateway_initialized: url=%s job=%s",
|
|
297
|
-
settings.observability.pushgateway_url,
|
|
298
|
-
settings.observability.pushgateway_job,
|
|
299
|
-
)
|
|
300
|
-
except Exception as e:
|
|
301
|
-
logger.warning("pushgateway_init_failed: error=%s", str(e))
|
|
302
|
-
self._pushgateway_client = None
|
|
303
|
-
|
|
304
290
|
def record_request(
|
|
305
291
|
self,
|
|
306
292
|
method: str,
|
|
@@ -473,57 +459,6 @@ class PrometheusMetrics:
|
|
|
473
459
|
"""Check if metrics collection is enabled."""
|
|
474
460
|
return self._enabled
|
|
475
461
|
|
|
476
|
-
def push_to_gateway(self, method: str = "push") -> bool:
|
|
477
|
-
"""
|
|
478
|
-
Push current metrics to Pushgateway using official prometheus_client methods.
|
|
479
|
-
|
|
480
|
-
Args:
|
|
481
|
-
method: Push method - "push" (replace), "pushadd" (add), or "delete"
|
|
482
|
-
|
|
483
|
-
Returns:
|
|
484
|
-
True if push succeeded, False otherwise
|
|
485
|
-
"""
|
|
486
|
-
|
|
487
|
-
if not self._enabled or not self._pushgateway_client:
|
|
488
|
-
return False
|
|
489
|
-
|
|
490
|
-
result = self._pushgateway_client.push_metrics(self.registry, method)
|
|
491
|
-
return bool(result)
|
|
492
|
-
|
|
493
|
-
def push_add_to_gateway(self) -> bool:
|
|
494
|
-
"""
|
|
495
|
-
Add current metrics to existing job/instance in Pushgateway (pushadd operation).
|
|
496
|
-
|
|
497
|
-
This is useful when you want to add metrics without replacing existing ones.
|
|
498
|
-
|
|
499
|
-
Returns:
|
|
500
|
-
True if push succeeded, False otherwise
|
|
501
|
-
"""
|
|
502
|
-
return self.push_to_gateway(method="pushadd")
|
|
503
|
-
|
|
504
|
-
def delete_from_gateway(self) -> bool:
|
|
505
|
-
"""
|
|
506
|
-
Delete all metrics for the configured job from Pushgateway.
|
|
507
|
-
|
|
508
|
-
This removes all metrics associated with the job, useful for cleanup.
|
|
509
|
-
|
|
510
|
-
Returns:
|
|
511
|
-
True if delete succeeded, False otherwise
|
|
512
|
-
"""
|
|
513
|
-
|
|
514
|
-
if not self._enabled or not self._pushgateway_client:
|
|
515
|
-
return False
|
|
516
|
-
|
|
517
|
-
result = self._pushgateway_client.delete_metrics()
|
|
518
|
-
return bool(result)
|
|
519
|
-
|
|
520
|
-
def is_pushgateway_enabled(self) -> bool:
|
|
521
|
-
"""Check if Pushgateway client is enabled and configured."""
|
|
522
|
-
return (
|
|
523
|
-
self._pushgateway_client is not None
|
|
524
|
-
and self._pushgateway_client.is_enabled()
|
|
525
|
-
)
|
|
526
|
-
|
|
527
462
|
# Claude SDK Pool metrics methods
|
|
528
463
|
|
|
529
464
|
def update_pool_gauges(
|
|
@@ -618,71 +553,3 @@ class PrometheusMetrics:
|
|
|
618
553
|
return
|
|
619
554
|
|
|
620
555
|
self.pool_clients_active.set(count)
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
# Global metrics instance
|
|
624
|
-
_global_metrics: PrometheusMetrics | None = None
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
def get_metrics(
|
|
628
|
-
namespace: str = "ccproxy",
|
|
629
|
-
registry: CollectorRegistry | None = None,
|
|
630
|
-
pushgateway_client: Any | None = None,
|
|
631
|
-
settings: Any | None = None,
|
|
632
|
-
) -> PrometheusMetrics:
|
|
633
|
-
"""
|
|
634
|
-
Get or create global metrics instance with dependency injection.
|
|
635
|
-
|
|
636
|
-
Args:
|
|
637
|
-
namespace: Metric namespace prefix
|
|
638
|
-
registry: Custom Prometheus registry
|
|
639
|
-
pushgateway_client: Optional pushgateway client for dependency injection
|
|
640
|
-
settings: Optional settings instance to avoid circular imports
|
|
641
|
-
|
|
642
|
-
Returns:
|
|
643
|
-
PrometheusMetrics instance with full pushgateway support:
|
|
644
|
-
- push_to_gateway(): Replace all metrics (default)
|
|
645
|
-
- push_add_to_gateway(): Add metrics to existing job
|
|
646
|
-
- delete_from_gateway(): Delete all metrics for job
|
|
647
|
-
"""
|
|
648
|
-
global _global_metrics
|
|
649
|
-
|
|
650
|
-
if _global_metrics is None:
|
|
651
|
-
# Create pushgateway client if not provided via DI
|
|
652
|
-
if pushgateway_client is None:
|
|
653
|
-
from .pushgateway import get_pushgateway_client
|
|
654
|
-
|
|
655
|
-
pushgateway_client = get_pushgateway_client()
|
|
656
|
-
|
|
657
|
-
_global_metrics = PrometheusMetrics(
|
|
658
|
-
namespace=namespace,
|
|
659
|
-
registry=registry,
|
|
660
|
-
pushgateway_client=pushgateway_client,
|
|
661
|
-
)
|
|
662
|
-
|
|
663
|
-
return _global_metrics
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
def reset_metrics() -> None:
|
|
667
|
-
"""Reset global metrics instance (mainly for testing)."""
|
|
668
|
-
global _global_metrics
|
|
669
|
-
_global_metrics = None
|
|
670
|
-
|
|
671
|
-
# Clear Prometheus registry to avoid duplicate metrics in tests
|
|
672
|
-
if PROMETHEUS_AVAILABLE:
|
|
673
|
-
try:
|
|
674
|
-
from prometheus_client import REGISTRY
|
|
675
|
-
|
|
676
|
-
# Clear all collectors from the registry
|
|
677
|
-
collectors = list(REGISTRY._collector_to_names.keys())
|
|
678
|
-
for collector in collectors:
|
|
679
|
-
REGISTRY.unregister(collector)
|
|
680
|
-
except Exception:
|
|
681
|
-
# If clearing the registry fails, just continue
|
|
682
|
-
# This is mainly for testing and shouldn't break functionality
|
|
683
|
-
pass
|
|
684
|
-
|
|
685
|
-
# Also reset pushgateway client
|
|
686
|
-
from .pushgateway import reset_pushgateway_client
|
|
687
|
-
|
|
688
|
-
reset_pushgateway_client()
|