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,239 +1,80 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Stream processing utilities for converting between different streaming formats.
|
|
2
2
|
|
|
3
|
-
This module provides
|
|
4
|
-
streaming
|
|
3
|
+
This module provides stream processors that convert between different LLM
|
|
4
|
+
streaming response formats (e.g., Anthropic to OpenAI, OpenAI to Anthropic).
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
import json
|
|
8
|
+
import os
|
|
10
9
|
import time
|
|
11
10
|
from collections.abc import AsyncIterator
|
|
12
11
|
from typing import Any, Literal
|
|
13
12
|
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
from .models import (
|
|
17
|
-
generate_openai_response_id,
|
|
18
|
-
)
|
|
13
|
+
from ccproxy.core.logging import get_logger
|
|
19
14
|
|
|
15
|
+
from .formatters import AnthropicSSEFormatter, OpenAISSEFormatter
|
|
20
16
|
|
|
21
|
-
logger = structlog.get_logger(__name__)
|
|
22
17
|
|
|
18
|
+
logger = get_logger(__name__)
|
|
23
19
|
|
|
24
|
-
class OpenAISSEFormatter:
|
|
25
|
-
"""Formats streaming responses to match OpenAI's SSE format."""
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"""Format a data event for OpenAI-compatible Server-Sent Events.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
data: Event data dictionary
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Formatted SSE string
|
|
36
|
-
"""
|
|
37
|
-
json_data = json.dumps(data, separators=(",", ":"))
|
|
38
|
-
return f"data: {json_data}\n\n"
|
|
21
|
+
class AnthropicStreamProcessor:
|
|
22
|
+
"""Processes OpenAI streaming data into Anthropic SSE format."""
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
message_id: str, model: str, created: int, role: str = "assistant"
|
|
43
|
-
) -> str:
|
|
44
|
-
"""Format the first chunk with role and basic metadata.
|
|
24
|
+
def __init__(self, model: str = "claude-3-5-sonnet-20241022"):
|
|
25
|
+
"""Initialize the stream processor.
|
|
45
26
|
|
|
46
27
|
Args:
|
|
47
|
-
|
|
48
|
-
model: Model name being used
|
|
49
|
-
created: Unix timestamp when the completion was created
|
|
50
|
-
role: Role of the assistant
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
Formatted SSE string
|
|
28
|
+
model: Model name for responses
|
|
54
29
|
"""
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"object": "chat.completion.chunk",
|
|
58
|
-
"created": created,
|
|
59
|
-
"model": model,
|
|
60
|
-
"choices": [
|
|
61
|
-
{
|
|
62
|
-
"index": 0,
|
|
63
|
-
"delta": {"role": role},
|
|
64
|
-
"logprobs": None,
|
|
65
|
-
"finish_reason": None,
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
}
|
|
69
|
-
return OpenAISSEFormatter.format_data_event(data)
|
|
70
|
-
|
|
71
|
-
@staticmethod
|
|
72
|
-
def format_content_chunk(
|
|
73
|
-
message_id: str, model: str, created: int, content: str, choice_index: int = 0
|
|
74
|
-
) -> str:
|
|
75
|
-
"""Format a content chunk with text delta.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
message_id: Unique identifier for the completion
|
|
79
|
-
model: Model name being used
|
|
80
|
-
created: Unix timestamp when the completion was created
|
|
81
|
-
content: Text content to include in the delta
|
|
82
|
-
choice_index: Index of the choice (usually 0)
|
|
30
|
+
self.model = model
|
|
31
|
+
self.formatter = AnthropicSSEFormatter()
|
|
83
32
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
data
|
|
88
|
-
"id": message_id,
|
|
89
|
-
"object": "chat.completion.chunk",
|
|
90
|
-
"created": created,
|
|
91
|
-
"model": model,
|
|
92
|
-
"choices": [
|
|
93
|
-
{
|
|
94
|
-
"index": choice_index,
|
|
95
|
-
"delta": {"content": content},
|
|
96
|
-
"logprobs": None,
|
|
97
|
-
"finish_reason": None,
|
|
98
|
-
}
|
|
99
|
-
],
|
|
100
|
-
}
|
|
101
|
-
return OpenAISSEFormatter.format_data_event(data)
|
|
102
|
-
|
|
103
|
-
@staticmethod
|
|
104
|
-
def format_tool_call_chunk(
|
|
105
|
-
message_id: str,
|
|
106
|
-
model: str,
|
|
107
|
-
created: int,
|
|
108
|
-
tool_call_id: str,
|
|
109
|
-
function_name: str | None = None,
|
|
110
|
-
function_arguments: str | None = None,
|
|
111
|
-
tool_call_index: int = 0,
|
|
112
|
-
choice_index: int = 0,
|
|
113
|
-
) -> str:
|
|
114
|
-
"""Format a tool call chunk.
|
|
33
|
+
async def process_stream(
|
|
34
|
+
self, stream: AsyncIterator[dict[str, Any]]
|
|
35
|
+
) -> AsyncIterator[str]:
|
|
36
|
+
"""Process OpenAI-format streaming data into Anthropic SSE format.
|
|
115
37
|
|
|
116
38
|
Args:
|
|
117
|
-
|
|
118
|
-
model: Model name being used
|
|
119
|
-
created: Unix timestamp when the completion was created
|
|
120
|
-
tool_call_id: ID of the tool call
|
|
121
|
-
function_name: Name of the function being called
|
|
122
|
-
function_arguments: Arguments for the function
|
|
123
|
-
tool_call_index: Index of the tool call
|
|
124
|
-
choice_index: Index of the choice (usually 0)
|
|
39
|
+
stream: Async iterator of OpenAI-style response chunks
|
|
125
40
|
|
|
126
|
-
|
|
127
|
-
|
|
41
|
+
Yields:
|
|
42
|
+
Anthropic-formatted SSE strings with proper event: lines
|
|
128
43
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"id": tool_call_id,
|
|
132
|
-
"type": "function",
|
|
133
|
-
"function": {},
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if function_name is not None:
|
|
137
|
-
tool_call["function"]["name"] = function_name
|
|
138
|
-
|
|
139
|
-
if function_arguments is not None:
|
|
140
|
-
tool_call["function"]["arguments"] = function_arguments
|
|
141
|
-
|
|
142
|
-
data = {
|
|
143
|
-
"id": message_id,
|
|
144
|
-
"object": "chat.completion.chunk",
|
|
145
|
-
"created": created,
|
|
146
|
-
"model": model,
|
|
147
|
-
"choices": [
|
|
148
|
-
{
|
|
149
|
-
"index": choice_index,
|
|
150
|
-
"delta": {"tool_calls": [tool_call]},
|
|
151
|
-
"logprobs": None,
|
|
152
|
-
"finish_reason": None,
|
|
153
|
-
}
|
|
154
|
-
],
|
|
155
|
-
}
|
|
156
|
-
return OpenAISSEFormatter.format_data_event(data)
|
|
157
|
-
|
|
158
|
-
@staticmethod
|
|
159
|
-
def format_final_chunk(
|
|
160
|
-
message_id: str,
|
|
161
|
-
model: str,
|
|
162
|
-
created: int,
|
|
163
|
-
finish_reason: str = "stop",
|
|
164
|
-
choice_index: int = 0,
|
|
165
|
-
usage: dict[str, int] | None = None,
|
|
166
|
-
) -> str:
|
|
167
|
-
"""Format the final chunk with finish_reason.
|
|
44
|
+
message_started = False
|
|
45
|
+
content_block_started = False
|
|
168
46
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
created: Unix timestamp when the completion was created
|
|
173
|
-
finish_reason: Reason for completion (stop, length, tool_calls, etc.)
|
|
174
|
-
choice_index: Index of the choice (usually 0)
|
|
175
|
-
usage: Optional usage information to include
|
|
47
|
+
async for chunk in stream:
|
|
48
|
+
if not isinstance(chunk, dict):
|
|
49
|
+
continue
|
|
176
50
|
|
|
177
|
-
|
|
178
|
-
Formatted SSE string
|
|
179
|
-
"""
|
|
180
|
-
data = {
|
|
181
|
-
"id": message_id,
|
|
182
|
-
"object": "chat.completion.chunk",
|
|
183
|
-
"created": created,
|
|
184
|
-
"model": model,
|
|
185
|
-
"choices": [
|
|
186
|
-
{
|
|
187
|
-
"index": choice_index,
|
|
188
|
-
"delta": {},
|
|
189
|
-
"logprobs": None,
|
|
190
|
-
"finish_reason": finish_reason,
|
|
191
|
-
}
|
|
192
|
-
],
|
|
193
|
-
}
|
|
51
|
+
chunk_type = chunk.get("type")
|
|
194
52
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
53
|
+
if chunk_type == "message_start":
|
|
54
|
+
if not message_started:
|
|
55
|
+
yield self.formatter.format_event("message_start", chunk)
|
|
56
|
+
message_started = True
|
|
198
57
|
|
|
199
|
-
|
|
58
|
+
elif chunk_type == "content_block_start":
|
|
59
|
+
if not content_block_started:
|
|
60
|
+
yield self.formatter.format_event("content_block_start", chunk)
|
|
61
|
+
content_block_started = True
|
|
200
62
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
message_id: str, model: str, created: int, error_type: str, error_message: str
|
|
204
|
-
) -> str:
|
|
205
|
-
"""Format an error chunk.
|
|
63
|
+
elif chunk_type == "content_block_delta":
|
|
64
|
+
yield self.formatter.format_event("content_block_delta", chunk)
|
|
206
65
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
model: Model name being used
|
|
210
|
-
created: Unix timestamp when the completion was created
|
|
211
|
-
error_type: Type of error
|
|
212
|
-
error_message: Error message
|
|
66
|
+
elif chunk_type == "ping":
|
|
67
|
+
yield self.formatter.format_ping()
|
|
213
68
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
"""
|
|
217
|
-
data = {
|
|
218
|
-
"id": message_id,
|
|
219
|
-
"object": "chat.completion.chunk",
|
|
220
|
-
"created": created,
|
|
221
|
-
"model": model,
|
|
222
|
-
"choices": [
|
|
223
|
-
{"index": 0, "delta": {}, "logprobs": None, "finish_reason": "error"}
|
|
224
|
-
],
|
|
225
|
-
"error": {"type": error_type, "message": error_message},
|
|
226
|
-
}
|
|
227
|
-
return OpenAISSEFormatter.format_data_event(data)
|
|
69
|
+
elif chunk_type == "content_block_stop":
|
|
70
|
+
yield self.formatter.format_event("content_block_stop", chunk)
|
|
228
71
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
"""Format the final DONE event.
|
|
72
|
+
elif chunk_type == "message_delta":
|
|
73
|
+
yield self.formatter.format_event("message_delta", chunk)
|
|
232
74
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return "data: [DONE]\n\n"
|
|
75
|
+
elif chunk_type == "message_stop":
|
|
76
|
+
yield self.formatter.format_event("message_stop", chunk)
|
|
77
|
+
break
|
|
237
78
|
|
|
238
79
|
|
|
239
80
|
class OpenAIStreamProcessor:
|
|
@@ -246,6 +87,7 @@ class OpenAIStreamProcessor:
|
|
|
246
87
|
created: int | None = None,
|
|
247
88
|
enable_usage: bool = True,
|
|
248
89
|
enable_tool_calls: bool = True,
|
|
90
|
+
enable_thinking_serialization: bool | None = None,
|
|
249
91
|
output_format: Literal["sse", "dict"] = "sse",
|
|
250
92
|
):
|
|
251
93
|
"""Initialize the stream processor.
|
|
@@ -258,12 +100,45 @@ class OpenAIStreamProcessor:
|
|
|
258
100
|
enable_tool_calls: Whether to process tool calls
|
|
259
101
|
output_format: Output format - "sse" for Server-Sent Events strings, "dict" for dict objects
|
|
260
102
|
"""
|
|
261
|
-
|
|
103
|
+
# Import here to avoid circular imports
|
|
104
|
+
from ccproxy.llms.models.openai import generate_responses_id
|
|
105
|
+
|
|
106
|
+
self.message_id = message_id or generate_responses_id()
|
|
262
107
|
self.model = model
|
|
263
108
|
self.created = created or int(time.time())
|
|
264
109
|
self.enable_usage = enable_usage
|
|
265
110
|
self.enable_tool_calls = enable_tool_calls
|
|
266
111
|
self.output_format = output_format
|
|
112
|
+
if enable_thinking_serialization is None:
|
|
113
|
+
# Prefer service Settings.llm.openai_thinking_xml if available
|
|
114
|
+
setting_val: bool | None = None
|
|
115
|
+
try:
|
|
116
|
+
from ccproxy.config.settings import Settings
|
|
117
|
+
|
|
118
|
+
cfg = Settings.from_config()
|
|
119
|
+
setting_val = bool(
|
|
120
|
+
getattr(getattr(cfg, "llm", {}), "openai_thinking_xml", True)
|
|
121
|
+
)
|
|
122
|
+
except Exception:
|
|
123
|
+
setting_val = None
|
|
124
|
+
|
|
125
|
+
if setting_val is not None:
|
|
126
|
+
self.enable_thinking_serialization = setting_val
|
|
127
|
+
else:
|
|
128
|
+
# Fallback to env-based toggle
|
|
129
|
+
env_val = (
|
|
130
|
+
os.getenv("LLM__OPENAI_THINKING_XML")
|
|
131
|
+
or os.getenv("OPENAI_STREAM_ENABLE_THINKING_SERIALIZATION")
|
|
132
|
+
or "true"
|
|
133
|
+
).lower()
|
|
134
|
+
self.enable_thinking_serialization = env_val not in (
|
|
135
|
+
"0",
|
|
136
|
+
"false",
|
|
137
|
+
"no",
|
|
138
|
+
"off",
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
self.enable_thinking_serialization = enable_thinking_serialization
|
|
267
142
|
self.formatter = OpenAISSEFormatter()
|
|
268
143
|
|
|
269
144
|
# State tracking
|
|
@@ -287,31 +162,44 @@ class OpenAIStreamProcessor:
|
|
|
287
162
|
Yields:
|
|
288
163
|
OpenAI-formatted SSE strings or dict objects based on output_format
|
|
289
164
|
"""
|
|
165
|
+
# Get logger with request context at the start of the function
|
|
166
|
+
logger = get_logger(__name__)
|
|
167
|
+
|
|
290
168
|
try:
|
|
291
169
|
chunk_count = 0
|
|
292
170
|
processed_count = 0
|
|
171
|
+
logger.debug(
|
|
172
|
+
"openai_stream_processor_start",
|
|
173
|
+
message_id=self.message_id,
|
|
174
|
+
model=self.model,
|
|
175
|
+
output_format=self.output_format,
|
|
176
|
+
enable_usage=self.enable_usage,
|
|
177
|
+
enable_tool_calls=self.enable_tool_calls,
|
|
178
|
+
category="streaming_conversion",
|
|
179
|
+
enable_thinking_serialization=self.enable_thinking_serialization,
|
|
180
|
+
)
|
|
181
|
+
|
|
293
182
|
async for chunk in claude_stream:
|
|
294
183
|
chunk_count += 1
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
184
|
+
chunk_type = chunk.get("type", "unknown")
|
|
185
|
+
|
|
186
|
+
logger.trace(
|
|
187
|
+
"openai_processor_input_chunk",
|
|
188
|
+
chunk_number=chunk_count,
|
|
189
|
+
chunk_type=chunk_type,
|
|
190
|
+
category="format_detection",
|
|
300
191
|
)
|
|
192
|
+
|
|
301
193
|
async for sse_chunk in self._process_chunk(chunk):
|
|
302
194
|
processed_count += 1
|
|
303
|
-
logger.debug(
|
|
304
|
-
"openai_stream_chunk_processed",
|
|
305
|
-
processed_count=processed_count,
|
|
306
|
-
sse_chunk=sse_chunk,
|
|
307
|
-
)
|
|
308
195
|
yield sse_chunk
|
|
309
196
|
|
|
310
197
|
logger.debug(
|
|
311
198
|
"openai_stream_complete",
|
|
312
199
|
total_chunks=chunk_count,
|
|
313
200
|
processed_chunks=processed_count,
|
|
314
|
-
|
|
201
|
+
message_id=self.message_id,
|
|
202
|
+
category="streaming_conversion",
|
|
315
203
|
)
|
|
316
204
|
|
|
317
205
|
# Send final chunk
|
|
@@ -327,7 +215,23 @@ class OpenAIStreamProcessor:
|
|
|
327
215
|
if self.output_format == "sse":
|
|
328
216
|
yield self.formatter.format_done()
|
|
329
217
|
|
|
218
|
+
except (OSError, PermissionError) as e:
|
|
219
|
+
logger.error("stream_processing_io_error", error=str(e), exc_info=e)
|
|
220
|
+
# Send error chunk for IO errors
|
|
221
|
+
if self.output_format == "sse":
|
|
222
|
+
yield self.formatter.format_error_chunk(
|
|
223
|
+
self.message_id,
|
|
224
|
+
self.model,
|
|
225
|
+
self.created,
|
|
226
|
+
"error",
|
|
227
|
+
f"IO error: {str(e)}",
|
|
228
|
+
)
|
|
229
|
+
yield self.formatter.format_done()
|
|
230
|
+
else:
|
|
231
|
+
# Dict format error
|
|
232
|
+
yield self._create_chunk_dict(finish_reason="error")
|
|
330
233
|
except Exception as e:
|
|
234
|
+
logger.error("stream_processing_error", error=str(e), exc_info=e)
|
|
331
235
|
# Send error chunk
|
|
332
236
|
if self.output_format == "sse":
|
|
333
237
|
yield self.formatter.format_error_chunk(
|
|
@@ -357,14 +261,29 @@ class OpenAIStreamProcessor:
|
|
|
357
261
|
# Claude SDK format
|
|
358
262
|
chunk_data = chunk.get("data", {})
|
|
359
263
|
chunk_type = chunk_data.get("type")
|
|
264
|
+
format_source = "claude_sdk"
|
|
360
265
|
else:
|
|
361
266
|
# Standard Anthropic API format
|
|
362
267
|
chunk_data = chunk
|
|
363
268
|
chunk_type = chunk.get("type")
|
|
269
|
+
format_source = "anthropic_api"
|
|
270
|
+
|
|
271
|
+
logger.trace(
|
|
272
|
+
"openai_processor_chunk_conversion",
|
|
273
|
+
format_source=format_source,
|
|
274
|
+
chunk_type=chunk_type,
|
|
275
|
+
event_type=event_type,
|
|
276
|
+
category="format_detection",
|
|
277
|
+
)
|
|
364
278
|
|
|
365
279
|
if chunk_type == "message_start":
|
|
366
280
|
# Send initial role chunk
|
|
367
281
|
if not self.role_sent:
|
|
282
|
+
logger.trace(
|
|
283
|
+
"openai_conversion_message_start",
|
|
284
|
+
action="sending_role_chunk",
|
|
285
|
+
category="streaming_conversion",
|
|
286
|
+
)
|
|
368
287
|
yield self._format_chunk_output(delta={"role": "assistant"})
|
|
369
288
|
self.role_sent = True
|
|
370
289
|
|
|
@@ -378,7 +297,7 @@ class OpenAIStreamProcessor:
|
|
|
378
297
|
elif block.get("type") == "system_message":
|
|
379
298
|
# Handle system message content block
|
|
380
299
|
system_text = block.get("text", "")
|
|
381
|
-
source = block.get("source", "
|
|
300
|
+
source = block.get("source", "ccproxy")
|
|
382
301
|
# Format as text with clear source attribution
|
|
383
302
|
formatted_text = f"[{source}]: {system_text}"
|
|
384
303
|
yield self._format_chunk_output(delta={"content": formatted_text})
|
|
@@ -387,7 +306,7 @@ class OpenAIStreamProcessor:
|
|
|
387
306
|
tool_id = block.get("id", "")
|
|
388
307
|
tool_name = block.get("name", "")
|
|
389
308
|
tool_input = block.get("input", {})
|
|
390
|
-
source = block.get("source", "
|
|
309
|
+
source = block.get("source", "ccproxy")
|
|
391
310
|
|
|
392
311
|
# For dict format, immediately yield the tool call
|
|
393
312
|
if self.output_format == "dict":
|
|
@@ -416,7 +335,7 @@ class OpenAIStreamProcessor:
|
|
|
416
335
|
}
|
|
417
336
|
elif block.get("type") == "tool_result_sdk":
|
|
418
337
|
# Handle custom tool_result_sdk content block
|
|
419
|
-
source = block.get("source", "
|
|
338
|
+
source = block.get("source", "ccproxy")
|
|
420
339
|
tool_use_id = block.get("tool_use_id", "")
|
|
421
340
|
result_content = block.get("content", "")
|
|
422
341
|
is_error = block.get("is_error", False)
|
|
@@ -425,7 +344,7 @@ class OpenAIStreamProcessor:
|
|
|
425
344
|
yield self._format_chunk_output(delta={"content": formatted_text})
|
|
426
345
|
elif block.get("type") == "result_message":
|
|
427
346
|
# Handle custom result_message content block
|
|
428
|
-
source = block.get("source", "
|
|
347
|
+
source = block.get("source", "ccproxy")
|
|
429
348
|
result_data = block.get("data", {})
|
|
430
349
|
session_id = result_data.get("session_id", "")
|
|
431
350
|
stop_reason = result_data.get("stop_reason", "")
|
|
@@ -454,6 +373,11 @@ class OpenAIStreamProcessor:
|
|
|
454
373
|
# Text content
|
|
455
374
|
text = delta.get("text", "")
|
|
456
375
|
if text:
|
|
376
|
+
logger.trace(
|
|
377
|
+
"openai_conversion_text_delta",
|
|
378
|
+
text_length=len(text),
|
|
379
|
+
category="streaming_conversion",
|
|
380
|
+
)
|
|
457
381
|
yield self._format_chunk_output(delta={"content": text})
|
|
458
382
|
|
|
459
383
|
elif delta_type == "thinking_delta" and self.thinking_block_active:
|
|
@@ -485,24 +409,45 @@ class OpenAIStreamProcessor:
|
|
|
485
409
|
self.thinking_block_active = False
|
|
486
410
|
if self.current_thinking_text:
|
|
487
411
|
# Format thinking block with signature
|
|
488
|
-
|
|
489
|
-
|
|
412
|
+
if self.enable_thinking_serialization:
|
|
413
|
+
thinking_content = (
|
|
414
|
+
f'<thinking signature="{self.current_thinking_signature}">'
|
|
415
|
+
f"{self.current_thinking_text}</thinking>"
|
|
416
|
+
)
|
|
417
|
+
yield self._format_chunk_output(
|
|
418
|
+
delta={"content": thinking_content}
|
|
419
|
+
)
|
|
490
420
|
# Reset thinking state
|
|
491
421
|
self.current_thinking_text = ""
|
|
492
422
|
self.current_thinking_signature = None
|
|
493
423
|
|
|
494
|
-
elif
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
424
|
+
elif self.tool_calls and self.enable_tool_calls:
|
|
425
|
+
# Send completed tool calls for both SSE and dict formats
|
|
426
|
+
# Previous bug: Only sent for SSE format, causing dict format (SDK mode) to miss tool calls
|
|
427
|
+
logger.trace(
|
|
428
|
+
"openai_stream_sending_tool_calls",
|
|
429
|
+
tool_count=len(self.tool_calls),
|
|
430
|
+
output_format=self.output_format,
|
|
431
|
+
category="streaming_conversion",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
for tool_call_index, (tool_call_id, tool_call) in enumerate(
|
|
435
|
+
self.tool_calls.items()
|
|
436
|
+
):
|
|
437
|
+
logger.trace(
|
|
438
|
+
"openai_stream_tool_call_yielding",
|
|
439
|
+
tool_call_id=tool_call_id,
|
|
440
|
+
tool_name=tool_call["name"],
|
|
441
|
+
has_arguments=bool(tool_call["arguments"]),
|
|
442
|
+
index=tool_call_index,
|
|
443
|
+
category="streaming_conversion",
|
|
444
|
+
)
|
|
445
|
+
|
|
501
446
|
yield self._format_chunk_output(
|
|
502
447
|
delta={
|
|
503
448
|
"tool_calls": [
|
|
504
449
|
{
|
|
505
|
-
"index":
|
|
450
|
+
"index": tool_call_index,
|
|
506
451
|
"id": tool_call["id"],
|
|
507
452
|
"type": "function",
|
|
508
453
|
"function": {
|
|
@@ -516,10 +461,24 @@ class OpenAIStreamProcessor:
|
|
|
516
461
|
}
|
|
517
462
|
)
|
|
518
463
|
|
|
464
|
+
# Clear tool_calls after yielding to prevent duplicates
|
|
465
|
+
logger.trace(
|
|
466
|
+
"openai_stream_clearing_tool_calls",
|
|
467
|
+
cleared_count=len(self.tool_calls),
|
|
468
|
+
category="streaming_conversion",
|
|
469
|
+
)
|
|
470
|
+
self.tool_calls.clear()
|
|
471
|
+
|
|
519
472
|
elif chunk_type == "message_delta":
|
|
520
473
|
# Usage information
|
|
521
474
|
usage = chunk_data.get("usage", {})
|
|
522
475
|
if usage and self.enable_usage:
|
|
476
|
+
logger.trace(
|
|
477
|
+
"openai_conversion_usage_info",
|
|
478
|
+
input_tokens=usage.get("input_tokens", 0),
|
|
479
|
+
output_tokens=usage.get("output_tokens", 0),
|
|
480
|
+
category="streaming_conversion",
|
|
481
|
+
)
|
|
523
482
|
self.usage_info = {
|
|
524
483
|
"prompt_tokens": usage.get("input_tokens", 0),
|
|
525
484
|
"completion_tokens": usage.get("output_tokens", 0),
|
|
@@ -620,13 +579,7 @@ class OpenAIStreamProcessor:
|
|
|
620
579
|
tool_call.get("function", {}).get("arguments"),
|
|
621
580
|
)
|
|
622
581
|
else:
|
|
623
|
-
# Empty delta
|
|
624
|
-
return self.formatter.
|
|
625
|
-
self.message_id, self.model, self.created, "
|
|
582
|
+
# Empty delta - send chunk with null finish_reason
|
|
583
|
+
return self.formatter.format_content_chunk(
|
|
584
|
+
self.message_id, self.model, self.created, ""
|
|
626
585
|
)
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
__all__ = [
|
|
630
|
-
"OpenAISSEFormatter",
|
|
631
|
-
"OpenAIStreamProcessor",
|
|
632
|
-
]
|