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.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  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 +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  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 +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  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 +68 -446
  387. ccproxy/utils/version_checker.py +273 -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.7.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 -1251
  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 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  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.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.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.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,619 @@
1
+ from copy import deepcopy
2
+ from datetime import datetime
3
+ from typing import Annotated, Any, Literal
4
+
5
+ from pydantic import Field, model_validator
6
+
7
+ from ccproxy.llms.formatters import LlmBaseModel
8
+
9
+
10
+ # ===================================================================
11
+ # Error Models
12
+ # ===================================================================
13
+
14
+
15
+ class ErrorDetail(LlmBaseModel):
16
+ """Base model for an error."""
17
+
18
+ message: str
19
+
20
+
21
+ class InvalidRequestError(ErrorDetail):
22
+ """Error for an invalid request."""
23
+
24
+ type: Literal["invalid_request_error"] = Field(
25
+ default="invalid_request_error", alias="type"
26
+ )
27
+
28
+
29
+ class AuthenticationError(ErrorDetail):
30
+ """Error for authentication issues."""
31
+
32
+ type: Literal["authentication_error"] = Field(
33
+ default="authentication_error", alias="type"
34
+ )
35
+
36
+
37
+ class BillingError(ErrorDetail):
38
+ """Error for billing issues."""
39
+
40
+ type: Literal["billing_error"] = Field(default="billing_error", alias="type")
41
+
42
+
43
+ class PermissionError(ErrorDetail):
44
+ """Error for permission issues."""
45
+
46
+ type: Literal["permission_error"] = Field(default="permission_error", alias="type")
47
+
48
+
49
+ class NotFoundError(ErrorDetail):
50
+ """Error for a resource not being found."""
51
+
52
+ type: Literal["not_found_error"] = Field(default="not_found_error", alias="type")
53
+
54
+
55
+ class RateLimitError(ErrorDetail):
56
+ """Error for rate limiting."""
57
+
58
+ type: Literal["rate_limit_error"] = Field(default="rate_limit_error", alias="type")
59
+
60
+
61
+ class GatewayTimeoutError(ErrorDetail):
62
+ """Error for a gateway timeout."""
63
+
64
+ type: Literal["timeout_error"] = Field(default="timeout_error", alias="type")
65
+
66
+
67
+ class APIError(ErrorDetail):
68
+ """A generic API error."""
69
+
70
+ type: Literal["api_error"] = Field(default="api_error", alias="type")
71
+
72
+
73
+ class OverloadedError(ErrorDetail):
74
+ """Error for when the server is overloaded."""
75
+
76
+ type: Literal["overloaded_error"] = Field(default="overloaded_error", alias="type")
77
+
78
+
79
+ ErrorType = Annotated[
80
+ InvalidRequestError
81
+ | AuthenticationError
82
+ | BillingError
83
+ | PermissionError
84
+ | NotFoundError
85
+ | RateLimitError
86
+ | GatewayTimeoutError
87
+ | APIError
88
+ | OverloadedError,
89
+ Field(discriminator="type"),
90
+ ]
91
+
92
+
93
+ class ErrorResponse(LlmBaseModel):
94
+ """The structure of an error response."""
95
+
96
+ type: Literal["error"] = Field(default="error", alias="type")
97
+ error: ErrorType
98
+
99
+
100
+ # ===================================================================
101
+ # Models API Models (/v1/models)
102
+ # ===================================================================
103
+
104
+
105
+ class ModelInfo(LlmBaseModel):
106
+ """Information about an available model."""
107
+
108
+ id: str
109
+ type: Literal["model"] = Field(default="model", alias="type")
110
+ created_at: datetime
111
+ display_name: str
112
+
113
+
114
+ class ListModelsResponse(LlmBaseModel):
115
+ """Response containing a list of available models."""
116
+
117
+ data: list[ModelInfo]
118
+ first_id: str | None = None
119
+ last_id: str | None = None
120
+ has_more: bool
121
+
122
+
123
+ # ===================================================================
124
+ # Messages API Models (/v1/messages)
125
+ # ===================================================================
126
+
127
+ # --- Base Models & Common Structures for Messages ---
128
+
129
+
130
+ class ContentBlockBase(LlmBaseModel):
131
+ """Base model for a content block."""
132
+
133
+ pass
134
+
135
+
136
+ class TextBlock(ContentBlockBase):
137
+ """A block of text content."""
138
+
139
+ type: Literal["text"] = Field(default="text", alias="type")
140
+ text: str
141
+
142
+
143
+ class TextDelta(ContentBlockBase):
144
+ """A delta chunk of text content used in streaming events."""
145
+
146
+ type: Literal["text_delta"] = Field(default="text_delta", alias="type")
147
+ text: str
148
+
149
+
150
+ class ImageSource(LlmBaseModel):
151
+ """Source of an image."""
152
+
153
+ type: Literal["base64"] = Field(default="base64", alias="type")
154
+ media_type: Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
155
+ data: str
156
+
157
+
158
+ class ImageBlock(ContentBlockBase):
159
+ """A block of image content."""
160
+
161
+ type: Literal["image"] = Field(default="image", alias="type")
162
+ source: ImageSource
163
+
164
+
165
+ class ToolUseBlock(ContentBlockBase):
166
+ """Block for a tool use."""
167
+
168
+ type: Literal["tool_use"] = Field(default="tool_use", alias="type")
169
+ id: str
170
+ name: str
171
+ input: dict[str, Any]
172
+
173
+
174
+ class ToolResultBlock(ContentBlockBase):
175
+ """Block for the result of a tool use."""
176
+
177
+ type: Literal["tool_result"] = Field(default="tool_result", alias="type")
178
+ tool_use_id: str
179
+ content: str | list[TextBlock | ImageBlock] = ""
180
+ is_error: bool = False
181
+
182
+
183
+ class ThinkingBlock(ContentBlockBase):
184
+ """Block representing the model's thinking process."""
185
+
186
+ type: Literal["thinking"] = Field(default="thinking", alias="type")
187
+ thinking: str
188
+ signature: str
189
+
190
+
191
+ class ThinkingDelta(ContentBlockBase):
192
+ """Partial thinking content emitted during streaming."""
193
+
194
+ type: Literal["thinking_delta"] = Field(default="thinking_delta", alias="type")
195
+ thinking: str = ""
196
+
197
+
198
+ class SignatureDelta(ContentBlockBase):
199
+ """Partial signature content for a thinking block."""
200
+
201
+ type: Literal["signature_delta"] = Field(default="signature_delta", alias="type")
202
+ signature: str = ""
203
+
204
+
205
+ class InputJsonDelta(ContentBlockBase):
206
+ """Partial JSON payload for a tool use block."""
207
+
208
+ type: Literal["input_json_delta"] = Field(default="input_json_delta", alias="type")
209
+ partial_json: str = ""
210
+
211
+
212
+ class RedactedThinkingBlock(ContentBlockBase):
213
+ """A block specifying internal, redacted thinking by the model."""
214
+
215
+ type: Literal["redacted_thinking"] = Field(
216
+ default="redacted_thinking", alias="type"
217
+ )
218
+ data: str
219
+
220
+
221
+ RequestContentBlock = Annotated[
222
+ TextBlock | ImageBlock | ToolUseBlock | ToolResultBlock, Field(discriminator="type")
223
+ ]
224
+
225
+ ResponseContentBlock = Annotated[
226
+ TextBlock | ToolUseBlock | ThinkingBlock | RedactedThinkingBlock,
227
+ Field(discriminator="type"),
228
+ ]
229
+
230
+
231
+ class Message(LlmBaseModel):
232
+ """A message in the conversation."""
233
+
234
+ role: Literal["user", "assistant"]
235
+ content: str | list[RequestContentBlock]
236
+
237
+
238
+ class CacheCreation(LlmBaseModel):
239
+ """Breakdown of cached tokens."""
240
+
241
+ ephemeral_1h_input_tokens: int
242
+ ephemeral_5m_input_tokens: int
243
+
244
+
245
+ class ServerToolUsage(LlmBaseModel):
246
+ """Server-side tool usage statistics."""
247
+
248
+ web_search_requests: int
249
+
250
+
251
+ class Usage(LlmBaseModel):
252
+ """Token usage statistics."""
253
+
254
+ input_tokens: int | None = None
255
+ output_tokens: int | None = None
256
+ cache_creation: CacheCreation | None = None
257
+ cache_creation_input_tokens: int | None = None
258
+ cache_read_input_tokens: int | None = None
259
+ server_tool_use: ServerToolUsage | None = None
260
+ service_tier: Literal["standard", "priority", "batch"] | None = None
261
+
262
+
263
+ # --- Tool Definitions ---
264
+ def _normalize_tool_payload(value: Any) -> Any:
265
+ """Return a mutable dict with required tool fields normalized."""
266
+
267
+ if not isinstance(value, dict):
268
+ return value
269
+
270
+ normalized: dict[str, Any] = deepcopy(value)
271
+ custom = normalized.get("custom")
272
+ if isinstance(custom, dict):
273
+ for key in ("name", "description", "input_schema"):
274
+ normalized.setdefault(key, custom.get(key))
275
+
276
+ normalized.setdefault("input_schema", normalized.get("input_schema") or {})
277
+
278
+ if "type" not in normalized:
279
+ normalized["type"] = "custom"
280
+
281
+ return normalized
282
+
283
+
284
+ class ToolBase(LlmBaseModel):
285
+ """Shared fields for custom tool definitions."""
286
+
287
+ name: str = Field(
288
+ ..., min_length=1, max_length=128, pattern=r"^[a-zA-Z0-9_-]{1,128}$"
289
+ )
290
+ description: str | None = None
291
+ input_schema: dict[str, Any] = Field(default_factory=dict)
292
+
293
+ @model_validator(mode="before")
294
+ @classmethod
295
+ def _merge_nested_custom(cls, value: Any) -> Any:
296
+ """Support nested {"custom": {...}} payloads by flattening fields."""
297
+ return _normalize_tool_payload(value)
298
+
299
+
300
+ class Tool(ToolBase):
301
+ """Definition of a custom tool in the current Anthropic schema."""
302
+
303
+ type: Literal["tool"] = Field(default="tool", alias="type")
304
+
305
+
306
+ class LegacyCustomTool(ToolBase):
307
+ """Backward-compatible support for earlier 'custom' tool payloads."""
308
+
309
+ type: Literal["custom"] = Field(default="custom", alias="type")
310
+
311
+
312
+ class WebSearchTool(LlmBaseModel):
313
+ """Definition for the built-in web search tool."""
314
+
315
+ type: Literal["web_search_20250305"] = Field(
316
+ default="web_search_20250305", alias="type"
317
+ )
318
+ name: Literal["web_search"] = "web_search"
319
+
320
+
321
+ # Add other specific built-in tool models here as needed
322
+ AnyTool = Annotated[
323
+ Tool | LegacyCustomTool | WebSearchTool, # Union of all tool types
324
+ Field(discriminator="type"),
325
+ ]
326
+
327
+ # --- Supporting models for CreateMessageRequest ---
328
+
329
+
330
+ class Metadata(LlmBaseModel):
331
+ """Metadata about the request."""
332
+
333
+ user_id: str | None = Field(None, max_length=256)
334
+
335
+
336
+ class ThinkingConfigBase(LlmBaseModel):
337
+ """Base model for thinking configuration."""
338
+
339
+ pass
340
+
341
+
342
+ class ThinkingConfigEnabled(ThinkingConfigBase):
343
+ """Configuration for enabled thinking."""
344
+
345
+ type: Literal["enabled"] = Field(default="enabled", alias="type")
346
+ budget_tokens: int = Field(..., ge=1024)
347
+
348
+
349
+ class ThinkingConfigDisabled(ThinkingConfigBase):
350
+ """Configuration for disabled thinking."""
351
+
352
+ type: Literal["disabled"] = Field(default="disabled", alias="type")
353
+
354
+
355
+ ThinkingConfig = Annotated[
356
+ ThinkingConfigEnabled | ThinkingConfigDisabled, Field(discriminator="type")
357
+ ]
358
+
359
+
360
+ class ToolChoiceBase(LlmBaseModel):
361
+ """Base model for tool choice."""
362
+
363
+ pass
364
+
365
+
366
+ class ToolChoiceAuto(ToolChoiceBase):
367
+ """The model will automatically decide whether to use tools."""
368
+
369
+ type: Literal["auto"] = Field(default="auto", alias="type")
370
+ disable_parallel_tool_use: bool = False
371
+
372
+
373
+ class ToolChoiceAny(ToolChoiceBase):
374
+ """The model will use any available tools."""
375
+
376
+ type: Literal["any"] = Field(default="any", alias="type")
377
+ disable_parallel_tool_use: bool = False
378
+
379
+
380
+ class ToolChoiceTool(ToolChoiceBase):
381
+ """The model will use the specified tool."""
382
+
383
+ type: Literal["tool"] = Field(default="tool", alias="type")
384
+ name: str
385
+ disable_parallel_tool_use: bool = False
386
+
387
+
388
+ class ToolChoiceNone(ToolChoiceBase):
389
+ """The model will not use any tools."""
390
+
391
+ type: Literal["none"] = Field(default="none", alias="type")
392
+
393
+
394
+ ToolChoice = Annotated[
395
+ ToolChoiceAuto | ToolChoiceAny | ToolChoiceTool | ToolChoiceNone,
396
+ Field(discriminator="type"),
397
+ ]
398
+
399
+
400
+ class RequestMCPServerToolConfiguration(LlmBaseModel):
401
+ """Tool configuration for an MCP server."""
402
+
403
+ allowed_tools: list[str] | None = None
404
+ enabled: bool | None = None
405
+
406
+
407
+ class RequestMCPServerURLDefinition(LlmBaseModel):
408
+ """URL definition for an MCP server."""
409
+
410
+ name: str
411
+ type: Literal["url"] = Field(default="url", alias="type")
412
+ url: str
413
+ authorization_token: str | None = None
414
+ tool_configuration: RequestMCPServerToolConfiguration | None = None
415
+
416
+
417
+ class Container(LlmBaseModel):
418
+ """Information about the container used in a request."""
419
+
420
+ id: str
421
+ expires_at: datetime
422
+
423
+
424
+ # --- Request Models ---
425
+
426
+
427
+ class CreateMessageRequest(LlmBaseModel):
428
+ """Request model for creating a new message."""
429
+
430
+ model: str
431
+ messages: list[Message]
432
+ max_tokens: int
433
+ container: str | None = None
434
+ mcp_servers: list[RequestMCPServerURLDefinition] | None = None
435
+ metadata: Metadata | None = None
436
+ service_tier: Literal["auto", "standard_only"] | None = None
437
+ stop_sequences: list[str] | None = None
438
+ stream: bool = False
439
+ system: str | list[TextBlock] | None = None
440
+ temperature: float | None = Field(default=None, ge=0.0, le=1.0)
441
+ thinking: ThinkingConfig | None = None
442
+ tools: list[AnyTool] | None = None
443
+ tool_choice: ToolChoice | None = Field(default=None)
444
+ top_k: int | None = None
445
+ top_p: float | None = Field(default=None, ge=0.0, le=1.0)
446
+
447
+ @model_validator(mode="before")
448
+ @classmethod
449
+ def _normalize_tools(cls, data: Any) -> Any:
450
+ if not isinstance(data, dict):
451
+ return data
452
+
453
+ tools = data.get("tools")
454
+ if isinstance(tools, list):
455
+ data["tools"] = [_normalize_tool_payload(tool) for tool in tools]
456
+
457
+ return data
458
+
459
+
460
+ class CountMessageTokensRequest(LlmBaseModel):
461
+ """Request model for counting tokens in a message."""
462
+
463
+ model: str
464
+ messages: list[Message]
465
+ system: str | list[TextBlock] | None = None
466
+ tools: list[AnyTool] | None = None
467
+
468
+ @model_validator(mode="before")
469
+ @classmethod
470
+ def _normalize_tools(cls, data: Any) -> Any:
471
+ if not isinstance(data, dict):
472
+ return data
473
+
474
+ tools = data.get("tools")
475
+ if isinstance(tools, list):
476
+ data["tools"] = [_normalize_tool_payload(tool) for tool in tools]
477
+
478
+ return data
479
+
480
+
481
+ # --- Response Models ---
482
+
483
+
484
+ class MessageResponse(LlmBaseModel):
485
+ """Response model for a created message."""
486
+
487
+ id: str
488
+ type: Literal["message"] = Field(default="message", alias="type")
489
+ role: Literal["assistant"]
490
+ content: list[ResponseContentBlock]
491
+ model: str
492
+ stop_reason: (
493
+ Literal[
494
+ "end_turn",
495
+ "max_tokens",
496
+ "stop_sequence",
497
+ "tool_use",
498
+ "pause_turn",
499
+ "refusal",
500
+ ]
501
+ | None
502
+ ) = None
503
+ stop_sequence: str | None = None
504
+ usage: Usage
505
+ container: Container | None = None
506
+
507
+
508
+ class CountMessageTokensResponse(LlmBaseModel):
509
+ """Response model for a token count request."""
510
+
511
+ input_tokens: int
512
+
513
+
514
+ # ===================================================================
515
+ # Streaming Models for /v1/messages
516
+ # ===================================================================
517
+
518
+
519
+ class PingEvent(LlmBaseModel):
520
+ """A keep-alive event."""
521
+
522
+ type: Literal["ping"] = Field(default="ping", alias="type")
523
+
524
+
525
+ class ErrorEvent(LlmBaseModel):
526
+ """An error event in the stream."""
527
+
528
+ type: Literal["error"] = Field(default="error", alias="type")
529
+ error: ErrorDetail
530
+
531
+
532
+ class MessageStartEvent(LlmBaseModel):
533
+ """Event sent when a message stream starts."""
534
+
535
+ type: Literal["message_start"] = Field(default="message_start", alias="type")
536
+ message: MessageResponse
537
+
538
+
539
+ class ContentBlockStartEvent(LlmBaseModel):
540
+ """Event when a content block starts."""
541
+
542
+ type: Literal["content_block_start"] = Field(
543
+ default="content_block_start", alias="type"
544
+ )
545
+ index: int
546
+ content_block: ResponseContentBlock
547
+
548
+
549
+ class ContentBlockDeltaEvent(LlmBaseModel):
550
+ """Event for a delta in a content block."""
551
+
552
+ type: Literal["content_block_delta"] = Field(
553
+ default="content_block_delta", alias="type"
554
+ )
555
+ index: int
556
+ # Anthropic streams use delta.type == "text_delta" during streaming.
557
+ # Accept both TextBlock (some SDKs may coerce) and TextDelta.
558
+ delta: Annotated[
559
+ TextBlock
560
+ | TextDelta
561
+ | ThinkingBlock
562
+ | ThinkingDelta
563
+ | SignatureDelta
564
+ | InputJsonDelta,
565
+ Field(discriminator="type"),
566
+ ]
567
+
568
+
569
+ class ContentBlockStopEvent(LlmBaseModel):
570
+ """Event when a content block stops."""
571
+
572
+ type: Literal["content_block_stop"] = Field(
573
+ default="content_block_stop", alias="type"
574
+ )
575
+ index: int
576
+
577
+
578
+ class MessageDelta(LlmBaseModel):
579
+ """The delta in a message delta event."""
580
+
581
+ stop_reason: (
582
+ Literal[
583
+ "end_turn",
584
+ "max_tokens",
585
+ "stop_sequence",
586
+ "tool_use",
587
+ "pause_turn",
588
+ "refusal",
589
+ ]
590
+ | None
591
+ ) = None
592
+ stop_sequence: str | None = None
593
+
594
+
595
+ class MessageDeltaEvent(LlmBaseModel):
596
+ """Event for a delta in the message metadata."""
597
+
598
+ type: Literal["message_delta"] = Field(default="message_delta", alias="type")
599
+ delta: MessageDelta
600
+ usage: Usage
601
+
602
+
603
+ class MessageStopEvent(LlmBaseModel):
604
+ """Event sent when a message stream stops."""
605
+
606
+ type: Literal["message_stop"] = Field(default="message_stop", alias="type")
607
+
608
+
609
+ MessageStreamEvent = Annotated[
610
+ PingEvent
611
+ | ErrorEvent
612
+ | MessageStartEvent
613
+ | ContentBlockStartEvent
614
+ | ContentBlockDeltaEvent
615
+ | ContentBlockStopEvent
616
+ | MessageDeltaEvent
617
+ | MessageStopEvent,
618
+ Field(discriminator="type"),
619
+ ]