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
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Shared token usage conversion helpers for formatter adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ccproxy.llms.formatters.utils import (
|
|
8
|
+
anthropic_usage_snapshot,
|
|
9
|
+
openai_completion_usage_snapshot,
|
|
10
|
+
openai_response_usage_snapshot,
|
|
11
|
+
)
|
|
12
|
+
from ccproxy.llms.models import anthropic as anthropic_models
|
|
13
|
+
from ccproxy.llms.models import openai as openai_models
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def convert_openai_responses_usage_to_completion_usage(
|
|
17
|
+
usage: Any,
|
|
18
|
+
) -> openai_models.CompletionUsage:
|
|
19
|
+
"""Normalize Responses usage into the legacy CompletionUsage envelope."""
|
|
20
|
+
|
|
21
|
+
snapshot = openai_response_usage_snapshot(usage)
|
|
22
|
+
|
|
23
|
+
prompt_tokens_details = openai_models.PromptTokensDetails(
|
|
24
|
+
cached_tokens=snapshot.cache_read_tokens,
|
|
25
|
+
audio_tokens=0,
|
|
26
|
+
)
|
|
27
|
+
completion_tokens_details = openai_models.CompletionTokensDetails(
|
|
28
|
+
reasoning_tokens=snapshot.reasoning_tokens,
|
|
29
|
+
audio_tokens=0,
|
|
30
|
+
accepted_prediction_tokens=0,
|
|
31
|
+
rejected_prediction_tokens=0,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return openai_models.CompletionUsage(
|
|
35
|
+
prompt_tokens=snapshot.input_tokens,
|
|
36
|
+
completion_tokens=snapshot.output_tokens,
|
|
37
|
+
total_tokens=snapshot.input_tokens + snapshot.output_tokens,
|
|
38
|
+
prompt_tokens_details=prompt_tokens_details,
|
|
39
|
+
completion_tokens_details=completion_tokens_details,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def convert_openai_completion_usage_to_responses_usage(
|
|
44
|
+
usage: Any,
|
|
45
|
+
) -> openai_models.ResponseUsage:
|
|
46
|
+
"""Map Completion usage payloads into Responses Usage structures."""
|
|
47
|
+
|
|
48
|
+
snapshot = openai_completion_usage_snapshot(usage)
|
|
49
|
+
|
|
50
|
+
input_tokens_details = openai_models.InputTokensDetails(
|
|
51
|
+
cached_tokens=snapshot.cache_read_tokens
|
|
52
|
+
)
|
|
53
|
+
output_tokens_details = openai_models.OutputTokensDetails(
|
|
54
|
+
reasoning_tokens=snapshot.reasoning_tokens
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return openai_models.ResponseUsage(
|
|
58
|
+
input_tokens=snapshot.input_tokens,
|
|
59
|
+
input_tokens_details=input_tokens_details,
|
|
60
|
+
output_tokens=snapshot.output_tokens,
|
|
61
|
+
output_tokens_details=output_tokens_details,
|
|
62
|
+
total_tokens=snapshot.input_tokens + snapshot.output_tokens,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def convert_openai_responses_usage_to_anthropic_usage(
|
|
67
|
+
usage: Any,
|
|
68
|
+
) -> anthropic_models.Usage:
|
|
69
|
+
"""Translate OpenAI Responses usage into Anthropic Usage models."""
|
|
70
|
+
|
|
71
|
+
snapshot = openai_response_usage_snapshot(usage)
|
|
72
|
+
|
|
73
|
+
return anthropic_models.Usage(
|
|
74
|
+
input_tokens=snapshot.input_tokens,
|
|
75
|
+
output_tokens=snapshot.output_tokens,
|
|
76
|
+
cache_read_input_tokens=snapshot.cache_read_tokens,
|
|
77
|
+
cache_creation_input_tokens=snapshot.cache_creation_tokens,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def convert_anthropic_usage_to_openai_completion_usage(
|
|
82
|
+
usage: Any,
|
|
83
|
+
) -> openai_models.CompletionUsage:
|
|
84
|
+
"""Translate Anthropic Usage values into OpenAI Completion usage."""
|
|
85
|
+
|
|
86
|
+
snapshot = anthropic_usage_snapshot(usage)
|
|
87
|
+
cached_tokens = snapshot.cache_read_tokens or snapshot.cache_creation_tokens
|
|
88
|
+
|
|
89
|
+
prompt_tokens_details = openai_models.PromptTokensDetails(
|
|
90
|
+
cached_tokens=cached_tokens,
|
|
91
|
+
audio_tokens=0,
|
|
92
|
+
)
|
|
93
|
+
completion_tokens_details = openai_models.CompletionTokensDetails(
|
|
94
|
+
reasoning_tokens=0,
|
|
95
|
+
audio_tokens=0,
|
|
96
|
+
accepted_prediction_tokens=0,
|
|
97
|
+
rejected_prediction_tokens=0,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return openai_models.CompletionUsage(
|
|
101
|
+
prompt_tokens=snapshot.input_tokens,
|
|
102
|
+
completion_tokens=snapshot.output_tokens,
|
|
103
|
+
total_tokens=snapshot.input_tokens + snapshot.output_tokens,
|
|
104
|
+
prompt_tokens_details=prompt_tokens_details,
|
|
105
|
+
completion_tokens_details=completion_tokens_details,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def convert_anthropic_usage_to_openai_responses_usage(
|
|
110
|
+
usage: Any,
|
|
111
|
+
) -> openai_models.ResponseUsage:
|
|
112
|
+
"""Translate Anthropic Usage values into OpenAI Responses usage."""
|
|
113
|
+
|
|
114
|
+
snapshot = anthropic_usage_snapshot(usage)
|
|
115
|
+
cached_tokens = snapshot.cache_read_tokens or snapshot.cache_creation_tokens
|
|
116
|
+
|
|
117
|
+
input_tokens_details = openai_models.InputTokensDetails(cached_tokens=cached_tokens)
|
|
118
|
+
output_tokens_details = openai_models.OutputTokensDetails(reasoning_tokens=0)
|
|
119
|
+
|
|
120
|
+
return openai_models.ResponseUsage(
|
|
121
|
+
input_tokens=snapshot.input_tokens,
|
|
122
|
+
input_tokens_details=input_tokens_details,
|
|
123
|
+
output_tokens=snapshot.output_tokens,
|
|
124
|
+
output_tokens_details=output_tokens_details,
|
|
125
|
+
total_tokens=snapshot.input_tokens + snapshot.output_tokens,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = [
|
|
130
|
+
"convert_anthropic_usage_to_openai_completion_usage",
|
|
131
|
+
"convert_anthropic_usage_to_openai_responses_usage",
|
|
132
|
+
"convert_openai_completion_usage_to_responses_usage",
|
|
133
|
+
"convert_openai_responses_usage_to_anthropic_usage",
|
|
134
|
+
"convert_openai_responses_usage_to_completion_usage",
|
|
135
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Shared constant mappings for LLM adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ANTHROPIC_TO_OPENAI_FINISH_REASON: Final[dict[str, str]] = {
|
|
9
|
+
"end_turn": "stop",
|
|
10
|
+
"max_tokens": "length",
|
|
11
|
+
"stop_sequence": "stop",
|
|
12
|
+
"tool_use": "tool_calls",
|
|
13
|
+
# Anthropic-specific values mapped to closest reasonable OpenAI value
|
|
14
|
+
"pause_turn": "stop",
|
|
15
|
+
"refusal": "stop",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
OPENAI_TO_ANTHROPIC_STOP_REASON: Final[dict[str, str]] = {
|
|
19
|
+
"stop": "end_turn",
|
|
20
|
+
"length": "max_tokens",
|
|
21
|
+
"tool_calls": "tool_use",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
OPENAI_TO_ANTHROPIC_ERROR_TYPE: Final[dict[str, str]] = {
|
|
25
|
+
"invalid_request_error": "invalid_request_error",
|
|
26
|
+
"authentication_error": "invalid_request_error",
|
|
27
|
+
"permission_error": "invalid_request_error",
|
|
28
|
+
"not_found_error": "invalid_request_error",
|
|
29
|
+
"rate_limit_error": "rate_limit_error",
|
|
30
|
+
"internal_server_error": "api_error",
|
|
31
|
+
"overloaded_error": "api_error",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ANTHROPIC_TO_OPENAI_ERROR_TYPE: Final[dict[str, str]] = {
|
|
35
|
+
"invalid_request_error": "invalid_request_error",
|
|
36
|
+
"authentication_error": "authentication_error",
|
|
37
|
+
"permission_error": "permission_error",
|
|
38
|
+
"not_found_error": "invalid_request_error", # OpenAI doesn't expose not_found
|
|
39
|
+
"rate_limit_error": "rate_limit_error",
|
|
40
|
+
"api_error": "api_error",
|
|
41
|
+
"overloaded_error": "api_error",
|
|
42
|
+
"billing_error": "invalid_request_error",
|
|
43
|
+
"timeout_error": "api_error",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
DEFAULT_MAX_TOKENS: Final[int] = 1024
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"ANTHROPIC_TO_OPENAI_FINISH_REASON",
|
|
51
|
+
"OPENAI_TO_ANTHROPIC_STOP_REASON",
|
|
52
|
+
"OPENAI_TO_ANTHROPIC_ERROR_TYPE",
|
|
53
|
+
"ANTHROPIC_TO_OPENAI_ERROR_TYPE",
|
|
54
|
+
"DEFAULT_MAX_TOKENS",
|
|
55
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Context helpers for formatter conversions using async contextvars."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_REQUEST_VAR: ContextVar[Any | None] = ContextVar("formatter_request", default=None)
|
|
10
|
+
_INSTRUCTIONS_VAR: ContextVar[str | None] = ContextVar(
|
|
11
|
+
"formatter_instructions", default=None
|
|
12
|
+
)
|
|
13
|
+
_TOOLS_VAR: ContextVar[list[Any] | None] = ContextVar("formatter_tools", default=None)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register_request(request: Any | None, instructions: str | None = None) -> None:
|
|
17
|
+
"""Record the most recent upstream request for streaming conversions."""
|
|
18
|
+
|
|
19
|
+
normalized = instructions.strip() if isinstance(instructions, str) else None
|
|
20
|
+
|
|
21
|
+
_REQUEST_VAR.set(request)
|
|
22
|
+
_INSTRUCTIONS_VAR.set(normalized)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from ccproxy.core.request_context import RequestContext
|
|
26
|
+
|
|
27
|
+
ctx = RequestContext.get_current()
|
|
28
|
+
if ctx is not None:
|
|
29
|
+
formatter_state = ctx.metadata.setdefault("formatter_state", {})
|
|
30
|
+
if request is None:
|
|
31
|
+
formatter_state.pop("request", None)
|
|
32
|
+
else:
|
|
33
|
+
formatter_state["request"] = request
|
|
34
|
+
|
|
35
|
+
if normalized:
|
|
36
|
+
formatter_state["instructions"] = normalized
|
|
37
|
+
elif instructions is None:
|
|
38
|
+
formatter_state.pop("instructions", None)
|
|
39
|
+
except Exception:
|
|
40
|
+
# Request context propagation is best-effort; proceed even when
|
|
41
|
+
# request context is unavailable (e.g., during unit tests).
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_last_request() -> Any | None:
|
|
46
|
+
"""Return the cached upstream request for the active conversion, if any."""
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from ccproxy.core.request_context import RequestContext
|
|
50
|
+
|
|
51
|
+
ctx = RequestContext.get_current()
|
|
52
|
+
if ctx is not None:
|
|
53
|
+
formatter_state = ctx.metadata.get("formatter_state", {})
|
|
54
|
+
if "request" in formatter_state:
|
|
55
|
+
return formatter_state["request"]
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
return _REQUEST_VAR.get()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_last_instructions() -> str | None:
|
|
63
|
+
"""Return the cached instruction string from the last registered request."""
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
from ccproxy.core.request_context import RequestContext
|
|
67
|
+
|
|
68
|
+
ctx = RequestContext.get_current()
|
|
69
|
+
if ctx is not None:
|
|
70
|
+
formatter_state = ctx.metadata.get("formatter_state", {})
|
|
71
|
+
instructions = formatter_state.get("instructions")
|
|
72
|
+
if isinstance(instructions, str) and instructions.strip():
|
|
73
|
+
return instructions.strip()
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
return _INSTRUCTIONS_VAR.get()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def register_request_tools(tools: list[Any] | None) -> None:
|
|
81
|
+
"""Cache request tool definitions for downstream streaming responses."""
|
|
82
|
+
|
|
83
|
+
normalized = list(tools) if tools else None
|
|
84
|
+
_TOOLS_VAR.set(normalized)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
from ccproxy.core.request_context import RequestContext
|
|
88
|
+
|
|
89
|
+
ctx = RequestContext.get_current()
|
|
90
|
+
if ctx is not None:
|
|
91
|
+
formatter_state = ctx.metadata.setdefault("formatter_state", {})
|
|
92
|
+
if normalized is None:
|
|
93
|
+
formatter_state.pop("tools", None)
|
|
94
|
+
else:
|
|
95
|
+
formatter_state["tools"] = normalized
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_last_request_tools() -> list[Any] | None:
|
|
101
|
+
"""Return cached request tool definitions, if any."""
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
from ccproxy.core.request_context import RequestContext
|
|
105
|
+
|
|
106
|
+
ctx = RequestContext.get_current()
|
|
107
|
+
if ctx is not None:
|
|
108
|
+
formatter_state = ctx.metadata.get("formatter_state", {})
|
|
109
|
+
tools = formatter_state.get("tools")
|
|
110
|
+
if isinstance(tools, list):
|
|
111
|
+
return list(tools)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
cached = _TOOLS_VAR.get()
|
|
116
|
+
return list(cached) if cached else None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Compatibility layer for adapter mapping utilities.
|
|
2
|
+
|
|
3
|
+
This shim was previously used to re-export usage converters that have now been
|
|
4
|
+
inlined into their respective adapter helpers. It now only re-exports constants
|
|
5
|
+
and error conversion utilities that remain shared.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ccproxy.llms.formatters.anthropic_to_openai import (
|
|
11
|
+
convert__anthropic_to_openai__error,
|
|
12
|
+
)
|
|
13
|
+
from ccproxy.llms.formatters.constants import (
|
|
14
|
+
ANTHROPIC_TO_OPENAI_ERROR_TYPE,
|
|
15
|
+
ANTHROPIC_TO_OPENAI_FINISH_REASON,
|
|
16
|
+
DEFAULT_MAX_TOKENS,
|
|
17
|
+
OPENAI_TO_ANTHROPIC_ERROR_TYPE,
|
|
18
|
+
OPENAI_TO_ANTHROPIC_STOP_REASON,
|
|
19
|
+
)
|
|
20
|
+
from ccproxy.llms.formatters.openai_to_anthropic import (
|
|
21
|
+
convert__openai_to_anthropic__error,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ANTHROPIC_TO_OPENAI_ERROR_TYPE",
|
|
27
|
+
"ANTHROPIC_TO_OPENAI_FINISH_REASON",
|
|
28
|
+
"DEFAULT_MAX_TOKENS",
|
|
29
|
+
"OPENAI_TO_ANTHROPIC_ERROR_TYPE",
|
|
30
|
+
"OPENAI_TO_ANTHROPIC_STOP_REASON",
|
|
31
|
+
"convert__anthropic_to_openai__error",
|
|
32
|
+
"convert__openai_to_anthropic__error",
|
|
33
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Facade module exposing OpenAI→Anthropic formatter entry points."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
|
|
6
|
+
from . import streams as _streams
|
|
7
|
+
from .errors import convert__openai_to_anthropic__error
|
|
8
|
+
from .requests import (
|
|
9
|
+
convert__openai_chat_to_anthropic_message__request,
|
|
10
|
+
convert__openai_responses_to_anthropic_message__request,
|
|
11
|
+
)
|
|
12
|
+
from .responses import (
|
|
13
|
+
convert__openai_chat_to_anthropic_messages__response,
|
|
14
|
+
convert__openai_responses_to_anthropic_message__response,
|
|
15
|
+
convert__openai_responses_usage_to_anthropic__usage,
|
|
16
|
+
convert__openai_responses_usage_to_openai_completion__usage,
|
|
17
|
+
)
|
|
18
|
+
from .streams import (
|
|
19
|
+
OpenAIChatToAnthropicStreamAdapter,
|
|
20
|
+
OpenAIResponsesToAnthropicStreamAdapter,
|
|
21
|
+
convert__openai_chat_to_anthropic_messages__stream,
|
|
22
|
+
convert__openai_responses_to_anthropic_messages__stream,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"convert__openai_to_anthropic__error",
|
|
28
|
+
"convert__openai_chat_to_anthropic_message__request",
|
|
29
|
+
"convert__openai_responses_to_anthropic_message__request",
|
|
30
|
+
"convert__openai_chat_to_anthropic_messages__response",
|
|
31
|
+
"convert__openai_responses_to_anthropic_message__response",
|
|
32
|
+
"convert__openai_responses_usage_to_anthropic__usage",
|
|
33
|
+
"convert__openai_responses_usage_to_openai_completion__usage",
|
|
34
|
+
"OpenAIChatToAnthropicStreamAdapter",
|
|
35
|
+
"OpenAIResponsesToAnthropicStreamAdapter",
|
|
36
|
+
"convert__openai_chat_to_anthropic_messages__stream",
|
|
37
|
+
"convert__openai_responses_to_anthropic_messages__stream",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _OpenAIToAnthropicModule(ModuleType):
|
|
42
|
+
_propagated_names = {
|
|
43
|
+
"OpenAIChatToAnthropicStreamAdapter",
|
|
44
|
+
"OpenAIResponsesToAnthropicStreamAdapter",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
48
|
+
super().__setattr__(name, value)
|
|
49
|
+
if name in self._propagated_names and hasattr(_streams, name):
|
|
50
|
+
setattr(_streams, name, value)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_module = sys.modules[__name__]
|
|
54
|
+
if not isinstance(_module, _OpenAIToAnthropicModule):
|
|
55
|
+
_module.__class__ = _OpenAIToAnthropicModule
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Shared helper utilities for OpenAI→Anthropic formatters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ccproxy.llms.formatters.utils import strict_parse_tool_arguments
|
|
9
|
+
from ccproxy.llms.models import openai as openai_models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _to_mapping(value: Any) -> Mapping[str, Any] | None:
|
|
13
|
+
if isinstance(value, Mapping):
|
|
14
|
+
return value
|
|
15
|
+
if hasattr(value, "model_dump"):
|
|
16
|
+
dumped = value.model_dump()
|
|
17
|
+
if isinstance(dumped, Mapping):
|
|
18
|
+
return dumped
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _normalize_text_and_images(
|
|
23
|
+
content: Any,
|
|
24
|
+
) -> tuple[list[str], list[dict[str, Any]]]:
|
|
25
|
+
text_parts: list[str] = []
|
|
26
|
+
image_blocks: list[dict[str, Any]] = []
|
|
27
|
+
|
|
28
|
+
if not isinstance(content, list):
|
|
29
|
+
return text_parts, image_blocks
|
|
30
|
+
|
|
31
|
+
for part in content:
|
|
32
|
+
mapping = _to_mapping(part)
|
|
33
|
+
if not mapping:
|
|
34
|
+
continue
|
|
35
|
+
part_type = str(mapping.get("type", "")).lower()
|
|
36
|
+
if part_type in {"text", "input_text"}:
|
|
37
|
+
text_val = mapping.get("text")
|
|
38
|
+
if isinstance(text_val, str) and text_val:
|
|
39
|
+
text_parts.append(text_val)
|
|
40
|
+
elif part_type == "image_url":
|
|
41
|
+
image_info = mapping.get("image_url")
|
|
42
|
+
image_map = _to_mapping(image_info)
|
|
43
|
+
if not image_map:
|
|
44
|
+
continue
|
|
45
|
+
url = image_map.get("url")
|
|
46
|
+
if isinstance(url, str) and url.startswith("data:"):
|
|
47
|
+
try:
|
|
48
|
+
header, b64data = url.split(",", 1)
|
|
49
|
+
mediatype = header.split(";")[0].split(":", 1)[1]
|
|
50
|
+
except (ValueError, IndexError):
|
|
51
|
+
continue
|
|
52
|
+
image_blocks.append(
|
|
53
|
+
{
|
|
54
|
+
"type": "image",
|
|
55
|
+
"source": {
|
|
56
|
+
"type": "base64",
|
|
57
|
+
"media_type": mediatype,
|
|
58
|
+
"data": b64data,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return text_parts, image_blocks
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _stringify_content(content: Any) -> str:
|
|
67
|
+
if content is None:
|
|
68
|
+
return ""
|
|
69
|
+
if isinstance(content, str):
|
|
70
|
+
return content
|
|
71
|
+
if isinstance(content, list):
|
|
72
|
+
text_parts, _ = _normalize_text_and_images(content)
|
|
73
|
+
return " ".join(text_parts)
|
|
74
|
+
return str(content)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _coerce_system_content(content: Any) -> str | None:
|
|
78
|
+
if isinstance(content, str):
|
|
79
|
+
return content
|
|
80
|
+
if isinstance(content, list):
|
|
81
|
+
text_parts, _ = _normalize_text_and_images(content)
|
|
82
|
+
return " ".join(text_parts) if text_parts else None
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _build_user_blocks(content: Any) -> str | list[dict[str, Any]] | None:
|
|
87
|
+
if content is None:
|
|
88
|
+
return None
|
|
89
|
+
if isinstance(content, str):
|
|
90
|
+
return content
|
|
91
|
+
|
|
92
|
+
text_parts, image_blocks = _normalize_text_and_images(content)
|
|
93
|
+
|
|
94
|
+
if not text_parts and not image_blocks:
|
|
95
|
+
return ""
|
|
96
|
+
|
|
97
|
+
blocks: list[dict[str, Any]] = []
|
|
98
|
+
if text_parts:
|
|
99
|
+
blocks.append({"type": "text", "text": " ".join(text_parts)})
|
|
100
|
+
blocks.extend(image_blocks)
|
|
101
|
+
|
|
102
|
+
if len(blocks) == 1 and blocks[0]["type"] == "text":
|
|
103
|
+
return str(blocks[0]["text"])
|
|
104
|
+
return blocks
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _build_assistant_blocks(
|
|
108
|
+
content: Any, tool_calls: list[openai_models.ToolCall] | None
|
|
109
|
+
) -> list[dict[str, Any]]:
|
|
110
|
+
blocks: list[dict[str, Any]] = []
|
|
111
|
+
|
|
112
|
+
normalized_content = _build_user_blocks(content)
|
|
113
|
+
if isinstance(normalized_content, str):
|
|
114
|
+
text = normalized_content.strip()
|
|
115
|
+
if text:
|
|
116
|
+
blocks.append({"type": "text", "text": text})
|
|
117
|
+
elif isinstance(normalized_content, list):
|
|
118
|
+
blocks.extend(normalized_content)
|
|
119
|
+
|
|
120
|
+
for call in tool_calls or []:
|
|
121
|
+
args_dict = strict_parse_tool_arguments(call.function.arguments)
|
|
122
|
+
blocks.append(
|
|
123
|
+
{
|
|
124
|
+
"type": "tool_use",
|
|
125
|
+
"id": call.id,
|
|
126
|
+
"name": call.function.name,
|
|
127
|
+
"input": args_dict,
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return blocks
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
__all__ = [
|
|
135
|
+
"_to_mapping",
|
|
136
|
+
"_normalize_text_and_images",
|
|
137
|
+
"_stringify_content",
|
|
138
|
+
"_coerce_system_content",
|
|
139
|
+
"_build_user_blocks",
|
|
140
|
+
"_build_assistant_blocks",
|
|
141
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""OpenAI→Anthropic error conversion entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from ccproxy.llms.formatters.constants import OPENAI_TO_ANTHROPIC_ERROR_TYPE
|
|
8
|
+
from ccproxy.llms.models import anthropic as anthropic_models
|
|
9
|
+
from ccproxy.llms.models import openai as openai_models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def convert__openai_to_anthropic__error(error: BaseModel) -> BaseModel:
|
|
13
|
+
"""Convert an OpenAI error payload to the Anthropic envelope."""
|
|
14
|
+
if isinstance(error, openai_models.ErrorResponse):
|
|
15
|
+
openai_error = error.error
|
|
16
|
+
error_message = openai_error.message
|
|
17
|
+
openai_error_type = openai_error.type or "api_error"
|
|
18
|
+
anthropic_error_type = OPENAI_TO_ANTHROPIC_ERROR_TYPE.get(
|
|
19
|
+
openai_error_type, "api_error"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if anthropic_error_type == "invalid_request_error":
|
|
23
|
+
anthropic_error: anthropic_models.ErrorType = (
|
|
24
|
+
anthropic_models.InvalidRequestError(message=error_message)
|
|
25
|
+
)
|
|
26
|
+
elif anthropic_error_type == "rate_limit_error":
|
|
27
|
+
anthropic_error = anthropic_models.RateLimitError(message=error_message)
|
|
28
|
+
else:
|
|
29
|
+
anthropic_error = anthropic_models.APIError(message=error_message)
|
|
30
|
+
|
|
31
|
+
return anthropic_models.ErrorResponse(error=anthropic_error)
|
|
32
|
+
|
|
33
|
+
if hasattr(error, "error") and hasattr(error.error, "message"):
|
|
34
|
+
error_message = error.error.message
|
|
35
|
+
fallback_error: anthropic_models.ErrorType = anthropic_models.APIError(
|
|
36
|
+
message=error_message
|
|
37
|
+
)
|
|
38
|
+
return anthropic_models.ErrorResponse(error=fallback_error)
|
|
39
|
+
|
|
40
|
+
error_message = "Unknown error occurred"
|
|
41
|
+
if hasattr(error, "message"):
|
|
42
|
+
error_message = error.message
|
|
43
|
+
elif hasattr(error, "model_dump"):
|
|
44
|
+
error_dict = error.model_dump()
|
|
45
|
+
error_message = str(error_dict.get("message", error_dict))
|
|
46
|
+
|
|
47
|
+
generic_error: anthropic_models.ErrorType = anthropic_models.APIError(
|
|
48
|
+
message=error_message
|
|
49
|
+
)
|
|
50
|
+
return anthropic_models.ErrorResponse(error=generic_error)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["convert__openai_to_anthropic__error"]
|