ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +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.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -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.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
"""Generate scaffold files for new CCProxy plugins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from textwrap import dedent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PluginTemplateType(str, Enum):
|
|
13
|
+
"""Supported plugin template variants."""
|
|
14
|
+
|
|
15
|
+
SYSTEM = "system"
|
|
16
|
+
PROVIDER = "provider"
|
|
17
|
+
AUTH = "auth"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class TemplateContext:
|
|
22
|
+
"""Precomputed naming helpers for template rendering."""
|
|
23
|
+
|
|
24
|
+
plugin_name: str
|
|
25
|
+
pascal_name: str
|
|
26
|
+
description: str
|
|
27
|
+
version: str
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def config_class(self) -> str:
|
|
31
|
+
return f"{self.pascal_name}Config"
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def runtime_class(self) -> str:
|
|
35
|
+
return f"{self.pascal_name}Runtime"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def factory_class(self) -> str:
|
|
39
|
+
return f"{self.pascal_name}Factory"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def adapter_class(self) -> str:
|
|
43
|
+
return f"{self.pascal_name}Adapter"
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def detection_class(self) -> str:
|
|
47
|
+
return f"{self.pascal_name}DetectionService"
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def oauth_provider_class(self) -> str:
|
|
51
|
+
return f"{self.pascal_name}OAuthProvider"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build_plugin_scaffold(
|
|
55
|
+
plugin_name: str,
|
|
56
|
+
description: str,
|
|
57
|
+
version: str,
|
|
58
|
+
template_type: PluginTemplateType,
|
|
59
|
+
*,
|
|
60
|
+
include_tests: bool = False,
|
|
61
|
+
) -> dict[str, str]:
|
|
62
|
+
"""Return a mapping of relative file paths to scaffold contents."""
|
|
63
|
+
|
|
64
|
+
pascal_name = _to_pascal_case(plugin_name)
|
|
65
|
+
ctx = TemplateContext(
|
|
66
|
+
plugin_name=plugin_name,
|
|
67
|
+
pascal_name=pascal_name,
|
|
68
|
+
description=description,
|
|
69
|
+
version=version,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
builders: dict[
|
|
73
|
+
PluginTemplateType, Callable[[TemplateContext, bool], dict[str, str]]
|
|
74
|
+
] = {
|
|
75
|
+
PluginTemplateType.SYSTEM: _build_system_template,
|
|
76
|
+
PluginTemplateType.PROVIDER: _build_provider_template,
|
|
77
|
+
PluginTemplateType.AUTH: _build_auth_template,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
builder = builders.get(template_type)
|
|
81
|
+
if builder is None:
|
|
82
|
+
raise ValueError(f"Unsupported template type: {template_type}")
|
|
83
|
+
|
|
84
|
+
return builder(ctx, include_tests)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _build_system_template(ctx: TemplateContext, include_tests: bool) -> dict[str, str]:
|
|
88
|
+
"""Build files for a system plugin scaffold."""
|
|
89
|
+
|
|
90
|
+
files: dict[str, str] = {
|
|
91
|
+
"__init__.py": dedent(
|
|
92
|
+
f'''
|
|
93
|
+
"""Runtime package for {ctx.pascal_name} plugin."""
|
|
94
|
+
|
|
95
|
+
from .plugin import factory
|
|
96
|
+
|
|
97
|
+
__all__ = ["factory"]
|
|
98
|
+
'''
|
|
99
|
+
).strip()
|
|
100
|
+
+ "\n",
|
|
101
|
+
"config.py": dedent(
|
|
102
|
+
f'''
|
|
103
|
+
"""Configuration for the {ctx.pascal_name} plugin."""
|
|
104
|
+
|
|
105
|
+
from pydantic import BaseModel, Field
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class {ctx.config_class}(BaseModel):
|
|
109
|
+
"""Runtime configuration toggles for {ctx.plugin_name}."""
|
|
110
|
+
|
|
111
|
+
enabled: bool = Field(
|
|
112
|
+
default=True,
|
|
113
|
+
description="Enable the plugin once configured.",
|
|
114
|
+
)
|
|
115
|
+
'''
|
|
116
|
+
).strip()
|
|
117
|
+
+ "\n",
|
|
118
|
+
"plugin.py": dedent(
|
|
119
|
+
f'''
|
|
120
|
+
"""System plugin runtime for {ctx.plugin_name}."""
|
|
121
|
+
|
|
122
|
+
from __future__ import annotations
|
|
123
|
+
|
|
124
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
125
|
+
from ccproxy.core.plugins import (
|
|
126
|
+
PluginManifest,
|
|
127
|
+
SystemPluginFactory,
|
|
128
|
+
SystemPluginRuntime,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
from .config import {ctx.config_class}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
logger = get_plugin_logger()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class {ctx.runtime_class}(SystemPluginRuntime):
|
|
138
|
+
"""Runtime implementation for {ctx.plugin_name}."""
|
|
139
|
+
|
|
140
|
+
def __init__(self, manifest: PluginManifest) -> None:
|
|
141
|
+
super().__init__(manifest)
|
|
142
|
+
self.config: {ctx.config_class} | None = None
|
|
143
|
+
|
|
144
|
+
async def _on_initialize(self) -> None:
|
|
145
|
+
await super()._on_initialize()
|
|
146
|
+
if not self.context:
|
|
147
|
+
raise RuntimeError("Plugin context is not available")
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
self.config = self.context.get({ctx.config_class})
|
|
151
|
+
except ValueError:
|
|
152
|
+
self.config = {ctx.config_class}()
|
|
153
|
+
logger.debug(
|
|
154
|
+
"plugin_using_default_config",
|
|
155
|
+
plugin=self.name,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if not self.config.enabled:
|
|
159
|
+
logger.info("plugin_disabled", plugin=self.name)
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
logger.info("plugin_initialized", plugin=self.name)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class {ctx.factory_class}(SystemPluginFactory):
|
|
166
|
+
"""Factory for the {ctx.plugin_name} system plugin."""
|
|
167
|
+
|
|
168
|
+
def __init__(self) -> None:
|
|
169
|
+
manifest = PluginManifest(
|
|
170
|
+
name="{ctx.plugin_name}",
|
|
171
|
+
version="{ctx.version}",
|
|
172
|
+
description="{ctx.description}",
|
|
173
|
+
is_provider=False,
|
|
174
|
+
config_class={ctx.config_class},
|
|
175
|
+
)
|
|
176
|
+
super().__init__(manifest)
|
|
177
|
+
|
|
178
|
+
def create_runtime(self) -> {ctx.runtime_class}:
|
|
179
|
+
return {ctx.runtime_class}(self.manifest)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
factory = {ctx.factory_class}()
|
|
183
|
+
'''
|
|
184
|
+
).strip()
|
|
185
|
+
+ "\n",
|
|
186
|
+
"README.md": dedent(
|
|
187
|
+
f"""
|
|
188
|
+
# {ctx.pascal_name} Plugin
|
|
189
|
+
|
|
190
|
+
Generated with `ccproxy plugins scaffold {ctx.plugin_name}`.
|
|
191
|
+
|
|
192
|
+
## Next steps
|
|
193
|
+
|
|
194
|
+
- Update `config.py` with any required settings.
|
|
195
|
+
- Implement runtime logic in `plugin.py`.
|
|
196
|
+
- Add tests under `tests/` to cover the behaviour you introduce.
|
|
197
|
+
"""
|
|
198
|
+
).strip()
|
|
199
|
+
+ "\n",
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if include_tests:
|
|
203
|
+
files.update(
|
|
204
|
+
{
|
|
205
|
+
"tests/__init__.py": "\n",
|
|
206
|
+
f"tests/test_{ctx.plugin_name}.py": dedent(
|
|
207
|
+
f'''
|
|
208
|
+
"""Smoke tests for the {ctx.plugin_name} plugin scaffold."""
|
|
209
|
+
|
|
210
|
+
from ccproxy.plugins.{ctx.plugin_name}.plugin import factory
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_manifest_defaults() -> None:
|
|
214
|
+
manifest = factory.get_manifest()
|
|
215
|
+
assert manifest.name == "{ctx.plugin_name}"
|
|
216
|
+
assert manifest.version == "{ctx.version}"
|
|
217
|
+
'''
|
|
218
|
+
).strip()
|
|
219
|
+
+ "\n",
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return files
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _build_provider_template(
|
|
227
|
+
ctx: TemplateContext, include_tests: bool
|
|
228
|
+
) -> dict[str, str]:
|
|
229
|
+
"""Build files for a provider plugin scaffold."""
|
|
230
|
+
|
|
231
|
+
files: dict[str, str] = {
|
|
232
|
+
"__init__.py": dedent(
|
|
233
|
+
f'''
|
|
234
|
+
"""Provider plugin package for {ctx.pascal_name}."""
|
|
235
|
+
|
|
236
|
+
from .plugin import factory
|
|
237
|
+
|
|
238
|
+
__all__ = ["factory"]
|
|
239
|
+
'''
|
|
240
|
+
).strip()
|
|
241
|
+
+ "\n",
|
|
242
|
+
"config.py": dedent(
|
|
243
|
+
f'''
|
|
244
|
+
"""Configuration for the {ctx.pascal_name} provider."""
|
|
245
|
+
|
|
246
|
+
from pydantic import BaseModel, Field, HttpUrl
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class {ctx.config_class}(BaseModel):
|
|
250
|
+
"""Runtime configuration options for {ctx.plugin_name}."""
|
|
251
|
+
|
|
252
|
+
enabled: bool = Field(
|
|
253
|
+
default=False,
|
|
254
|
+
description="Enable once adapter logic has been implemented.",
|
|
255
|
+
)
|
|
256
|
+
base_url: HttpUrl = Field(
|
|
257
|
+
default="https://api.example.com",
|
|
258
|
+
description="Upstream API base URL.",
|
|
259
|
+
)
|
|
260
|
+
supports_streaming: bool = Field(
|
|
261
|
+
default=False,
|
|
262
|
+
description="Set to true when streaming is implemented.",
|
|
263
|
+
)
|
|
264
|
+
requires_auth: bool = Field(
|
|
265
|
+
default=True,
|
|
266
|
+
description="Whether requests need authentication headers.",
|
|
267
|
+
)
|
|
268
|
+
'''
|
|
269
|
+
).strip()
|
|
270
|
+
+ "\n",
|
|
271
|
+
"adapter.py": dedent(
|
|
272
|
+
f'''
|
|
273
|
+
"""HTTP adapter stub for {ctx.plugin_name}."""
|
|
274
|
+
|
|
275
|
+
from __future__ import annotations
|
|
276
|
+
|
|
277
|
+
from typing import Any
|
|
278
|
+
|
|
279
|
+
from fastapi import HTTPException, Request
|
|
280
|
+
from starlette.responses import JSONResponse
|
|
281
|
+
|
|
282
|
+
from ccproxy.services.adapters.http_adapter import BaseHTTPAdapter
|
|
283
|
+
from ccproxy.streaming import DeferredStreaming
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class {ctx.adapter_class}(BaseHTTPAdapter):
|
|
287
|
+
"""Minimal adapter that surfaces implementation TODOs."""
|
|
288
|
+
|
|
289
|
+
async def handle_request(
|
|
290
|
+
self, request: Request
|
|
291
|
+
) -> JSONResponse | DeferredStreaming:
|
|
292
|
+
raise HTTPException(
|
|
293
|
+
status_code=503,
|
|
294
|
+
detail="{ctx.plugin_name} adapter is not implemented yet.",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
async def handle_streaming(
|
|
298
|
+
self,
|
|
299
|
+
request: Request,
|
|
300
|
+
endpoint: str,
|
|
301
|
+
**_: Any,
|
|
302
|
+
) -> DeferredStreaming:
|
|
303
|
+
raise HTTPException(
|
|
304
|
+
status_code=503,
|
|
305
|
+
detail=(
|
|
306
|
+
"Streaming for {ctx.plugin_name} is not implemented yet."
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
async def cleanup(self) -> None:
|
|
311
|
+
"""Adapter cleanup hook."""
|
|
312
|
+
|
|
313
|
+
return None
|
|
314
|
+
'''
|
|
315
|
+
).strip()
|
|
316
|
+
+ "\n",
|
|
317
|
+
"plugin.py": dedent(
|
|
318
|
+
f'''
|
|
319
|
+
"""Provider plugin runtime for {ctx.plugin_name}."""
|
|
320
|
+
|
|
321
|
+
from __future__ import annotations
|
|
322
|
+
|
|
323
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
324
|
+
from ccproxy.core.plugins import (
|
|
325
|
+
BaseProviderPluginFactory,
|
|
326
|
+
PluginContext,
|
|
327
|
+
PluginManifest,
|
|
328
|
+
ProviderPluginRuntime,
|
|
329
|
+
)
|
|
330
|
+
from ccproxy.models.provider import ProviderConfig
|
|
331
|
+
|
|
332
|
+
from .adapter import {ctx.adapter_class}
|
|
333
|
+
from .config import {ctx.config_class}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
logger = get_plugin_logger()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class {ctx.runtime_class}(ProviderPluginRuntime):
|
|
340
|
+
"""Runtime implementation for {ctx.plugin_name}."""
|
|
341
|
+
|
|
342
|
+
def __init__(self, manifest: PluginManifest) -> None:
|
|
343
|
+
super().__init__(manifest)
|
|
344
|
+
self.config: {ctx.config_class} | None = None
|
|
345
|
+
|
|
346
|
+
async def _on_initialize(self) -> None:
|
|
347
|
+
if not self.context:
|
|
348
|
+
raise RuntimeError("Plugin context is not available")
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
self.config = self.context.get({ctx.config_class})
|
|
352
|
+
except ValueError:
|
|
353
|
+
self.config = {ctx.config_class}()
|
|
354
|
+
|
|
355
|
+
if not self.config.enabled:
|
|
356
|
+
logger.info("plugin_disabled", plugin=self.name)
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
await super()._on_initialize()
|
|
360
|
+
logger.info("plugin_initialized", plugin=self.name)
|
|
361
|
+
|
|
362
|
+
async def _on_validate(self) -> bool:
|
|
363
|
+
config = self.config or {ctx.config_class}()
|
|
364
|
+
if not config.enabled:
|
|
365
|
+
return True
|
|
366
|
+
return await super()._on_validate()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class {ctx.factory_class}(BaseProviderPluginFactory):
|
|
370
|
+
"""Factory for the {ctx.plugin_name} provider plugin."""
|
|
371
|
+
|
|
372
|
+
plugin_name = "{ctx.plugin_name}"
|
|
373
|
+
plugin_description = "{ctx.description}"
|
|
374
|
+
plugin_version = "{ctx.version}"
|
|
375
|
+
runtime_class = {ctx.runtime_class}
|
|
376
|
+
adapter_class = {ctx.adapter_class}
|
|
377
|
+
config_class = {ctx.config_class}
|
|
378
|
+
|
|
379
|
+
async def create_adapter(
|
|
380
|
+
self, context: PluginContext
|
|
381
|
+
) -> {ctx.adapter_class}:
|
|
382
|
+
config = context.get({ctx.config_class})
|
|
383
|
+
provider_config = ProviderConfig(
|
|
384
|
+
name=self.plugin_name,
|
|
385
|
+
base_url=str(config.base_url),
|
|
386
|
+
supports_streaming=config.supports_streaming,
|
|
387
|
+
requires_auth=config.requires_auth,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return {ctx.adapter_class}(
|
|
391
|
+
config=provider_config,
|
|
392
|
+
auth_manager=None,
|
|
393
|
+
http_pool_manager=context.http_pool_manager,
|
|
394
|
+
streaming_handler=context.streaming_handler,
|
|
395
|
+
format_registry=context.format_registry,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def create_detection_service(
|
|
399
|
+
self, context: PluginContext
|
|
400
|
+
) -> None:
|
|
401
|
+
config = context.get({ctx.config_class})
|
|
402
|
+
if not config.enabled:
|
|
403
|
+
return None
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
async def create_credentials_manager(
|
|
407
|
+
self, context: PluginContext
|
|
408
|
+
) -> None:
|
|
409
|
+
config = context.get({ctx.config_class})
|
|
410
|
+
if not config.enabled:
|
|
411
|
+
return None
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
factory = {ctx.factory_class}()
|
|
416
|
+
'''
|
|
417
|
+
).strip()
|
|
418
|
+
+ "\n",
|
|
419
|
+
"README.md": dedent(
|
|
420
|
+
f"""
|
|
421
|
+
# {ctx.pascal_name} Provider Plugin
|
|
422
|
+
|
|
423
|
+
Generated with `ccproxy plugins scaffold {ctx.plugin_name} --type provider`.
|
|
424
|
+
|
|
425
|
+
## Next steps
|
|
426
|
+
|
|
427
|
+
- Fill in `adapter.py` with provider-specific HTTP logic.
|
|
428
|
+
- Wire authentication and detection in `plugin.py`.
|
|
429
|
+
- Provide plugin-specific tests before enabling the plugin.
|
|
430
|
+
"""
|
|
431
|
+
).strip()
|
|
432
|
+
+ "\n",
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if include_tests:
|
|
436
|
+
files.update(
|
|
437
|
+
{
|
|
438
|
+
"tests/__init__.py": "\n",
|
|
439
|
+
f"tests/test_{ctx.plugin_name}_manifest.py": dedent(
|
|
440
|
+
f'''
|
|
441
|
+
"""Manifest smoke tests for the {ctx.plugin_name} provider."""
|
|
442
|
+
|
|
443
|
+
from ccproxy.plugins.{ctx.plugin_name}.plugin import factory
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_manifest_marks_provider() -> None:
|
|
447
|
+
manifest = factory.get_manifest()
|
|
448
|
+
assert manifest.is_provider is True
|
|
449
|
+
'''
|
|
450
|
+
).strip()
|
|
451
|
+
+ "\n",
|
|
452
|
+
}
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
return files
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _build_auth_template(ctx: TemplateContext, include_tests: bool) -> dict[str, str]:
|
|
459
|
+
"""Build files for an auth provider plugin scaffold."""
|
|
460
|
+
|
|
461
|
+
files: dict[str, str] = {
|
|
462
|
+
"__init__.py": dedent(
|
|
463
|
+
f'''
|
|
464
|
+
"""Auth provider plugin package for {ctx.pascal_name}."""
|
|
465
|
+
|
|
466
|
+
from .plugin import factory
|
|
467
|
+
|
|
468
|
+
__all__ = ["factory"]
|
|
469
|
+
'''
|
|
470
|
+
).strip()
|
|
471
|
+
+ "\n",
|
|
472
|
+
"config.py": dedent(
|
|
473
|
+
f'''
|
|
474
|
+
"""Configuration for the {ctx.pascal_name} auth provider."""
|
|
475
|
+
|
|
476
|
+
from pydantic import BaseModel, Field
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class {ctx.config_class}(BaseModel):
|
|
480
|
+
"""Runtime options for {ctx.plugin_name} OAuth."""
|
|
481
|
+
|
|
482
|
+
enabled: bool = Field(
|
|
483
|
+
default=False,
|
|
484
|
+
description="Enable once OAuth endpoints are implemented.",
|
|
485
|
+
)
|
|
486
|
+
client_id: str = Field(
|
|
487
|
+
default="your-client-id",
|
|
488
|
+
description="OAuth client identifier.",
|
|
489
|
+
)
|
|
490
|
+
client_secret: str = Field(
|
|
491
|
+
default="your-client-secret",
|
|
492
|
+
description="OAuth client secret.",
|
|
493
|
+
)
|
|
494
|
+
auth_base_url: str = Field(
|
|
495
|
+
default="https://auth.example.com",
|
|
496
|
+
description="Base URL for OAuth authorization endpoints.",
|
|
497
|
+
)
|
|
498
|
+
token_url: str = Field(
|
|
499
|
+
default="https://auth.example.com/token",
|
|
500
|
+
description="Token exchange endpoint.",
|
|
501
|
+
)
|
|
502
|
+
'''
|
|
503
|
+
).strip()
|
|
504
|
+
+ "\n",
|
|
505
|
+
"provider.py": dedent(
|
|
506
|
+
f'''
|
|
507
|
+
"""OAuth provider stub for {ctx.plugin_name}."""
|
|
508
|
+
|
|
509
|
+
from __future__ import annotations
|
|
510
|
+
|
|
511
|
+
from typing import Any
|
|
512
|
+
|
|
513
|
+
from ccproxy.auth.oauth.registry import OAuthProviderInfo, OAuthProviderProtocol
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class {ctx.oauth_provider_class}(OAuthProviderProtocol):
|
|
517
|
+
"""Skeleton OAuth provider awaiting implementation."""
|
|
518
|
+
|
|
519
|
+
def __init__(self, *, display_name: str) -> None:
|
|
520
|
+
self._display_name = display_name
|
|
521
|
+
|
|
522
|
+
@property
|
|
523
|
+
def provider_name(self) -> str:
|
|
524
|
+
return "{ctx.plugin_name}"
|
|
525
|
+
|
|
526
|
+
@property
|
|
527
|
+
def provider_display_name(self) -> str:
|
|
528
|
+
return self._display_name
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def supports_pkce(self) -> bool:
|
|
532
|
+
return True
|
|
533
|
+
|
|
534
|
+
async def get_authorization_url(
|
|
535
|
+
self,
|
|
536
|
+
state: str,
|
|
537
|
+
code_verifier: str | None = None,
|
|
538
|
+
redirect_uri: str | None = None,
|
|
539
|
+
) -> str:
|
|
540
|
+
raise NotImplementedError("Build authorization URL logic")
|
|
541
|
+
|
|
542
|
+
async def handle_callback(
|
|
543
|
+
self,
|
|
544
|
+
code: str,
|
|
545
|
+
state: str,
|
|
546
|
+
code_verifier: str | None = None,
|
|
547
|
+
redirect_uri: str | None = None,
|
|
548
|
+
) -> Any:
|
|
549
|
+
raise NotImplementedError("Exchange auth code for tokens")
|
|
550
|
+
|
|
551
|
+
async def refresh_access_token(self, refresh_token: str) -> Any:
|
|
552
|
+
raise NotImplementedError("Refresh tokens using the provider API")
|
|
553
|
+
|
|
554
|
+
async def revoke_token(self, token: str) -> None:
|
|
555
|
+
raise NotImplementedError("Revoke an issued token if supported")
|
|
556
|
+
|
|
557
|
+
async def get_profile(self, access_token: str) -> Any:
|
|
558
|
+
raise NotImplementedError("Fetch account profile information")
|
|
559
|
+
|
|
560
|
+
def describe(self) -> OAuthProviderInfo:
|
|
561
|
+
return OAuthProviderInfo(
|
|
562
|
+
name=self.provider_name,
|
|
563
|
+
display_name=self.provider_display_name,
|
|
564
|
+
description="{ctx.description}",
|
|
565
|
+
plugin_name="{ctx.plugin_name}",
|
|
566
|
+
)
|
|
567
|
+
'''
|
|
568
|
+
).strip()
|
|
569
|
+
+ "\n",
|
|
570
|
+
"plugin.py": dedent(
|
|
571
|
+
f'''
|
|
572
|
+
"""Auth provider plugin runtime for {ctx.plugin_name}."""
|
|
573
|
+
|
|
574
|
+
from __future__ import annotations
|
|
575
|
+
|
|
576
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
577
|
+
from ccproxy.core.plugins import (
|
|
578
|
+
AuthProviderPluginFactory,
|
|
579
|
+
AuthProviderPluginRuntime,
|
|
580
|
+
PluginContext,
|
|
581
|
+
PluginManifest,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
from .config import {ctx.config_class}
|
|
585
|
+
from .provider import {ctx.oauth_provider_class}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
logger = get_plugin_logger()
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class {ctx.runtime_class}(AuthProviderPluginRuntime):
|
|
592
|
+
"""Runtime implementation for {ctx.plugin_name} OAuth."""
|
|
593
|
+
|
|
594
|
+
def __init__(self, manifest: PluginManifest) -> None:
|
|
595
|
+
super().__init__(manifest)
|
|
596
|
+
self.config: {ctx.config_class} | None = None
|
|
597
|
+
|
|
598
|
+
async def _on_initialize(self) -> None:
|
|
599
|
+
if not self.context:
|
|
600
|
+
raise RuntimeError("Plugin context is not available")
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
self.config = self.context.get({ctx.config_class})
|
|
604
|
+
except ValueError:
|
|
605
|
+
self.config = {ctx.config_class}()
|
|
606
|
+
|
|
607
|
+
if not self.config.enabled:
|
|
608
|
+
logger.info("plugin_disabled", plugin=self.name)
|
|
609
|
+
return
|
|
610
|
+
|
|
611
|
+
await super()._on_initialize()
|
|
612
|
+
logger.info("plugin_initialized", plugin=self.name)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class {ctx.factory_class}(AuthProviderPluginFactory):
|
|
616
|
+
"""Factory for the {ctx.plugin_name} OAuth plugin."""
|
|
617
|
+
|
|
618
|
+
cli_safe = True
|
|
619
|
+
|
|
620
|
+
def __init__(self) -> None:
|
|
621
|
+
manifest = PluginManifest(
|
|
622
|
+
name="{ctx.plugin_name}",
|
|
623
|
+
version="{ctx.version}",
|
|
624
|
+
description="{ctx.description}",
|
|
625
|
+
is_provider=True,
|
|
626
|
+
config_class={ctx.config_class},
|
|
627
|
+
)
|
|
628
|
+
super().__init__(manifest)
|
|
629
|
+
|
|
630
|
+
def create_context(self, service_container: object) -> PluginContext:
|
|
631
|
+
context = super().create_context(service_container)
|
|
632
|
+
config = context.get({ctx.config_class})
|
|
633
|
+
|
|
634
|
+
if config.enabled:
|
|
635
|
+
context["auth_provider"] = self.create_auth_provider(context)
|
|
636
|
+
|
|
637
|
+
return context
|
|
638
|
+
|
|
639
|
+
def create_runtime(self) -> {ctx.runtime_class}:
|
|
640
|
+
return {ctx.runtime_class}(self.manifest)
|
|
641
|
+
|
|
642
|
+
def create_auth_provider(
|
|
643
|
+
self, context: PluginContext | None = None
|
|
644
|
+
) -> {ctx.oauth_provider_class}:
|
|
645
|
+
return {ctx.oauth_provider_class}(display_name="{ctx.pascal_name}")
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
factory = {ctx.factory_class}()
|
|
649
|
+
'''
|
|
650
|
+
).strip()
|
|
651
|
+
+ "\n",
|
|
652
|
+
"README.md": dedent(
|
|
653
|
+
f"""
|
|
654
|
+
# {ctx.pascal_name} OAuth Plugin
|
|
655
|
+
|
|
656
|
+
Generated with `ccproxy plugins scaffold {ctx.plugin_name} --type auth`.
|
|
657
|
+
|
|
658
|
+
## Next steps
|
|
659
|
+
|
|
660
|
+
- Implement the OAuth flow in `provider.py`.
|
|
661
|
+
- Wire storage and credential handling inside `plugin.py`.
|
|
662
|
+
- Provide CLI guidance for user sign-in.
|
|
663
|
+
"""
|
|
664
|
+
).strip()
|
|
665
|
+
+ "\n",
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if include_tests:
|
|
669
|
+
files.update(
|
|
670
|
+
{
|
|
671
|
+
"tests/__init__.py": "\n",
|
|
672
|
+
f"tests/test_{ctx.plugin_name}_auth.py": dedent(
|
|
673
|
+
f'''
|
|
674
|
+
"""Manifest smoke tests for the {ctx.plugin_name} auth plugin."""
|
|
675
|
+
|
|
676
|
+
from ccproxy.plugins.{ctx.plugin_name}.plugin import factory
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def test_manifest_marks_provider() -> None:
|
|
680
|
+
manifest = factory.get_manifest()
|
|
681
|
+
assert manifest.is_provider is True
|
|
682
|
+
'''
|
|
683
|
+
).strip()
|
|
684
|
+
+ "\n",
|
|
685
|
+
}
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
return files
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _to_pascal_case(name: str) -> str:
|
|
692
|
+
"""Convert snake-case or kebab-case names to PascalCase."""
|
|
693
|
+
|
|
694
|
+
parts = re.split(r"[_\-\s]+", name)
|
|
695
|
+
return "".join(part.capitalize() for part in parts if part)
|