ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +434 -219
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +144 -168
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +388 -524
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +540 -19
- ccproxy/data/codex_headers_fallback.json +114 -7
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +61 -105
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +268 -276
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +68 -446
- ccproxy/utils/version_checker.py +273 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1251
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -243
- ccproxy/services/codex_detection_service.py +0 -252
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.7.dist-info/METADATA +0 -615
- ccproxy_api-0.1.7.dist-info/RECORD +0 -191
- ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
"""Direct dict-based conversion functions for use with DictFormatAdapter.
|
|
2
|
+
|
|
3
|
+
This module provides simple wrapper functions around the existing formatter functions
|
|
4
|
+
that operate directly on dictionaries instead of typed Pydantic models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import AsyncIterator
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .protocols import StreamAccumulatorProtocol
|
|
15
|
+
|
|
16
|
+
from pydantic import TypeAdapter, ValidationError
|
|
17
|
+
|
|
18
|
+
from ccproxy.core import logging
|
|
19
|
+
from ccproxy.core.constants import (
|
|
20
|
+
FORMAT_ANTHROPIC_MESSAGES as ANTHROPIC_MESSAGES,
|
|
21
|
+
)
|
|
22
|
+
from ccproxy.core.constants import (
|
|
23
|
+
FORMAT_OPENAI_CHAT as OPENAI_CHAT,
|
|
24
|
+
)
|
|
25
|
+
from ccproxy.core.constants import (
|
|
26
|
+
FORMAT_OPENAI_RESPONSES as OPENAI_RESPONSES,
|
|
27
|
+
)
|
|
28
|
+
from ccproxy.llms.formatters import (
|
|
29
|
+
anthropic_to_openai,
|
|
30
|
+
openai_to_anthropic,
|
|
31
|
+
openai_to_openai,
|
|
32
|
+
)
|
|
33
|
+
from ccproxy.llms.formatters import anthropic_to_openai as a2o
|
|
34
|
+
from ccproxy.llms.models import anthropic as anthropic_models
|
|
35
|
+
from ccproxy.llms.models import openai as openai_models
|
|
36
|
+
from ccproxy.llms.models.anthropic import MessageStreamEvent
|
|
37
|
+
|
|
38
|
+
from .format_adapter import DictFormatAdapter
|
|
39
|
+
from .format_registry import FormatRegistry
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
FormatDict = dict[str, Any]
|
|
43
|
+
|
|
44
|
+
logger = logging.get_logger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_type_adapter_cache: dict[Any, TypeAdapter[Any]] = {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _validate_stream_event(model: Any, data: dict[str, Any]) -> Any:
|
|
51
|
+
"""Validate a streaming event against the provided model.
|
|
52
|
+
|
|
53
|
+
Raises ValidationError when the payload does not conform so callers can fail fast.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
adapter = _type_adapter_cache.get(model)
|
|
57
|
+
if adapter is None:
|
|
58
|
+
adapter = TypeAdapter(model)
|
|
59
|
+
_type_adapter_cache[model] = adapter
|
|
60
|
+
return adapter.validate_python(data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Generic stream mapper to DRY conversion loops
|
|
64
|
+
async def map_stream(
|
|
65
|
+
stream: AsyncIterator[FormatDict],
|
|
66
|
+
*,
|
|
67
|
+
validator_model: Any,
|
|
68
|
+
converter: Any,
|
|
69
|
+
accumulator: StreamAccumulatorProtocol | None = None,
|
|
70
|
+
) -> AsyncIterator[FormatDict]:
|
|
71
|
+
"""Map stream with optional accumulation before validation.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
stream: Input stream of format dictionaries
|
|
75
|
+
validator_model: Pydantic model for validation
|
|
76
|
+
converter: Converter function to apply to validated stream
|
|
77
|
+
accumulator: Optional accumulator for handling partial chunks
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Converted stream
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
async def _typed_stream() -> AsyncIterator[Any]:
|
|
84
|
+
async for chunk_data in stream:
|
|
85
|
+
if accumulator:
|
|
86
|
+
# Accumulate chunk and get complete object if ready
|
|
87
|
+
complete_object = accumulator.accumulate_chunk(chunk_data)
|
|
88
|
+
|
|
89
|
+
if complete_object is None:
|
|
90
|
+
# Still accumulating, skip this chunk
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Have complete object, validate it
|
|
94
|
+
try:
|
|
95
|
+
yield _validate_stream_event(validator_model, complete_object)
|
|
96
|
+
except ValidationError as exc:
|
|
97
|
+
logger.debug(
|
|
98
|
+
"stream_chunk_validation_failed",
|
|
99
|
+
model=str(validator_model),
|
|
100
|
+
error=str(exc),
|
|
101
|
+
action="raise",
|
|
102
|
+
)
|
|
103
|
+
raise
|
|
104
|
+
else:
|
|
105
|
+
# No accumulator, validate directly
|
|
106
|
+
try:
|
|
107
|
+
yield _validate_stream_event(validator_model, chunk_data)
|
|
108
|
+
except ValidationError as exc:
|
|
109
|
+
logger.debug(
|
|
110
|
+
"stream_chunk_validation_failed",
|
|
111
|
+
model=str(validator_model),
|
|
112
|
+
error=str(exc),
|
|
113
|
+
action="raise",
|
|
114
|
+
)
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
converted_chunks = converter(_typed_stream())
|
|
118
|
+
async for converted_chunk in converted_chunks:
|
|
119
|
+
if hasattr(converted_chunk, "model_dump"):
|
|
120
|
+
yield converted_chunk.model_dump(exclude_unset=True)
|
|
121
|
+
else:
|
|
122
|
+
yield converted_chunk
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# OpenAI to Anthropic converters (for plugins that target Anthropic APIs)
|
|
126
|
+
async def convert_openai_to_anthropic_request(data: FormatDict) -> FormatDict:
|
|
127
|
+
"""Convert OpenAI ChatCompletion request to Anthropic CreateMessage request."""
|
|
128
|
+
# Convert dict to typed model
|
|
129
|
+
request = openai_models.ChatCompletionRequest.model_validate(data)
|
|
130
|
+
|
|
131
|
+
# Use existing formatter function
|
|
132
|
+
result = (
|
|
133
|
+
await openai_to_anthropic.convert__openai_chat_to_anthropic_message__request(
|
|
134
|
+
request
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Convert back to dict
|
|
139
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
140
|
+
return result_dict
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def convert_anthropic_to_openai_response(data: FormatDict) -> FormatDict:
|
|
144
|
+
"""Convert Anthropic MessageResponse to OpenAI ChatCompletion response."""
|
|
145
|
+
# Convert dict to typed model
|
|
146
|
+
response = anthropic_models.MessageResponse.model_validate(data)
|
|
147
|
+
|
|
148
|
+
# Use existing formatter function
|
|
149
|
+
result = anthropic_to_openai.convert__anthropic_message_to_openai_chat__response(
|
|
150
|
+
response
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Convert back to dict
|
|
154
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
155
|
+
return result_dict
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def convert_anthropic_to_openai_stream(
|
|
159
|
+
stream: AsyncIterator[FormatDict],
|
|
160
|
+
) -> AsyncIterator[FormatDict]:
|
|
161
|
+
"""Convert Anthropic MessageStream to OpenAI ChatCompletion stream."""
|
|
162
|
+
|
|
163
|
+
async for out_chunk in map_stream(
|
|
164
|
+
stream,
|
|
165
|
+
validator_model=anthropic_models.MessageStreamEvent,
|
|
166
|
+
converter=anthropic_to_openai.convert__anthropic_message_to_openai_chat__stream,
|
|
167
|
+
):
|
|
168
|
+
yield out_chunk
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def convert_openai_to_anthropic_error(data: FormatDict) -> FormatDict:
|
|
172
|
+
"""Convert OpenAI error to Anthropic error."""
|
|
173
|
+
# Convert dict to typed model
|
|
174
|
+
error = openai_models.ErrorResponse.model_validate(data)
|
|
175
|
+
|
|
176
|
+
# Use existing formatter function
|
|
177
|
+
result = openai_to_anthropic.convert__openai_to_anthropic__error(error)
|
|
178
|
+
|
|
179
|
+
# Convert back to dict
|
|
180
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
181
|
+
return result_dict
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Anthropic to OpenAI converters (reverse direction, if needed)
|
|
185
|
+
async def convert_anthropic_to_openai_request(data: FormatDict) -> FormatDict:
|
|
186
|
+
"""Convert Anthropic CreateMessage request to OpenAI ChatCompletion request."""
|
|
187
|
+
# Convert dict to typed model
|
|
188
|
+
request = anthropic_models.CreateMessageRequest.model_validate(data)
|
|
189
|
+
|
|
190
|
+
# Use existing formatter function
|
|
191
|
+
result = anthropic_to_openai.convert__anthropic_message_to_openai_chat__request(
|
|
192
|
+
request
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Convert back to dict
|
|
196
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
197
|
+
return result_dict
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def convert_openai_to_anthropic_response(data: FormatDict) -> FormatDict:
|
|
201
|
+
"""Convert OpenAI ChatCompletion response to Anthropic MessageResponse."""
|
|
202
|
+
# Convert dict to typed model
|
|
203
|
+
response = openai_models.ChatCompletionResponse.model_validate(data)
|
|
204
|
+
|
|
205
|
+
# Use existing formatter function
|
|
206
|
+
result = openai_to_anthropic.convert__openai_chat_to_anthropic_messages__response(
|
|
207
|
+
response
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Convert back to dict
|
|
211
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
212
|
+
return result_dict
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def convert_openai_to_anthropic_stream(
|
|
216
|
+
stream: AsyncIterator[FormatDict],
|
|
217
|
+
) -> AsyncIterator[FormatDict]:
|
|
218
|
+
"""Convert OpenAI ChatCompletion stream to Anthropic MessageStream."""
|
|
219
|
+
from .chat_accumulator import ChatCompletionAccumulator
|
|
220
|
+
|
|
221
|
+
# Use accumulator to handle partial tool calls
|
|
222
|
+
accumulator = ChatCompletionAccumulator()
|
|
223
|
+
|
|
224
|
+
async for out_chunk in map_stream(
|
|
225
|
+
stream,
|
|
226
|
+
validator_model=openai_models.ChatCompletionChunk,
|
|
227
|
+
converter=openai_to_anthropic.convert__openai_chat_to_anthropic_messages__stream,
|
|
228
|
+
accumulator=accumulator,
|
|
229
|
+
):
|
|
230
|
+
yield out_chunk
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def convert_anthropic_to_openai_error(data: FormatDict) -> FormatDict:
|
|
234
|
+
"""Convert Anthropic error to OpenAI error."""
|
|
235
|
+
# Convert dict to typed model
|
|
236
|
+
error = anthropic_models.ErrorResponse.model_validate(data)
|
|
237
|
+
|
|
238
|
+
# Use existing formatter function
|
|
239
|
+
result = anthropic_to_openai.convert__anthropic_to_openai__error(error)
|
|
240
|
+
|
|
241
|
+
# Convert back to dict
|
|
242
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
243
|
+
return result_dict
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# OpenAI Responses format converters (for Codex plugin)
|
|
247
|
+
async def convert_openai_responses_to_anthropic_request(data: FormatDict) -> FormatDict:
|
|
248
|
+
"""Convert OpenAI Responses request to Anthropic CreateMessage request."""
|
|
249
|
+
# Convert dict to typed model
|
|
250
|
+
request = openai_models.ResponseRequest.model_validate(data)
|
|
251
|
+
|
|
252
|
+
# Use existing formatter function
|
|
253
|
+
result = (
|
|
254
|
+
openai_to_anthropic.convert__openai_responses_to_anthropic_message__request(
|
|
255
|
+
request
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Convert back to dict
|
|
260
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
261
|
+
return result_dict
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
async def convert_openai_responses_to_anthropic_response(
|
|
265
|
+
data: FormatDict,
|
|
266
|
+
) -> FormatDict:
|
|
267
|
+
"""Convert OpenAI Responses response to Anthropic MessageResponse."""
|
|
268
|
+
# Convert dict to typed model
|
|
269
|
+
response = openai_models.ResponseObject.model_validate(data)
|
|
270
|
+
|
|
271
|
+
# Use existing formatter function
|
|
272
|
+
result = (
|
|
273
|
+
openai_to_anthropic.convert__openai_responses_to_anthropic_message__response(
|
|
274
|
+
response
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Convert back to dict
|
|
279
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
280
|
+
return result_dict
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
async def convert_anthropic_to_openai_responses_request(data: FormatDict) -> FormatDict:
|
|
284
|
+
"""Convert Anthropic CreateMessage request to OpenAI Responses request."""
|
|
285
|
+
# Convert dict to typed model
|
|
286
|
+
request = anthropic_models.CreateMessageRequest.model_validate(data)
|
|
287
|
+
|
|
288
|
+
# Use existing formatter function
|
|
289
|
+
result = (
|
|
290
|
+
anthropic_to_openai.convert__anthropic_message_to_openai_responses__request(
|
|
291
|
+
request
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Convert back to dict
|
|
296
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
297
|
+
return result_dict
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
async def convert_anthropic_to_openai_responses_response(
|
|
301
|
+
data: FormatDict,
|
|
302
|
+
) -> FormatDict:
|
|
303
|
+
"""Convert Anthropic MessageResponse to OpenAI Responses response."""
|
|
304
|
+
# Convert dict to typed model
|
|
305
|
+
response = anthropic_models.MessageResponse.model_validate(data)
|
|
306
|
+
|
|
307
|
+
# Use existing formatter function
|
|
308
|
+
result = (
|
|
309
|
+
anthropic_to_openai.convert__anthropic_message_to_openai_responses__response(
|
|
310
|
+
response
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Convert back to dict
|
|
315
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
316
|
+
return result_dict
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# OpenAI Chat ↔ OpenAI Responses converters (for Codex plugin)
|
|
320
|
+
async def convert_openai_chat_to_openai_responses_request(
|
|
321
|
+
data: FormatDict,
|
|
322
|
+
) -> FormatDict:
|
|
323
|
+
"""Convert OpenAI ChatCompletion request to OpenAI Responses request."""
|
|
324
|
+
# Convert dict to typed model
|
|
325
|
+
request = openai_models.ChatCompletionRequest.model_validate(data)
|
|
326
|
+
|
|
327
|
+
# Use existing formatter function
|
|
328
|
+
result = await openai_to_openai.convert__openai_chat_to_openai_responses__request(
|
|
329
|
+
request
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Convert back to dict
|
|
333
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
334
|
+
return result_dict
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
async def convert_openai_responses_to_openai_chat_response(
|
|
338
|
+
data: FormatDict,
|
|
339
|
+
) -> FormatDict:
|
|
340
|
+
"""Convert OpenAI Responses response to OpenAI ChatCompletion response."""
|
|
341
|
+
if isinstance(data, dict):
|
|
342
|
+
if data.get("object") == "chat.completion" or (
|
|
343
|
+
"choices" in data and "response" not in data and "model" in data
|
|
344
|
+
):
|
|
345
|
+
return data
|
|
346
|
+
|
|
347
|
+
# Convert dict to typed model
|
|
348
|
+
response = openai_models.ResponseObject.model_validate(data)
|
|
349
|
+
|
|
350
|
+
# Use existing formatter function
|
|
351
|
+
result = openai_to_openai.convert__openai_responses_to_openai_chat__response(
|
|
352
|
+
response
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Convert back to dict
|
|
356
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
357
|
+
return result_dict
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
async def convert_openai_chat_to_openai_responses_response(
|
|
361
|
+
data: FormatDict,
|
|
362
|
+
) -> FormatDict:
|
|
363
|
+
"""Convert OpenAI ChatCompletion response to OpenAI Responses response."""
|
|
364
|
+
# Convert dict to typed model
|
|
365
|
+
response = openai_models.ChatCompletionResponse.model_validate(data)
|
|
366
|
+
|
|
367
|
+
# Use existing formatter function
|
|
368
|
+
result = await openai_to_openai.convert__openai_chat_to_openai_responses__response(
|
|
369
|
+
response
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Convert back to dict
|
|
373
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
374
|
+
return result_dict
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
async def convert_openai_responses_to_openai_chat_stream(
|
|
378
|
+
stream: AsyncIterator[FormatDict],
|
|
379
|
+
) -> AsyncIterator[FormatDict]:
|
|
380
|
+
"""Convert OpenAI Responses stream to OpenAI ChatCompletion stream."""
|
|
381
|
+
from ccproxy.llms.models.openai import AnyStreamEvent
|
|
382
|
+
|
|
383
|
+
async for out_chunk in map_stream(
|
|
384
|
+
stream,
|
|
385
|
+
validator_model=AnyStreamEvent,
|
|
386
|
+
converter=openai_to_openai.convert__openai_responses_to_openai_chat__stream,
|
|
387
|
+
):
|
|
388
|
+
yield out_chunk
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
async def convert_openai_chat_to_openai_responses_stream(
|
|
392
|
+
stream: AsyncIterator[FormatDict],
|
|
393
|
+
) -> AsyncIterator[FormatDict]:
|
|
394
|
+
"""Convert OpenAI ChatCompletion stream to OpenAI Responses stream."""
|
|
395
|
+
from .chat_accumulator import ChatCompletionAccumulator
|
|
396
|
+
|
|
397
|
+
# Use accumulator to handle partial tool calls
|
|
398
|
+
accumulator = ChatCompletionAccumulator()
|
|
399
|
+
|
|
400
|
+
async for out_chunk in map_stream(
|
|
401
|
+
stream,
|
|
402
|
+
validator_model=openai_models.ChatCompletionChunk,
|
|
403
|
+
converter=openai_to_openai.convert__openai_chat_to_openai_responses__stream,
|
|
404
|
+
accumulator=accumulator,
|
|
405
|
+
):
|
|
406
|
+
yield out_chunk
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
async def convert_anthropic_to_openai_responses_stream(
|
|
410
|
+
stream: AsyncIterator[FormatDict],
|
|
411
|
+
) -> AsyncIterator[FormatDict]:
|
|
412
|
+
"""Convert Anthropic MessageStream to OpenAI Responses stream.
|
|
413
|
+
|
|
414
|
+
Avoid dict→model→dict churn by using the shared map_stream helper.
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
async for out_chunk in map_stream(
|
|
418
|
+
stream,
|
|
419
|
+
validator_model=MessageStreamEvent,
|
|
420
|
+
converter=a2o.convert__anthropic_message_to_openai_responses__stream,
|
|
421
|
+
):
|
|
422
|
+
yield out_chunk
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
async def convert_openai_responses_to_anthropic_stream(
|
|
426
|
+
stream: AsyncIterator[FormatDict],
|
|
427
|
+
) -> AsyncIterator[FormatDict]:
|
|
428
|
+
"""Convert OpenAI Responses stream to Anthropic MessageStream."""
|
|
429
|
+
# Since there's no direct openai.responses -> anthropic stream converter,
|
|
430
|
+
# we'll convert responses -> chat -> anthropic
|
|
431
|
+
chat_stream = convert_openai_responses_to_openai_chat_stream(stream)
|
|
432
|
+
anthropic_stream = convert_openai_to_anthropic_stream(chat_stream)
|
|
433
|
+
async for chunk in anthropic_stream:
|
|
434
|
+
yield chunk
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
async def convert_openai_responses_to_openai_chat_request(
|
|
438
|
+
data: FormatDict,
|
|
439
|
+
) -> FormatDict:
|
|
440
|
+
"""Convert OpenAI Responses request to OpenAI ChatCompletion request."""
|
|
441
|
+
# Convert dict to typed model
|
|
442
|
+
request = openai_models.ResponseRequest.model_validate(data)
|
|
443
|
+
|
|
444
|
+
# Use existing formatter function
|
|
445
|
+
result = await openai_to_openai.convert__openai_responses_to_openaichat__request(
|
|
446
|
+
request
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Convert back to dict
|
|
450
|
+
result_dict: FormatDict = result.model_dump(exclude_unset=True)
|
|
451
|
+
return result_dict
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# Passthrough and additional error conversion functions
|
|
455
|
+
# OpenAI↔OpenAI error formats are identical; return input unchanged.
|
|
456
|
+
async def convert_openai_responses_to_anthropic_error(data: FormatDict) -> FormatDict:
|
|
457
|
+
"""Convert OpenAI Responses error to Anthropic error."""
|
|
458
|
+
# OpenAI errors are similar across formats - use existing converter
|
|
459
|
+
return await convert_openai_to_anthropic_error(data)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
async def convert_anthropic_to_openai_responses_error(data: FormatDict) -> FormatDict:
|
|
463
|
+
"""Convert Anthropic error to OpenAI Responses error."""
|
|
464
|
+
# Use existing anthropic -> openai error converter (errors are same format)
|
|
465
|
+
return await convert_anthropic_to_openai_error(data)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
async def convert_openai_responses_to_openai_chat_error(data: FormatDict) -> FormatDict:
|
|
469
|
+
"""Convert OpenAI Responses error to OpenAI ChatCompletion error."""
|
|
470
|
+
# Errors have the same format between OpenAI endpoints - passthrough
|
|
471
|
+
return data
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
async def convert_openai_chat_to_openai_responses_error(data: FormatDict) -> FormatDict:
|
|
475
|
+
"""Convert OpenAI ChatCompletion error to OpenAI Responses error."""
|
|
476
|
+
# Errors have the same format between OpenAI endpoints - passthrough
|
|
477
|
+
return data
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
__all__ = [
|
|
481
|
+
"convert_openai_to_anthropic_request",
|
|
482
|
+
"convert_anthropic_to_openai_response",
|
|
483
|
+
"convert_anthropic_to_openai_stream",
|
|
484
|
+
"convert_openai_to_anthropic_error",
|
|
485
|
+
"convert_anthropic_to_openai_request",
|
|
486
|
+
"convert_openai_to_anthropic_response",
|
|
487
|
+
"convert_openai_to_anthropic_stream",
|
|
488
|
+
"convert_anthropic_to_openai_error",
|
|
489
|
+
"convert_openai_responses_to_anthropic_request",
|
|
490
|
+
"convert_openai_responses_to_anthropic_response",
|
|
491
|
+
"convert_openai_responses_to_anthropic_error",
|
|
492
|
+
"convert_anthropic_to_openai_responses_request",
|
|
493
|
+
"convert_anthropic_to_openai_responses_response",
|
|
494
|
+
"convert_anthropic_to_openai_responses_error",
|
|
495
|
+
"convert_anthropic_to_openai_responses_stream",
|
|
496
|
+
"convert_openai_responses_to_anthropic_stream",
|
|
497
|
+
"convert_openai_chat_to_openai_responses_request",
|
|
498
|
+
"convert_openai_responses_to_openai_chat_response",
|
|
499
|
+
"convert_openai_responses_to_openai_chat_error",
|
|
500
|
+
"convert_openai_chat_to_openai_responses_response",
|
|
501
|
+
"convert_openai_chat_to_openai_responses_error",
|
|
502
|
+
"convert_openai_chat_to_openai_responses_stream",
|
|
503
|
+
"convert_openai_responses_to_openai_chat_stream",
|
|
504
|
+
"convert_openai_responses_to_openai_chat_request",
|
|
505
|
+
]
|
|
506
|
+
|
|
507
|
+
# Centralized pair→stage mapping and registration helpers
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def get_converter_map() -> dict[tuple[str, str], dict[str, Any]]:
|
|
511
|
+
"""Return a mapping of (from, to) → {request, response, error, stream} callables.
|
|
512
|
+
|
|
513
|
+
Missing stages are allowed (e.g., error), and will default to passthrough in composition.
|
|
514
|
+
"""
|
|
515
|
+
return {
|
|
516
|
+
# OpenAI Chat → Anthropic Messages
|
|
517
|
+
(OPENAI_CHAT, ANTHROPIC_MESSAGES): {
|
|
518
|
+
"request": convert_openai_to_anthropic_request,
|
|
519
|
+
"response": convert_anthropic_to_openai_response,
|
|
520
|
+
"error": convert_anthropic_to_openai_error,
|
|
521
|
+
"stream": convert_anthropic_to_openai_stream,
|
|
522
|
+
},
|
|
523
|
+
# Anthropic Messages → OpenAI Chat
|
|
524
|
+
(ANTHROPIC_MESSAGES, OPENAI_CHAT): {
|
|
525
|
+
"request": convert_anthropic_to_openai_request,
|
|
526
|
+
"response": convert_openai_to_anthropic_response,
|
|
527
|
+
"error": convert_openai_to_anthropic_error,
|
|
528
|
+
"stream": convert_openai_to_anthropic_stream,
|
|
529
|
+
},
|
|
530
|
+
# OpenAI Chat ↔ OpenAI Responses
|
|
531
|
+
(OPENAI_CHAT, OPENAI_RESPONSES): {
|
|
532
|
+
"request": convert_openai_chat_to_openai_responses_request,
|
|
533
|
+
"response": convert_openai_chat_to_openai_responses_response,
|
|
534
|
+
"error": convert_openai_chat_to_openai_responses_error,
|
|
535
|
+
"stream": convert_openai_chat_to_openai_responses_stream,
|
|
536
|
+
},
|
|
537
|
+
(OPENAI_RESPONSES, OPENAI_CHAT): {
|
|
538
|
+
"request": convert_openai_responses_to_openai_chat_request,
|
|
539
|
+
"response": convert_openai_responses_to_openai_chat_response,
|
|
540
|
+
"error": convert_openai_responses_to_openai_chat_error,
|
|
541
|
+
"stream": convert_openai_responses_to_openai_chat_stream,
|
|
542
|
+
},
|
|
543
|
+
# OpenAI Responses ↔ Anthropic Messages
|
|
544
|
+
(OPENAI_RESPONSES, ANTHROPIC_MESSAGES): {
|
|
545
|
+
"request": convert_openai_responses_to_anthropic_request,
|
|
546
|
+
"response": convert_openai_responses_to_anthropic_response,
|
|
547
|
+
"error": convert_openai_responses_to_anthropic_error,
|
|
548
|
+
"stream": convert_openai_responses_to_anthropic_stream,
|
|
549
|
+
},
|
|
550
|
+
(ANTHROPIC_MESSAGES, OPENAI_RESPONSES): {
|
|
551
|
+
"request": convert_anthropic_to_openai_responses_request,
|
|
552
|
+
"response": convert_anthropic_to_openai_responses_response,
|
|
553
|
+
"error": convert_anthropic_to_openai_responses_error,
|
|
554
|
+
"stream": convert_anthropic_to_openai_responses_stream,
|
|
555
|
+
},
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def register_converters(registry: FormatRegistry, *, plugin_name: str = "core") -> None:
|
|
560
|
+
"""Register DictFormatAdapter instances for all known pairs into the registry."""
|
|
561
|
+
for (src, dst), stages in get_converter_map().items():
|
|
562
|
+
adapter = DictFormatAdapter(
|
|
563
|
+
request=stages.get("request"),
|
|
564
|
+
response=stages.get("response"),
|
|
565
|
+
error=stages.get("error"),
|
|
566
|
+
stream=stages.get("stream"),
|
|
567
|
+
name=f"{src}->{dst}",
|
|
568
|
+
)
|
|
569
|
+
registry.register(
|
|
570
|
+
from_format=src, to_format=dst, adapter=adapter, plugin_name=plugin_name
|
|
571
|
+
)
|