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,571 @@
|
|
|
1
|
+
"""Claude OAuth provider for plugin registration."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from base64 import urlsafe_b64encode
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from urllib.parse import urlencode
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from ccproxy.auth.oauth.protocol import ProfileLoggingMixin, StandardProfileFields
|
|
12
|
+
from ccproxy.auth.oauth.registry import CliAuthConfig, FlowType, OAuthProviderInfo
|
|
13
|
+
from ccproxy.auth.storage.generic import GenericJsonStorage
|
|
14
|
+
from ccproxy.config.settings import Settings
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ccproxy.services.cli_detection import CLIDetectionService
|
|
19
|
+
|
|
20
|
+
from .manager import ClaudeApiTokenManager
|
|
21
|
+
|
|
22
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
23
|
+
|
|
24
|
+
from .client import ClaudeOAuthClient
|
|
25
|
+
from .config import ClaudeOAuthConfig
|
|
26
|
+
from .models import ClaudeCredentials, ClaudeProfileInfo
|
|
27
|
+
from .storage import ClaudeOAuthStorage
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = get_plugin_logger()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ClaudeOAuthProvider(ProfileLoggingMixin):
|
|
34
|
+
"""Claude OAuth provider implementation for registry."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
config: ClaudeOAuthConfig | None = None,
|
|
39
|
+
storage: ClaudeOAuthStorage | None = None,
|
|
40
|
+
http_client: httpx.AsyncClient | None = None,
|
|
41
|
+
hook_manager: Any | None = None,
|
|
42
|
+
detection_service: "CLIDetectionService | None" = None,
|
|
43
|
+
settings: Settings | None = None,
|
|
44
|
+
):
|
|
45
|
+
"""Initialize Claude OAuth provider.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
config: OAuth configuration
|
|
49
|
+
storage: Token storage
|
|
50
|
+
http_client: Optional HTTP client (for request tracing support)
|
|
51
|
+
hook_manager: Optional hook manager for emitting events
|
|
52
|
+
detection_service: Optional CLI detection service for headers
|
|
53
|
+
settings: Optional settings for HTTP client configuration
|
|
54
|
+
"""
|
|
55
|
+
self.config = config or ClaudeOAuthConfig()
|
|
56
|
+
self.storage = storage or ClaudeOAuthStorage()
|
|
57
|
+
self.hook_manager = hook_manager
|
|
58
|
+
self.detection_service = detection_service
|
|
59
|
+
self.http_client = http_client
|
|
60
|
+
self.settings = settings
|
|
61
|
+
self._cached_profile: ClaudeProfileInfo | None = (
|
|
62
|
+
None # Cache enhanced profile data for UI display
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.client = ClaudeOAuthClient(
|
|
66
|
+
self.config,
|
|
67
|
+
self.storage,
|
|
68
|
+
http_client,
|
|
69
|
+
hook_manager=hook_manager,
|
|
70
|
+
detection_service=detection_service,
|
|
71
|
+
settings=settings,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def provider_name(self) -> str:
|
|
76
|
+
"""Internal provider name."""
|
|
77
|
+
return "claude-api"
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def provider_display_name(self) -> str:
|
|
81
|
+
"""Display name for UI."""
|
|
82
|
+
return "Claude API"
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def supports_pkce(self) -> bool:
|
|
86
|
+
"""Whether this provider supports PKCE."""
|
|
87
|
+
return self.config.use_pkce
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def supports_refresh(self) -> bool:
|
|
91
|
+
"""Whether this provider supports token refresh."""
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def requires_client_secret(self) -> bool:
|
|
96
|
+
"""Whether this provider requires a client secret."""
|
|
97
|
+
return False # Claude uses PKCE-like flow without client secret
|
|
98
|
+
|
|
99
|
+
async def get_authorization_url(
|
|
100
|
+
self,
|
|
101
|
+
state: str,
|
|
102
|
+
code_verifier: str | None = None,
|
|
103
|
+
redirect_uri: str | None = None,
|
|
104
|
+
) -> str:
|
|
105
|
+
"""Get the authorization URL for OAuth flow.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
state: OAuth state parameter for CSRF protection
|
|
109
|
+
code_verifier: PKCE code verifier (if PKCE is supported)
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Authorization URL to redirect user to
|
|
113
|
+
"""
|
|
114
|
+
# Use provided redirect URI or fall back to config default
|
|
115
|
+
if redirect_uri is None:
|
|
116
|
+
redirect_uri = self.config.get_redirect_uri()
|
|
117
|
+
|
|
118
|
+
params = {
|
|
119
|
+
"code": "true", # Required by Claude OAuth
|
|
120
|
+
"client_id": self.config.client_id,
|
|
121
|
+
"redirect_uri": redirect_uri,
|
|
122
|
+
"response_type": "code",
|
|
123
|
+
"scope": " ".join(self.config.scopes),
|
|
124
|
+
"state": state,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Add PKCE challenge if supported and verifier provided
|
|
128
|
+
if self.config.use_pkce and code_verifier:
|
|
129
|
+
code_challenge = (
|
|
130
|
+
urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
|
|
131
|
+
.decode()
|
|
132
|
+
.rstrip("=")
|
|
133
|
+
)
|
|
134
|
+
params["code_challenge"] = code_challenge
|
|
135
|
+
params["code_challenge_method"] = "S256"
|
|
136
|
+
|
|
137
|
+
auth_url = f"{self.config.authorize_url}?{urlencode(params)}"
|
|
138
|
+
|
|
139
|
+
logger.info(
|
|
140
|
+
"claude_oauth_auth_url_generated",
|
|
141
|
+
state=state,
|
|
142
|
+
has_pkce=bool(code_verifier and self.config.use_pkce),
|
|
143
|
+
category="auth",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return auth_url
|
|
147
|
+
|
|
148
|
+
async def handle_callback(
|
|
149
|
+
self,
|
|
150
|
+
code: str,
|
|
151
|
+
state: str,
|
|
152
|
+
code_verifier: str | None = None,
|
|
153
|
+
redirect_uri: str | None = None,
|
|
154
|
+
) -> Any:
|
|
155
|
+
"""Handle OAuth callback and exchange code for tokens.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
code: Authorization code from OAuth callback
|
|
159
|
+
state: State parameter for validation
|
|
160
|
+
code_verifier: PKCE code verifier (if PKCE is used)
|
|
161
|
+
redirect_uri: Redirect URI used in authorization (optional)
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Claude credentials object
|
|
165
|
+
"""
|
|
166
|
+
# Use the client's handle_callback method which includes code exchange
|
|
167
|
+
# If a specific redirect_uri was provided, create a temporary client with that URI
|
|
168
|
+
if redirect_uri and redirect_uri != self.client.redirect_uri:
|
|
169
|
+
# Create temporary config with the specific redirect URI
|
|
170
|
+
temp_config = ClaudeOAuthConfig(
|
|
171
|
+
client_id=self.config.client_id,
|
|
172
|
+
redirect_uri=redirect_uri,
|
|
173
|
+
scopes=self.config.scopes,
|
|
174
|
+
base_url=self.config.base_url,
|
|
175
|
+
authorize_url=self.config.authorize_url,
|
|
176
|
+
token_url=self.config.token_url,
|
|
177
|
+
use_pkce=self.config.use_pkce,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Create temporary client with the correct redirect URI
|
|
181
|
+
temp_client = ClaudeOAuthClient(
|
|
182
|
+
temp_config,
|
|
183
|
+
self.storage,
|
|
184
|
+
self.http_client,
|
|
185
|
+
hook_manager=self.hook_manager,
|
|
186
|
+
detection_service=self.detection_service,
|
|
187
|
+
settings=self.settings,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
credentials = await temp_client.handle_callback(
|
|
191
|
+
code, state, code_verifier or ""
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
# Use the regular client
|
|
195
|
+
credentials = await self.client.handle_callback(
|
|
196
|
+
code, state, code_verifier or ""
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# The client already saves to storage if available, but we can save again
|
|
200
|
+
# to our specific storage if needed
|
|
201
|
+
if self.storage:
|
|
202
|
+
await self.storage.save(credentials)
|
|
203
|
+
|
|
204
|
+
logger.info(
|
|
205
|
+
"claude_oauth_callback_handled",
|
|
206
|
+
state=state,
|
|
207
|
+
has_credentials=bool(credentials),
|
|
208
|
+
category="auth",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return credentials
|
|
212
|
+
|
|
213
|
+
async def refresh_access_token(self, refresh_token: str) -> Any:
|
|
214
|
+
"""Refresh access token using refresh token.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
refresh_token: Refresh token from previous auth
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
New token response
|
|
221
|
+
"""
|
|
222
|
+
credentials = await self.client.refresh_token(refresh_token)
|
|
223
|
+
|
|
224
|
+
# Store updated credentials
|
|
225
|
+
if self.storage:
|
|
226
|
+
await self.storage.save(credentials)
|
|
227
|
+
|
|
228
|
+
logger.info("claude_oauth_token_refreshed", category="auth")
|
|
229
|
+
|
|
230
|
+
return credentials
|
|
231
|
+
|
|
232
|
+
async def revoke_token(self, token: str) -> None:
|
|
233
|
+
"""Revoke an access or refresh token.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
token: Token to revoke
|
|
237
|
+
"""
|
|
238
|
+
# Claude doesn't have a revoke endpoint, so we just delete stored credentials
|
|
239
|
+
if self.storage:
|
|
240
|
+
await self.storage.delete()
|
|
241
|
+
|
|
242
|
+
logger.info("claude_oauth_token_revoked_locally", category="auth")
|
|
243
|
+
|
|
244
|
+
def get_provider_info(self) -> OAuthProviderInfo:
|
|
245
|
+
"""Get provider information for discovery.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Provider information
|
|
249
|
+
"""
|
|
250
|
+
return OAuthProviderInfo(
|
|
251
|
+
name=self.provider_name,
|
|
252
|
+
display_name=self.provider_display_name,
|
|
253
|
+
description="OAuth authentication for Claude AI",
|
|
254
|
+
supports_pkce=self.supports_pkce,
|
|
255
|
+
scopes=self.config.scopes,
|
|
256
|
+
is_available=True,
|
|
257
|
+
plugin_name="oauth_claude",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async def validate_token(self, access_token: str) -> bool:
|
|
261
|
+
"""Validate an access token.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
access_token: Token to validate
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
True if token is valid
|
|
268
|
+
"""
|
|
269
|
+
# Claude doesn't have a validation endpoint, so we check if stored token matches
|
|
270
|
+
if self.storage:
|
|
271
|
+
credentials = await self.storage.load()
|
|
272
|
+
if credentials and credentials.claude_ai_oauth:
|
|
273
|
+
stored_token = (
|
|
274
|
+
credentials.claude_ai_oauth.access_token.get_secret_value()
|
|
275
|
+
)
|
|
276
|
+
return stored_token == access_token
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
async def get_user_info(self, access_token: str) -> dict[str, Any] | None:
|
|
280
|
+
"""Get user information using access token.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
access_token: Valid access token
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
User information or None
|
|
287
|
+
"""
|
|
288
|
+
# Load stored credentials which contain user info
|
|
289
|
+
if self.storage:
|
|
290
|
+
credentials = await self.storage.load()
|
|
291
|
+
if credentials and credentials.claude_ai_oauth:
|
|
292
|
+
return {
|
|
293
|
+
"subscription_type": credentials.claude_ai_oauth.subscription_type,
|
|
294
|
+
"scopes": credentials.claude_ai_oauth.scopes,
|
|
295
|
+
}
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
def get_storage(self) -> Any:
|
|
299
|
+
"""Get storage implementation for this provider.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Storage implementation
|
|
303
|
+
"""
|
|
304
|
+
return self.storage
|
|
305
|
+
|
|
306
|
+
def get_config(self) -> Any:
|
|
307
|
+
"""Get configuration for this provider.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Configuration implementation
|
|
311
|
+
"""
|
|
312
|
+
return self.config
|
|
313
|
+
|
|
314
|
+
async def save_credentials(
|
|
315
|
+
self, credentials: Any, custom_path: Any | None = None
|
|
316
|
+
) -> bool:
|
|
317
|
+
"""Save credentials using provider's storage mechanism.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
credentials: Claude credentials object
|
|
321
|
+
custom_path: Optional custom storage path (Path object)
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
True if saved successfully, False otherwise
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
if custom_path:
|
|
328
|
+
# Use custom path for storage
|
|
329
|
+
storage = GenericJsonStorage(Path(custom_path), ClaudeCredentials)
|
|
330
|
+
manager = await self.create_token_manager(storage=storage)
|
|
331
|
+
else:
|
|
332
|
+
# Use default storage
|
|
333
|
+
manager = await self.create_token_manager()
|
|
334
|
+
|
|
335
|
+
return await manager.save_credentials(credentials)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(
|
|
338
|
+
"Failed to save Claude credentials",
|
|
339
|
+
error=str(e),
|
|
340
|
+
exc_info=e,
|
|
341
|
+
has_custom_path=bool(custom_path),
|
|
342
|
+
)
|
|
343
|
+
return False
|
|
344
|
+
|
|
345
|
+
async def load_credentials(self, custom_path: Any | None = None) -> Any | None:
|
|
346
|
+
"""Load credentials from provider's storage.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
custom_path: Optional custom storage path (Path object)
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Credentials if found, None otherwise
|
|
353
|
+
"""
|
|
354
|
+
try:
|
|
355
|
+
if custom_path:
|
|
356
|
+
# Load from custom path
|
|
357
|
+
storage = GenericJsonStorage(Path(custom_path), ClaudeCredentials)
|
|
358
|
+
manager = await self.create_token_manager(storage=storage)
|
|
359
|
+
else:
|
|
360
|
+
# Load from default storage
|
|
361
|
+
manager = await self.create_token_manager()
|
|
362
|
+
|
|
363
|
+
credentials = await manager.load_credentials()
|
|
364
|
+
|
|
365
|
+
# Use standardized profile logging with rich Claude profile data
|
|
366
|
+
if credentials:
|
|
367
|
+
profile = await manager.get_profile()
|
|
368
|
+
if profile:
|
|
369
|
+
# Cache profile for UI display
|
|
370
|
+
self._cached_profile = profile
|
|
371
|
+
# Create enhanced standardized profile with rich Claude data
|
|
372
|
+
standard_profile = self._create_enhanced_profile(
|
|
373
|
+
credentials, profile
|
|
374
|
+
)
|
|
375
|
+
self._log_profile_dump("claude", standard_profile)
|
|
376
|
+
|
|
377
|
+
return credentials
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.error(
|
|
380
|
+
"Failed to load Claude credentials",
|
|
381
|
+
error=str(e),
|
|
382
|
+
exc_info=e,
|
|
383
|
+
has_custom_path=bool(custom_path),
|
|
384
|
+
)
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
async def create_token_manager(
|
|
388
|
+
self, storage: Any | None = None
|
|
389
|
+
) -> "ClaudeApiTokenManager":
|
|
390
|
+
"""Create token manager with proper dependency injection.
|
|
391
|
+
|
|
392
|
+
Provided to allow core/CLI code to obtain a manager without
|
|
393
|
+
importing plugin classes directly.
|
|
394
|
+
"""
|
|
395
|
+
from .manager import ClaudeApiTokenManager
|
|
396
|
+
|
|
397
|
+
return await ClaudeApiTokenManager.create(
|
|
398
|
+
storage=storage,
|
|
399
|
+
http_client=self.http_client,
|
|
400
|
+
oauth_provider=self, # Inject self as protocol
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def _extract_standard_profile(
|
|
404
|
+
self, credentials: ClaudeCredentials
|
|
405
|
+
) -> StandardProfileFields:
|
|
406
|
+
"""Extract standardized profile fields from Claude credentials for UI display.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
credentials: Claude credentials with profile information
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
StandardProfileFields with clean, UI-friendly data
|
|
413
|
+
"""
|
|
414
|
+
# Use cached enhanced profile data if available
|
|
415
|
+
if self._cached_profile:
|
|
416
|
+
return self._create_enhanced_profile(credentials, self._cached_profile)
|
|
417
|
+
|
|
418
|
+
# Fallback to basic credential info
|
|
419
|
+
from typing import Any
|
|
420
|
+
|
|
421
|
+
profile_data: dict[str, Any] = {
|
|
422
|
+
"account_id": getattr(credentials, "account_id", "unknown"),
|
|
423
|
+
"provider_type": "claude-api",
|
|
424
|
+
"active": getattr(credentials, "active", True),
|
|
425
|
+
"expired": False, # Claude handles expiration internally
|
|
426
|
+
"has_refresh_token": bool(getattr(credentials, "refresh_token", None)),
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
# Store raw credential data for debugging
|
|
430
|
+
raw_data = {}
|
|
431
|
+
if hasattr(credentials, "model_dump"):
|
|
432
|
+
raw_data["credentials"] = credentials.model_dump()
|
|
433
|
+
|
|
434
|
+
profile_data["raw_profile_data"] = raw_data
|
|
435
|
+
|
|
436
|
+
return StandardProfileFields(**profile_data)
|
|
437
|
+
|
|
438
|
+
def _create_enhanced_profile(
|
|
439
|
+
self, credentials: ClaudeCredentials, profile: Any
|
|
440
|
+
) -> StandardProfileFields:
|
|
441
|
+
"""Create enhanced standardized profile with rich Claude profile data.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
credentials: Claude credentials
|
|
445
|
+
profile: Rich profile data from manager
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
StandardProfileFields with full Claude profile information
|
|
449
|
+
"""
|
|
450
|
+
# Create basic profile data without recursion
|
|
451
|
+
basic_profile_data: dict[str, Any] = {
|
|
452
|
+
"account_id": getattr(credentials, "account_id", "unknown"),
|
|
453
|
+
"provider_type": "claude-api",
|
|
454
|
+
"active": getattr(credentials, "active", True),
|
|
455
|
+
"expired": False, # Claude handles expiration internally
|
|
456
|
+
"has_refresh_token": bool(getattr(credentials, "refresh_token", None)),
|
|
457
|
+
"raw_profile_data": {},
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# Extract profile data
|
|
461
|
+
profile_dict = (
|
|
462
|
+
profile.model_dump()
|
|
463
|
+
if hasattr(profile, "model_dump")
|
|
464
|
+
else {"profile": str(profile)}
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Map Claude profile fields to standard fields
|
|
468
|
+
updates = {}
|
|
469
|
+
|
|
470
|
+
if profile_dict.get("account_id"):
|
|
471
|
+
updates["account_id"] = profile_dict["account_id"]
|
|
472
|
+
|
|
473
|
+
if profile_dict.get("email"):
|
|
474
|
+
updates["email"] = profile_dict["email"]
|
|
475
|
+
|
|
476
|
+
if profile_dict.get("display_name"):
|
|
477
|
+
updates["display_name"] = profile_dict["display_name"]
|
|
478
|
+
|
|
479
|
+
# Extract subscription information from extras
|
|
480
|
+
extras = profile_dict.get("extras", {})
|
|
481
|
+
if isinstance(extras, dict):
|
|
482
|
+
account = extras.get("account", {})
|
|
483
|
+
if isinstance(account, dict):
|
|
484
|
+
# Map Claude subscription types
|
|
485
|
+
if account.get("has_claude_max"):
|
|
486
|
+
updates.update(
|
|
487
|
+
{
|
|
488
|
+
"subscription_type": "max",
|
|
489
|
+
"subscription_status": "active",
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
elif account.get("has_claude_pro"):
|
|
493
|
+
updates.update(
|
|
494
|
+
{
|
|
495
|
+
"subscription_type": "pro",
|
|
496
|
+
"subscription_status": "active",
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Features
|
|
501
|
+
updates["features"] = {
|
|
502
|
+
"claude_max": account.get("has_claude_max", False),
|
|
503
|
+
"claude_pro": account.get("has_claude_pro", False),
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
# Organization info
|
|
507
|
+
org = extras.get("organization", {})
|
|
508
|
+
if isinstance(org, dict):
|
|
509
|
+
updates.update(
|
|
510
|
+
{
|
|
511
|
+
"organization_name": org.get("name"),
|
|
512
|
+
"organization_role": "member", # Claude doesn't provide role details
|
|
513
|
+
}
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Store full profile data in raw data (start from basic profile data)
|
|
517
|
+
from typing import cast
|
|
518
|
+
|
|
519
|
+
base_raw = cast(dict[str, Any], basic_profile_data.get("raw_profile_data", {}))
|
|
520
|
+
raw_data = dict(base_raw)
|
|
521
|
+
raw_data["full_profile"] = profile_dict
|
|
522
|
+
updates["raw_profile_data"] = raw_data
|
|
523
|
+
|
|
524
|
+
# Create new profile with updates starting from basic profile data
|
|
525
|
+
profile_data = dict(basic_profile_data)
|
|
526
|
+
profile_data.update(updates)
|
|
527
|
+
|
|
528
|
+
return StandardProfileFields(**profile_data)
|
|
529
|
+
|
|
530
|
+
async def exchange_manual_code(self, code: str) -> Any:
|
|
531
|
+
"""Exchange manual authorization code for tokens.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
code: Authorization code from manual entry
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Claude credentials object
|
|
538
|
+
"""
|
|
539
|
+
# For manual code flow, use OOB redirect URI and no state validation
|
|
540
|
+
credentials: ClaudeCredentials = await self.client.handle_callback(
|
|
541
|
+
code, "manual", ""
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
if self.storage:
|
|
545
|
+
await self.storage.save(credentials)
|
|
546
|
+
|
|
547
|
+
logger.info(
|
|
548
|
+
"claude_oauth_manual_code_exchanged",
|
|
549
|
+
has_credentials=bool(credentials),
|
|
550
|
+
category="auth",
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return credentials
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def cli(self) -> CliAuthConfig:
|
|
557
|
+
"""Get CLI authentication configuration for this provider."""
|
|
558
|
+
return CliAuthConfig(
|
|
559
|
+
preferred_flow=FlowType.browser,
|
|
560
|
+
callback_port=54545,
|
|
561
|
+
callback_path="/callback",
|
|
562
|
+
supports_manual_code=True,
|
|
563
|
+
supports_device_flow=False,
|
|
564
|
+
fixed_redirect_uri=None,
|
|
565
|
+
manual_redirect_uri="https://console.anthropic.com/oauth/code/callback",
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
async def cleanup(self) -> None:
|
|
569
|
+
"""Cleanup resources."""
|
|
570
|
+
if self.client:
|
|
571
|
+
await self.client.close()
|