ccproxy-api 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +439 -212
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +145 -176
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +402 -530
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +558 -0
- ccproxy/data/codex_headers_fallback.json +121 -0
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +63 -107
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +346 -314
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +95 -342
- ccproxy/utils/version_checker.py +279 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1231
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -269
- ccproxy/services/codex_detection_service.py +0 -263
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.6.dist-info/METADATA +0 -615
- ccproxy_api-0.1.6.dist-info/RECORD +0 -189
- ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,90 +1,96 @@
|
|
|
1
|
-
"""Claude SDK
|
|
1
|
+
"""Claude SDK handler for orchestrating SDK operations.
|
|
2
|
+
|
|
3
|
+
This module contains the core business logic migrated from claude_sdk_service.py,
|
|
4
|
+
handling SDK operations while maintaining clean separation of concerns.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from collections.abc import AsyncIterator
|
|
4
8
|
from typing import Any
|
|
9
|
+
from uuid import uuid4
|
|
5
10
|
|
|
6
|
-
import
|
|
7
|
-
from claude_code_sdk import ClaudeCodeOptions
|
|
11
|
+
from claude_agent_sdk import ClaudeAgentOptions
|
|
8
12
|
|
|
9
13
|
from ccproxy.auth.manager import AuthManager
|
|
10
|
-
from ccproxy.
|
|
11
|
-
from ccproxy.
|
|
12
|
-
from ccproxy.
|
|
13
|
-
from ccproxy.
|
|
14
|
-
|
|
15
|
-
from ccproxy.
|
|
16
|
-
from ccproxy.
|
|
17
|
-
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
from
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
14
|
+
from ccproxy.core.errors import ClaudeProxyError, ServiceUnavailableError
|
|
15
|
+
from ccproxy.core.logging import get_plugin_logger
|
|
16
|
+
from ccproxy.core.request_context import RequestContext
|
|
17
|
+
from ccproxy.llms.models import anthropic as anthropic_models
|
|
18
|
+
|
|
19
|
+
# from ccproxy.observability.metrics import # Metrics moved to plugin PrometheusMetrics
|
|
20
|
+
from ccproxy.utils.model_mapper import ModelMapper, add_model_alias
|
|
21
|
+
|
|
22
|
+
from . import models as sdk_models
|
|
23
|
+
from .client import ClaudeSDKClient
|
|
24
|
+
from .config import ClaudeSDKSettings, SDKMessageMode
|
|
25
|
+
from .converter import MessageConverter
|
|
26
|
+
from .exceptions import StreamTimeoutError
|
|
27
|
+
from .hooks import ClaudeSDKStreamingHook
|
|
28
|
+
from .manager import SessionManager
|
|
29
|
+
from .models import MessageResponse, SDKMessage, create_sdk_message
|
|
30
|
+
from .options import OptionsHandler
|
|
31
|
+
from .streaming import ClaudeStreamProcessor
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
logger = get_plugin_logger()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _convert_sdk_message_mode(core_mode: Any) -> SDKMessageMode:
|
|
38
|
+
"""Convert core SDKMessageMode to plugin SDKMessageMode."""
|
|
39
|
+
if hasattr(core_mode, "value"):
|
|
40
|
+
# Convert enum value to plugin enum
|
|
41
|
+
if core_mode.value == "forward":
|
|
42
|
+
return SDKMessageMode.FORWARD
|
|
43
|
+
elif core_mode.value == "ignore":
|
|
44
|
+
return SDKMessageMode.IGNORE
|
|
45
|
+
elif core_mode.value == "formatted":
|
|
46
|
+
return SDKMessageMode.FORMATTED
|
|
47
|
+
return SDKMessageMode.FORWARD # Default fallback
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ClaudeSDKHandler:
|
|
35
51
|
"""
|
|
36
|
-
|
|
52
|
+
Handler for Claude SDK operations orchestration.
|
|
37
53
|
|
|
38
|
-
This class
|
|
39
|
-
|
|
40
|
-
separation of concerns.
|
|
54
|
+
This class encapsulates the business logic for SDK operations,
|
|
55
|
+
migrated from the original claude_sdk_service.py.
|
|
41
56
|
"""
|
|
42
57
|
|
|
43
58
|
def __init__(
|
|
44
59
|
self,
|
|
60
|
+
config: ClaudeSDKSettings,
|
|
45
61
|
sdk_client: ClaudeSDKClient | None = None,
|
|
46
62
|
auth_manager: AuthManager | None = None,
|
|
47
|
-
metrics:
|
|
48
|
-
settings: Settings | None = None,
|
|
63
|
+
metrics: Any | None = None, # Metrics now handled by metrics plugin
|
|
49
64
|
session_manager: SessionManager | None = None,
|
|
65
|
+
hook_manager: Any | None = None, # HookManager for emitting events
|
|
50
66
|
) -> None:
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
sdk_client: Claude SDK client instance
|
|
56
|
-
auth_manager: Authentication manager (optional)
|
|
57
|
-
metrics: Prometheus metrics instance (optional)
|
|
58
|
-
settings: Application settings (optional)
|
|
59
|
-
session_manager: Session manager for dependency injection (optional)
|
|
60
|
-
"""
|
|
67
|
+
"""Initialize Claude SDK handler."""
|
|
68
|
+
self.config = config
|
|
61
69
|
self.sdk_client = sdk_client or ClaudeSDKClient(
|
|
62
|
-
|
|
70
|
+
config=config, session_manager=session_manager
|
|
63
71
|
)
|
|
64
72
|
self.auth_manager = auth_manager
|
|
65
73
|
self.metrics = metrics
|
|
66
|
-
self.
|
|
74
|
+
self.hook_manager = hook_manager
|
|
67
75
|
self.message_converter = MessageConverter()
|
|
68
|
-
self.options_handler = OptionsHandler(
|
|
76
|
+
self.options_handler = OptionsHandler(config=config)
|
|
77
|
+
self.model_mapper = ModelMapper(config.model_mappings)
|
|
78
|
+
|
|
79
|
+
# Create streaming hook if hook_manager is available
|
|
80
|
+
streaming_hook = None
|
|
81
|
+
if hook_manager:
|
|
82
|
+
streaming_hook = ClaudeSDKStreamingHook(hook_manager=hook_manager)
|
|
83
|
+
|
|
69
84
|
self.stream_processor = ClaudeStreamProcessor(
|
|
70
85
|
message_converter=self.message_converter,
|
|
71
86
|
metrics=self.metrics,
|
|
87
|
+
streaming_hook=streaming_hook,
|
|
72
88
|
)
|
|
73
89
|
|
|
74
90
|
def _convert_messages_to_sdk_message(
|
|
75
91
|
self, messages: list[dict[str, Any]], session_id: str | None = None
|
|
76
|
-
) ->
|
|
77
|
-
"""Convert list of Anthropic messages to single SDKMessage.
|
|
78
|
-
|
|
79
|
-
Takes the last user message from the list and converts it to SDKMessage format.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
messages: List of Anthropic API messages
|
|
83
|
-
session_id: Optional session ID for conversation continuity
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
SDKMessage ready to send to Claude SDK
|
|
87
|
-
"""
|
|
92
|
+
) -> SDKMessage:
|
|
93
|
+
"""Convert list of Anthropic messages to single SDKMessage."""
|
|
88
94
|
# Find the last user message
|
|
89
95
|
last_user_message = None
|
|
90
96
|
for msg in reversed(messages):
|
|
@@ -117,15 +123,9 @@ class ClaudeSDKService:
|
|
|
117
123
|
self,
|
|
118
124
|
ctx: RequestContext,
|
|
119
125
|
session_id: str | None,
|
|
120
|
-
options:
|
|
126
|
+
options: ClaudeAgentOptions,
|
|
121
127
|
) -> None:
|
|
122
|
-
"""Capture session metadata for access logging.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
ctx: Request context to add metadata to
|
|
126
|
-
session_id: Optional session ID
|
|
127
|
-
options: Claude Code options
|
|
128
|
-
"""
|
|
128
|
+
"""Capture session metadata for access logging."""
|
|
129
129
|
if (
|
|
130
130
|
session_id
|
|
131
131
|
and hasattr(self.sdk_client, "_session_manager")
|
|
@@ -167,13 +167,14 @@ class ClaudeSDKService:
|
|
|
167
167
|
"failed_to_capture_session_metadata",
|
|
168
168
|
session_id=session_id,
|
|
169
169
|
error=str(e),
|
|
170
|
+
exc_info=e,
|
|
170
171
|
)
|
|
171
172
|
else:
|
|
172
|
-
# Add basic session metadata for direct connections
|
|
173
|
+
# Add basic session metadata for direct connections
|
|
173
174
|
ctx.add_metadata(
|
|
174
175
|
session_type="direct",
|
|
175
176
|
session_pool_enabled=False,
|
|
176
|
-
session_is_new=True,
|
|
177
|
+
session_is_new=True,
|
|
177
178
|
)
|
|
178
179
|
|
|
179
180
|
async def create_completion(
|
|
@@ -187,32 +188,19 @@ class ClaudeSDKService:
|
|
|
187
188
|
session_id: str | None = None,
|
|
188
189
|
**kwargs: Any,
|
|
189
190
|
) -> MessageResponse | AsyncIterator[dict[str, Any]]:
|
|
190
|
-
"""
|
|
191
|
-
Create a completion using Claude SDK with business logic orchestration.
|
|
192
|
-
|
|
193
|
-
Args:
|
|
194
|
-
messages: List of messages in Anthropic format
|
|
195
|
-
model: The model to use
|
|
196
|
-
temperature: Temperature for response generation
|
|
197
|
-
max_tokens: Maximum tokens in response
|
|
198
|
-
stream: Whether to stream responses
|
|
199
|
-
session_id: Optional session ID for Claude SDK integration
|
|
200
|
-
request_context: Existing request context to use instead of creating new one
|
|
201
|
-
**kwargs: Additional arguments
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
Response dict or async iterator of response chunks if streaming
|
|
205
|
-
|
|
206
|
-
Raises:
|
|
207
|
-
ClaudeProxyError: If request fails
|
|
208
|
-
ServiceUnavailableError: If service is unavailable
|
|
209
|
-
"""
|
|
210
|
-
|
|
191
|
+
"""Create a completion using Claude SDK with business logic orchestration."""
|
|
211
192
|
# Extract system message and create options
|
|
212
193
|
system_message = self.options_handler.extract_system_message(messages)
|
|
213
194
|
|
|
214
|
-
|
|
215
|
-
|
|
195
|
+
if isinstance(request_context, RequestContext):
|
|
196
|
+
metadata = request_context.metadata
|
|
197
|
+
else:
|
|
198
|
+
metadata = None
|
|
199
|
+
|
|
200
|
+
match = self.model_mapper.map(model)
|
|
201
|
+
if match.mapped != match.original and isinstance(metadata, dict):
|
|
202
|
+
add_model_alias(metadata, match.original, match.mapped)
|
|
203
|
+
model = match.mapped
|
|
216
204
|
|
|
217
205
|
options = self.options_handler.create_options(
|
|
218
206
|
model=model,
|
|
@@ -223,9 +211,7 @@ class ClaudeSDKService:
|
|
|
223
211
|
**kwargs,
|
|
224
212
|
)
|
|
225
213
|
|
|
226
|
-
#
|
|
227
|
-
|
|
228
|
-
# Use existing context, but update metadata for this service (preserve original service_type)
|
|
214
|
+
# Use existing context
|
|
229
215
|
ctx = request_context
|
|
230
216
|
metadata = {
|
|
231
217
|
"endpoint": "messages",
|
|
@@ -235,19 +221,13 @@ class ClaudeSDKService:
|
|
|
235
221
|
if session_id:
|
|
236
222
|
metadata["session_id"] = session_id
|
|
237
223
|
ctx.add_metadata(**metadata)
|
|
238
|
-
# Use existing request ID from context
|
|
239
224
|
request_id = ctx.request_id
|
|
240
225
|
|
|
241
226
|
try:
|
|
242
|
-
#
|
|
227
|
+
# Removed SDK request logging (simple_request_logger removed)
|
|
243
228
|
timestamp = ctx.get_log_timestamp_prefix() if ctx else None
|
|
244
|
-
await self._log_sdk_request(
|
|
245
|
-
request_id, messages, options, model, stream, session_id, timestamp
|
|
246
|
-
)
|
|
247
229
|
|
|
248
230
|
if stream:
|
|
249
|
-
# For streaming, return the async iterator directly
|
|
250
|
-
# Access logging will be handled by the stream processor when ResultMessage is received
|
|
251
231
|
return self._stream_completion(
|
|
252
232
|
ctx, messages, options, model, session_id, timestamp
|
|
253
233
|
)
|
|
@@ -257,7 +237,6 @@ class ClaudeSDKService:
|
|
|
257
237
|
)
|
|
258
238
|
return result
|
|
259
239
|
except (ClaudeProxyError, ServiceUnavailableError) as e:
|
|
260
|
-
# Add error info to context for automatic access logging
|
|
261
240
|
ctx.add_metadata(error_message=str(e), error_type=type(e).__name__)
|
|
262
241
|
raise
|
|
263
242
|
|
|
@@ -265,27 +244,14 @@ class ClaudeSDKService:
|
|
|
265
244
|
self,
|
|
266
245
|
ctx: RequestContext,
|
|
267
246
|
messages: list[dict[str, Any]],
|
|
268
|
-
options:
|
|
247
|
+
options: ClaudeAgentOptions,
|
|
269
248
|
model: str,
|
|
270
249
|
session_id: str | None = None,
|
|
271
250
|
timestamp: str | None = None,
|
|
272
251
|
) -> MessageResponse:
|
|
273
|
-
"""
|
|
274
|
-
Complete a non-streaming request with business logic.
|
|
275
|
-
|
|
276
|
-
Args:
|
|
277
|
-
prompt: The formatted prompt
|
|
278
|
-
options: Claude SDK options
|
|
279
|
-
model: The model being used
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
Response in Anthropic format
|
|
283
|
-
|
|
284
|
-
Raises:
|
|
285
|
-
ClaudeProxyError: If completion fails
|
|
286
|
-
"""
|
|
252
|
+
"""Complete a non-streaming request with business logic."""
|
|
287
253
|
request_id = ctx.request_id
|
|
288
|
-
logger.debug("
|
|
254
|
+
logger.debug("completion_start", request_id=request_id)
|
|
289
255
|
|
|
290
256
|
# Convert messages to single SDKMessage
|
|
291
257
|
sdk_message = self._convert_messages_to_sdk_message(messages, session_id)
|
|
@@ -295,7 +261,7 @@ class ClaudeSDKService:
|
|
|
295
261
|
sdk_message, options, request_id, session_id
|
|
296
262
|
)
|
|
297
263
|
|
|
298
|
-
# Capture session metadata
|
|
264
|
+
# Capture session metadata
|
|
299
265
|
await self._capture_session_metadata(ctx, session_id, options)
|
|
300
266
|
|
|
301
267
|
# Create a listener and collect all messages
|
|
@@ -325,13 +291,13 @@ class ClaudeSDKService:
|
|
|
325
291
|
status_code=500,
|
|
326
292
|
)
|
|
327
293
|
|
|
328
|
-
logger.debug("
|
|
294
|
+
logger.debug("completion_received")
|
|
329
295
|
mode = (
|
|
330
|
-
self.
|
|
331
|
-
if self.
|
|
296
|
+
_convert_sdk_message_mode(self.config.sdk_message_mode)
|
|
297
|
+
if self.config
|
|
332
298
|
else SDKMessageMode.FORWARD
|
|
333
299
|
)
|
|
334
|
-
pretty_format = self.
|
|
300
|
+
pretty_format = self.config.pretty_format if self.config else True
|
|
335
301
|
|
|
336
302
|
response = self.message_converter.convert_to_anthropic_response(
|
|
337
303
|
assistant_message, result_message, model, mode, pretty_format
|
|
@@ -358,19 +324,19 @@ class ClaudeSDKService:
|
|
|
358
324
|
},
|
|
359
325
|
)
|
|
360
326
|
if content_block:
|
|
361
|
-
# Only validate as SDKMessageMode if it's a system_message type
|
|
362
327
|
if content_block.get("type") == "system_message":
|
|
363
328
|
response.content.append(
|
|
364
329
|
sdk_models.SDKMessageMode.model_validate(content_block)
|
|
365
330
|
)
|
|
366
331
|
else:
|
|
367
|
-
# For other types (like text blocks in FORMATTED mode), create appropriate content block
|
|
368
332
|
if content_block.get("type") == "text":
|
|
333
|
+
# Convert SDK TextBlock to core TextContentBlock
|
|
369
334
|
response.content.append(
|
|
370
|
-
|
|
335
|
+
anthropic_models.TextBlock(
|
|
336
|
+
type="text", text=content_block["text"]
|
|
337
|
+
)
|
|
371
338
|
)
|
|
372
339
|
else:
|
|
373
|
-
# Fallback for other content block types
|
|
374
340
|
logger.warning(
|
|
375
341
|
"unknown_content_block_type",
|
|
376
342
|
content_block_type=content_block.get("type"),
|
|
@@ -378,14 +344,20 @@ class ClaudeSDKService:
|
|
|
378
344
|
elif isinstance(message, sdk_models.UserMessage):
|
|
379
345
|
for block in message.content:
|
|
380
346
|
if isinstance(block, sdk_models.ToolResultBlock):
|
|
381
|
-
|
|
347
|
+
# Convert SDK ToolResultBlock to ToolResultSDKBlock
|
|
348
|
+
response.content.append(
|
|
349
|
+
sdk_models.ToolResultSDKBlock(
|
|
350
|
+
type="tool_result_sdk",
|
|
351
|
+
tool_use_id=block.tool_use_id,
|
|
352
|
+
content=block.content,
|
|
353
|
+
is_error=block.is_error,
|
|
354
|
+
source="claude_agent_sdk",
|
|
355
|
+
)
|
|
356
|
+
)
|
|
382
357
|
|
|
383
358
|
cost_usd = result_message.total_cost_usd
|
|
384
359
|
usage = result_message.usage_model
|
|
385
360
|
|
|
386
|
-
# if cost_usd is not None and response.usage:
|
|
387
|
-
# response.usage.cost_usd = cost_usd
|
|
388
|
-
|
|
389
361
|
logger.debug(
|
|
390
362
|
"claude_sdk_completion_completed",
|
|
391
363
|
model=model,
|
|
@@ -407,12 +379,6 @@ class ClaudeSDKService:
|
|
|
407
379
|
session_id=result_message.session_id,
|
|
408
380
|
num_turns=result_message.num_turns,
|
|
409
381
|
)
|
|
410
|
-
# Add success status to context for automatic access logging
|
|
411
|
-
ctx.add_metadata(status_code=200)
|
|
412
|
-
|
|
413
|
-
# Log SDK response
|
|
414
|
-
if request_id:
|
|
415
|
-
await self._log_sdk_response(request_id, response, timestamp)
|
|
416
382
|
|
|
417
383
|
return response
|
|
418
384
|
|
|
@@ -420,40 +386,29 @@ class ClaudeSDKService:
|
|
|
420
386
|
self,
|
|
421
387
|
ctx: RequestContext,
|
|
422
388
|
messages: list[dict[str, Any]],
|
|
423
|
-
options:
|
|
389
|
+
options: ClaudeAgentOptions,
|
|
424
390
|
model: str,
|
|
425
391
|
session_id: str | None = None,
|
|
426
392
|
timestamp: str | None = None,
|
|
427
393
|
) -> AsyncIterator[dict[str, Any]]:
|
|
428
|
-
"""
|
|
429
|
-
Stream completion responses with business logic.
|
|
430
|
-
|
|
431
|
-
Args:
|
|
432
|
-
prompt: The formatted prompt
|
|
433
|
-
options: Claude SDK options
|
|
434
|
-
model: The model being used
|
|
435
|
-
ctx: Optional request context for metrics
|
|
436
|
-
|
|
437
|
-
Yields:
|
|
438
|
-
Response chunks in Anthropic format
|
|
439
|
-
"""
|
|
394
|
+
"""Stream completion responses with business logic."""
|
|
440
395
|
request_id = ctx.request_id
|
|
441
396
|
sdk_message_mode = (
|
|
442
|
-
self.
|
|
443
|
-
if self.
|
|
397
|
+
_convert_sdk_message_mode(self.config.sdk_message_mode)
|
|
398
|
+
if self.config
|
|
444
399
|
else SDKMessageMode.FORWARD
|
|
445
400
|
)
|
|
446
|
-
pretty_format = self.
|
|
401
|
+
pretty_format = self.config.pretty_format if self.config else True
|
|
447
402
|
|
|
448
403
|
# Convert messages to single SDKMessage
|
|
449
404
|
sdk_message = self._convert_messages_to_sdk_message(messages, session_id)
|
|
450
405
|
|
|
451
|
-
# Get stream handle
|
|
406
|
+
# Get stream handle
|
|
452
407
|
stream_handle = await self.sdk_client.query_completion(
|
|
453
408
|
sdk_message, options, request_id, session_id
|
|
454
409
|
)
|
|
455
410
|
|
|
456
|
-
# Store handle in session client if available
|
|
411
|
+
# Store handle in session client if available
|
|
457
412
|
if (
|
|
458
413
|
session_id
|
|
459
414
|
and hasattr(self.sdk_client, "_session_manager")
|
|
@@ -472,9 +427,10 @@ class ClaudeSDKService:
|
|
|
472
427
|
"failed_to_store_stream_handle",
|
|
473
428
|
session_id=session_id,
|
|
474
429
|
error=str(e),
|
|
430
|
+
exc_info=e,
|
|
475
431
|
)
|
|
476
432
|
|
|
477
|
-
# Capture session metadata
|
|
433
|
+
# Capture session metadata
|
|
478
434
|
await self._capture_session_metadata(ctx, session_id, options)
|
|
479
435
|
|
|
480
436
|
# Create a listener for this stream
|
|
@@ -489,19 +445,13 @@ class ClaudeSDKService:
|
|
|
489
445
|
sdk_message_mode=sdk_message_mode,
|
|
490
446
|
pretty_format=pretty_format,
|
|
491
447
|
):
|
|
492
|
-
# Log streaming chunk
|
|
493
|
-
if request_id:
|
|
494
|
-
await self._log_sdk_streaming_chunk(request_id, chunk, timestamp)
|
|
495
448
|
yield chunk
|
|
496
449
|
except GeneratorExit:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"claude_sdk_service_client_disconnected",
|
|
450
|
+
logger.debug(
|
|
451
|
+
"claude_sdk_handler_client_disconnected",
|
|
500
452
|
request_id=request_id,
|
|
501
453
|
session_id=session_id,
|
|
502
|
-
message="Client disconnected from SDK service stream, propagating to stream handle",
|
|
503
454
|
)
|
|
504
|
-
# CRITICAL: Re-raise GeneratorExit to trigger interrupt in create_listener()
|
|
505
455
|
raise
|
|
506
456
|
except StreamTimeoutError as e:
|
|
507
457
|
# Send error events to the client
|
|
@@ -513,12 +463,9 @@ class ClaudeSDKService:
|
|
|
513
463
|
request_id=request_id,
|
|
514
464
|
)
|
|
515
465
|
|
|
516
|
-
# Create a unique message ID for the error response
|
|
517
|
-
from uuid import uuid4
|
|
518
|
-
|
|
519
466
|
error_message_id = f"msg_error_{uuid4()}"
|
|
520
467
|
|
|
521
|
-
# Yield
|
|
468
|
+
# Yield error events
|
|
522
469
|
yield {
|
|
523
470
|
"type": "message_start",
|
|
524
471
|
"message": {
|
|
@@ -533,14 +480,12 @@ class ClaudeSDKService:
|
|
|
533
480
|
},
|
|
534
481
|
}
|
|
535
482
|
|
|
536
|
-
# Yield content_block_start for error message
|
|
537
483
|
yield {
|
|
538
484
|
"type": "content_block_start",
|
|
539
485
|
"index": 0,
|
|
540
486
|
"content_block": {"type": "text", "text": ""},
|
|
541
487
|
}
|
|
542
488
|
|
|
543
|
-
# Yield error text delta
|
|
544
489
|
error_text = f"Error: {e}"
|
|
545
490
|
yield {
|
|
546
491
|
"type": "content_block_delta",
|
|
@@ -548,136 +493,25 @@ class ClaudeSDKService:
|
|
|
548
493
|
"delta": {"type": "text_delta", "text": error_text},
|
|
549
494
|
}
|
|
550
495
|
|
|
551
|
-
|
|
552
|
-
yield {
|
|
553
|
-
"type": "content_block_stop",
|
|
554
|
-
"index": 0,
|
|
555
|
-
}
|
|
496
|
+
yield {"type": "content_block_stop", "index": 0}
|
|
556
497
|
|
|
557
|
-
# Yield message_delta with stop reason
|
|
558
498
|
yield {
|
|
559
499
|
"type": "message_delta",
|
|
560
500
|
"delta": {"stop_reason": "error", "stop_sequence": None},
|
|
561
501
|
"usage": {"output_tokens": len(error_text.split())},
|
|
562
502
|
}
|
|
563
503
|
|
|
564
|
-
|
|
565
|
-
yield {
|
|
566
|
-
"type": "message_stop",
|
|
567
|
-
}
|
|
504
|
+
yield {"type": "message_stop"}
|
|
568
505
|
|
|
569
|
-
# Update context with error status
|
|
570
506
|
ctx.add_metadata(
|
|
571
|
-
status_code=504,
|
|
507
|
+
status_code=504,
|
|
572
508
|
error_message=str(e),
|
|
573
509
|
error_type="stream_timeout",
|
|
574
510
|
session_id=e.session_id,
|
|
575
511
|
)
|
|
576
512
|
|
|
577
|
-
async def _log_sdk_request(
|
|
578
|
-
self,
|
|
579
|
-
request_id: str,
|
|
580
|
-
messages: list[dict[str, Any]],
|
|
581
|
-
options: "ClaudeCodeOptions",
|
|
582
|
-
model: str,
|
|
583
|
-
stream: bool,
|
|
584
|
-
session_id: str | None = None,
|
|
585
|
-
timestamp: str | None = None,
|
|
586
|
-
) -> None:
|
|
587
|
-
"""Log SDK input parameters as JSON dump.
|
|
588
|
-
|
|
589
|
-
Args:
|
|
590
|
-
request_id: Request identifier
|
|
591
|
-
messages: List of Anthropic API messages
|
|
592
|
-
options: Claude SDK options
|
|
593
|
-
model: The model being used
|
|
594
|
-
stream: Whether streaming is enabled
|
|
595
|
-
session_id: Optional session ID for Claude SDK integration
|
|
596
|
-
timestamp: Optional timestamp prefix
|
|
597
|
-
"""
|
|
598
|
-
# timestamp is already provided from context, no need for fallback
|
|
599
|
-
|
|
600
|
-
# JSON dump of the parameters passed to SDK completion
|
|
601
|
-
sdk_request_data = {
|
|
602
|
-
"messages": messages,
|
|
603
|
-
"options": options,
|
|
604
|
-
"stream": stream,
|
|
605
|
-
"request_id": request_id,
|
|
606
|
-
}
|
|
607
|
-
if session_id:
|
|
608
|
-
sdk_request_data["session_id"] = session_id
|
|
609
|
-
|
|
610
|
-
await write_request_log(
|
|
611
|
-
request_id=request_id,
|
|
612
|
-
log_type="sdk_request",
|
|
613
|
-
data=sdk_request_data,
|
|
614
|
-
timestamp=timestamp,
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
async def _log_sdk_response(
|
|
618
|
-
self,
|
|
619
|
-
request_id: str,
|
|
620
|
-
result: Any,
|
|
621
|
-
timestamp: str | None = None,
|
|
622
|
-
) -> None:
|
|
623
|
-
"""Log SDK response result as JSON dump.
|
|
624
|
-
|
|
625
|
-
Args:
|
|
626
|
-
request_id: Request identifier
|
|
627
|
-
result: The result from _complete_non_streaming
|
|
628
|
-
timestamp: Optional timestamp prefix
|
|
629
|
-
"""
|
|
630
|
-
# timestamp is already provided from context, no need for fallback
|
|
631
|
-
|
|
632
|
-
# JSON dump of the result from _complete_non_streaming
|
|
633
|
-
sdk_response_data = {
|
|
634
|
-
"result": result.model_dump()
|
|
635
|
-
if hasattr(result, "model_dump")
|
|
636
|
-
else str(result),
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
await write_request_log(
|
|
640
|
-
request_id=request_id,
|
|
641
|
-
log_type="sdk_response",
|
|
642
|
-
data=sdk_response_data,
|
|
643
|
-
timestamp=timestamp,
|
|
644
|
-
)
|
|
645
|
-
|
|
646
|
-
async def _log_sdk_streaming_chunk(
|
|
647
|
-
self,
|
|
648
|
-
request_id: str,
|
|
649
|
-
chunk: dict[str, Any],
|
|
650
|
-
timestamp: str | None = None,
|
|
651
|
-
) -> None:
|
|
652
|
-
"""Log streaming chunk as JSON dump.
|
|
653
|
-
|
|
654
|
-
Args:
|
|
655
|
-
request_id: Request identifier
|
|
656
|
-
chunk: The streaming chunk from process_stream
|
|
657
|
-
timestamp: Optional timestamp prefix
|
|
658
|
-
"""
|
|
659
|
-
# timestamp is already provided from context, no need for fallback
|
|
660
|
-
|
|
661
|
-
# Append streaming chunk as JSON to raw file
|
|
662
|
-
import json
|
|
663
|
-
|
|
664
|
-
from ccproxy.utils.simple_request_logger import append_streaming_log
|
|
665
|
-
|
|
666
|
-
chunk_data = json.dumps(chunk, default=str) + "\n"
|
|
667
|
-
await append_streaming_log(
|
|
668
|
-
request_id=request_id,
|
|
669
|
-
log_type="sdk_streaming",
|
|
670
|
-
data=chunk_data.encode("utf-8"),
|
|
671
|
-
timestamp=timestamp,
|
|
672
|
-
)
|
|
673
|
-
|
|
674
513
|
async def validate_health(self) -> bool:
|
|
675
|
-
"""
|
|
676
|
-
Validate that the service is healthy.
|
|
677
|
-
|
|
678
|
-
Returns:
|
|
679
|
-
True if healthy, False otherwise
|
|
680
|
-
"""
|
|
514
|
+
"""Validate that the handler is healthy."""
|
|
681
515
|
try:
|
|
682
516
|
return await self.sdk_client.validate_health()
|
|
683
517
|
except Exception as e:
|
|
@@ -685,29 +519,14 @@ class ClaudeSDKService:
|
|
|
685
519
|
"health_check_failed",
|
|
686
520
|
error=str(e),
|
|
687
521
|
error_type=type(e).__name__,
|
|
688
|
-
exc_info=
|
|
522
|
+
exc_info=e,
|
|
689
523
|
)
|
|
690
524
|
return False
|
|
691
525
|
|
|
692
526
|
async def interrupt_session(self, session_id: str) -> bool:
|
|
693
|
-
"""Interrupt a Claude session due to client disconnection.
|
|
694
|
-
|
|
695
|
-
Args:
|
|
696
|
-
session_id: The session ID to interrupt
|
|
697
|
-
|
|
698
|
-
Returns:
|
|
699
|
-
True if session was found and interrupted, False otherwise
|
|
700
|
-
"""
|
|
527
|
+
"""Interrupt a Claude session due to client disconnection."""
|
|
701
528
|
return await self.sdk_client.interrupt_session(session_id)
|
|
702
529
|
|
|
703
530
|
async def close(self) -> None:
|
|
704
|
-
"""Close the
|
|
531
|
+
"""Close the handler and cleanup resources."""
|
|
705
532
|
await self.sdk_client.close()
|
|
706
|
-
|
|
707
|
-
async def __aenter__(self) -> "ClaudeSDKService":
|
|
708
|
-
"""Async context manager entry."""
|
|
709
|
-
return self
|
|
710
|
-
|
|
711
|
-
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
712
|
-
"""Async context manager exit."""
|
|
713
|
-
await self.close()
|