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/scheduler/tasks.py
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
"""Base scheduled task classes and task implementations."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import contextlib
|
|
5
4
|
import random
|
|
6
5
|
import time
|
|
7
6
|
from abc import ABC, abstractmethod
|
|
8
|
-
from datetime import UTC
|
|
7
|
+
from datetime import UTC, datetime
|
|
9
8
|
from typing import Any
|
|
10
9
|
|
|
11
10
|
import structlog
|
|
11
|
+
from packaging import version as pkg_version
|
|
12
|
+
|
|
13
|
+
from ccproxy.core.async_task_manager import create_managed_task
|
|
14
|
+
from ccproxy.scheduler.errors import SchedulerError
|
|
15
|
+
from ccproxy.utils.version_checker import (
|
|
16
|
+
VersionCheckState,
|
|
17
|
+
commit_refs_match,
|
|
18
|
+
compare_versions,
|
|
19
|
+
extract_commit_from_version,
|
|
20
|
+
fetch_latest_branch_commit,
|
|
21
|
+
fetch_latest_github_version,
|
|
22
|
+
get_branch_override,
|
|
23
|
+
get_current_version,
|
|
24
|
+
get_version_check_state_path,
|
|
25
|
+
load_check_state,
|
|
26
|
+
resolve_branch_for_commit,
|
|
27
|
+
save_check_state,
|
|
28
|
+
)
|
|
12
29
|
|
|
13
30
|
|
|
14
31
|
logger = structlog.get_logger(__name__)
|
|
@@ -50,6 +67,7 @@ class BaseScheduledTask(ABC):
|
|
|
50
67
|
self._last_run_time: float = 0
|
|
51
68
|
self._running = False
|
|
52
69
|
self._task: asyncio.Task[Any] | None = None
|
|
70
|
+
self._stop_complete: asyncio.Event | None = None
|
|
53
71
|
|
|
54
72
|
@abstractmethod
|
|
55
73
|
async def run(self) -> bool:
|
|
@@ -111,12 +129,27 @@ class BaseScheduledTask(ABC):
|
|
|
111
129
|
return
|
|
112
130
|
|
|
113
131
|
self._running = True
|
|
132
|
+
self._stop_complete = asyncio.Event()
|
|
114
133
|
logger.debug("task_starting", task_name=self.name)
|
|
115
134
|
|
|
116
135
|
try:
|
|
117
136
|
await self.setup()
|
|
118
|
-
self._task =
|
|
137
|
+
self._task = await create_managed_task(
|
|
138
|
+
self._run_loop(),
|
|
139
|
+
name=f"scheduled_task_{self.name}",
|
|
140
|
+
creator="BaseScheduledTask",
|
|
141
|
+
)
|
|
119
142
|
logger.debug("task_started", task_name=self.name)
|
|
143
|
+
except SchedulerError as e:
|
|
144
|
+
self._running = False
|
|
145
|
+
logger.error(
|
|
146
|
+
"task_start_scheduler_error",
|
|
147
|
+
task_name=self.name,
|
|
148
|
+
error=str(e),
|
|
149
|
+
error_type=type(e).__name__,
|
|
150
|
+
exc_info=e,
|
|
151
|
+
)
|
|
152
|
+
raise
|
|
120
153
|
except Exception as e:
|
|
121
154
|
self._running = False
|
|
122
155
|
logger.error(
|
|
@@ -124,6 +157,7 @@ class BaseScheduledTask(ABC):
|
|
|
124
157
|
task_name=self.name,
|
|
125
158
|
error=str(e),
|
|
126
159
|
error_type=type(e).__name__,
|
|
160
|
+
exc_info=e,
|
|
127
161
|
)
|
|
128
162
|
raise
|
|
129
163
|
|
|
@@ -135,21 +169,55 @@ class BaseScheduledTask(ABC):
|
|
|
135
169
|
self._running = False
|
|
136
170
|
logger.debug("task_stopping", task_name=self.name)
|
|
137
171
|
|
|
138
|
-
# Cancel the running task
|
|
172
|
+
# Cancel the running task and wait for it to complete
|
|
139
173
|
if self._task and not self._task.done():
|
|
140
174
|
self._task.cancel()
|
|
141
|
-
|
|
175
|
+
try:
|
|
176
|
+
# Wait for the task to complete cancellation
|
|
142
177
|
await self._task
|
|
178
|
+
except asyncio.CancelledError:
|
|
179
|
+
# Expected when task is cancelled
|
|
180
|
+
pass
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.warning(
|
|
183
|
+
"task_stop_unexpected_error",
|
|
184
|
+
task_name=self.name,
|
|
185
|
+
error=str(e),
|
|
186
|
+
error_type=type(e).__name__,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Ensure the task reference is cleared
|
|
190
|
+
self._task = None
|
|
191
|
+
|
|
192
|
+
# Wait for the completion event to be signaled
|
|
193
|
+
if self._stop_complete is not None:
|
|
194
|
+
try:
|
|
195
|
+
await asyncio.wait_for(self._stop_complete.wait(), timeout=1.0)
|
|
196
|
+
except TimeoutError:
|
|
197
|
+
logger.warning(
|
|
198
|
+
"task_stop_completion_timeout",
|
|
199
|
+
task_name=self.name,
|
|
200
|
+
message="Task stop completion event not signaled within timeout",
|
|
201
|
+
)
|
|
143
202
|
|
|
144
203
|
try:
|
|
145
204
|
await self.cleanup()
|
|
146
205
|
logger.debug("task_stopped", task_name=self.name)
|
|
206
|
+
except SchedulerError as e:
|
|
207
|
+
logger.error(
|
|
208
|
+
"task_cleanup_scheduler_error",
|
|
209
|
+
task_name=self.name,
|
|
210
|
+
error=str(e),
|
|
211
|
+
error_type=type(e).__name__,
|
|
212
|
+
exc_info=e,
|
|
213
|
+
)
|
|
147
214
|
except Exception as e:
|
|
148
215
|
logger.error(
|
|
149
216
|
"task_cleanup_failed",
|
|
150
217
|
task_name=self.name,
|
|
151
218
|
error=str(e),
|
|
152
219
|
error_type=type(e).__name__,
|
|
220
|
+
exc_info=e,
|
|
153
221
|
)
|
|
154
222
|
|
|
155
223
|
async def _run_loop(self) -> None:
|
|
@@ -199,6 +267,32 @@ class BaseScheduledTask(ABC):
|
|
|
199
267
|
except asyncio.CancelledError:
|
|
200
268
|
logger.debug("task_cancelled", task_name=self.name)
|
|
201
269
|
break
|
|
270
|
+
except TimeoutError as e:
|
|
271
|
+
self._consecutive_failures += 1
|
|
272
|
+
logger.error(
|
|
273
|
+
"task_execution_timeout_error",
|
|
274
|
+
task_name=self.name,
|
|
275
|
+
error=str(e),
|
|
276
|
+
error_type=type(e).__name__,
|
|
277
|
+
consecutive_failures=self._consecutive_failures,
|
|
278
|
+
exc_info=e,
|
|
279
|
+
)
|
|
280
|
+
# Use backoff delay for exceptions too
|
|
281
|
+
backoff_delay = self.calculate_next_delay()
|
|
282
|
+
await asyncio.sleep(backoff_delay)
|
|
283
|
+
except SchedulerError as e:
|
|
284
|
+
self._consecutive_failures += 1
|
|
285
|
+
logger.error(
|
|
286
|
+
"task_execution_scheduler_error",
|
|
287
|
+
task_name=self.name,
|
|
288
|
+
error=str(e),
|
|
289
|
+
error_type=type(e).__name__,
|
|
290
|
+
consecutive_failures=self._consecutive_failures,
|
|
291
|
+
exc_info=e,
|
|
292
|
+
)
|
|
293
|
+
# Use backoff delay for exceptions too
|
|
294
|
+
backoff_delay = self.calculate_next_delay()
|
|
295
|
+
await asyncio.sleep(backoff_delay)
|
|
202
296
|
except Exception as e:
|
|
203
297
|
self._consecutive_failures += 1
|
|
204
298
|
logger.error(
|
|
@@ -207,12 +301,16 @@ class BaseScheduledTask(ABC):
|
|
|
207
301
|
error=str(e),
|
|
208
302
|
error_type=type(e).__name__,
|
|
209
303
|
consecutive_failures=self._consecutive_failures,
|
|
304
|
+
exc_info=e,
|
|
210
305
|
)
|
|
211
|
-
|
|
212
306
|
# Use backoff delay for exceptions too
|
|
213
307
|
backoff_delay = self.calculate_next_delay()
|
|
214
308
|
await asyncio.sleep(backoff_delay)
|
|
215
309
|
|
|
310
|
+
# Signal that the task has completed
|
|
311
|
+
if self._stop_complete is not None:
|
|
312
|
+
self._stop_complete.set()
|
|
313
|
+
|
|
216
314
|
@property
|
|
217
315
|
def is_running(self) -> bool:
|
|
218
316
|
"""Check if the task is currently running."""
|
|
@@ -246,243 +344,6 @@ class BaseScheduledTask(ABC):
|
|
|
246
344
|
}
|
|
247
345
|
|
|
248
346
|
|
|
249
|
-
class PushgatewayTask(BaseScheduledTask):
|
|
250
|
-
"""Task for pushing metrics to Pushgateway periodically."""
|
|
251
|
-
|
|
252
|
-
def __init__(
|
|
253
|
-
self,
|
|
254
|
-
name: str,
|
|
255
|
-
interval_seconds: float,
|
|
256
|
-
enabled: bool = True,
|
|
257
|
-
max_backoff_seconds: float = 300.0,
|
|
258
|
-
):
|
|
259
|
-
"""
|
|
260
|
-
Initialize pushgateway task.
|
|
261
|
-
|
|
262
|
-
Args:
|
|
263
|
-
name: Task name
|
|
264
|
-
interval_seconds: Interval between pushgateway operations
|
|
265
|
-
enabled: Whether task is enabled
|
|
266
|
-
max_backoff_seconds: Maximum backoff delay for failures
|
|
267
|
-
"""
|
|
268
|
-
super().__init__(
|
|
269
|
-
name=name,
|
|
270
|
-
interval_seconds=interval_seconds,
|
|
271
|
-
enabled=enabled,
|
|
272
|
-
max_backoff_seconds=max_backoff_seconds,
|
|
273
|
-
)
|
|
274
|
-
self._metrics_instance: Any | None = None
|
|
275
|
-
|
|
276
|
-
async def setup(self) -> None:
|
|
277
|
-
"""Initialize metrics instance for pushgateway operations."""
|
|
278
|
-
try:
|
|
279
|
-
from ccproxy.observability.metrics import get_metrics
|
|
280
|
-
|
|
281
|
-
self._metrics_instance = get_metrics()
|
|
282
|
-
logger.debug("pushgateway_task_setup_complete", task_name=self.name)
|
|
283
|
-
except Exception as e:
|
|
284
|
-
logger.error(
|
|
285
|
-
"pushgateway_task_setup_failed",
|
|
286
|
-
task_name=self.name,
|
|
287
|
-
error=str(e),
|
|
288
|
-
error_type=type(e).__name__,
|
|
289
|
-
)
|
|
290
|
-
raise
|
|
291
|
-
|
|
292
|
-
async def run(self) -> bool:
|
|
293
|
-
"""Execute pushgateway metrics push."""
|
|
294
|
-
try:
|
|
295
|
-
if not self._metrics_instance:
|
|
296
|
-
logger.warning("pushgateway_no_metrics_instance", task_name=self.name)
|
|
297
|
-
return False
|
|
298
|
-
|
|
299
|
-
if not self._metrics_instance.is_pushgateway_enabled():
|
|
300
|
-
logger.debug("pushgateway_disabled", task_name=self.name)
|
|
301
|
-
return True # Not an error, just disabled
|
|
302
|
-
|
|
303
|
-
success = bool(self._metrics_instance.push_to_gateway())
|
|
304
|
-
|
|
305
|
-
if success:
|
|
306
|
-
logger.debug("pushgateway_push_success", task_name=self.name)
|
|
307
|
-
else:
|
|
308
|
-
logger.warning("pushgateway_push_failed", task_name=self.name)
|
|
309
|
-
|
|
310
|
-
return success
|
|
311
|
-
|
|
312
|
-
except Exception as e:
|
|
313
|
-
logger.error(
|
|
314
|
-
"pushgateway_task_error",
|
|
315
|
-
task_name=self.name,
|
|
316
|
-
error=str(e),
|
|
317
|
-
error_type=type(e).__name__,
|
|
318
|
-
)
|
|
319
|
-
return False
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
class StatsPrintingTask(BaseScheduledTask):
|
|
323
|
-
"""Task for printing stats summary periodically."""
|
|
324
|
-
|
|
325
|
-
def __init__(
|
|
326
|
-
self,
|
|
327
|
-
name: str,
|
|
328
|
-
interval_seconds: float,
|
|
329
|
-
enabled: bool = True,
|
|
330
|
-
):
|
|
331
|
-
"""
|
|
332
|
-
Initialize stats printing task.
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
name: Task name
|
|
336
|
-
interval_seconds: Interval between stats printing
|
|
337
|
-
enabled: Whether task is enabled
|
|
338
|
-
"""
|
|
339
|
-
super().__init__(
|
|
340
|
-
name=name,
|
|
341
|
-
interval_seconds=interval_seconds,
|
|
342
|
-
enabled=enabled,
|
|
343
|
-
)
|
|
344
|
-
self._stats_collector_instance: Any | None = None
|
|
345
|
-
self._metrics_instance: Any | None = None
|
|
346
|
-
|
|
347
|
-
async def setup(self) -> None:
|
|
348
|
-
"""Initialize stats collector and metrics instances."""
|
|
349
|
-
try:
|
|
350
|
-
from ccproxy.config.settings import get_settings
|
|
351
|
-
from ccproxy.observability.metrics import get_metrics
|
|
352
|
-
from ccproxy.observability.stats_printer import get_stats_collector
|
|
353
|
-
|
|
354
|
-
self._metrics_instance = get_metrics()
|
|
355
|
-
settings = get_settings()
|
|
356
|
-
self._stats_collector_instance = get_stats_collector(
|
|
357
|
-
settings=settings.observability,
|
|
358
|
-
metrics_instance=self._metrics_instance,
|
|
359
|
-
)
|
|
360
|
-
logger.debug("stats_printing_task_setup_complete", task_name=self.name)
|
|
361
|
-
except Exception as e:
|
|
362
|
-
logger.error(
|
|
363
|
-
"stats_printing_task_setup_failed",
|
|
364
|
-
task_name=self.name,
|
|
365
|
-
error=str(e),
|
|
366
|
-
error_type=type(e).__name__,
|
|
367
|
-
)
|
|
368
|
-
raise
|
|
369
|
-
|
|
370
|
-
async def run(self) -> bool:
|
|
371
|
-
"""Execute stats printing."""
|
|
372
|
-
try:
|
|
373
|
-
if not self._stats_collector_instance:
|
|
374
|
-
logger.warning("stats_printing_no_collector", task_name=self.name)
|
|
375
|
-
return False
|
|
376
|
-
|
|
377
|
-
await self._stats_collector_instance.print_stats()
|
|
378
|
-
logger.debug("stats_printing_success", task_name=self.name)
|
|
379
|
-
return True
|
|
380
|
-
|
|
381
|
-
except Exception as e:
|
|
382
|
-
logger.error(
|
|
383
|
-
"stats_printing_task_error",
|
|
384
|
-
task_name=self.name,
|
|
385
|
-
error=str(e),
|
|
386
|
-
error_type=type(e).__name__,
|
|
387
|
-
)
|
|
388
|
-
return False
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
class PricingCacheUpdateTask(BaseScheduledTask):
|
|
392
|
-
"""Task for updating pricing cache periodically."""
|
|
393
|
-
|
|
394
|
-
def __init__(
|
|
395
|
-
self,
|
|
396
|
-
name: str,
|
|
397
|
-
interval_seconds: float,
|
|
398
|
-
enabled: bool = True,
|
|
399
|
-
force_refresh_on_startup: bool = False,
|
|
400
|
-
pricing_updater: Any | None = None,
|
|
401
|
-
):
|
|
402
|
-
"""
|
|
403
|
-
Initialize pricing cache update task.
|
|
404
|
-
|
|
405
|
-
Args:
|
|
406
|
-
name: Task name
|
|
407
|
-
interval_seconds: Interval between pricing updates
|
|
408
|
-
enabled: Whether task is enabled
|
|
409
|
-
force_refresh_on_startup: Whether to force refresh on first run
|
|
410
|
-
pricing_updater: Injected pricing updater instance
|
|
411
|
-
"""
|
|
412
|
-
super().__init__(
|
|
413
|
-
name=name,
|
|
414
|
-
interval_seconds=interval_seconds,
|
|
415
|
-
enabled=enabled,
|
|
416
|
-
)
|
|
417
|
-
self.force_refresh_on_startup = force_refresh_on_startup
|
|
418
|
-
self._pricing_updater = pricing_updater
|
|
419
|
-
self._first_run = True
|
|
420
|
-
|
|
421
|
-
async def setup(self) -> None:
|
|
422
|
-
"""Initialize pricing updater instance if not injected."""
|
|
423
|
-
if self._pricing_updater is None:
|
|
424
|
-
try:
|
|
425
|
-
from ccproxy.config.pricing import PricingSettings
|
|
426
|
-
from ccproxy.pricing.cache import PricingCache
|
|
427
|
-
from ccproxy.pricing.updater import PricingUpdater
|
|
428
|
-
|
|
429
|
-
# Create pricing components with dependency injection
|
|
430
|
-
settings = PricingSettings()
|
|
431
|
-
cache = PricingCache(settings)
|
|
432
|
-
self._pricing_updater = PricingUpdater(cache, settings)
|
|
433
|
-
logger.debug("pricing_update_task_setup_complete", task_name=self.name)
|
|
434
|
-
except Exception as e:
|
|
435
|
-
logger.error(
|
|
436
|
-
"pricing_update_task_setup_failed",
|
|
437
|
-
task_name=self.name,
|
|
438
|
-
error=str(e),
|
|
439
|
-
error_type=type(e).__name__,
|
|
440
|
-
)
|
|
441
|
-
raise
|
|
442
|
-
else:
|
|
443
|
-
logger.debug(
|
|
444
|
-
"pricing_update_task_using_injected_updater", task_name=self.name
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
async def run(self) -> bool:
|
|
448
|
-
"""Execute pricing cache update."""
|
|
449
|
-
try:
|
|
450
|
-
if not self._pricing_updater:
|
|
451
|
-
logger.warning("pricing_update_no_updater", task_name=self.name)
|
|
452
|
-
return False
|
|
453
|
-
|
|
454
|
-
# Force refresh on first run if configured
|
|
455
|
-
force_refresh = self._first_run and self.force_refresh_on_startup
|
|
456
|
-
self._first_run = False
|
|
457
|
-
|
|
458
|
-
if force_refresh:
|
|
459
|
-
logger.info("pricing_update_force_refresh_startup", task_name=self.name)
|
|
460
|
-
refresh_result = await self._pricing_updater.force_refresh()
|
|
461
|
-
success = bool(refresh_result)
|
|
462
|
-
else:
|
|
463
|
-
# Regular update check
|
|
464
|
-
pricing_data = await self._pricing_updater.get_current_pricing(
|
|
465
|
-
force_refresh=False
|
|
466
|
-
)
|
|
467
|
-
success = pricing_data is not None
|
|
468
|
-
|
|
469
|
-
if success:
|
|
470
|
-
logger.debug("pricing_update_success", task_name=self.name)
|
|
471
|
-
else:
|
|
472
|
-
logger.warning("pricing_update_failed", task_name=self.name)
|
|
473
|
-
|
|
474
|
-
return success
|
|
475
|
-
|
|
476
|
-
except Exception as e:
|
|
477
|
-
logger.error(
|
|
478
|
-
"pricing_update_task_error",
|
|
479
|
-
task_name=self.name,
|
|
480
|
-
error=str(e),
|
|
481
|
-
error_type=type(e).__name__,
|
|
482
|
-
)
|
|
483
|
-
return False
|
|
484
|
-
|
|
485
|
-
|
|
486
347
|
class PoolStatsTask(BaseScheduledTask):
|
|
487
348
|
"""Task for displaying pool statistics periodically."""
|
|
488
349
|
|
|
@@ -601,6 +462,7 @@ class PoolStatsTask(BaseScheduledTask):
|
|
|
601
462
|
task_name=self.name,
|
|
602
463
|
error=str(e),
|
|
603
464
|
error_type=type(e).__name__,
|
|
465
|
+
exc_info=e,
|
|
604
466
|
)
|
|
605
467
|
return False
|
|
606
468
|
|
|
@@ -647,8 +509,6 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
647
509
|
current_version: Current version string
|
|
648
510
|
latest_version: Latest version string
|
|
649
511
|
"""
|
|
650
|
-
from ccproxy.utils.version_checker import compare_versions
|
|
651
|
-
|
|
652
512
|
if compare_versions(current_version, latest_version):
|
|
653
513
|
logger.warning(
|
|
654
514
|
"version_update_available",
|
|
@@ -656,7 +516,7 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
656
516
|
current_version=current_version,
|
|
657
517
|
latest_version=latest_version,
|
|
658
518
|
source=source,
|
|
659
|
-
|
|
519
|
+
description=(f"New version available: {latest_version}"),
|
|
660
520
|
)
|
|
661
521
|
else:
|
|
662
522
|
logger.debug(
|
|
@@ -665,7 +525,7 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
665
525
|
current_version=current_version,
|
|
666
526
|
latest_version=latest_version,
|
|
667
527
|
source=source,
|
|
668
|
-
|
|
528
|
+
description=(
|
|
669
529
|
f"No update: latest_version={latest_version} "
|
|
670
530
|
f"current_version={current_version}"
|
|
671
531
|
),
|
|
@@ -679,17 +539,6 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
679
539
|
task_name=self.name,
|
|
680
540
|
first_run=self._first_run,
|
|
681
541
|
)
|
|
682
|
-
from datetime import datetime
|
|
683
|
-
|
|
684
|
-
from ccproxy.utils.version_checker import (
|
|
685
|
-
VersionCheckState,
|
|
686
|
-
fetch_latest_github_version,
|
|
687
|
-
get_current_version,
|
|
688
|
-
get_version_check_state_path,
|
|
689
|
-
load_check_state,
|
|
690
|
-
save_check_state,
|
|
691
|
-
)
|
|
692
|
-
|
|
693
542
|
state_path = get_version_check_state_path()
|
|
694
543
|
current_time = datetime.now(UTC)
|
|
695
544
|
|
|
@@ -709,7 +558,38 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
709
558
|
|
|
710
559
|
# Load previous state if available
|
|
711
560
|
prev_state: VersionCheckState | None = await load_check_state(state_path)
|
|
561
|
+
|
|
562
|
+
current_version = get_current_version()
|
|
563
|
+
current_commit = extract_commit_from_version(current_version)
|
|
564
|
+
|
|
565
|
+
if prev_state is not None:
|
|
566
|
+
invalidation_reason: str | None = None
|
|
567
|
+
if (
|
|
568
|
+
prev_state.running_version is not None
|
|
569
|
+
and prev_state.running_version != current_version
|
|
570
|
+
):
|
|
571
|
+
invalidation_reason = "version"
|
|
572
|
+
elif (
|
|
573
|
+
prev_state.running_commit is not None
|
|
574
|
+
and current_commit is not None
|
|
575
|
+
and not commit_refs_match(prev_state.running_commit, current_commit)
|
|
576
|
+
):
|
|
577
|
+
invalidation_reason = "commit"
|
|
578
|
+
|
|
579
|
+
if invalidation_reason is not None:
|
|
580
|
+
logger.debug(
|
|
581
|
+
"version_check_cache_invalidated",
|
|
582
|
+
task_name=self.name,
|
|
583
|
+
reason=invalidation_reason,
|
|
584
|
+
cached_running_version=prev_state.running_version,
|
|
585
|
+
cached_running_commit=prev_state.running_commit,
|
|
586
|
+
current_version=current_version,
|
|
587
|
+
current_commit=current_commit,
|
|
588
|
+
)
|
|
589
|
+
prev_state = None
|
|
590
|
+
|
|
712
591
|
latest_version: str | None = None
|
|
592
|
+
latest_branch_commit: str | None = None
|
|
713
593
|
source: str | None = None
|
|
714
594
|
|
|
715
595
|
# If we have a recent state within the freshness window, avoid network call
|
|
@@ -725,6 +605,7 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
725
605
|
max_age_hours=max_age_hours,
|
|
726
606
|
)
|
|
727
607
|
latest_version = prev_state.latest_version_found
|
|
608
|
+
latest_branch_commit = prev_state.latest_branch_commit
|
|
728
609
|
source = "cache"
|
|
729
610
|
else:
|
|
730
611
|
logger.debug(
|
|
@@ -734,26 +615,118 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
734
615
|
max_age_hours=max_age_hours,
|
|
735
616
|
)
|
|
736
617
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
618
|
+
current_version_parsed = pkg_version.parse(current_version)
|
|
619
|
+
branch_name: str | None = None
|
|
620
|
+
|
|
621
|
+
if current_version_parsed.is_devrelease and current_commit is not None:
|
|
622
|
+
branch_name = get_branch_override()
|
|
623
|
+
if branch_name is None and prev_state is not None:
|
|
624
|
+
branch_name = prev_state.latest_branch_name
|
|
625
|
+
if branch_name is None:
|
|
626
|
+
branch_name = await resolve_branch_for_commit(current_commit)
|
|
627
|
+
|
|
628
|
+
if branch_name is not None:
|
|
629
|
+
if source == "cache" and (
|
|
630
|
+
prev_state is None
|
|
631
|
+
or prev_state.latest_branch_name != branch_name
|
|
632
|
+
or not prev_state.latest_branch_commit
|
|
633
|
+
):
|
|
634
|
+
latest_branch_commit = None
|
|
635
|
+
source = None
|
|
636
|
+
|
|
637
|
+
if latest_branch_commit is None:
|
|
638
|
+
latest_branch_commit = await fetch_latest_branch_commit(branch_name)
|
|
639
|
+
if latest_branch_commit is None:
|
|
640
|
+
logger.warning(
|
|
641
|
+
"version_check_branch_fetch_failed",
|
|
642
|
+
task_name=self.name,
|
|
643
|
+
branch=branch_name,
|
|
644
|
+
)
|
|
645
|
+
return False
|
|
646
|
+
|
|
647
|
+
await save_check_state(
|
|
648
|
+
state_path,
|
|
649
|
+
VersionCheckState(
|
|
650
|
+
last_check_at=current_time,
|
|
651
|
+
latest_version_found=(
|
|
652
|
+
latest_version
|
|
653
|
+
or (
|
|
654
|
+
prev_state.latest_version_found
|
|
655
|
+
if prev_state is not None
|
|
656
|
+
else None
|
|
657
|
+
)
|
|
658
|
+
),
|
|
659
|
+
latest_branch_name=branch_name,
|
|
660
|
+
latest_branch_commit=latest_branch_commit,
|
|
661
|
+
running_version=current_version,
|
|
662
|
+
running_commit=current_commit,
|
|
663
|
+
),
|
|
664
|
+
)
|
|
665
|
+
source = "network"
|
|
666
|
+
|
|
667
|
+
if current_commit is None:
|
|
668
|
+
logger.debug(
|
|
669
|
+
"branch_revision_no_commit_to_compare",
|
|
670
|
+
task_name=self.name,
|
|
671
|
+
branch=branch_name,
|
|
672
|
+
source=source,
|
|
673
|
+
)
|
|
674
|
+
else:
|
|
675
|
+
update_available = not commit_refs_match(
|
|
676
|
+
current_commit, latest_branch_commit
|
|
677
|
+
)
|
|
678
|
+
if update_available:
|
|
679
|
+
logger.warning(
|
|
680
|
+
"branch_revision_update_available",
|
|
681
|
+
task_name=self.name,
|
|
682
|
+
branch=branch_name,
|
|
683
|
+
current_commit=current_commit,
|
|
684
|
+
latest_commit=latest_branch_commit,
|
|
685
|
+
source=source,
|
|
686
|
+
description=(
|
|
687
|
+
"New commits available for branch "
|
|
688
|
+
f"{branch_name}: {latest_branch_commit}"
|
|
689
|
+
),
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
logger.debug(
|
|
693
|
+
"branch_revision_up_to_date",
|
|
694
|
+
task_name=self.name,
|
|
695
|
+
branch=branch_name,
|
|
696
|
+
current_commit=current_commit,
|
|
697
|
+
source=source,
|
|
698
|
+
)
|
|
699
|
+
else:
|
|
740
700
|
if latest_version is None:
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
701
|
+
latest_version = await fetch_latest_github_version()
|
|
702
|
+
if latest_version is None:
|
|
703
|
+
logger.warning(
|
|
704
|
+
"version_check_fetch_failed", task_name=self.name
|
|
705
|
+
)
|
|
706
|
+
return False
|
|
707
|
+
await save_check_state(
|
|
708
|
+
state_path,
|
|
709
|
+
VersionCheckState(
|
|
710
|
+
last_check_at=current_time,
|
|
711
|
+
latest_version_found=latest_version,
|
|
712
|
+
latest_branch_name=(
|
|
713
|
+
prev_state.latest_branch_name
|
|
714
|
+
if prev_state is not None
|
|
715
|
+
else None
|
|
716
|
+
),
|
|
717
|
+
latest_branch_commit=(
|
|
718
|
+
prev_state.latest_branch_commit
|
|
719
|
+
if prev_state is not None
|
|
720
|
+
else None
|
|
721
|
+
),
|
|
722
|
+
running_version=current_version,
|
|
723
|
+
running_commit=current_commit,
|
|
724
|
+
),
|
|
725
|
+
)
|
|
726
|
+
source = "network"
|
|
727
|
+
self._log_version_comparison(
|
|
728
|
+
current_version, latest_version, source=source
|
|
747
729
|
)
|
|
748
|
-
await save_check_state(state_path, new_state)
|
|
749
|
-
source = "network"
|
|
750
|
-
else:
|
|
751
|
-
# Ensure state file at least exists; if it didn't, we wouldn't be here
|
|
752
|
-
pass
|
|
753
|
-
|
|
754
|
-
# Compare versions and log result
|
|
755
|
-
current_version = get_current_version()
|
|
756
|
-
self._log_version_comparison(current_version, latest_version, source=source)
|
|
757
730
|
|
|
758
731
|
# Mark first run as complete
|
|
759
732
|
if self._first_run:
|
|
@@ -761,11 +734,30 @@ class VersionUpdateCheckTask(BaseScheduledTask):
|
|
|
761
734
|
|
|
762
735
|
return True
|
|
763
736
|
|
|
737
|
+
except ImportError as e:
|
|
738
|
+
logger.error(
|
|
739
|
+
"version_check_task_import_error",
|
|
740
|
+
task_name=self.name,
|
|
741
|
+
error=str(e),
|
|
742
|
+
error_type=type(e).__name__,
|
|
743
|
+
exc_info=e,
|
|
744
|
+
)
|
|
745
|
+
return False
|
|
746
|
+
|
|
764
747
|
except Exception as e:
|
|
765
748
|
logger.error(
|
|
766
749
|
"version_check_task_error",
|
|
767
750
|
task_name=self.name,
|
|
768
751
|
error=str(e),
|
|
769
752
|
error_type=type(e).__name__,
|
|
753
|
+
exc_info=e,
|
|
770
754
|
)
|
|
771
755
|
return False
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
# Test helper task exposed for tests that import from this module
|
|
759
|
+
class MockScheduledTask(BaseScheduledTask):
|
|
760
|
+
"""Minimal mock task used by tests for registration and lifecycle checks."""
|
|
761
|
+
|
|
762
|
+
async def run(self) -> bool:
|
|
763
|
+
return True
|