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,146 @@
|
|
|
1
|
+
"""Plugin entry point for the credential balancer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
8
|
+
from ccproxy.core.plugins import (
|
|
9
|
+
PluginContext,
|
|
10
|
+
PluginManifest,
|
|
11
|
+
SystemPluginFactory,
|
|
12
|
+
SystemPluginRuntime,
|
|
13
|
+
)
|
|
14
|
+
from ccproxy.services.auth_registry import AuthManagerRegistry
|
|
15
|
+
|
|
16
|
+
from .config import CredentialBalancerSettings
|
|
17
|
+
from .hook import CredentialBalancerHook
|
|
18
|
+
from .manager import CredentialBalancerTokenManager
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = get_plugin_logger()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CredentialBalancerRuntime(SystemPluginRuntime):
|
|
25
|
+
"""Runtime responsible for registering auth managers and hooks."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, manifest: PluginManifest):
|
|
28
|
+
super().__init__(manifest)
|
|
29
|
+
self._registrations: list[tuple[str, CredentialBalancerTokenManager]] = []
|
|
30
|
+
self._hook: CredentialBalancerHook | None = None
|
|
31
|
+
self._registry: AuthManagerRegistry | None = None
|
|
32
|
+
|
|
33
|
+
async def _on_initialize(self) -> None:
|
|
34
|
+
await super()._on_initialize()
|
|
35
|
+
if not self.context:
|
|
36
|
+
raise RuntimeError("Context not set")
|
|
37
|
+
|
|
38
|
+
config = self.context.get("config")
|
|
39
|
+
if not isinstance(config, CredentialBalancerSettings):
|
|
40
|
+
logger.debug("credential_balancer_using_default_config")
|
|
41
|
+
config = CredentialBalancerSettings()
|
|
42
|
+
|
|
43
|
+
if not config.enabled:
|
|
44
|
+
logger.info("credential_balancer_disabled")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if not config.providers:
|
|
48
|
+
logger.warning("credential_balancer_no_providers_configured")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
service_container = self.context.get("service_container")
|
|
52
|
+
if not service_container:
|
|
53
|
+
raise RuntimeError("Service container unavailable for credential balancer")
|
|
54
|
+
|
|
55
|
+
registry = service_container.get_auth_manager_registry()
|
|
56
|
+
self._registry = registry
|
|
57
|
+
|
|
58
|
+
base_logger = self.context.get("logger") or get_plugin_logger(__name__)
|
|
59
|
+
managers: list[CredentialBalancerTokenManager] = []
|
|
60
|
+
|
|
61
|
+
for pool in config.providers:
|
|
62
|
+
manager_name = pool.manager_name
|
|
63
|
+
if manager_name is None:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Credential balancer pool '{pool.provider}' missing manager name"
|
|
66
|
+
)
|
|
67
|
+
manager_logger = base_logger.bind(pool=manager_name)
|
|
68
|
+
# Use async factory to create manager with composed AuthManagers
|
|
69
|
+
manager = await CredentialBalancerTokenManager.create(
|
|
70
|
+
pool, logger=manager_logger
|
|
71
|
+
)
|
|
72
|
+
registry.register_instance(manager_name, manager)
|
|
73
|
+
managers.append(manager)
|
|
74
|
+
self._registrations.append((manager_name, manager))
|
|
75
|
+
logger.info(
|
|
76
|
+
"credential_balancer_manager_registered",
|
|
77
|
+
manager=manager_name,
|
|
78
|
+
provider=pool.provider,
|
|
79
|
+
strategy=pool.strategy.value,
|
|
80
|
+
credentials=len(pool.credentials),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if managers:
|
|
84
|
+
hook_registry = self.context.get("hook_registry")
|
|
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:
|
|
91
|
+
hook = CredentialBalancerHook(managers)
|
|
92
|
+
hook_registry.register(hook)
|
|
93
|
+
self._hook = hook
|
|
94
|
+
logger.debug("credential_balancer_hook_registered")
|
|
95
|
+
else:
|
|
96
|
+
logger.warning("credential_balancer_hook_registry_missing")
|
|
97
|
+
|
|
98
|
+
async def _on_shutdown(self) -> None:
|
|
99
|
+
await super()._on_shutdown()
|
|
100
|
+
if self.context and self._hook:
|
|
101
|
+
hook_registry = self.context.get("hook_registry")
|
|
102
|
+
if not hook_registry:
|
|
103
|
+
app = self.context.get("app")
|
|
104
|
+
if app and hasattr(app.state, "hook_registry"):
|
|
105
|
+
hook_registry = app.state.hook_registry
|
|
106
|
+
if hook_registry:
|
|
107
|
+
hook_registry.unregister(self._hook)
|
|
108
|
+
logger.debug("credential_balancer_hook_unregistered")
|
|
109
|
+
self._hook = None
|
|
110
|
+
|
|
111
|
+
if self._registry:
|
|
112
|
+
for name, _ in self._registrations:
|
|
113
|
+
try:
|
|
114
|
+
self._registry.unregister(name)
|
|
115
|
+
except Exception:
|
|
116
|
+
logger.debug(
|
|
117
|
+
"credential_balancer_registry_unregistration_failed",
|
|
118
|
+
manager=name,
|
|
119
|
+
)
|
|
120
|
+
self._registrations.clear()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class CredentialBalancerFactory(SystemPluginFactory):
|
|
124
|
+
"""Factory for the credential balancer plugin."""
|
|
125
|
+
|
|
126
|
+
def __init__(self) -> None:
|
|
127
|
+
manifest = PluginManifest(
|
|
128
|
+
name="credential_balancer",
|
|
129
|
+
version="0.1.0",
|
|
130
|
+
description="Rotate across multiple credential files for upstream providers",
|
|
131
|
+
is_provider=False,
|
|
132
|
+
config_class=CredentialBalancerSettings,
|
|
133
|
+
)
|
|
134
|
+
super().__init__(manifest)
|
|
135
|
+
|
|
136
|
+
def create_runtime(self) -> CredentialBalancerRuntime:
|
|
137
|
+
return CredentialBalancerRuntime(self.manifest)
|
|
138
|
+
|
|
139
|
+
def create_context(self, core_services: Any) -> PluginContext:
|
|
140
|
+
context = super().create_context(core_services)
|
|
141
|
+
return context
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
factory = CredentialBalancerFactory()
|
|
145
|
+
|
|
146
|
+
__all__ = ["CredentialBalancerFactory", "CredentialBalancerRuntime", "factory"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Dashboard Plugin
|
|
2
|
+
|
|
3
|
+
Serves the CCProxy dashboard SPA and supporting APIs.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
- Mounts static assets for the dashboard when available on disk
|
|
7
|
+
- Registers dashboard routes for health, session, and telemetry views
|
|
8
|
+
- Integrates with FastAPI app mounting during plugin initialization
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
- `DashboardPluginConfig` toggles static asset mounting and route exposure
|
|
12
|
+
- Defaults to auto-mounting assets under `/dashboard/assets` when present
|
|
13
|
+
- Generate defaults with `python3 scripts/generate_config_from_model.py \
|
|
14
|
+
--format toml --plugin dashboard --config-class DashboardPluginConfig`
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[plugins.dashboard]
|
|
18
|
+
# enabled = true
|
|
19
|
+
# mount_static = true
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Related Components
|
|
23
|
+
- `plugin.py`: runtime for mounting static files
|
|
24
|
+
- `routes.py`: FastAPI router for dashboard APIs
|
|
25
|
+
- `config.py`: settings model for plugin toggles
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Dashboard plugin (serves SPA and favicon; mounts assets)."""
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DashboardPluginConfig(BaseModel):
|
|
5
|
+
enabled: bool = Field(default=True, description="Enable dashboard routes")
|
|
6
|
+
mount_static: bool = Field(
|
|
7
|
+
default=True, description="Mount /dashboard/assets static files if present"
|
|
8
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from fastapi.staticfiles import StaticFiles
|
|
6
|
+
|
|
7
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
8
|
+
from ccproxy.core.plugins import (
|
|
9
|
+
PluginManifest,
|
|
10
|
+
RouteSpec,
|
|
11
|
+
SystemPluginFactory,
|
|
12
|
+
SystemPluginRuntime,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .config import DashboardPluginConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = get_plugin_logger()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DashboardRuntime(SystemPluginRuntime):
|
|
22
|
+
async def _on_initialize(self) -> None:
|
|
23
|
+
if not self.context:
|
|
24
|
+
raise RuntimeError("Context not set")
|
|
25
|
+
from typing import cast
|
|
26
|
+
|
|
27
|
+
cfg = cast(DashboardPluginConfig | None, self.context.get("config"))
|
|
28
|
+
app = self.context.get("app")
|
|
29
|
+
if not app or not hasattr(app, "mount"):
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
# Optionally mount static assets for the SPA
|
|
33
|
+
cfg = cfg or DashboardPluginConfig()
|
|
34
|
+
if cfg.mount_static:
|
|
35
|
+
current_file = Path(__file__)
|
|
36
|
+
project_root = current_file.parent.parent.parent
|
|
37
|
+
dashboard_static_path = project_root / "ccproxy" / "static" / "dashboard"
|
|
38
|
+
if dashboard_static_path.exists():
|
|
39
|
+
try:
|
|
40
|
+
app.mount(
|
|
41
|
+
"/dashboard/assets",
|
|
42
|
+
StaticFiles(directory=str(dashboard_static_path)),
|
|
43
|
+
name="dashboard-static",
|
|
44
|
+
)
|
|
45
|
+
logger.debug(
|
|
46
|
+
"dashboard_static_files_mounted",
|
|
47
|
+
path=str(dashboard_static_path),
|
|
48
|
+
)
|
|
49
|
+
except Exception as e: # pragma: no cover
|
|
50
|
+
logger.warning("dashboard_static_mount_failed", error=str(e))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DashboardFactory(SystemPluginFactory):
|
|
54
|
+
def __init__(self) -> None:
|
|
55
|
+
from .routes import router as dashboard_router
|
|
56
|
+
|
|
57
|
+
manifest = PluginManifest(
|
|
58
|
+
name="dashboard",
|
|
59
|
+
version="0.1.0",
|
|
60
|
+
description="Dashboard SPA routes and static asset mounting",
|
|
61
|
+
is_provider=False,
|
|
62
|
+
config_class=DashboardPluginConfig,
|
|
63
|
+
routes=[RouteSpec(router=dashboard_router, prefix="", tags=["dashboard"])],
|
|
64
|
+
)
|
|
65
|
+
super().__init__(manifest)
|
|
66
|
+
|
|
67
|
+
def create_runtime(self) -> DashboardRuntime:
|
|
68
|
+
return DashboardRuntime(self.manifest)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
factory = DashboardFactory()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, HTTPException
|
|
6
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get("/dashboard")
|
|
13
|
+
async def get_metrics_dashboard() -> HTMLResponse:
|
|
14
|
+
current_file = Path(__file__)
|
|
15
|
+
project_root = current_file.parent.parent.parent
|
|
16
|
+
dashboard_folder = project_root / "ccproxy" / "static" / "dashboard"
|
|
17
|
+
dashboard_index = dashboard_folder / "index.html"
|
|
18
|
+
|
|
19
|
+
if not dashboard_folder.exists():
|
|
20
|
+
raise HTTPException(
|
|
21
|
+
status_code=404,
|
|
22
|
+
detail="Dashboard not found. Build it with 'cd dashboard && bun run build:prod'",
|
|
23
|
+
)
|
|
24
|
+
if not dashboard_index.exists():
|
|
25
|
+
raise HTTPException(
|
|
26
|
+
status_code=404,
|
|
27
|
+
detail="Dashboard index.html not found. Rebuild with 'cd dashboard && bun run build:prod'",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
html_content = dashboard_index.read_text(encoding="utf-8")
|
|
32
|
+
return HTMLResponse(
|
|
33
|
+
content=html_content,
|
|
34
|
+
status_code=200,
|
|
35
|
+
headers={
|
|
36
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
37
|
+
"Pragma": "no-cache",
|
|
38
|
+
"Expires": "0",
|
|
39
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
except (OSError, PermissionError) as e:
|
|
43
|
+
raise HTTPException(
|
|
44
|
+
status_code=500, detail=f"Dashboard file access error: {str(e)}"
|
|
45
|
+
) from e
|
|
46
|
+
except UnicodeDecodeError as e:
|
|
47
|
+
raise HTTPException(
|
|
48
|
+
status_code=500, detail=f"Dashboard file encoding error: {str(e)}"
|
|
49
|
+
) from e
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise HTTPException(
|
|
52
|
+
status_code=500, detail=f"Failed to serve dashboard: {str(e)}"
|
|
53
|
+
) from e
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.get("/dashboard/favicon.svg")
|
|
57
|
+
async def get_dashboard_favicon() -> FileResponse:
|
|
58
|
+
current_file = Path(__file__)
|
|
59
|
+
project_root = current_file.parent.parent.parent
|
|
60
|
+
favicon_path = project_root / "ccproxy" / "static" / "dashboard" / "favicon.svg"
|
|
61
|
+
if not favicon_path.exists():
|
|
62
|
+
raise HTTPException(status_code=404, detail="Favicon not found")
|
|
63
|
+
return FileResponse(
|
|
64
|
+
path=str(favicon_path),
|
|
65
|
+
media_type="image/svg+xml",
|
|
66
|
+
headers={"Cache-Control": "public, max-age=3600"},
|
|
67
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Docker Plugin
|
|
2
|
+
|
|
3
|
+
Provides Docker-backed execution for CCProxy via CLI extensions.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
- Wraps requests with `DockerAdapter` to run providers inside containers
|
|
7
|
+
- Extends the `ccproxy serve` CLI with Docker-specific arguments
|
|
8
|
+
- Applies CLI overrides to runtime configuration before adapter startup
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
- `DockerConfig` controls image, workspace, env vars, and volume mounts
|
|
12
|
+
- CLI flags override the configuration and are declared via `cli_arguments`
|
|
13
|
+
- Generate defaults with `python3 scripts/generate_config_from_model.py \
|
|
14
|
+
--format toml --plugin docker --config-class DockerConfig`
|
|
15
|
+
|
|
16
|
+
```toml
|
|
17
|
+
[plugins.docker]
|
|
18
|
+
# enabled = true
|
|
19
|
+
# docker_image = "anthropics/claude-cli:latest"
|
|
20
|
+
# docker_home_directory = "/home/user"
|
|
21
|
+
# docker_workspace_directory = "/workspace"
|
|
22
|
+
# docker_volumes = []
|
|
23
|
+
# docker_environment = []
|
|
24
|
+
# user_mapping_enabled = true
|
|
25
|
+
# user_uid = 1000
|
|
26
|
+
# user_gid = 1000
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Related Components
|
|
30
|
+
- `adapter.py`: executor that launches Docker containers
|
|
31
|
+
- `plugin.py`: runtime handling CLI context and overrides
|
|
32
|
+
- `config.py`: settings model for Docker execution
|
|
@@ -10,6 +10,7 @@ This module provides a comprehensive Docker integration system with support for:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from .adapter import DockerAdapter, create_docker_adapter
|
|
13
|
+
from .config import DockerConfig
|
|
13
14
|
from .docker_path import DockerPath, DockerPathSet
|
|
14
15
|
from .middleware import (
|
|
15
16
|
LoggerOutputMiddleware,
|
|
@@ -44,6 +45,8 @@ __all__ = [
|
|
|
44
45
|
"DockerPathSet",
|
|
45
46
|
# User context
|
|
46
47
|
"DockerUserContext",
|
|
48
|
+
# Configuration
|
|
49
|
+
"DockerConfig",
|
|
47
50
|
# Type aliases
|
|
48
51
|
"DockerEnv",
|
|
49
52
|
"DockerPortSpec",
|
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
5
|
import shlex
|
|
6
|
+
import subprocess
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import cast
|
|
8
|
+
from typing import Any, cast
|
|
8
9
|
|
|
9
|
-
from
|
|
10
|
+
from fastapi import Request
|
|
11
|
+
from starlette.responses import Response, StreamingResponse
|
|
10
12
|
|
|
13
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
14
|
+
from ccproxy.services.adapters.base import BaseAdapter
|
|
15
|
+
from ccproxy.streaming import DeferredStreaming
|
|
16
|
+
|
|
17
|
+
from .config import DockerConfig
|
|
11
18
|
from .middleware import LoggerOutputMiddleware
|
|
12
19
|
from .models import DockerUserContext
|
|
13
20
|
from .protocol import (
|
|
@@ -25,11 +32,19 @@ from .stream_process import (
|
|
|
25
32
|
from .validators import create_docker_error, validate_port_spec
|
|
26
33
|
|
|
27
34
|
|
|
28
|
-
logger =
|
|
35
|
+
logger = get_plugin_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DockerAdapter(BaseAdapter, DockerAdapterProtocol):
|
|
39
|
+
"""Docker adapter implementing both BaseAdapter and DockerAdapterProtocol."""
|
|
29
40
|
|
|
41
|
+
def __init__(self, config: DockerConfig | None = None):
|
|
42
|
+
"""Initialize Docker adapter.
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
Args:
|
|
45
|
+
config: Docker configuration
|
|
46
|
+
"""
|
|
47
|
+
self.config = config or DockerConfig()
|
|
33
48
|
|
|
34
49
|
async def _needs_sudo(self) -> bool:
|
|
35
50
|
"""Check if Docker requires sudo by testing docker info command."""
|
|
@@ -286,8 +301,6 @@ class DockerAdapter:
|
|
|
286
301
|
# Note: We can't use await here since this method replaces the process
|
|
287
302
|
# Use a simple check instead
|
|
288
303
|
try:
|
|
289
|
-
import subprocess
|
|
290
|
-
|
|
291
304
|
subprocess.run(
|
|
292
305
|
["docker", "info"], check=True, capture_output=True, text=True
|
|
293
306
|
)
|
|
@@ -441,7 +454,6 @@ class DockerAdapter:
|
|
|
441
454
|
|
|
442
455
|
# Build the Docker command to check image existence
|
|
443
456
|
docker_cmd = ["docker", "inspect", image_full_name]
|
|
444
|
-
cmd_str = " ".join(shlex.quote(arg) for arg in docker_cmd)
|
|
445
457
|
|
|
446
458
|
try:
|
|
447
459
|
# Run Docker inspect command
|
|
@@ -559,6 +571,92 @@ class DockerAdapter:
|
|
|
559
571
|
)
|
|
560
572
|
raise error from e
|
|
561
573
|
|
|
574
|
+
# Legacy methods for backward compatibility with plugin system
|
|
575
|
+
|
|
576
|
+
def build_docker_run_args(
|
|
577
|
+
self,
|
|
578
|
+
settings: Any,
|
|
579
|
+
command: list[str] | None = None,
|
|
580
|
+
docker_image: str | None = None,
|
|
581
|
+
docker_env: list[str] | None = None,
|
|
582
|
+
docker_volume: list[str] | None = None,
|
|
583
|
+
docker_arg: list[str] | None = None,
|
|
584
|
+
docker_home: str | None = None,
|
|
585
|
+
docker_workspace: str | None = None,
|
|
586
|
+
user_mapping_enabled: bool | None = None,
|
|
587
|
+
user_uid: int | None = None,
|
|
588
|
+
user_gid: int | None = None,
|
|
589
|
+
) -> tuple[str, list[str], list[str], list[str], dict[str, Any], dict[str, Any]]:
|
|
590
|
+
"""Build Docker run arguments.
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
Tuple of (image, volumes, environment, command, user_context, metadata)
|
|
594
|
+
"""
|
|
595
|
+
# Use CLI overrides or config defaults
|
|
596
|
+
image = docker_image or self.config.docker_image
|
|
597
|
+
home_dir = docker_home or str(self.config.get_effective_home_directory())
|
|
598
|
+
workspace_dir = docker_workspace or str(
|
|
599
|
+
self.config.get_effective_workspace_directory()
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Build volumes
|
|
603
|
+
volumes = [
|
|
604
|
+
f"{home_dir}:/data/home",
|
|
605
|
+
f"{workspace_dir}:/data/workspace",
|
|
606
|
+
]
|
|
607
|
+
volumes.extend(self.config.get_all_volumes(docker_volume))
|
|
608
|
+
|
|
609
|
+
# Build environment variables
|
|
610
|
+
env_vars = [
|
|
611
|
+
"CLAUDE_HOME=/data/home",
|
|
612
|
+
"CLAUDE_WORKSPACE=/data/workspace",
|
|
613
|
+
]
|
|
614
|
+
env_vars.extend(self.config.get_all_environment_vars(docker_env))
|
|
615
|
+
|
|
616
|
+
# User mapping
|
|
617
|
+
user_context = {}
|
|
618
|
+
if user_mapping_enabled is None:
|
|
619
|
+
user_mapping_enabled = self.config.user_mapping_enabled
|
|
620
|
+
|
|
621
|
+
if user_mapping_enabled:
|
|
622
|
+
uid = user_uid or self.config.user_uid or os.getuid()
|
|
623
|
+
gid = user_gid or self.config.user_gid or os.getgid()
|
|
624
|
+
user_context = {"uid": uid, "gid": gid}
|
|
625
|
+
|
|
626
|
+
metadata = {
|
|
627
|
+
"config": self.config,
|
|
628
|
+
"cli_overrides": {
|
|
629
|
+
"docker_image": docker_image,
|
|
630
|
+
"docker_env": docker_env,
|
|
631
|
+
"docker_volume": docker_volume,
|
|
632
|
+
"docker_arg": docker_arg,
|
|
633
|
+
"docker_home": docker_home,
|
|
634
|
+
"docker_workspace": docker_workspace,
|
|
635
|
+
"user_mapping_enabled": user_mapping_enabled,
|
|
636
|
+
"user_uid": user_uid,
|
|
637
|
+
"user_gid": user_gid,
|
|
638
|
+
},
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return image, volumes, env_vars, command or [], user_context, metadata
|
|
642
|
+
|
|
643
|
+
async def handle_request(
|
|
644
|
+
self, request: Request
|
|
645
|
+
) -> Response | StreamingResponse | DeferredStreaming:
|
|
646
|
+
"""Handle request (not used for Docker adapter)."""
|
|
647
|
+
raise NotImplementedError("Docker adapter does not handle HTTP requests")
|
|
648
|
+
|
|
649
|
+
async def handle_streaming(
|
|
650
|
+
self, request: Request, endpoint: str, **kwargs: Any
|
|
651
|
+
) -> StreamingResponse | DeferredStreaming:
|
|
652
|
+
"""Handle streaming request (not used for Docker adapter)."""
|
|
653
|
+
raise NotImplementedError("Docker adapter does not handle streaming requests")
|
|
654
|
+
|
|
655
|
+
async def cleanup(self) -> None:
|
|
656
|
+
"""Cleanup Docker adapter resources."""
|
|
657
|
+
# No persistent resources to cleanup for Docker adapter
|
|
658
|
+
pass
|
|
659
|
+
|
|
562
660
|
|
|
563
661
|
def create_docker_adapter(
|
|
564
662
|
image: str | None = None,
|
|
@@ -582,7 +680,7 @@ def create_docker_adapter(
|
|
|
582
680
|
|
|
583
681
|
Example:
|
|
584
682
|
>>> adapter = create_docker_adapter()
|
|
585
|
-
>>> if adapter.is_available():
|
|
586
|
-
... adapter.run_container("ubuntu:latest", [], {})
|
|
683
|
+
>>> if await adapter.is_available():
|
|
684
|
+
... await adapter.run_container("ubuntu:latest", [], {})
|
|
587
685
|
"""
|
|
588
686
|
return DockerAdapter()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Docker plugin configuration."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DockerConfig(BaseModel):
|
|
9
|
+
"""Configuration for Docker plugin."""
|
|
10
|
+
|
|
11
|
+
enabled: bool = Field(
|
|
12
|
+
default=True,
|
|
13
|
+
description="Enable Docker functionality",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
docker_image: str = Field(
|
|
17
|
+
default="anthropics/claude-cli:latest",
|
|
18
|
+
description="Docker image to use for running commands",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
docker_home_directory: str | None = Field(
|
|
22
|
+
default=None,
|
|
23
|
+
description="Home directory to mount in Docker container",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
docker_workspace_directory: str | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Workspace directory to mount in Docker container",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
docker_volumes: list[str] = Field(
|
|
32
|
+
default_factory=list,
|
|
33
|
+
description="Additional volume mounts for Docker container",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
docker_environment: list[str] = Field(
|
|
37
|
+
default_factory=list,
|
|
38
|
+
description="Environment variables to pass to Docker container",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
user_mapping_enabled: bool = Field(
|
|
42
|
+
default=True,
|
|
43
|
+
description="Enable user mapping for Docker containers",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
user_uid: int | None = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
description="User UID for Docker user mapping",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
user_gid: int | None = Field(
|
|
52
|
+
default=None,
|
|
53
|
+
description="User GID for Docker user mapping",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def get_effective_home_directory(self) -> Path:
|
|
57
|
+
"""Get the effective home directory for Docker mounting."""
|
|
58
|
+
if self.docker_home_directory:
|
|
59
|
+
return Path(self.docker_home_directory)
|
|
60
|
+
return Path.home()
|
|
61
|
+
|
|
62
|
+
def get_effective_workspace_directory(self) -> Path:
|
|
63
|
+
"""Get the effective workspace directory for Docker mounting."""
|
|
64
|
+
if self.docker_workspace_directory:
|
|
65
|
+
return Path(self.docker_workspace_directory)
|
|
66
|
+
return Path.cwd()
|
|
67
|
+
|
|
68
|
+
def get_all_volumes(self, additional_volumes: list[str] | None = None) -> list[str]:
|
|
69
|
+
"""Get all volume mounts including defaults and additional."""
|
|
70
|
+
volumes = self.docker_volumes.copy()
|
|
71
|
+
if additional_volumes:
|
|
72
|
+
volumes.extend(additional_volumes)
|
|
73
|
+
return volumes
|
|
74
|
+
|
|
75
|
+
def get_all_environment_vars(
|
|
76
|
+
self, additional_env: list[str] | None = None
|
|
77
|
+
) -> list[str]:
|
|
78
|
+
"""Get all environment variables including defaults and additional."""
|
|
79
|
+
env_vars = self.docker_environment.copy()
|
|
80
|
+
if additional_env:
|
|
81
|
+
env_vars.extend(additional_env)
|
|
82
|
+
return env_vars
|
|
@@ -4,10 +4,11 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Self
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, field_validator
|
|
7
|
-
from structlog import get_logger
|
|
8
7
|
|
|
8
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
logger = get_plugin_logger(__name__)
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class DockerPath(BaseModel):
|
|
@@ -116,7 +117,7 @@ class DockerPathSet:
|
|
|
116
117
|
"""
|
|
117
118
|
self.base_host_path = Path(base_host_path).resolve() if base_host_path else None
|
|
118
119
|
self.paths: dict[str, DockerPath] = {}
|
|
119
|
-
self.logger =
|
|
120
|
+
self.logger = get_plugin_logger(f"{__name__}.{self.__class__.__name__}")
|
|
120
121
|
|
|
121
122
|
def add(
|
|
122
123
|
self, name: str, container_path: str, host_subpath: str | None = None
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
6
6
|
|
|
7
7
|
from .stream_process import OutputMiddleware, create_chained_middleware
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
logger =
|
|
10
|
+
logger = get_plugin_logger(__name__)
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class LoggerOutputMiddleware(OutputMiddleware[str]):
|