ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +434 -219
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +144 -168
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +388 -524
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +540 -19
- ccproxy/data/codex_headers_fallback.json +114 -7
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +61 -105
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +268 -276
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +68 -446
- ccproxy/utils/version_checker.py +273 -6
- ccproxy_api-0.2.0a4.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0a4.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1251
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -243
- ccproxy/services/codex_detection_service.py +0 -252
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.7.dist-info/METADATA +0 -615
- ccproxy_api-0.1.7.dist-info/RECORD +0 -191
- ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""GitHub Copilot-specific authentication models."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
ConfigDict,
|
|
9
|
+
Field,
|
|
10
|
+
SecretStr,
|
|
11
|
+
computed_field,
|
|
12
|
+
field_serializer,
|
|
13
|
+
field_validator,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from ccproxy.auth.models.base import BaseProfileInfo, BaseTokenInfo
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CopilotOAuthToken(BaseModel):
|
|
20
|
+
"""OAuth token information for GitHub Copilot."""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(
|
|
23
|
+
populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
access_token: SecretStr = Field(..., alias="access_token")
|
|
27
|
+
token_type: str = Field(default="bearer", alias="token_type")
|
|
28
|
+
expires_in: int | None = Field(default=None, alias="expires_in")
|
|
29
|
+
refresh_token: SecretStr | None = Field(default=None, alias="refresh_token")
|
|
30
|
+
scope: str = Field(default="read:user", alias="scope")
|
|
31
|
+
created_at: int | None = Field(default=None, alias="created_at")
|
|
32
|
+
|
|
33
|
+
@field_serializer("access_token", "refresh_token")
|
|
34
|
+
def serialize_secret(self, value: SecretStr | None) -> str | None:
|
|
35
|
+
"""Serialize SecretStr to plain string for JSON output."""
|
|
36
|
+
return value.get_secret_value() if value else None
|
|
37
|
+
|
|
38
|
+
@field_validator("access_token", "refresh_token", mode="before")
|
|
39
|
+
@classmethod
|
|
40
|
+
def validate_tokens(cls, v: str | SecretStr | None) -> SecretStr | None:
|
|
41
|
+
"""Convert string values to SecretStr."""
|
|
42
|
+
if v is None:
|
|
43
|
+
return None
|
|
44
|
+
if isinstance(v, str):
|
|
45
|
+
return SecretStr(v)
|
|
46
|
+
return v
|
|
47
|
+
|
|
48
|
+
def __repr__(self) -> str:
|
|
49
|
+
"""Safe string representation that masks sensitive tokens."""
|
|
50
|
+
access_token_str = self.access_token.get_secret_value()
|
|
51
|
+
access_preview = (
|
|
52
|
+
f"{access_token_str[:8]}...{access_token_str[-8:]}"
|
|
53
|
+
if len(access_token_str) > 16
|
|
54
|
+
else "***"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
refresh_preview = "***"
|
|
58
|
+
if self.refresh_token:
|
|
59
|
+
refresh_token_str = self.refresh_token.get_secret_value()
|
|
60
|
+
refresh_preview = (
|
|
61
|
+
f"{refresh_token_str[:8]}...{refresh_token_str[-8:]}"
|
|
62
|
+
if len(refresh_token_str) > 16
|
|
63
|
+
else "***"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
expires_at = (
|
|
67
|
+
datetime.fromtimestamp(
|
|
68
|
+
self.created_at + self.expires_in, tz=UTC
|
|
69
|
+
).isoformat()
|
|
70
|
+
if self.expires_in and self.created_at
|
|
71
|
+
else "None"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
f"CopilotOAuthToken(access_token='{access_preview}', "
|
|
76
|
+
f"refresh_token='{refresh_preview}', "
|
|
77
|
+
f"expires_at={expires_at}, "
|
|
78
|
+
f"scope='{self.scope}')"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def is_expired(self) -> bool:
|
|
83
|
+
"""Check if the token is expired."""
|
|
84
|
+
if not self.expires_in or not self.created_at:
|
|
85
|
+
# If no expiration info, assume not expired
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
now = datetime.now(UTC).timestamp()
|
|
89
|
+
expires_at = self.created_at + self.expires_in
|
|
90
|
+
return now >= expires_at
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def expires_at_datetime(self) -> datetime:
|
|
94
|
+
"""Get expiration as datetime object."""
|
|
95
|
+
if not self.expires_in or not self.created_at:
|
|
96
|
+
# Return a far future date if no expiration info
|
|
97
|
+
return datetime.fromtimestamp(2147483647, tz=UTC) # Year 2038
|
|
98
|
+
|
|
99
|
+
return datetime.fromtimestamp(self.created_at + self.expires_in, tz=UTC)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class CopilotEndpoints(BaseModel):
|
|
103
|
+
"""Copilot API endpoints configuration."""
|
|
104
|
+
|
|
105
|
+
api: str | None = Field(default=None, description="API endpoint URL")
|
|
106
|
+
origin_tracker: str | None = Field(
|
|
107
|
+
default=None, alias="origin-tracker", description="Origin tracker endpoint URL"
|
|
108
|
+
)
|
|
109
|
+
proxy: str | None = Field(default=None, description="Proxy endpoint URL")
|
|
110
|
+
telemetry: str | None = Field(default=None, description="Telemetry endpoint URL")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CopilotTokenResponse(BaseModel):
|
|
114
|
+
"""Copilot token exchange response."""
|
|
115
|
+
|
|
116
|
+
# Core required fields (backward compatibility)
|
|
117
|
+
token: SecretStr = Field(..., description="Copilot service token")
|
|
118
|
+
expires_at: datetime | None = Field(
|
|
119
|
+
default=None, description="Token expiration datetime"
|
|
120
|
+
)
|
|
121
|
+
refresh_in: int | None = Field(
|
|
122
|
+
default=None, description="Refresh interval in seconds"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Extended optional fields from full API response
|
|
126
|
+
annotations_enabled: bool | None = Field(
|
|
127
|
+
default=None, description="Whether annotations are enabled"
|
|
128
|
+
)
|
|
129
|
+
blackbird_clientside_indexing: bool | None = Field(
|
|
130
|
+
default=None, description="Whether blackbird clientside indexing is enabled"
|
|
131
|
+
)
|
|
132
|
+
chat_enabled: bool | None = Field(
|
|
133
|
+
default=None, description="Whether chat is enabled"
|
|
134
|
+
)
|
|
135
|
+
chat_jetbrains_enabled: bool | None = Field(
|
|
136
|
+
default=None, description="Whether JetBrains chat is enabled"
|
|
137
|
+
)
|
|
138
|
+
code_quote_enabled: bool | None = Field(
|
|
139
|
+
default=None, description="Whether code quote is enabled"
|
|
140
|
+
)
|
|
141
|
+
code_review_enabled: bool | None = Field(
|
|
142
|
+
default=None, description="Whether code review is enabled"
|
|
143
|
+
)
|
|
144
|
+
codesearch: bool | None = Field(
|
|
145
|
+
default=None, description="Whether code search is enabled"
|
|
146
|
+
)
|
|
147
|
+
copilotignore_enabled: bool | None = Field(
|
|
148
|
+
default=None, description="Whether copilotignore is enabled"
|
|
149
|
+
)
|
|
150
|
+
endpoints: CopilotEndpoints | None = Field(
|
|
151
|
+
default=None, description="API endpoints configuration"
|
|
152
|
+
)
|
|
153
|
+
individual: bool | None = Field(
|
|
154
|
+
default=None, description="Whether this is an individual account"
|
|
155
|
+
)
|
|
156
|
+
limited_user_quotas: dict[str, Any] | None = Field(
|
|
157
|
+
default=None, description="Limited user quotas if any"
|
|
158
|
+
)
|
|
159
|
+
limited_user_reset_date: int | None = Field(
|
|
160
|
+
default=None, description="Limited user reset date if any"
|
|
161
|
+
)
|
|
162
|
+
prompt_8k: bool | None = Field(
|
|
163
|
+
default=None, description="Whether 8k prompts are enabled"
|
|
164
|
+
)
|
|
165
|
+
public_suggestions: str | None = Field(
|
|
166
|
+
default=None, description="Public suggestions setting"
|
|
167
|
+
)
|
|
168
|
+
sku: str | None = Field(default=None, description="SKU identifier")
|
|
169
|
+
snippy_load_test_enabled: bool | None = Field(
|
|
170
|
+
default=None, description="Whether snippy load test is enabled"
|
|
171
|
+
)
|
|
172
|
+
telemetry: str | None = Field(default=None, description="Telemetry setting")
|
|
173
|
+
tracking_id: str | None = Field(default=None, description="Tracking ID")
|
|
174
|
+
vsc_electron_fetcher_v2: bool | None = Field(
|
|
175
|
+
default=None, description="Whether VSCode electron fetcher v2 is enabled"
|
|
176
|
+
)
|
|
177
|
+
xcode: bool | None = Field(
|
|
178
|
+
default=None, description="Whether Xcode integration is enabled"
|
|
179
|
+
)
|
|
180
|
+
xcode_chat: bool | None = Field(
|
|
181
|
+
default=None, description="Whether Xcode chat is enabled"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@field_serializer("token")
|
|
185
|
+
def serialize_secret(self, value: SecretStr) -> str:
|
|
186
|
+
"""Serialize SecretStr to plain string for JSON output."""
|
|
187
|
+
return value.get_secret_value()
|
|
188
|
+
|
|
189
|
+
@field_serializer("expires_at")
|
|
190
|
+
def serialize_datetime(self, value: datetime | None) -> int | None:
|
|
191
|
+
"""Serialize datetime back to Unix timestamp."""
|
|
192
|
+
if value is None:
|
|
193
|
+
return None
|
|
194
|
+
return int(value.timestamp())
|
|
195
|
+
|
|
196
|
+
@field_validator("token", mode="before")
|
|
197
|
+
@classmethod
|
|
198
|
+
def validate_token(cls, v: str | SecretStr) -> SecretStr:
|
|
199
|
+
"""Convert string values to SecretStr."""
|
|
200
|
+
if isinstance(v, str):
|
|
201
|
+
return SecretStr(v)
|
|
202
|
+
return v
|
|
203
|
+
|
|
204
|
+
@field_validator("expires_at", mode="before")
|
|
205
|
+
@classmethod
|
|
206
|
+
def validate_expires_at(cls, v: int | str | datetime | None) -> datetime | None:
|
|
207
|
+
"""Convert integer Unix timestamp or ISO string to datetime object."""
|
|
208
|
+
if v is None:
|
|
209
|
+
return None
|
|
210
|
+
if isinstance(v, datetime):
|
|
211
|
+
return v
|
|
212
|
+
if isinstance(v, int):
|
|
213
|
+
# Convert Unix timestamp to datetime
|
|
214
|
+
return datetime.fromtimestamp(v, tz=UTC)
|
|
215
|
+
if isinstance(v, str):
|
|
216
|
+
# Try to parse as ISO string, fallback to Unix timestamp
|
|
217
|
+
try:
|
|
218
|
+
return datetime.fromisoformat(v.replace("Z", "+00:00"))
|
|
219
|
+
except ValueError:
|
|
220
|
+
try:
|
|
221
|
+
return datetime.fromtimestamp(int(v), tz=UTC)
|
|
222
|
+
except ValueError:
|
|
223
|
+
return None
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def is_expired(self) -> bool:
|
|
228
|
+
"""Check if the Copilot token is expired."""
|
|
229
|
+
if not self.expires_at:
|
|
230
|
+
# If no expiration info, assume not expired
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
now = datetime.now(UTC)
|
|
234
|
+
return now >= self.expires_at
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class CopilotCredentials(BaseModel):
|
|
238
|
+
"""Copilot credentials containing OAuth and Copilot tokens."""
|
|
239
|
+
|
|
240
|
+
model_config = ConfigDict(
|
|
241
|
+
populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
oauth_token: CopilotOAuthToken = Field(..., description="GitHub OAuth token")
|
|
245
|
+
copilot_token: CopilotTokenResponse | None = Field(
|
|
246
|
+
default=None, description="Copilot service token"
|
|
247
|
+
)
|
|
248
|
+
account_type: str = Field(
|
|
249
|
+
default="individual",
|
|
250
|
+
description="Account type (individual/business/enterprise)",
|
|
251
|
+
)
|
|
252
|
+
created_at: int = Field(
|
|
253
|
+
default_factory=lambda: int(datetime.now(UTC).timestamp()),
|
|
254
|
+
description="Timestamp when credentials were created",
|
|
255
|
+
)
|
|
256
|
+
updated_at: int = Field(
|
|
257
|
+
default_factory=lambda: int(datetime.now(UTC).timestamp()),
|
|
258
|
+
description="Timestamp when credentials were last updated",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def __repr__(self) -> str:
|
|
262
|
+
"""Safe representation without exposing secrets."""
|
|
263
|
+
copilot_status = "present" if self.copilot_token else "missing"
|
|
264
|
+
return (
|
|
265
|
+
f"CopilotCredentials(oauth_token={repr(self.oauth_token)}, "
|
|
266
|
+
f"copilot_token={copilot_status}, "
|
|
267
|
+
f"account_type='{self.account_type}')"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def is_expired(self) -> bool:
|
|
271
|
+
"""Check if credentials are expired (BaseCredentials protocol)."""
|
|
272
|
+
return self.oauth_token.is_expired
|
|
273
|
+
|
|
274
|
+
def to_dict(self) -> dict[str, Any]:
|
|
275
|
+
"""Convert to dictionary for storage (BaseCredentials protocol)."""
|
|
276
|
+
return self.model_dump(mode="json")
|
|
277
|
+
|
|
278
|
+
@classmethod
|
|
279
|
+
def from_dict(cls, data: dict[str, Any]) -> "CopilotCredentials":
|
|
280
|
+
"""Create from dictionary (BaseCredentials protocol)."""
|
|
281
|
+
return cls.model_validate(data)
|
|
282
|
+
|
|
283
|
+
def refresh_updated_at(self) -> None:
|
|
284
|
+
"""Update the updated_at timestamp."""
|
|
285
|
+
self.updated_at = int(datetime.now(UTC).timestamp())
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class CopilotProfileInfo(BaseProfileInfo):
|
|
289
|
+
"""GitHub profile information for Copilot users."""
|
|
290
|
+
|
|
291
|
+
# Required fields from BaseProfileInfo
|
|
292
|
+
account_id: str = Field(..., description="GitHub user ID")
|
|
293
|
+
provider_type: str = Field(default="copilot", description="Provider type")
|
|
294
|
+
|
|
295
|
+
# GitHub-specific fields
|
|
296
|
+
login: str = Field(..., description="GitHub username")
|
|
297
|
+
name: str | None = Field(default=None, description="Full name")
|
|
298
|
+
avatar_url: str | None = Field(default=None, description="Avatar URL")
|
|
299
|
+
html_url: str | None = Field(default=None, description="Profile URL")
|
|
300
|
+
copilot_plan: str | None = Field(
|
|
301
|
+
default=None, description="Copilot subscription plan"
|
|
302
|
+
)
|
|
303
|
+
copilot_access: bool = Field(default=False, description="Has Copilot access")
|
|
304
|
+
|
|
305
|
+
@computed_field
|
|
306
|
+
def computed_display_name(self) -> str:
|
|
307
|
+
"""Display name for UI."""
|
|
308
|
+
if self.display_name:
|
|
309
|
+
return self.display_name
|
|
310
|
+
return self.name or self.login
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class CopilotTokenInfo(BaseTokenInfo):
|
|
314
|
+
"""Token information for Copilot credentials."""
|
|
315
|
+
|
|
316
|
+
provider: Literal["copilot"] = "copilot"
|
|
317
|
+
oauth_expires_at: datetime | None = None
|
|
318
|
+
copilot_expires_at: datetime | None = None
|
|
319
|
+
account_type: str = "individual"
|
|
320
|
+
copilot_access: bool = False
|
|
321
|
+
|
|
322
|
+
@computed_field
|
|
323
|
+
def computed_is_expired(self) -> bool:
|
|
324
|
+
"""Check if any token is expired."""
|
|
325
|
+
now = datetime.now(UTC)
|
|
326
|
+
|
|
327
|
+
# Check OAuth token expiration
|
|
328
|
+
if self.oauth_expires_at and now >= self.oauth_expires_at:
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
# Check Copilot token expiration if available
|
|
332
|
+
return bool(self.copilot_expires_at and now >= self.copilot_expires_at)
|
|
333
|
+
|
|
334
|
+
@computed_field
|
|
335
|
+
def computed_display_name(self) -> str:
|
|
336
|
+
"""Display name for UI."""
|
|
337
|
+
return f"GitHub Copilot ({self.account_type})"
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class DeviceCodeResponse(BaseModel):
|
|
341
|
+
"""GitHub device code authorization response."""
|
|
342
|
+
|
|
343
|
+
device_code: str = Field(..., description="Device verification code")
|
|
344
|
+
user_code: str = Field(..., description="User verification code")
|
|
345
|
+
verification_uri: str = Field(..., description="Verification URL")
|
|
346
|
+
expires_in: int = Field(..., description="Code expiration time in seconds")
|
|
347
|
+
interval: int = Field(..., description="Polling interval in seconds")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class DeviceTokenPollResponse(BaseModel):
|
|
351
|
+
"""Response from device code token polling."""
|
|
352
|
+
|
|
353
|
+
access_token: str | None = Field(
|
|
354
|
+
default=None, description="Access token if authorized"
|
|
355
|
+
)
|
|
356
|
+
token_type: str | None = Field(default=None, description="Token type")
|
|
357
|
+
scope: str | None = Field(default=None, description="Granted scopes")
|
|
358
|
+
error: str | None = Field(default=None, description="Error code if any")
|
|
359
|
+
error_description: str | None = Field(default=None, description="Error description")
|
|
360
|
+
error_uri: str | None = Field(default=None, description="Error URI")
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def is_pending(self) -> bool:
|
|
364
|
+
"""Check if authorization is still pending."""
|
|
365
|
+
return self.error == "authorization_pending"
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def is_slow_down(self) -> bool:
|
|
369
|
+
"""Check if we should slow down polling."""
|
|
370
|
+
return self.error == "slow_down"
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def is_expired(self) -> bool:
|
|
374
|
+
"""Check if device code has expired."""
|
|
375
|
+
return self.error == "expired_token"
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def is_denied(self) -> bool:
|
|
379
|
+
"""Check if user denied authorization."""
|
|
380
|
+
return self.error == "access_denied"
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def is_success(self) -> bool:
|
|
384
|
+
"""Check if authorization was successful."""
|
|
385
|
+
return self.access_token is not None and self.error is None
|