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.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +439 -212
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +145 -176
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +402 -530
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +558 -0
  97. ccproxy/data/codex_headers_fallback.json +121 -0
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +63 -107
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +346 -314
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +95 -342
  387. ccproxy/utils/version_checker.py +279 -6
  388. ccproxy_api-0.2.0.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1231
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -269
  458. ccproxy/services/codex_detection_service.py +0 -263
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.6.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.6.dist-info/RECORD +0 -189
  473. ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,530 @@
1
+ """OpenAI→Anthropic streaming conversion entry points."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import AsyncGenerator, AsyncIterator
7
+ from typing import Any, cast
8
+
9
+ import ccproxy.core.logging
10
+ from ccproxy.llms.formatters.common import (
11
+ IndexedToolCallTracker,
12
+ ToolCallTracker,
13
+ emit_anthropic_tool_use_events,
14
+ )
15
+ from ccproxy.llms.formatters.utils import (
16
+ map_openai_finish_to_anthropic_stop,
17
+ openai_usage_to_anthropic_usage,
18
+ )
19
+ from ccproxy.llms.models import anthropic as anthropic_models
20
+ from ccproxy.llms.models import openai as openai_models
21
+ from ccproxy.llms.streaming.accumulators import OpenAIAccumulator
22
+
23
+
24
+ logger = ccproxy.core.logging.get_logger(__name__)
25
+
26
+
27
+ class OpenAIResponsesToAnthropicStreamAdapter:
28
+ """Stateful adapter for OpenAI Responses → Anthropic streaming."""
29
+
30
+ async def run(
31
+ self,
32
+ stream: AsyncIterator[Any],
33
+ ) -> AsyncGenerator[anthropic_models.MessageStreamEvent, None]:
34
+ async for event in self._convert_responses_stream(stream):
35
+ yield event
36
+
37
+ async def _convert_responses_stream(
38
+ self,
39
+ stream: AsyncIterator[Any],
40
+ ) -> AsyncGenerator[anthropic_models.MessageStreamEvent, None]:
41
+ """Translate OpenAI Responses streaming events into Anthropic message events."""
42
+
43
+ def _event_to_dict(raw: Any) -> dict[str, Any]:
44
+ if isinstance(raw, dict):
45
+ return raw
46
+ if hasattr(raw, "root"):
47
+ return _event_to_dict(raw.root)
48
+ if hasattr(raw, "model_dump"):
49
+ return cast(dict[str, Any], raw.model_dump(mode="json"))
50
+ return cast(dict[str, Any], {})
51
+
52
+ def _parse_tool_input(text: str) -> dict[str, Any]:
53
+ if not text:
54
+ return cast(dict[str, Any], {})
55
+ try:
56
+ parsed = json.loads(text)
57
+ return parsed if isinstance(parsed, dict) else {"arguments": text}
58
+ except Exception:
59
+ return {"arguments": text}
60
+
61
+ message_started = False
62
+ text_block_active = False
63
+ current_index = 0
64
+ final_stop_reason: str | None = None
65
+ final_stop_sequence: str | None = None
66
+ usage = anthropic_models.Usage(input_tokens=0, output_tokens=0)
67
+ reasoning_buffer: list[str] = []
68
+ tool_states = IndexedToolCallTracker()
69
+
70
+ async for raw_event in stream:
71
+ event = _event_to_dict(raw_event)
72
+ event_type = event.get("type") or event.get("event")
73
+ if not event_type:
74
+ continue
75
+
76
+ if event_type == "error":
77
+ payload = event.get("error") or {}
78
+ detail = (
79
+ anthropic_models.ErrorDetail(**payload)
80
+ if isinstance(payload, dict)
81
+ else anthropic_models.ErrorDetail(message=str(payload))
82
+ )
83
+ yield anthropic_models.ErrorEvent(type="error", error=detail)
84
+ return
85
+
86
+ if not message_started:
87
+ response_meta = event.get("response") or {}
88
+ yield anthropic_models.MessageStartEvent(
89
+ type="message_start",
90
+ message=anthropic_models.MessageResponse(
91
+ id=response_meta.get("id", "resp_stream"),
92
+ type="message",
93
+ role="assistant",
94
+ content=[],
95
+ model=response_meta.get("model", ""),
96
+ stop_reason=None,
97
+ stop_sequence=None,
98
+ usage=usage,
99
+ ),
100
+ )
101
+ message_started = True
102
+
103
+ if event_type == "response.output_text.delta":
104
+ delta = event.get("delta")
105
+ text = ""
106
+ if isinstance(delta, dict):
107
+ text = delta.get("text") or ""
108
+ elif isinstance(delta, str):
109
+ text = delta
110
+ if text:
111
+ if not text_block_active:
112
+ yield anthropic_models.ContentBlockStartEvent(
113
+ type="content_block_start",
114
+ index=current_index,
115
+ content_block=anthropic_models.TextBlock(
116
+ type="text", text=""
117
+ ),
118
+ )
119
+ text_block_active = True
120
+ yield anthropic_models.ContentBlockDeltaEvent(
121
+ type="content_block_delta",
122
+ index=current_index,
123
+ delta=anthropic_models.TextBlock(type="text", text=text),
124
+ )
125
+ elif event_type == "response.output_text.done":
126
+ if text_block_active:
127
+ yield anthropic_models.ContentBlockStopEvent(
128
+ type="content_block_stop", index=current_index
129
+ )
130
+ text_block_active = False
131
+ current_index += 1
132
+
133
+ elif event_type == "response.reasoning_summary_text.delta":
134
+ delta = event.get("delta")
135
+ summary_piece = delta.get("text") if isinstance(delta, dict) else delta
136
+ if isinstance(summary_piece, str):
137
+ reasoning_buffer.append(summary_piece)
138
+
139
+ elif event_type == "response.reasoning_summary_text.done":
140
+ if text_block_active:
141
+ yield anthropic_models.ContentBlockStopEvent(
142
+ type="content_block_stop", index=current_index
143
+ )
144
+ text_block_active = False
145
+ current_index += 1
146
+ summary = "".join(reasoning_buffer)
147
+ reasoning_buffer.clear()
148
+ if summary:
149
+ yield anthropic_models.ContentBlockStartEvent(
150
+ type="content_block_start",
151
+ index=current_index,
152
+ content_block=anthropic_models.ThinkingBlock(
153
+ type="thinking",
154
+ thinking=summary,
155
+ signature="",
156
+ ),
157
+ )
158
+ yield anthropic_models.ContentBlockStopEvent(
159
+ type="content_block_stop", index=current_index
160
+ )
161
+ current_index += 1
162
+
163
+ elif event_type == "response.function_call_arguments.delta":
164
+ output_index = int(event.get("output_index", 0) or 0)
165
+ state = tool_states.ensure(output_index)
166
+ if state.output_index < 0:
167
+ state.output_index = output_index
168
+
169
+ delta = event.get("delta") or {}
170
+ delta_text = (
171
+ delta.get("arguments") if isinstance(delta, dict) else delta
172
+ )
173
+ if isinstance(delta_text, str):
174
+ state.add_arguments_part(delta_text)
175
+ state.append_arguments(delta_text)
176
+
177
+ item_id_val = event.get("item_id")
178
+ if isinstance(item_id_val, str) and item_id_val:
179
+ if not state.item_id:
180
+ state.item_id = item_id_val
181
+
182
+ name_val = event.get("name")
183
+ if isinstance(name_val, str) and name_val:
184
+ if not state.name:
185
+ state.name = name_val
186
+
187
+ call_id_val = event.get("call_id")
188
+ if isinstance(call_id_val, str) and call_id_val:
189
+ if not state.call_id:
190
+ state.call_id = call_id_val
191
+
192
+ elif event_type == "response.function_call_arguments.done":
193
+ output_index = int(event.get("output_index", 0) or 0)
194
+ state = tool_states.ensure(output_index)
195
+ if state.output_index < 0:
196
+ state.output_index = output_index
197
+
198
+ arguments = event.get("arguments") if isinstance(event, dict) else None
199
+ if isinstance(arguments, str) and arguments:
200
+ state.append_arguments(arguments)
201
+ state.final_arguments = arguments
202
+ elif not state.final_arguments:
203
+ combined = state.arguments or "".join(state.arguments_parts)
204
+ if combined:
205
+ state.final_arguments = combined
206
+
207
+ item_id_val = event.get("item_id")
208
+ if isinstance(item_id_val, str) and item_id_val:
209
+ state.item_id = state.item_id or item_id_val
210
+
211
+ name_val = event.get("name")
212
+ if isinstance(name_val, str) and name_val:
213
+ state.name = state.name or name_val
214
+
215
+ call_id_val = event.get("call_id")
216
+ if isinstance(call_id_val, str) and call_id_val:
217
+ state.call_id = state.call_id or call_id_val
218
+
219
+ elif event_type == "response.output_item.added":
220
+ item = event.get("item") or {}
221
+ item_type = item.get("type")
222
+ if item_type == "output_tool_call":
223
+ output_index = int(
224
+ item.get("output_index", event.get("output_index", 0)) or 0
225
+ )
226
+ state = tool_states.ensure(output_index)
227
+ if state.output_index < 0:
228
+ state.output_index = output_index
229
+ item_id_val = item.get("id")
230
+ if isinstance(item_id_val, str) and item_id_val:
231
+ state.item_id = state.item_id or item_id_val
232
+ name_val = item.get("name")
233
+ if isinstance(name_val, str) and name_val:
234
+ state.name = state.name or name_val
235
+ call_id_val = item.get("call_id")
236
+ if isinstance(call_id_val, str) and call_id_val:
237
+ state.call_id = state.call_id or call_id_val
238
+
239
+ elif event_type == "response.output_item.done":
240
+ item = event.get("item") or {}
241
+ item_type = item.get("type")
242
+ if item_type == "output_text" and text_block_active:
243
+ yield anthropic_models.ContentBlockStopEvent(
244
+ type="content_block_stop", index=current_index
245
+ )
246
+ text_block_active = False
247
+ current_index += 1
248
+ elif item_type == "output_tool_call":
249
+ output_index = int(
250
+ item.get("output_index", event.get("output_index", 0)) or 0
251
+ )
252
+ state = tool_states.ensure(output_index)
253
+ if state.output_index < 0:
254
+ state.output_index = output_index
255
+
256
+ item_name = item.get("name")
257
+ if isinstance(item_name, str) and item_name:
258
+ state.name = state.name or item_name
259
+ item_call_id = item.get("call_id")
260
+ if isinstance(item_call_id, str) and item_call_id:
261
+ state.call_id = state.call_id or item_call_id
262
+ if item.get("id") and not state.item_id:
263
+ state.item_id = item.get("id")
264
+
265
+ args_str = (
266
+ state.final_arguments
267
+ or state.arguments
268
+ or "".join(state.arguments_parts)
269
+ )
270
+
271
+ yield anthropic_models.ContentBlockStartEvent(
272
+ type="content_block_start",
273
+ index=current_index,
274
+ content_block=anthropic_models.ToolUseBlock(
275
+ type="tool_use",
276
+ id=state.item_id or f"call_{state.index}",
277
+ name=state.name or "tool",
278
+ input=_parse_tool_input(args_str),
279
+ ),
280
+ )
281
+ yield anthropic_models.ContentBlockStopEvent(
282
+ type="content_block_stop", index=current_index
283
+ )
284
+ current_index += 1
285
+
286
+ elif event_type == "response.completed":
287
+ response = event.get("response") or {}
288
+ usage_data = response.get("usage") or {}
289
+ try:
290
+ usage = anthropic_models.Usage.model_validate(usage_data)
291
+ except Exception:
292
+ usage = anthropic_models.Usage(
293
+ input_tokens=usage_data.get("input_tokens", 0),
294
+ output_tokens=usage_data.get("output_tokens", 0),
295
+ )
296
+ final_stop_reason = response.get("stop_reason")
297
+ final_stop_sequence = response.get("stop_sequence")
298
+ break
299
+
300
+ if text_block_active:
301
+ yield anthropic_models.ContentBlockStopEvent(
302
+ type="content_block_stop", index=current_index
303
+ )
304
+
305
+ if message_started:
306
+ yield anthropic_models.MessageDeltaEvent(
307
+ type="message_delta",
308
+ delta=anthropic_models.MessageDelta(
309
+ stop_reason=map_openai_finish_to_anthropic_stop(final_stop_reason),
310
+ stop_sequence=final_stop_sequence,
311
+ ),
312
+ usage=usage,
313
+ )
314
+ yield anthropic_models.MessageStopEvent(type="message_stop")
315
+
316
+
317
+ class OpenAIChatToAnthropicStreamAdapter:
318
+ """Stateful adapter for OpenAI Chat → Anthropic streaming."""
319
+
320
+ def run(
321
+ self,
322
+ stream: AsyncIterator[Any],
323
+ ) -> AsyncGenerator[anthropic_models.MessageStreamEvent, None]:
324
+ return self._convert_chat_stream(stream)
325
+
326
+ def _convert_chat_stream(
327
+ self,
328
+ stream: AsyncIterator[openai_models.ChatCompletionChunk],
329
+ ) -> AsyncGenerator[anthropic_models.MessageStreamEvent, None]:
330
+ """Convert OpenAI ChatCompletion stream to Anthropic MessageStreamEvent stream."""
331
+
332
+ async def generator() -> AsyncGenerator[
333
+ anthropic_models.MessageStreamEvent, None
334
+ ]:
335
+ message_started = False
336
+ text_block_started = False
337
+ accumulated_content = ""
338
+ model_id = ""
339
+ current_index = 0
340
+ tool_tracker = ToolCallTracker()
341
+ openai_accumulator = OpenAIAccumulator()
342
+
343
+ def _parse_tool_input(text: str) -> dict[str, Any]:
344
+ if not text:
345
+ return {}
346
+ try:
347
+ parsed = json.loads(text)
348
+ return parsed if isinstance(parsed, dict) else {"arguments": text}
349
+ except Exception:
350
+ return {"arguments": text}
351
+
352
+ async for chunk in stream:
353
+ if isinstance(chunk, dict):
354
+ chunk_payload = chunk
355
+ else:
356
+ try:
357
+ chunk_payload = chunk.model_dump(mode="json", exclude_none=True)
358
+ except Exception:
359
+ chunk_payload = chunk.model_dump(exclude_none=True)
360
+
361
+ choices = chunk_payload.get("choices")
362
+ if not choices:
363
+ continue
364
+
365
+ choice = choices[0]
366
+ openai_accumulator.accumulate("", chunk_payload)
367
+
368
+ if chunk_payload.get("model"):
369
+ model_id = chunk_payload["model"]
370
+ elif not isinstance(chunk, dict):
371
+ model_id = getattr(chunk, "model", model_id) or model_id
372
+
373
+ delta = choice.get("delta") if isinstance(choice, dict) else {}
374
+ if not isinstance(delta, dict):
375
+ delta = {}
376
+ finish_reason = choice.get("finish_reason")
377
+ content_piece = delta.get("content")
378
+
379
+ if not message_started:
380
+ chunk_id = chunk_payload.get("id")
381
+ if chunk_id is None and not isinstance(chunk, dict):
382
+ chunk_id = getattr(chunk, "id", None)
383
+ chunk_id = chunk_id or "msg_stream"
384
+
385
+ yield anthropic_models.MessageStartEvent(
386
+ type="message_start",
387
+ message=anthropic_models.MessageResponse(
388
+ id=chunk_id,
389
+ type="message",
390
+ role="assistant",
391
+ content=[],
392
+ model=model_id,
393
+ stop_reason=None,
394
+ stop_sequence=None,
395
+ usage=anthropic_models.Usage(
396
+ input_tokens=0, output_tokens=0
397
+ ),
398
+ ),
399
+ )
400
+ message_started = True
401
+
402
+ if content_piece:
403
+ content_text = (
404
+ content_piece
405
+ if isinstance(content_piece, str)
406
+ else str(content_piece)
407
+ )
408
+ if not text_block_started:
409
+ yield anthropic_models.ContentBlockStartEvent(
410
+ type="content_block_start",
411
+ index=current_index,
412
+ content_block=anthropic_models.TextBlock(
413
+ type="text", text=""
414
+ ),
415
+ )
416
+ text_block_started = True
417
+
418
+ yield anthropic_models.ContentBlockDeltaEvent(
419
+ type="content_block_delta",
420
+ index=current_index,
421
+ delta=anthropic_models.TextBlock(
422
+ type="text", text=content_text
423
+ ),
424
+ )
425
+ accumulated_content += content_text
426
+
427
+ if finish_reason:
428
+ if text_block_started:
429
+ yield anthropic_models.ContentBlockStopEvent(
430
+ type="content_block_stop",
431
+ index=current_index,
432
+ )
433
+ text_block_started = False
434
+ current_index += 1
435
+
436
+ complete_calls = openai_accumulator.get_complete_tool_calls()
437
+ for tool in complete_calls:
438
+ tool_id_value = tool.get("id")
439
+ call_identifier = str(
440
+ tool_id_value
441
+ or f"call_{tool.get('index', len(tool_tracker.values()))}"
442
+ )
443
+ state = tool_tracker.ensure(call_identifier)
444
+ if state.name is None:
445
+ state.name = tool.get("function", {}).get(
446
+ "name"
447
+ ) or tool.get("name")
448
+ if state.call_id is None and isinstance(tool_id_value, str):
449
+ state.call_id = tool_id_value
450
+ function_payload = tool.get("function", {})
451
+ arguments_payload = function_payload.get("arguments")
452
+ if isinstance(arguments_payload, str) and arguments_payload:
453
+ state.final_arguments = arguments_payload
454
+ state.arguments = arguments_payload
455
+ if state.item_id is None:
456
+ state.item_id = call_identifier
457
+
458
+ for state in tool_tracker.values():
459
+ if state.item_id is None:
460
+ state.item_id = (
461
+ state.call_id or state.id or f"call_{state.index}"
462
+ )
463
+ for event in emit_anthropic_tool_use_events(
464
+ current_index,
465
+ state,
466
+ parser=_parse_tool_input,
467
+ ):
468
+ yield event
469
+ current_index += 1
470
+
471
+ usage_payload = chunk_payload.get("usage")
472
+ if usage_payload is None and not isinstance(chunk, dict):
473
+ usage_obj = getattr(chunk, "usage", None)
474
+ if usage_obj is not None:
475
+ if hasattr(usage_obj, "model_dump"):
476
+ usage_payload = usage_obj.model_dump()
477
+ else:
478
+ usage_payload = {
479
+ "prompt_tokens": getattr(
480
+ usage_obj, "prompt_tokens", 0
481
+ ),
482
+ "completion_tokens": getattr(
483
+ usage_obj, "completion_tokens", 0
484
+ ),
485
+ }
486
+
487
+ anthropic_usage = (
488
+ openai_usage_to_anthropic_usage(usage_payload)
489
+ if usage_payload is not None
490
+ else anthropic_models.Usage(input_tokens=0, output_tokens=0)
491
+ )
492
+
493
+ mapped_stop = map_openai_finish_to_anthropic_stop(finish_reason)
494
+
495
+ yield anthropic_models.MessageDeltaEvent(
496
+ type="message_delta",
497
+ delta=anthropic_models.MessageDelta(stop_reason=mapped_stop),
498
+ usage=anthropic_usage,
499
+ )
500
+ yield anthropic_models.MessageStopEvent(type="message_stop")
501
+ break
502
+
503
+ return generator()
504
+
505
+
506
+ async def convert__openai_responses_to_anthropic_messages__stream(
507
+ stream: AsyncIterator[Any],
508
+ ) -> AsyncGenerator[anthropic_models.MessageStreamEvent, None]:
509
+ """Translate OpenAI Responses streaming events into Anthropic message events."""
510
+
511
+ adapter = OpenAIResponsesToAnthropicStreamAdapter()
512
+ async for event in adapter.run(stream):
513
+ yield event
514
+
515
+
516
+ def convert__openai_chat_to_anthropic_messages__stream(
517
+ stream: AsyncIterator[Any],
518
+ ) -> AsyncGenerator[anthropic_models.MessageStreamEvent, None]:
519
+ """Translate OpenAI ChatCompletion streams into Anthropic message events."""
520
+
521
+ adapter = OpenAIChatToAnthropicStreamAdapter()
522
+ return adapter.run(stream)
523
+
524
+
525
+ __all__ = [
526
+ "OpenAIChatToAnthropicStreamAdapter",
527
+ "OpenAIResponsesToAnthropicStreamAdapter",
528
+ "convert__openai_chat_to_anthropic_messages__stream",
529
+ "convert__openai_responses_to_anthropic_messages__stream",
530
+ ]
@@ -0,0 +1,53 @@
1
+ """Facade module exposing OpenAI↔OpenAI formatter entry points."""
2
+
3
+ import sys
4
+ from types import ModuleType
5
+
6
+ from . import streams as _streams
7
+ from .requests import (
8
+ convert__openai_chat_to_openai_responses__request,
9
+ convert__openai_responses_to_openaichat__request,
10
+ )
11
+ from .responses import (
12
+ convert__openai_chat_to_openai_responses__response,
13
+ convert__openai_completion_usage_to_openai_responses__usage,
14
+ convert__openai_responses_to_openai_chat__response,
15
+ convert__openai_responses_usage_to_openai_completion__usage,
16
+ )
17
+ from .streams import (
18
+ OpenAIChatToResponsesStreamAdapter,
19
+ OpenAIResponsesToChatStreamAdapter,
20
+ convert__openai_chat_to_openai_responses__stream,
21
+ convert__openai_responses_to_openai_chat__stream,
22
+ )
23
+
24
+
25
+ __all__ = [
26
+ "convert__openai_chat_to_openai_responses__request",
27
+ "convert__openai_responses_to_openaichat__request",
28
+ "convert__openai_chat_to_openai_responses__response",
29
+ "convert__openai_completion_usage_to_openai_responses__usage",
30
+ "convert__openai_responses_to_openai_chat__response",
31
+ "convert__openai_responses_usage_to_openai_completion__usage",
32
+ "OpenAIChatToResponsesStreamAdapter",
33
+ "OpenAIResponsesToChatStreamAdapter",
34
+ "convert__openai_chat_to_openai_responses__stream",
35
+ "convert__openai_responses_to_openai_chat__stream",
36
+ ]
37
+
38
+
39
+ class _OpenAIToOpenAIModule(ModuleType):
40
+ _propagated_names = {
41
+ "OpenAIChatToResponsesStreamAdapter",
42
+ "OpenAIResponsesToChatStreamAdapter",
43
+ }
44
+
45
+ def __setattr__(self, name: str, value: object) -> None:
46
+ super().__setattr__(name, value)
47
+ if name in self._propagated_names and hasattr(_streams, name):
48
+ setattr(_streams, name, value)
49
+
50
+
51
+ _module = sys.modules[__name__]
52
+ if not isinstance(_module, _OpenAIToOpenAIModule):
53
+ _module.__class__ = _OpenAIToOpenAIModule