ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +434 -219
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +144 -168
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +388 -524
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +540 -19
- ccproxy/data/codex_headers_fallback.json +114 -7
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +61 -105
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +268 -276
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +68 -446
- ccproxy/utils/version_checker.py +273 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1251
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -243
- ccproxy/services/codex_detection_service.py +0 -252
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.7.dist-info/METADATA +0 -615
- ccproxy_api-0.1.7.dist-info/RECORD +0 -191
- ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Options handling for Claude SDK interactions."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from claude_agent_sdk import ClaudeAgentOptions
|
|
6
|
+
|
|
7
|
+
from .config import ClaudeSDKSettings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OptionsHandler:
|
|
11
|
+
"""
|
|
12
|
+
Handles creation and management of Claude SDK options.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, config: ClaudeSDKSettings) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Initialize options handler.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config: Plugin-specific configuration for Claude SDK
|
|
21
|
+
"""
|
|
22
|
+
self.config = config
|
|
23
|
+
|
|
24
|
+
def create_options(
|
|
25
|
+
self,
|
|
26
|
+
model: str,
|
|
27
|
+
temperature: float | None = None,
|
|
28
|
+
max_tokens: int | None = None,
|
|
29
|
+
system_message: str | None = None,
|
|
30
|
+
**additional_options: Any,
|
|
31
|
+
) -> ClaudeAgentOptions:
|
|
32
|
+
"""
|
|
33
|
+
Create Claude SDK options from API parameters.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
model: The model name
|
|
37
|
+
temperature: Temperature for response generation
|
|
38
|
+
max_tokens: Maximum tokens in response
|
|
39
|
+
system_message: System message to include
|
|
40
|
+
**additional_options: Additional options to set on the ClaudeAgentOptions instance
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Configured ClaudeAgentOptions instance
|
|
44
|
+
"""
|
|
45
|
+
# Start with configured defaults if available, otherwise create fresh instance
|
|
46
|
+
if self.config and self.config.code_options:
|
|
47
|
+
configured_opts = self.config.code_options
|
|
48
|
+
options = ClaudeAgentOptions()
|
|
49
|
+
|
|
50
|
+
# Copy all attributes from configured defaults
|
|
51
|
+
for attr in dir(configured_opts):
|
|
52
|
+
if not attr.startswith("_"):
|
|
53
|
+
configured_value = getattr(configured_opts, attr)
|
|
54
|
+
if configured_value is not None and hasattr(options, attr):
|
|
55
|
+
# Special handling for mcp_servers to ensure we copy the dict
|
|
56
|
+
if attr == "mcp_servers" and isinstance(configured_value, dict):
|
|
57
|
+
setattr(options, attr, configured_value.copy())
|
|
58
|
+
else:
|
|
59
|
+
setattr(options, attr, configured_value)
|
|
60
|
+
else:
|
|
61
|
+
options = ClaudeAgentOptions()
|
|
62
|
+
|
|
63
|
+
# Override the model (API parameter takes precedence)
|
|
64
|
+
options.model = model
|
|
65
|
+
|
|
66
|
+
# Apply system message if provided (this is supported by ClaudeAgentOptions)
|
|
67
|
+
if system_message is not None:
|
|
68
|
+
options.system_prompt = system_message
|
|
69
|
+
|
|
70
|
+
# If session_id is provided via additional_options, enable continue_conversation
|
|
71
|
+
if additional_options.get("session_id"):
|
|
72
|
+
options.continue_conversation = True
|
|
73
|
+
|
|
74
|
+
# Automatically map additional_options to ClaudeAgentOptions attributes
|
|
75
|
+
for key, value in additional_options.items():
|
|
76
|
+
if hasattr(options, key):
|
|
77
|
+
try:
|
|
78
|
+
# Attempt type conversion if the attribute already exists
|
|
79
|
+
attr_type = type(getattr(options, key))
|
|
80
|
+
# Only convert if the attribute is not None
|
|
81
|
+
if getattr(options, key) is not None:
|
|
82
|
+
setattr(options, key, attr_type(value))
|
|
83
|
+
else:
|
|
84
|
+
setattr(options, key, value)
|
|
85
|
+
except Exception:
|
|
86
|
+
# Fallback to direct assignment if conversion fails
|
|
87
|
+
setattr(options, key, value)
|
|
88
|
+
|
|
89
|
+
return options
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def extract_system_message(messages: list[dict[str, Any]]) -> str | None:
|
|
93
|
+
"""
|
|
94
|
+
Extract system message from Anthropic messages format.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
messages: List of messages in Anthropic format
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
System message content if found, None otherwise
|
|
101
|
+
"""
|
|
102
|
+
for message in messages:
|
|
103
|
+
if message.get("role") == "system":
|
|
104
|
+
content = message.get("content", "")
|
|
105
|
+
if isinstance(content, list):
|
|
106
|
+
# Handle content blocks
|
|
107
|
+
text_parts = []
|
|
108
|
+
for block in content:
|
|
109
|
+
if block.get("type") == "text":
|
|
110
|
+
text_parts.append(block.get("text", ""))
|
|
111
|
+
return " ".join(text_parts)
|
|
112
|
+
return str(content)
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def get_supported_models() -> list[str]:
|
|
117
|
+
"""
|
|
118
|
+
Get list of supported Claude models.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of supported model names
|
|
122
|
+
"""
|
|
123
|
+
from ccproxy.plugins.claude_shared.model_defaults import (
|
|
124
|
+
DEFAULT_CLAUDE_MODEL_CARDS,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return [card.id for card in DEFAULT_CLAUDE_MODEL_CARDS]
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def validate_model(model: str) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Validate if a model is supported.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
model: The model name to validate
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
True if supported, False otherwise
|
|
139
|
+
"""
|
|
140
|
+
return model in OptionsHandler.get_supported_models()
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def get_default_options() -> dict[str, Any]:
|
|
144
|
+
"""
|
|
145
|
+
Get default options for API parameters.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Dictionary of default API parameter values
|
|
149
|
+
"""
|
|
150
|
+
return {
|
|
151
|
+
"model": "claude-3-5-sonnet-20241022",
|
|
152
|
+
"temperature": 0.7,
|
|
153
|
+
"max_tokens": 4000,
|
|
154
|
+
}
|
|
@@ -14,7 +14,25 @@ import json
|
|
|
14
14
|
import re
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
|
-
from ccproxy.
|
|
17
|
+
from ccproxy.llms.models import openai as openai_models
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def format_openai_tool_call(tool_use: dict[str, Any]) -> openai_models.ToolCall:
|
|
21
|
+
"""Convert Anthropic tool use to OpenAI tool call format."""
|
|
22
|
+
tool_input = tool_use.get("input", {})
|
|
23
|
+
if isinstance(tool_input, dict):
|
|
24
|
+
arguments_str = json.dumps(tool_input)
|
|
25
|
+
else:
|
|
26
|
+
arguments_str = str(tool_input)
|
|
27
|
+
|
|
28
|
+
return openai_models.ToolCall(
|
|
29
|
+
id=tool_use.get("id", ""),
|
|
30
|
+
type="function",
|
|
31
|
+
function=openai_models.FunctionCall(
|
|
32
|
+
name=tool_use.get("name", ""),
|
|
33
|
+
arguments=arguments_str,
|
|
34
|
+
),
|
|
35
|
+
)
|
|
18
36
|
|
|
19
37
|
|
|
20
38
|
def parse_system_message_tags(text: str) -> str:
|
|
@@ -31,7 +49,7 @@ def parse_system_message_tags(text: str) -> str:
|
|
|
31
49
|
def replace_system_message(match: re.Match[str]) -> str:
|
|
32
50
|
try:
|
|
33
51
|
system_data = json.loads(match.group(1))
|
|
34
|
-
source = system_data.get("source", "
|
|
52
|
+
source = system_data.get("source", "claude_agent_sdk")
|
|
35
53
|
system_text = system_data.get("text", "")
|
|
36
54
|
return f"[{source}]: {system_text}"
|
|
37
55
|
except json.JSONDecodeError:
|
|
@@ -75,7 +93,7 @@ def parse_tool_use_sdk_tags(
|
|
|
75
93
|
tool_id = tool_data.get("id", "")
|
|
76
94
|
tool_name = tool_data.get("name", "")
|
|
77
95
|
tool_input = tool_data.get("input", {})
|
|
78
|
-
return f"[
|
|
96
|
+
return f"[claude_agent_sdk tool_use {tool_id}]: {tool_name}({json.dumps(tool_input)})"
|
|
79
97
|
except json.JSONDecodeError:
|
|
80
98
|
# Keep original if parsing fails
|
|
81
99
|
return match.group(0)
|
|
@@ -102,7 +120,7 @@ def parse_tool_result_sdk_tags(text: str) -> str:
|
|
|
102
120
|
result_content = result_data.get("content", "")
|
|
103
121
|
is_error = result_data.get("is_error", False)
|
|
104
122
|
error_indicator = " (ERROR)" if is_error else ""
|
|
105
|
-
return f"[
|
|
123
|
+
return f"[claude_agent_sdk tool_result {tool_use_id}{error_indicator}]: {result_content}"
|
|
106
124
|
except json.JSONDecodeError:
|
|
107
125
|
# Keep original if parsing fails
|
|
108
126
|
return match.group(0)
|
|
@@ -124,7 +142,7 @@ def parse_result_message_tags(text: str) -> str:
|
|
|
124
142
|
def replace_result_message(match: re.Match[str]) -> str:
|
|
125
143
|
try:
|
|
126
144
|
result_data = json.loads(match.group(1))
|
|
127
|
-
source = result_data.get("source", "
|
|
145
|
+
source = result_data.get("source", "claude_agent_sdk")
|
|
128
146
|
session_id = result_data.get("session_id", "")
|
|
129
147
|
stop_reason = result_data.get("stop_reason", "")
|
|
130
148
|
usage = result_data.get("usage", {})
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Claude SDK plugin v2 implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
6
|
+
from ccproxy.core.plugins import (
|
|
7
|
+
BaseProviderPluginFactory,
|
|
8
|
+
FormatAdapterSpec,
|
|
9
|
+
FormatPair,
|
|
10
|
+
PluginContext,
|
|
11
|
+
PluginManifest,
|
|
12
|
+
ProviderPluginRuntime,
|
|
13
|
+
TaskSpec,
|
|
14
|
+
)
|
|
15
|
+
from ccproxy.core.plugins.declaration import RouterSpec
|
|
16
|
+
from ccproxy.core.plugins.interfaces import DetectionServiceProtocol
|
|
17
|
+
from ccproxy.llms.streaming.accumulators import ClaudeAccumulator
|
|
18
|
+
from ccproxy.services.adapters.base import BaseAdapter
|
|
19
|
+
|
|
20
|
+
from .adapter import ClaudeSDKAdapter
|
|
21
|
+
from .config import ClaudeSDKSettings
|
|
22
|
+
from .detection_service import ClaudeSDKDetectionService
|
|
23
|
+
from .routes import router
|
|
24
|
+
from .tasks import ClaudeSDKDetectionRefreshTask
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
logger = get_plugin_logger()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ClaudeSDKRuntime(ProviderPluginRuntime):
|
|
31
|
+
"""Runtime for Claude SDK plugin."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, manifest: PluginManifest):
|
|
34
|
+
"""Initialize runtime."""
|
|
35
|
+
super().__init__(manifest)
|
|
36
|
+
self.session_manager: Any | None = None
|
|
37
|
+
|
|
38
|
+
async def _on_initialize(self) -> None:
|
|
39
|
+
"""Initialize the Claude SDK plugin."""
|
|
40
|
+
# Call parent initialization to set up adapter, detection_service, etc.
|
|
41
|
+
await super()._on_initialize()
|
|
42
|
+
|
|
43
|
+
if not self.context:
|
|
44
|
+
raise RuntimeError("Context not set")
|
|
45
|
+
|
|
46
|
+
# Get configuration
|
|
47
|
+
config = self.context.get("config")
|
|
48
|
+
if not isinstance(config, ClaudeSDKSettings):
|
|
49
|
+
logger.debug("plugin_no_config")
|
|
50
|
+
# Use default config if none provided
|
|
51
|
+
config = ClaudeSDKSettings()
|
|
52
|
+
logger.debug("plugin_using_default_config")
|
|
53
|
+
|
|
54
|
+
# Initialize adapter with session manager if enabled
|
|
55
|
+
if self.adapter and hasattr(self.adapter, "session_manager"):
|
|
56
|
+
self.session_manager = self.adapter.session_manager
|
|
57
|
+
if self.session_manager:
|
|
58
|
+
await self.session_manager.start()
|
|
59
|
+
logger.debug("session_manager_started")
|
|
60
|
+
|
|
61
|
+
# Initialize detection service if present
|
|
62
|
+
if self.detection_service and hasattr(
|
|
63
|
+
self.detection_service, "initialize_detection"
|
|
64
|
+
):
|
|
65
|
+
await self.detection_service.initialize_detection()
|
|
66
|
+
|
|
67
|
+
# Check CLI status
|
|
68
|
+
version = self.detection_service.get_version()
|
|
69
|
+
cli_path = self.detection_service.get_cli_path()
|
|
70
|
+
|
|
71
|
+
if cli_path:
|
|
72
|
+
# Single consolidated log message with both CLI detection and plugin initialization status
|
|
73
|
+
logger.debug(
|
|
74
|
+
"plugin_initialized",
|
|
75
|
+
plugin="claude_sdk",
|
|
76
|
+
version="0.1.0",
|
|
77
|
+
status="initialized",
|
|
78
|
+
has_credentials=True, # SDK handles its own auth
|
|
79
|
+
cli_available=True,
|
|
80
|
+
cli_version=version,
|
|
81
|
+
cli_path=cli_path,
|
|
82
|
+
cli_source="package_manager",
|
|
83
|
+
has_adapter=self.adapter is not None,
|
|
84
|
+
has_session_manager=self.session_manager is not None,
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
error_msg = "Claude CLI not found in PATH or common locations - SDK plugin requires installed CLI"
|
|
88
|
+
logger.error(
|
|
89
|
+
"plugin_initialization_failed",
|
|
90
|
+
status="failed",
|
|
91
|
+
error=error_msg,
|
|
92
|
+
)
|
|
93
|
+
raise RuntimeError(error_msg)
|
|
94
|
+
|
|
95
|
+
async def _on_shutdown(self) -> None:
|
|
96
|
+
"""Cleanup on shutdown."""
|
|
97
|
+
# Shutdown session manager first
|
|
98
|
+
if self.session_manager:
|
|
99
|
+
await self.session_manager.shutdown()
|
|
100
|
+
logger.debug("session_manager_shutdown")
|
|
101
|
+
|
|
102
|
+
# Call parent shutdown which handles adapter cleanup
|
|
103
|
+
await super()._on_shutdown()
|
|
104
|
+
|
|
105
|
+
async def _get_health_details(self) -> dict[str, Any]:
|
|
106
|
+
"""Get health check details."""
|
|
107
|
+
details = await super()._get_health_details()
|
|
108
|
+
|
|
109
|
+
# Add SDK-specific health info
|
|
110
|
+
details.update(
|
|
111
|
+
{
|
|
112
|
+
"has_session_manager": self.session_manager is not None,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Add CLI information if available
|
|
117
|
+
if self.detection_service:
|
|
118
|
+
details.update(
|
|
119
|
+
{
|
|
120
|
+
"cli_available": self.detection_service.is_claude_available(),
|
|
121
|
+
"cli_version": self.detection_service.get_version(),
|
|
122
|
+
"cli_path": self.detection_service.get_cli_path(),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return details
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ClaudeSDKFactory(BaseProviderPluginFactory):
|
|
130
|
+
"""Factory for Claude SDK plugin."""
|
|
131
|
+
|
|
132
|
+
# Plugin configuration via class attributes
|
|
133
|
+
plugin_name = "claude_sdk"
|
|
134
|
+
plugin_description = (
|
|
135
|
+
"Claude SDK plugin providing access to Claude through the Claude Code SDK"
|
|
136
|
+
)
|
|
137
|
+
runtime_class = ClaudeSDKRuntime
|
|
138
|
+
adapter_class = ClaudeSDKAdapter
|
|
139
|
+
detection_service_class = ClaudeSDKDetectionService
|
|
140
|
+
config_class = ClaudeSDKSettings
|
|
141
|
+
routers = [
|
|
142
|
+
RouterSpec(router=router, prefix="/claude/sdk"),
|
|
143
|
+
]
|
|
144
|
+
optional_requires = ["pricing"]
|
|
145
|
+
|
|
146
|
+
# No format adapters needed - core provides all required conversions
|
|
147
|
+
format_adapters: list[FormatAdapterSpec] = []
|
|
148
|
+
|
|
149
|
+
# Dependencies: All required adapters now provided by core
|
|
150
|
+
requires_format_adapters: list[FormatPair] = []
|
|
151
|
+
|
|
152
|
+
tasks = [
|
|
153
|
+
TaskSpec(
|
|
154
|
+
task_name="claude_sdk_detection_refresh",
|
|
155
|
+
task_type="claude_sdk_detection_refresh",
|
|
156
|
+
task_class=ClaudeSDKDetectionRefreshTask,
|
|
157
|
+
interval_seconds=3600,
|
|
158
|
+
enabled=True,
|
|
159
|
+
kwargs={"skip_initial_run": True},
|
|
160
|
+
)
|
|
161
|
+
]
|
|
162
|
+
tool_accumulator_class = ClaudeAccumulator
|
|
163
|
+
|
|
164
|
+
async def create_adapter(self, context: PluginContext) -> BaseAdapter:
|
|
165
|
+
"""Create the Claude SDK adapter.
|
|
166
|
+
|
|
167
|
+
This method overrides the base implementation because Claude SDK
|
|
168
|
+
has different dependencies than HTTP-based adapters.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
context: Plugin context
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
ClaudeSDKAdapter instance
|
|
175
|
+
"""
|
|
176
|
+
config = context.get("config")
|
|
177
|
+
if not isinstance(config, ClaudeSDKSettings):
|
|
178
|
+
raise RuntimeError("No configuration provided for Claude SDK adapter")
|
|
179
|
+
|
|
180
|
+
# Get optional dependencies
|
|
181
|
+
metrics = context.get("metrics")
|
|
182
|
+
|
|
183
|
+
# Try to get hook_manager from context (provided by core services)
|
|
184
|
+
hook_manager = context.get("hook_manager")
|
|
185
|
+
if not hook_manager:
|
|
186
|
+
# Try to get from app state as fallback
|
|
187
|
+
app = context.get("app")
|
|
188
|
+
if app and hasattr(app, "state") and hasattr(app.state, "hook_manager"):
|
|
189
|
+
hook_manager = app.state.hook_manager
|
|
190
|
+
|
|
191
|
+
if hook_manager:
|
|
192
|
+
logger.debug("claude_sdk_hook_manager_found", source="context_or_app")
|
|
193
|
+
|
|
194
|
+
# Create adapter with config and optional dependencies
|
|
195
|
+
# Note: ClaudeSDKAdapter doesn't use an HTTP client, but it still
|
|
196
|
+
# needs access to the shared format registry so it can
|
|
197
|
+
# compose request/response converters declared by the core.
|
|
198
|
+
format_registry = None
|
|
199
|
+
service_container = context.get("service_container")
|
|
200
|
+
if service_container:
|
|
201
|
+
try:
|
|
202
|
+
format_registry = service_container.get_format_registry()
|
|
203
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
204
|
+
logger.warning(
|
|
205
|
+
"claude_sdk_format_registry_unavailable",
|
|
206
|
+
error=str(exc),
|
|
207
|
+
category="format",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
adapter = ClaudeSDKAdapter(
|
|
211
|
+
config=config,
|
|
212
|
+
metrics=metrics,
|
|
213
|
+
hook_manager=hook_manager,
|
|
214
|
+
format_registry=format_registry,
|
|
215
|
+
context=context,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return adapter
|
|
219
|
+
|
|
220
|
+
def create_detection_service(
|
|
221
|
+
self, context: PluginContext
|
|
222
|
+
) -> DetectionServiceProtocol:
|
|
223
|
+
"""Create the Claude SDK detection service with validation.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
context: Plugin context
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
ClaudeSDKDetectionService instance
|
|
230
|
+
"""
|
|
231
|
+
settings = context.get("settings")
|
|
232
|
+
if not settings:
|
|
233
|
+
raise RuntimeError("No settings provided for Claude SDK detection service")
|
|
234
|
+
|
|
235
|
+
cli_service = context.get("cli_detection_service")
|
|
236
|
+
service = ClaudeSDKDetectionService(settings, cli_service)
|
|
237
|
+
return cast(DetectionServiceProtocol, service)
|
|
238
|
+
|
|
239
|
+
async def create_credentials_manager(self, context: PluginContext) -> None:
|
|
240
|
+
"""Create the credentials manager for Claude SDK.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
context: Plugin context
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
None - Claude SDK uses its own authentication mechanism
|
|
247
|
+
"""
|
|
248
|
+
# Claude SDK doesn't use a traditional credentials manager
|
|
249
|
+
# It uses the built-in CLI authentication
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
def create_context(self, core_services: Any) -> PluginContext:
|
|
253
|
+
"""Create context and set up detection service in tasks."""
|
|
254
|
+
# Get base context
|
|
255
|
+
context = super().create_context(core_services)
|
|
256
|
+
|
|
257
|
+
# Create detection service early so it can be passed to tasks
|
|
258
|
+
detection_service = self.create_detection_service(context)
|
|
259
|
+
|
|
260
|
+
# Update task kwargs with detection service
|
|
261
|
+
for task_spec in self.manifest.tasks:
|
|
262
|
+
if task_spec.task_name == "claude_sdk_detection_refresh":
|
|
263
|
+
task_spec.kwargs["detection_service"] = detection_service
|
|
264
|
+
|
|
265
|
+
return context
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# Export the factory instance
|
|
269
|
+
factory = ClaudeSDKFactory()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Routes for Claude SDK plugin."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Any
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, Request
|
|
6
|
+
from starlette.responses import Response, StreamingResponse
|
|
7
|
+
|
|
8
|
+
from ccproxy.api.decorators import with_format_chain
|
|
9
|
+
from ccproxy.api.dependencies import get_plugin_adapter
|
|
10
|
+
from ccproxy.auth.dependencies import ConditionalAuthDep
|
|
11
|
+
from ccproxy.core.constants import (
|
|
12
|
+
FORMAT_ANTHROPIC_MESSAGES,
|
|
13
|
+
FORMAT_OPENAI_CHAT,
|
|
14
|
+
FORMAT_OPENAI_RESPONSES,
|
|
15
|
+
)
|
|
16
|
+
from ccproxy.plugins.claude_sdk.adapter import ClaudeSDKAdapter
|
|
17
|
+
from ccproxy.streaming import DeferredStreaming
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
ClaudeSDKAdapterDep = Annotated[Any, Depends(get_plugin_adapter("claude_sdk"))]
|
|
21
|
+
router = APIRouter()
|
|
22
|
+
|
|
23
|
+
ResponseType = Response | StreamingResponse | DeferredStreaming
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def _handle_claude_sdk_request(
|
|
27
|
+
request: Request,
|
|
28
|
+
adapter: ClaudeSDKAdapter,
|
|
29
|
+
) -> ResponseType:
|
|
30
|
+
return await adapter.handle_request(request)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.post("/v1/messages", response_model=None)
|
|
34
|
+
@with_format_chain([FORMAT_ANTHROPIC_MESSAGES])
|
|
35
|
+
async def claude_sdk_messages(
|
|
36
|
+
request: Request,
|
|
37
|
+
auth: ConditionalAuthDep,
|
|
38
|
+
adapter: ClaudeSDKAdapterDep,
|
|
39
|
+
) -> ResponseType:
|
|
40
|
+
return await _handle_claude_sdk_request(request, adapter)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.post("/v1/chat/completions", response_model=None)
|
|
44
|
+
@with_format_chain(
|
|
45
|
+
[
|
|
46
|
+
FORMAT_OPENAI_CHAT,
|
|
47
|
+
FORMAT_ANTHROPIC_MESSAGES,
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
async def claude_sdk_chat_completions(
|
|
51
|
+
request: Request,
|
|
52
|
+
auth: ConditionalAuthDep,
|
|
53
|
+
adapter: ClaudeSDKAdapterDep,
|
|
54
|
+
) -> ResponseType:
|
|
55
|
+
return await _handle_claude_sdk_request(request, adapter)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.post("/v1/responses", response_model=None)
|
|
59
|
+
@with_format_chain([FORMAT_OPENAI_RESPONSES, FORMAT_ANTHROPIC_MESSAGES])
|
|
60
|
+
async def claude_sdk_responses(
|
|
61
|
+
request: Request,
|
|
62
|
+
auth: ConditionalAuthDep,
|
|
63
|
+
adapter: ClaudeSDKAdapterDep,
|
|
64
|
+
) -> ResponseType:
|
|
65
|
+
return await _handle_claude_sdk_request(request, adapter)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.post("/{session_id}/v1/messages", response_model=None)
|
|
69
|
+
@with_format_chain([FORMAT_ANTHROPIC_MESSAGES])
|
|
70
|
+
async def claude_sdk_messages_with_session(
|
|
71
|
+
request: Request,
|
|
72
|
+
session_id: str,
|
|
73
|
+
auth: ConditionalAuthDep,
|
|
74
|
+
adapter: ClaudeSDKAdapterDep,
|
|
75
|
+
) -> ResponseType:
|
|
76
|
+
request.state.session_id = session_id
|
|
77
|
+
return await _handle_claude_sdk_request(request, adapter)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@router.post("/{session_id}/v1/chat/completions", response_model=None)
|
|
81
|
+
@with_format_chain(
|
|
82
|
+
[
|
|
83
|
+
FORMAT_OPENAI_CHAT,
|
|
84
|
+
FORMAT_ANTHROPIC_MESSAGES,
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
async def claude_sdk_chat_completions_with_session(
|
|
88
|
+
request: Request,
|
|
89
|
+
session_id: str,
|
|
90
|
+
auth: ConditionalAuthDep,
|
|
91
|
+
adapter: ClaudeSDKAdapterDep,
|
|
92
|
+
) -> ResponseType:
|
|
93
|
+
request.state.session_id = session_id
|
|
94
|
+
return await _handle_claude_sdk_request(request, adapter)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@router.post("/{session_id}/v1/responses", response_model=None)
|
|
98
|
+
@with_format_chain([FORMAT_OPENAI_RESPONSES, FORMAT_ANTHROPIC_MESSAGES])
|
|
99
|
+
async def claude_sdk_responses_with_session(
|
|
100
|
+
request: Request,
|
|
101
|
+
auth: ConditionalAuthDep,
|
|
102
|
+
adapter: ClaudeSDKAdapterDep,
|
|
103
|
+
) -> ResponseType:
|
|
104
|
+
return await _handle_claude_sdk_request(request, adapter)
|