ccproxy-api 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +439 -212
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +145 -176
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +402 -530
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +558 -0
- ccproxy/data/codex_headers_fallback.json +121 -0
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +63 -107
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +346 -314
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +95 -342
- ccproxy/utils/version_checker.py +279 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1231
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -269
- ccproxy/services/codex_detection_service.py +0 -263
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.6.dist-info/METADATA +0 -615
- ccproxy_api-0.1.6.dist-info/RECORD +0 -189
- ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Hook implementation for command replay generation."""
|
|
2
|
+
|
|
3
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
4
|
+
from ccproxy.core.plugins.hooks import Hook
|
|
5
|
+
from ccproxy.core.plugins.hooks.base import HookContext
|
|
6
|
+
from ccproxy.core.plugins.hooks.events import HookEvent
|
|
7
|
+
from ccproxy.core.request_context import RequestContext
|
|
8
|
+
from ccproxy.utils.command_line import (
|
|
9
|
+
format_command_output,
|
|
10
|
+
generate_curl_command,
|
|
11
|
+
generate_xh_command,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .config import CommandReplayConfig
|
|
15
|
+
from .formatter import CommandFileFormatter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = get_plugin_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CommandReplayHook(Hook):
|
|
22
|
+
"""Hook for generating curl and xh command replays of provider requests.
|
|
23
|
+
|
|
24
|
+
Listens for PROVIDER_REQUEST_PREPARED events and generates command line
|
|
25
|
+
equivalents that can be used to replay the exact same HTTP requests.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
name = "command_replay"
|
|
29
|
+
events = [
|
|
30
|
+
HookEvent.PROVIDER_REQUEST_PREPARED,
|
|
31
|
+
# Also listen to HTTP_REQUEST for broader coverage
|
|
32
|
+
HookEvent.HTTP_REQUEST,
|
|
33
|
+
]
|
|
34
|
+
priority = 200 # Run after core tracing but before heavy processing
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
config: CommandReplayConfig | None = None,
|
|
39
|
+
file_formatter: CommandFileFormatter | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize the command replay hook.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
config: Command replay configuration
|
|
45
|
+
file_formatter: File formatter for writing commands to files
|
|
46
|
+
"""
|
|
47
|
+
self.config = config or CommandReplayConfig()
|
|
48
|
+
self.file_formatter = file_formatter
|
|
49
|
+
|
|
50
|
+
logger.debug(
|
|
51
|
+
"command_replay_hook_initialized",
|
|
52
|
+
enabled=self.config.enabled,
|
|
53
|
+
generate_curl=self.config.generate_curl,
|
|
54
|
+
generate_xh=self.config.generate_xh,
|
|
55
|
+
include_patterns=self.config.include_url_patterns,
|
|
56
|
+
only_provider_requests=self.config.only_provider_requests,
|
|
57
|
+
include_client_requests=self.config.include_client_requests,
|
|
58
|
+
write_to_files=self.config.write_to_files,
|
|
59
|
+
log_dir=self.config.log_dir,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def __call__(self, context: HookContext) -> None:
|
|
63
|
+
"""Handle hook events for command replay generation.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
context: Hook context with event data
|
|
67
|
+
"""
|
|
68
|
+
if not self.config.enabled:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# Debug logging
|
|
72
|
+
logger.debug(
|
|
73
|
+
"command_replay_hook_called",
|
|
74
|
+
hook_event=context.event.value if context.event else "unknown",
|
|
75
|
+
data_keys=list(context.data.keys()) if context.data else [],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
if context.event == HookEvent.PROVIDER_REQUEST_PREPARED:
|
|
80
|
+
await self._handle_provider_request(context)
|
|
81
|
+
elif context.event == HookEvent.HTTP_REQUEST:
|
|
82
|
+
await self._handle_http_request(context)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(
|
|
85
|
+
"command_replay_hook_error",
|
|
86
|
+
hook_event=context.event.value if context.event else "unknown",
|
|
87
|
+
error=str(e),
|
|
88
|
+
exc_info=e,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def _handle_provider_request(self, context: HookContext) -> None:
|
|
92
|
+
"""Handle PROVIDER_REQUEST_PREPARED event."""
|
|
93
|
+
await self._generate_commands(context, is_provider_request=True)
|
|
94
|
+
|
|
95
|
+
async def _handle_http_request(self, context: HookContext) -> None:
|
|
96
|
+
"""Handle HTTP_REQUEST event - for both provider and client requests."""
|
|
97
|
+
url = context.data.get("url", "")
|
|
98
|
+
is_provider = self._is_provider_request(url)
|
|
99
|
+
|
|
100
|
+
# Apply filtering based on configuration
|
|
101
|
+
if self.config.only_provider_requests and not is_provider:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
if not self.config.include_client_requests and not is_provider:
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
await self._generate_commands(context, is_provider_request=is_provider)
|
|
108
|
+
|
|
109
|
+
async def _generate_commands(
|
|
110
|
+
self, context: HookContext, is_provider_request: bool = False
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Generate curl and xh commands from request context.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
context: Hook context with request data
|
|
116
|
+
is_provider_request: Whether this came from PROVIDER_REQUEST_PREPARED
|
|
117
|
+
"""
|
|
118
|
+
# Extract request data
|
|
119
|
+
method = context.data.get("method", "GET")
|
|
120
|
+
url = context.data.get("url", "")
|
|
121
|
+
headers = context.data.get("headers", {})
|
|
122
|
+
body = context.data.get("body")
|
|
123
|
+
is_json = context.data.get("is_json", False)
|
|
124
|
+
|
|
125
|
+
# Get request ID for correlation
|
|
126
|
+
request_id = (
|
|
127
|
+
context.data.get("request_id")
|
|
128
|
+
or context.metadata.get("request_id")
|
|
129
|
+
or "unknown"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Get provider name if available
|
|
133
|
+
provider = context.provider or self._extract_provider_from_url(url)
|
|
134
|
+
|
|
135
|
+
# Check if we should generate commands for this URL
|
|
136
|
+
if not self.config.should_generate_for_url(url, is_provider_request):
|
|
137
|
+
logger.debug(
|
|
138
|
+
"command_replay_skipped_url_filter",
|
|
139
|
+
request_id=request_id,
|
|
140
|
+
url=url,
|
|
141
|
+
provider=provider,
|
|
142
|
+
is_provider_request=is_provider_request,
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Validate we have minimum required data
|
|
147
|
+
if not url or not method:
|
|
148
|
+
logger.warning(
|
|
149
|
+
"command_replay_insufficient_data",
|
|
150
|
+
request_id=request_id,
|
|
151
|
+
has_url=bool(url),
|
|
152
|
+
has_method=bool(method),
|
|
153
|
+
)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
commands = []
|
|
157
|
+
|
|
158
|
+
# Generate curl command
|
|
159
|
+
if self.config.generate_curl:
|
|
160
|
+
try:
|
|
161
|
+
curl_cmd = generate_curl_command(
|
|
162
|
+
method=method,
|
|
163
|
+
url=url,
|
|
164
|
+
headers=headers,
|
|
165
|
+
body=body,
|
|
166
|
+
is_json=is_json,
|
|
167
|
+
pretty=self.config.pretty_format,
|
|
168
|
+
)
|
|
169
|
+
commands.append(("curl", curl_cmd))
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(
|
|
172
|
+
"command_replay_curl_generation_error",
|
|
173
|
+
request_id=request_id,
|
|
174
|
+
error=str(e),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Generate xh command
|
|
178
|
+
if self.config.generate_xh:
|
|
179
|
+
try:
|
|
180
|
+
xh_cmd = generate_xh_command(
|
|
181
|
+
method=method,
|
|
182
|
+
url=url,
|
|
183
|
+
headers=headers,
|
|
184
|
+
body=body,
|
|
185
|
+
is_json=is_json,
|
|
186
|
+
pretty=self.config.pretty_format,
|
|
187
|
+
)
|
|
188
|
+
commands.append(("xh", xh_cmd))
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(
|
|
191
|
+
"command_replay_xh_generation_error",
|
|
192
|
+
request_id=request_id,
|
|
193
|
+
error=str(e),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Process generated commands
|
|
197
|
+
if commands:
|
|
198
|
+
curl_cmd = next((cmd for tool, cmd in commands if tool == "curl"), "")
|
|
199
|
+
xh_cmd = next((cmd for tool, cmd in commands if tool == "xh"), "")
|
|
200
|
+
|
|
201
|
+
# Write to files if enabled
|
|
202
|
+
written_files = []
|
|
203
|
+
if self.config.write_to_files and self.file_formatter:
|
|
204
|
+
try:
|
|
205
|
+
# Get timestamp prefix from current request context if available
|
|
206
|
+
timestamp_prefix = None
|
|
207
|
+
try:
|
|
208
|
+
current_context = RequestContext.get_current()
|
|
209
|
+
if current_context:
|
|
210
|
+
timestamp_prefix = (
|
|
211
|
+
current_context.get_log_timestamp_prefix()
|
|
212
|
+
)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
written_files = await self.file_formatter.write_commands(
|
|
217
|
+
request_id=request_id,
|
|
218
|
+
curl_command=curl_cmd,
|
|
219
|
+
xh_command=xh_cmd,
|
|
220
|
+
provider=provider,
|
|
221
|
+
timestamp_prefix=timestamp_prefix,
|
|
222
|
+
method=method,
|
|
223
|
+
url=url,
|
|
224
|
+
headers=headers,
|
|
225
|
+
body=body,
|
|
226
|
+
is_json=is_json,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if written_files:
|
|
230
|
+
logger.debug(
|
|
231
|
+
"command_replay_files_written",
|
|
232
|
+
request_id=request_id,
|
|
233
|
+
files=written_files,
|
|
234
|
+
provider=provider,
|
|
235
|
+
)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(
|
|
238
|
+
"command_replay_file_write_failed",
|
|
239
|
+
request_id=request_id,
|
|
240
|
+
error=str(e),
|
|
241
|
+
exc_info=e,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Log to console if enabled
|
|
245
|
+
if self.config.log_to_console:
|
|
246
|
+
output = format_command_output(
|
|
247
|
+
request_id=request_id,
|
|
248
|
+
curl_command=curl_cmd,
|
|
249
|
+
xh_command=xh_cmd,
|
|
250
|
+
provider=provider,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Add file info to console output if files were written
|
|
254
|
+
if written_files:
|
|
255
|
+
output += f"\n📁 Files written: {', '.join(written_files)}\n"
|
|
256
|
+
|
|
257
|
+
logger.debug("command_replay_generated", output=output)
|
|
258
|
+
|
|
259
|
+
def _is_provider_request(self, url: str) -> bool:
|
|
260
|
+
"""Determine if this is a request to a provider API.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
url: The request URL
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
True if this is a provider request
|
|
267
|
+
"""
|
|
268
|
+
provider_domains = [
|
|
269
|
+
"api.anthropic.com",
|
|
270
|
+
"claude.ai",
|
|
271
|
+
"api.openai.com",
|
|
272
|
+
"chatgpt.com",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
url_lower = url.lower()
|
|
276
|
+
return any(domain in url_lower for domain in provider_domains)
|
|
277
|
+
|
|
278
|
+
def _extract_provider_from_url(self, url: str) -> str | None:
|
|
279
|
+
"""Extract provider name from URL.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
url: The request URL
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Provider name or None if not recognized
|
|
286
|
+
"""
|
|
287
|
+
url_lower = url.lower()
|
|
288
|
+
|
|
289
|
+
if "anthropic.com" in url_lower or "claude.ai" in url_lower:
|
|
290
|
+
return "anthropic"
|
|
291
|
+
elif "openai.com" in url_lower or "chatgpt.com" in url_lower:
|
|
292
|
+
return "openai"
|
|
293
|
+
|
|
294
|
+
return None
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Command Replay plugin implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
6
|
+
from ccproxy.core.plugins import (
|
|
7
|
+
PluginManifest,
|
|
8
|
+
SystemPluginFactory,
|
|
9
|
+
SystemPluginRuntime,
|
|
10
|
+
)
|
|
11
|
+
from ccproxy.core.plugins.hooks import HookRegistry
|
|
12
|
+
|
|
13
|
+
from .config import CommandReplayConfig
|
|
14
|
+
from .formatter import CommandFileFormatter
|
|
15
|
+
from .hook import CommandReplayHook
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = get_plugin_logger()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CommandReplayRuntime(SystemPluginRuntime):
|
|
22
|
+
"""Runtime for the command replay plugin.
|
|
23
|
+
|
|
24
|
+
Generates curl and xh commands for provider requests to enable
|
|
25
|
+
easy replay and debugging of API calls.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, manifest: PluginManifest):
|
|
29
|
+
"""Initialize runtime."""
|
|
30
|
+
super().__init__(manifest)
|
|
31
|
+
self.config: CommandReplayConfig | None = None
|
|
32
|
+
self.hook: CommandReplayHook | None = None
|
|
33
|
+
self.file_formatter: CommandFileFormatter | None = None
|
|
34
|
+
|
|
35
|
+
async def _on_initialize(self) -> None:
|
|
36
|
+
"""Initialize the command replay plugin."""
|
|
37
|
+
if not self.context:
|
|
38
|
+
raise RuntimeError("Context not set")
|
|
39
|
+
|
|
40
|
+
# Get configuration
|
|
41
|
+
config = self.context.get("config")
|
|
42
|
+
if not isinstance(config, CommandReplayConfig):
|
|
43
|
+
logger.debug("plugin_no_config")
|
|
44
|
+
config = CommandReplayConfig()
|
|
45
|
+
logger.debug("plugin_using_default_config")
|
|
46
|
+
self.config = config
|
|
47
|
+
|
|
48
|
+
# Debug log the configuration being used
|
|
49
|
+
logger.debug(
|
|
50
|
+
"plugin_configuration_loaded",
|
|
51
|
+
enabled=config.enabled,
|
|
52
|
+
generate_curl=config.generate_curl,
|
|
53
|
+
generate_xh=config.generate_xh,
|
|
54
|
+
include_patterns=config.include_url_patterns,
|
|
55
|
+
exclude_patterns=config.exclude_url_patterns,
|
|
56
|
+
log_to_console=config.log_to_console,
|
|
57
|
+
log_level=config.log_level,
|
|
58
|
+
only_provider_requests=config.only_provider_requests,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if self.config.enabled:
|
|
62
|
+
# Initialize file formatter if file writing is enabled
|
|
63
|
+
if self.config.write_to_files:
|
|
64
|
+
self.file_formatter = CommandFileFormatter(
|
|
65
|
+
log_dir=self.config.log_dir,
|
|
66
|
+
enabled=True,
|
|
67
|
+
separate_files_per_command=self.config.separate_files_per_command,
|
|
68
|
+
)
|
|
69
|
+
logger.debug(
|
|
70
|
+
"command_replay_file_formatter_initialized",
|
|
71
|
+
log_dir=self.config.log_dir,
|
|
72
|
+
separate_files=self.config.separate_files_per_command,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Register hook for provider request events
|
|
76
|
+
self.hook = CommandReplayHook(
|
|
77
|
+
config=self.config,
|
|
78
|
+
file_formatter=self.file_formatter,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Try to get hook registry from context
|
|
82
|
+
hook_registry = self.context.get("hook_registry")
|
|
83
|
+
|
|
84
|
+
# If not found, try app state
|
|
85
|
+
if not hook_registry:
|
|
86
|
+
app = self.context.get("app")
|
|
87
|
+
if app and hasattr(app.state, "hook_registry"):
|
|
88
|
+
hook_registry = app.state.hook_registry
|
|
89
|
+
|
|
90
|
+
if hook_registry and isinstance(hook_registry, HookRegistry):
|
|
91
|
+
hook_registry.register(self.hook)
|
|
92
|
+
logger.debug(
|
|
93
|
+
"command_replay_hook_registered",
|
|
94
|
+
events=self.hook.events,
|
|
95
|
+
priority=self.hook.priority,
|
|
96
|
+
generate_curl=self.config.generate_curl,
|
|
97
|
+
generate_xh=self.config.generate_xh,
|
|
98
|
+
write_to_files=self.config.write_to_files,
|
|
99
|
+
log_dir=self.config.log_dir if self.config.write_to_files else None,
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
logger.warning(
|
|
103
|
+
"hook_registry_not_available",
|
|
104
|
+
fallback="disabled",
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
logger.debug("command_replay_plugin_disabled")
|
|
108
|
+
|
|
109
|
+
async def _on_shutdown(self) -> None:
|
|
110
|
+
"""Clean up plugin resources."""
|
|
111
|
+
if self.hook:
|
|
112
|
+
logger.info("command_replay_plugin_shutdown")
|
|
113
|
+
self.hook = None
|
|
114
|
+
|
|
115
|
+
if self.file_formatter:
|
|
116
|
+
self.file_formatter.cleanup()
|
|
117
|
+
self.file_formatter = None
|
|
118
|
+
|
|
119
|
+
def get_health_info(self) -> dict[str, Any]:
|
|
120
|
+
"""Get plugin health information."""
|
|
121
|
+
return {
|
|
122
|
+
"enabled": self.config.enabled if self.config else False,
|
|
123
|
+
"hook_registered": self.hook is not None,
|
|
124
|
+
"generate_curl": self.config.generate_curl if self.config else False,
|
|
125
|
+
"generate_xh": self.config.generate_xh if self.config else False,
|
|
126
|
+
"write_to_files": self.config.write_to_files if self.config else False,
|
|
127
|
+
"file_formatter_enabled": self.file_formatter is not None,
|
|
128
|
+
"log_dir": self.config.log_dir if self.config else None,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class CommandReplayFactory(SystemPluginFactory):
|
|
133
|
+
"""Factory for creating command replay plugin instances."""
|
|
134
|
+
|
|
135
|
+
def __init__(self) -> None:
|
|
136
|
+
"""Initialize factory with manifest."""
|
|
137
|
+
# Create manifest with static declarations
|
|
138
|
+
manifest = PluginManifest(
|
|
139
|
+
name="command_replay",
|
|
140
|
+
version="0.1.0",
|
|
141
|
+
description="Generates curl and xh commands for provider requests",
|
|
142
|
+
is_provider=False,
|
|
143
|
+
config_class=CommandReplayConfig,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Initialize with manifest
|
|
147
|
+
super().__init__(manifest)
|
|
148
|
+
|
|
149
|
+
def create_runtime(self) -> CommandReplayRuntime:
|
|
150
|
+
"""Create runtime instance."""
|
|
151
|
+
return CommandReplayRuntime(self.manifest)
|
|
152
|
+
|
|
153
|
+
def create_context(self, core_services: Any) -> Any:
|
|
154
|
+
"""Create context for the plugin."""
|
|
155
|
+
# Get base context from parent
|
|
156
|
+
context = super().create_context(core_services)
|
|
157
|
+
return context
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Export the factory for plugin discovery
|
|
161
|
+
factory = CommandReplayFactory()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copilot Plugin
|
|
2
|
+
|
|
3
|
+
Adds GitHub Copilot as a provider with OAuth, detection, and streaming support.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
- Wraps the Copilot HTTP adapter while emitting OpenAI-compatible streams
|
|
7
|
+
- Manages OAuth exchange and token refresh through `CopilotOAuthProvider`
|
|
8
|
+
- Exposes GitHub-flavored routes under `/copilot` alongside v1 proxy APIs
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
- `CopilotConfig` controls base URLs, scopes, cache paths, and CLI detection
|
|
12
|
+
- Depends on `CopilotTokenManager` for credential storage and refresh logic
|
|
13
|
+
- Generate defaults with `python3 scripts/generate_config_from_model.py \
|
|
14
|
+
--format toml --plugin copilot --config-class CopilotConfig`
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[plugins.copilot]
|
|
18
|
+
# enabled = true
|
|
19
|
+
# base_url = "https://api.githubcopilot.com"
|
|
20
|
+
# auth_type = "oauth"
|
|
21
|
+
# supports_streaming = true
|
|
22
|
+
# default_max_tokens = 4096
|
|
23
|
+
# account_type = "individual"
|
|
24
|
+
# request_timeout = 30
|
|
25
|
+
# max_retries = 3
|
|
26
|
+
# retry_delay = 1.0
|
|
27
|
+
|
|
28
|
+
[plugins.copilot.oauth]
|
|
29
|
+
# client_id = "Iv1.b507a08c87ecfe98"
|
|
30
|
+
# authorize_url = "https://github.com/login/device/code"
|
|
31
|
+
# token_url = "https://github.com/login/oauth/access_token"
|
|
32
|
+
# callback_port = 8080
|
|
33
|
+
# scopes = ["read:user"]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Related Components
|
|
37
|
+
- `adapter.py`: request translation and HTTP execution layer
|
|
38
|
+
- `oauth/provider.py`: OAuth flow implementation for GitHub accounts
|
|
39
|
+
- `routes.py`: FastAPI routers for GitHub and proxy endpoints
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""GitHub Copilot provider plugin for CCProxy.
|
|
2
|
+
|
|
3
|
+
This plugin provides OAuth authentication with GitHub and API proxying
|
|
4
|
+
capabilities for GitHub Copilot services, following the established patterns
|
|
5
|
+
from existing OAuth Claude and Codex plugins.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .plugin import CopilotPluginFactory, CopilotPluginRuntime, factory
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["CopilotPluginFactory", "CopilotPluginRuntime", "factory"]
|