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/config/core.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Core configuration settings - server, HTTP, CORS, logging, and plugins."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
from ccproxy.core.system import get_xdg_config_home
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ServerSettings(BaseModel):
|
|
12
|
+
"""Server-specific configuration settings."""
|
|
13
|
+
|
|
14
|
+
host: str = Field(
|
|
15
|
+
default="127.0.0.1",
|
|
16
|
+
description="Server host address",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
port: int = Field(
|
|
20
|
+
default=8000,
|
|
21
|
+
description="Server port number",
|
|
22
|
+
ge=1,
|
|
23
|
+
le=65535,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
workers: int = Field(
|
|
27
|
+
default=1,
|
|
28
|
+
description="Number of worker processes",
|
|
29
|
+
ge=1,
|
|
30
|
+
le=32,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
reload: bool = Field(
|
|
34
|
+
default=False,
|
|
35
|
+
description="Enable auto-reload for development",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
bypass_mode: bool = Field(
|
|
39
|
+
default=False,
|
|
40
|
+
description="Enable bypass mode for testing (uses mock responses instead of real API calls)",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HTTPSettings(BaseModel):
|
|
45
|
+
"""HTTP client configuration settings.
|
|
46
|
+
|
|
47
|
+
Controls how the core HTTP client handles compression and other HTTP-level settings.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
compression_enabled: bool = Field(
|
|
51
|
+
default=True,
|
|
52
|
+
description="Enable compression for provider requests (Accept-Encoding header)",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
accept_encoding: str = Field(
|
|
56
|
+
default="gzip, deflate",
|
|
57
|
+
description="Accept-Encoding header value when compression is enabled",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CORSSettings(BaseModel):
|
|
62
|
+
"""CORS-specific configuration settings."""
|
|
63
|
+
|
|
64
|
+
origins: list[str] = Field(
|
|
65
|
+
default_factory=lambda: [
|
|
66
|
+
"vscode-file://vscode-app",
|
|
67
|
+
"http://localhost/*",
|
|
68
|
+
"http://localhost:*/*",
|
|
69
|
+
"http://127.0.0.1:*/*",
|
|
70
|
+
"http://127.0.0.1:/*",
|
|
71
|
+
],
|
|
72
|
+
description="CORS allowed origins (avoid using '*' for security)",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
credentials: bool = Field(
|
|
76
|
+
default=True,
|
|
77
|
+
description="CORS allow credentials",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
methods: list[str] = Field(
|
|
81
|
+
default_factory=lambda: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
82
|
+
description="CORS allowed methods",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
headers: list[str] = Field(
|
|
86
|
+
default_factory=lambda: [
|
|
87
|
+
"Content-Type",
|
|
88
|
+
"Authorization",
|
|
89
|
+
"Accept",
|
|
90
|
+
"Origin",
|
|
91
|
+
"X-Requested-With",
|
|
92
|
+
],
|
|
93
|
+
description="CORS allowed headers",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
origin_regex: str | None = Field(
|
|
97
|
+
default=None,
|
|
98
|
+
description="CORS origin regex pattern",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expose_headers: list[str] = Field(
|
|
102
|
+
default_factory=list,
|
|
103
|
+
description="CORS exposed headers",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
max_age: int = Field(
|
|
107
|
+
default=600,
|
|
108
|
+
description="CORS preflight max age in seconds",
|
|
109
|
+
ge=0,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# === Logging Configuration ===
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
LogLevelName = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "TRACE"]
|
|
117
|
+
LOG_LEVEL_OPTIONS: tuple[str, ...] = (
|
|
118
|
+
"DEBUG",
|
|
119
|
+
"INFO",
|
|
120
|
+
"WARNING",
|
|
121
|
+
"ERROR",
|
|
122
|
+
"CRITICAL",
|
|
123
|
+
"TRACE",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
LogFormatName = Literal["auto", "rich", "json", "plain"]
|
|
127
|
+
LOG_FORMAT_OPTIONS: tuple[str, ...] = ("auto", "rich", "json", "plain")
|
|
128
|
+
|
|
129
|
+
LOG_FORMAT_DESCRIPTION = "Logging format: 'rich', 'json', 'plain', or 'auto' (auto-selects based on environment)"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class LoggingSettings(BaseModel):
|
|
133
|
+
"""Centralized logging configuration - core app only."""
|
|
134
|
+
|
|
135
|
+
level: LogLevelName = Field(
|
|
136
|
+
default="INFO",
|
|
137
|
+
description="Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL, TRACE)",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
format: LogFormatName = Field(
|
|
141
|
+
default="auto",
|
|
142
|
+
description=LOG_FORMAT_DESCRIPTION,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
file: str | None = Field(
|
|
146
|
+
default=None,
|
|
147
|
+
description="Path to JSON log file. If specified, logs will be written to this file in JSON format",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
verbose_api: bool = Field(
|
|
151
|
+
default=False,
|
|
152
|
+
description="Enable verbose API request/response logging",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
request_log_dir: str | None = Field(
|
|
156
|
+
default=None,
|
|
157
|
+
description="Directory to save individual request/response logs when verbose_api is enabled",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
plugin_log_base_dir: str = Field(
|
|
161
|
+
default="/tmp/ccproxy",
|
|
162
|
+
description="Shared base directory for all plugin log outputs",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@field_validator("level", mode="before")
|
|
166
|
+
@classmethod
|
|
167
|
+
def validate_log_level(cls, value: LogLevelName | str) -> LogLevelName:
|
|
168
|
+
"""Validate and normalize log level."""
|
|
169
|
+
if isinstance(value, str):
|
|
170
|
+
candidate = value.upper()
|
|
171
|
+
else:
|
|
172
|
+
candidate = value
|
|
173
|
+
|
|
174
|
+
if candidate not in LOG_LEVEL_OPTIONS:
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"Invalid log level: {value}. Must be one of {list(LOG_LEVEL_OPTIONS)}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return cast(LogLevelName, candidate)
|
|
180
|
+
|
|
181
|
+
@field_validator("format", mode="before")
|
|
182
|
+
@classmethod
|
|
183
|
+
def validate_log_format(cls, value: LogFormatName | str) -> LogFormatName:
|
|
184
|
+
"""Validate and normalize log format."""
|
|
185
|
+
if isinstance(value, str):
|
|
186
|
+
candidate = value.lower()
|
|
187
|
+
else:
|
|
188
|
+
candidate = value
|
|
189
|
+
|
|
190
|
+
if candidate not in LOG_FORMAT_OPTIONS:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"Invalid log format: {value}. Must be one of {list(LOG_FORMAT_OPTIONS)}"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return cast(LogFormatName, candidate)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _default_plugin_directories() -> list[Path]:
|
|
199
|
+
"""Default directories scanned for filesystem plugins."""
|
|
200
|
+
|
|
201
|
+
# package_plugins = Path(__file__).resolve().parent.parent.parent / "plugins"
|
|
202
|
+
# we don't include packages from ccproxy.plugins because they
|
|
203
|
+
# are using importlib.metadata entry points
|
|
204
|
+
user_plugins = get_xdg_config_home() / "ccproxy" / "plugins"
|
|
205
|
+
|
|
206
|
+
seen: set[Path] = set()
|
|
207
|
+
ordered: list[Path] = []
|
|
208
|
+
for candidate in [user_plugins]:
|
|
209
|
+
normalized = candidate.resolve()
|
|
210
|
+
if normalized in seen:
|
|
211
|
+
continue
|
|
212
|
+
seen.add(normalized)
|
|
213
|
+
ordered.append(candidate)
|
|
214
|
+
return ordered
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class PluginDiscoverySettings(BaseModel):
|
|
218
|
+
"""Configuration for filesystem plugin discovery."""
|
|
219
|
+
|
|
220
|
+
directories: list[Path] = Field(
|
|
221
|
+
default_factory=_default_plugin_directories,
|
|
222
|
+
description=(
|
|
223
|
+
"Ordered directories scanned for local plugins."
|
|
224
|
+
" Defaults to the bundled ccproxy/plugins directory and"
|
|
225
|
+
" ${XDG_CONFIG_HOME}/ccproxy/plugins."
|
|
226
|
+
),
|
|
227
|
+
)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Utility functions for generating environment variable configuration from Pydantic models."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, TextIO, TypeVar
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from pydantic.fields import FieldInfo
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T", bound=BaseModel)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_hidden_in_example(field_info: FieldInfo) -> bool:
|
|
16
|
+
"""Determine if a field should be omitted from generated example configs."""
|
|
17
|
+
if bool(field_info.exclude):
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
extra = getattr(field_info, "json_schema_extra", None) or {}
|
|
21
|
+
return bool(extra.get("config_example_hidden"))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_field_description(field_info: FieldInfo) -> str:
|
|
25
|
+
"""Get a human-readable description from a Pydantic field."""
|
|
26
|
+
if field_info.description:
|
|
27
|
+
return field_info.description
|
|
28
|
+
return "Configuration setting"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def format_value_for_env(value: Any) -> str:
|
|
32
|
+
"""Format a configuration value for environment variable output.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
value: The value to format
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
String representation suitable for environment variables
|
|
39
|
+
"""
|
|
40
|
+
if value is None:
|
|
41
|
+
return ""
|
|
42
|
+
elif isinstance(value, bool):
|
|
43
|
+
return "true" if value else "false"
|
|
44
|
+
elif isinstance(value, str):
|
|
45
|
+
return value
|
|
46
|
+
elif isinstance(value, int | float):
|
|
47
|
+
return str(value)
|
|
48
|
+
elif isinstance(value, list | dict):
|
|
49
|
+
# For complex types, try JSON representation
|
|
50
|
+
try:
|
|
51
|
+
return json.dumps(value)
|
|
52
|
+
except (TypeError, ValueError):
|
|
53
|
+
# If JSON serialization fails, use string representation
|
|
54
|
+
return str(value)
|
|
55
|
+
else:
|
|
56
|
+
return str(value)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def generate_env_vars_from_model(
|
|
60
|
+
model_class: type[T],
|
|
61
|
+
prefix: str = "",
|
|
62
|
+
include_hidden: bool = False,
|
|
63
|
+
) -> list[tuple[str, Any, str]]:
|
|
64
|
+
"""Generate environment variable names and values from a Pydantic model.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
model_class: The Pydantic model class to generate env vars from
|
|
68
|
+
prefix: Prefix for env var names (e.g., "SERVER" or "PLUGINS__MAX_TOKENS")
|
|
69
|
+
include_hidden: Whether to include fields marked as hidden in examples
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
List of tuples: (env_var_name, value, description)
|
|
73
|
+
"""
|
|
74
|
+
default_instance = model_class()
|
|
75
|
+
env_vars: list[tuple[str, Any, str]] = []
|
|
76
|
+
|
|
77
|
+
for field_name, field_info in model_class.model_fields.items():
|
|
78
|
+
if not include_hidden and is_hidden_in_example(field_info):
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
field_value = getattr(default_instance, field_name)
|
|
82
|
+
env_var_name = (
|
|
83
|
+
f"{prefix}__{field_name.upper()}" if prefix else field_name.upper()
|
|
84
|
+
)
|
|
85
|
+
description = get_field_description(field_info)
|
|
86
|
+
|
|
87
|
+
if isinstance(field_value, BaseModel):
|
|
88
|
+
# Recursively handle nested models
|
|
89
|
+
nested_vars = generate_env_vars_from_model(
|
|
90
|
+
field_value.__class__, env_var_name, include_hidden
|
|
91
|
+
)
|
|
92
|
+
env_vars.extend(nested_vars)
|
|
93
|
+
else:
|
|
94
|
+
# Convert Path to string
|
|
95
|
+
if isinstance(field_value, Path):
|
|
96
|
+
field_value = str(field_value)
|
|
97
|
+
|
|
98
|
+
env_vars.append((env_var_name, field_value, description))
|
|
99
|
+
|
|
100
|
+
return env_vars
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def generate_env_config(
|
|
104
|
+
model_class: type[BaseModel],
|
|
105
|
+
prefix: str = "",
|
|
106
|
+
include_hidden: bool = False,
|
|
107
|
+
commented: bool = True,
|
|
108
|
+
header_comment: str | None = None,
|
|
109
|
+
export_format: bool = True,
|
|
110
|
+
) -> str:
|
|
111
|
+
"""Generate environment variable configuration string.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
model_class: The Pydantic model class to generate config from
|
|
115
|
+
prefix: Prefix for env var names (e.g., "SERVER" or "PLUGINS__MAX_TOKENS")
|
|
116
|
+
include_hidden: Whether to include fields marked as hidden in examples
|
|
117
|
+
commented: Whether to comment out all settings (default True)
|
|
118
|
+
header_comment: Optional custom header comment
|
|
119
|
+
export_format: Whether to use 'export VAR=value' format (True) or 'VAR=value' (False)
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Environment variable configuration as a string
|
|
123
|
+
"""
|
|
124
|
+
lines = []
|
|
125
|
+
|
|
126
|
+
# Write header
|
|
127
|
+
if header_comment:
|
|
128
|
+
for line in header_comment.split("\n"):
|
|
129
|
+
lines.append(f"# {line}" if line else "#")
|
|
130
|
+
else:
|
|
131
|
+
lines.append("# Environment Variable Configuration")
|
|
132
|
+
lines.append("# This file contains environment variables for the application")
|
|
133
|
+
if commented:
|
|
134
|
+
lines.append("# Uncomment and set values as needed")
|
|
135
|
+
lines.append("")
|
|
136
|
+
|
|
137
|
+
# Generate environment variables
|
|
138
|
+
env_vars = generate_env_vars_from_model(model_class, prefix, include_hidden)
|
|
139
|
+
|
|
140
|
+
comment_prefix = "# " if commented else ""
|
|
141
|
+
export_prefix = "export " if export_format else ""
|
|
142
|
+
|
|
143
|
+
for env_var_name, value, description in env_vars:
|
|
144
|
+
# Write description as comment
|
|
145
|
+
lines.append(f"# {description}")
|
|
146
|
+
|
|
147
|
+
# Format the value
|
|
148
|
+
formatted_value = format_value_for_env(value)
|
|
149
|
+
|
|
150
|
+
# Write the environment variable
|
|
151
|
+
if formatted_value:
|
|
152
|
+
# Quote values that contain spaces or special characters
|
|
153
|
+
if isinstance(value, str) and (
|
|
154
|
+
" " in value or any(c in value for c in ["$", '"', "'", "\\"])
|
|
155
|
+
):
|
|
156
|
+
lines.append(
|
|
157
|
+
f'{comment_prefix}{export_prefix}{env_var_name}="{formatted_value}"'
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
lines.append(
|
|
161
|
+
f"{comment_prefix}{export_prefix}{env_var_name}={formatted_value}"
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
# Empty value
|
|
165
|
+
lines.append(f'{comment_prefix}{export_prefix}{env_var_name}=""')
|
|
166
|
+
|
|
167
|
+
lines.append("")
|
|
168
|
+
|
|
169
|
+
return "\n".join(lines)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def write_env_config(
|
|
173
|
+
output: TextIO | Path | str,
|
|
174
|
+
model_class: type[BaseModel],
|
|
175
|
+
prefix: str = "",
|
|
176
|
+
include_hidden: bool = False,
|
|
177
|
+
commented: bool = True,
|
|
178
|
+
header_comment: str | None = None,
|
|
179
|
+
export_format: bool = True,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""Write environment variable configuration directly to a stream or file.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
output: Output destination - can be a TextIO stream (file, StringIO, stdout),
|
|
185
|
+
a Path object, or a string path to a file
|
|
186
|
+
model_class: The Pydantic model class to generate config from
|
|
187
|
+
prefix: Prefix for env var names (e.g., "SERVER" or "PLUGINS__MAX_TOKENS")
|
|
188
|
+
include_hidden: Whether to include fields marked as hidden in examples
|
|
189
|
+
commented: Whether to comment out all settings (default True)
|
|
190
|
+
header_comment: Optional custom header comment
|
|
191
|
+
export_format: Whether to use 'export VAR=value' format (default True)
|
|
192
|
+
|
|
193
|
+
Examples:
|
|
194
|
+
# Write to stdout
|
|
195
|
+
write_env_config(sys.stdout, Settings)
|
|
196
|
+
|
|
197
|
+
# Write to file
|
|
198
|
+
write_env_config("env.sh", Settings)
|
|
199
|
+
write_env_config(Path("env.sh"), Settings)
|
|
200
|
+
|
|
201
|
+
# Write to StringIO
|
|
202
|
+
buffer = StringIO()
|
|
203
|
+
write_env_config(buffer, Settings)
|
|
204
|
+
content = buffer.getvalue()
|
|
205
|
+
|
|
206
|
+
# Write with prefix for plugin
|
|
207
|
+
write_env_config(sys.stdout, MaxTokensConfig, prefix="PLUGINS__MAX_TOKENS")
|
|
208
|
+
|
|
209
|
+
# Write without export (for .env files)
|
|
210
|
+
write_env_config("env.sh", Settings, export_format=False)
|
|
211
|
+
"""
|
|
212
|
+
# Generate env config string
|
|
213
|
+
env_string = generate_env_config(
|
|
214
|
+
model_class=model_class,
|
|
215
|
+
prefix=prefix,
|
|
216
|
+
include_hidden=include_hidden,
|
|
217
|
+
commented=commented,
|
|
218
|
+
header_comment=header_comment,
|
|
219
|
+
export_format=export_format,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Determine output type and write
|
|
223
|
+
if isinstance(output, str | Path):
|
|
224
|
+
# Write to file path
|
|
225
|
+
Path(output).write_text(env_string, encoding="utf-8")
|
|
226
|
+
else:
|
|
227
|
+
# Write to stream (TextIO, stdout, StringIO, etc.)
|
|
228
|
+
output.write(env_string)
|
|
229
|
+
if output not in (sys.stdout, sys.stderr):
|
|
230
|
+
# Ensure trailing newline for files (not needed for stdout/stderr)
|
|
231
|
+
if not env_string.endswith("\n"):
|
|
232
|
+
output.write("\n")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Runtime configuration settings - binary resolution configuration."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# === Binary Resolution Configuration ===
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BinarySettings(BaseModel):
|
|
10
|
+
"""Binary resolution and package manager fallback settings."""
|
|
11
|
+
|
|
12
|
+
fallback_enabled: bool = Field(
|
|
13
|
+
default=True,
|
|
14
|
+
description="Enable package manager fallback when binaries are not found",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
package_manager_only: bool = Field(
|
|
18
|
+
default=True,
|
|
19
|
+
description="Skip direct binary lookup and use package managers exclusively",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
preferred_package_manager: str | None = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Preferred package manager (bunx, pnpm, npx). If not set, auto-detects based on availability",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
package_manager_priority: list[str] = Field(
|
|
28
|
+
default_factory=lambda: ["bunx", "pnpm", "npx"],
|
|
29
|
+
description="Priority order for trying package managers when preferred is not set",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
cache_results: bool = Field(
|
|
33
|
+
default=True,
|
|
34
|
+
description="Cache binary resolution results to avoid repeated lookups",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@field_validator("preferred_package_manager")
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_preferred_package_manager(cls, v: str | None) -> str | None:
|
|
40
|
+
"""Validate preferred package manager."""
|
|
41
|
+
if v is not None:
|
|
42
|
+
valid_managers = ["bunx", "pnpm", "npx"]
|
|
43
|
+
if v not in valid_managers:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Invalid package manager: {v}. Must be one of {valid_managers}"
|
|
46
|
+
)
|
|
47
|
+
return v
|
|
48
|
+
|
|
49
|
+
@field_validator("package_manager_priority")
|
|
50
|
+
@classmethod
|
|
51
|
+
def validate_package_manager_priority(cls, v: list[str]) -> list[str]:
|
|
52
|
+
"""Validate package manager priority list."""
|
|
53
|
+
valid_managers = {"bunx", "pnpm", "npx"}
|
|
54
|
+
for manager in v:
|
|
55
|
+
if manager not in valid_managers:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"Invalid package manager in priority list: {manager}. "
|
|
58
|
+
f"Must be one of {valid_managers}"
|
|
59
|
+
)
|
|
60
|
+
# Remove duplicates while preserving order
|
|
61
|
+
seen = set()
|
|
62
|
+
result = []
|
|
63
|
+
for manager in v:
|
|
64
|
+
if manager not in seen:
|
|
65
|
+
seen.add(manager)
|
|
66
|
+
result.append(manager)
|
|
67
|
+
return result
|
ccproxy/config/security.py
CHANGED
|
@@ -1,16 +1,49 @@
|
|
|
1
|
-
"""Security configuration settings."""
|
|
1
|
+
"""Security and authentication configuration settings."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, SecretStr, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# === Authentication Configuration ===
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthSettings(BaseModel):
|
|
12
|
+
"""Configuration for authentication behavior and caching."""
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(extra="ignore")
|
|
15
|
+
|
|
16
|
+
credentials_ttl_seconds: float = Field(
|
|
17
|
+
3600.0,
|
|
18
|
+
description=(
|
|
19
|
+
"Cache duration for loaded credentials before rechecking storage. "
|
|
20
|
+
"Use nested env var AUTH__CREDENTIALS_TTL_SECONDS to override."
|
|
21
|
+
),
|
|
22
|
+
ge=0.0,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# === Security Configuration ===
|
|
4
27
|
|
|
5
28
|
|
|
6
29
|
class SecuritySettings(BaseModel):
|
|
7
30
|
"""Security-specific configuration settings."""
|
|
8
31
|
|
|
9
|
-
auth_token:
|
|
32
|
+
auth_token: SecretStr | None = Field(
|
|
10
33
|
default=None,
|
|
11
34
|
description="Bearer token for API authentication (optional)",
|
|
12
35
|
)
|
|
13
36
|
|
|
37
|
+
@field_validator("auth_token", mode="before")
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_auth_token(cls, v: Any) -> Any:
|
|
40
|
+
"""Convert string values to SecretStr."""
|
|
41
|
+
if v is None:
|
|
42
|
+
return None
|
|
43
|
+
if isinstance(v, str):
|
|
44
|
+
return SecretStr(v)
|
|
45
|
+
return v
|
|
46
|
+
|
|
14
47
|
confirmation_timeout_seconds: int = Field(
|
|
15
48
|
default=30,
|
|
16
49
|
ge=5,
|