ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0a4__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.0a4.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0a4.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.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,117 @@
1
+ """Streaming request handler for SSE and chunked responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ import httpx
9
+ import structlog
10
+
11
+ from ccproxy.core.plugins.hooks import HookManager
12
+ from ccproxy.core.request_context import RequestContext
13
+ from ccproxy.services.handler_config import HandlerConfig
14
+ from ccproxy.streaming.deferred import DeferredStreaming
15
+
16
+
17
+ logger = structlog.get_logger(__name__)
18
+
19
+
20
+ class StreamingHandler:
21
+ """Manages streaming request processing with header preservation and SSE adaptation."""
22
+
23
+ def __init__(
24
+ self,
25
+ hook_manager: HookManager | None = None,
26
+ ) -> None:
27
+ """Initialize with hook manager for stream events.
28
+
29
+ Args:
30
+ hook_manager: Optional hook manager for emitting stream events
31
+ """
32
+ self.hook_manager = hook_manager
33
+
34
+ def should_stream_response(self, headers: dict[str, str]) -> bool:
35
+ """Detect streaming intent from request headers.
36
+
37
+ - Prefer client `Accept: text/event-stream`
38
+ - Fallback to provider-style `Content-Type: text/event-stream` (rare for requests)
39
+ - Case-insensitive checks
40
+ """
41
+ accept = str(headers.get("accept", "")).lower()
42
+ if "text/event-stream" in accept:
43
+ return True
44
+
45
+ content_type = str(headers.get("content-type", "")).lower()
46
+ return "text/event-stream" in content_type
47
+
48
+ async def should_stream(
49
+ self, request_body: bytes, handler_config: HandlerConfig
50
+ ) -> bool:
51
+ """Check if request body has stream:true flag.
52
+
53
+ - Returns False if provider doesn't support streaming
54
+ - Parses JSON body for 'stream' field
55
+ - Handles parse errors gracefully
56
+ """
57
+ if not handler_config.supports_streaming:
58
+ return False
59
+
60
+ try:
61
+ data = json.loads(request_body)
62
+ return data.get("stream", False) is True
63
+ except (json.JSONDecodeError, TypeError):
64
+ return False
65
+
66
+ async def handle_streaming_request(
67
+ self,
68
+ method: str,
69
+ url: str,
70
+ headers: dict[str, str],
71
+ body: bytes,
72
+ handler_config: HandlerConfig,
73
+ request_context: RequestContext,
74
+ on_headers: Any | None = None,
75
+ client_config: dict[str, Any] | None = None,
76
+ client: httpx.AsyncClient | None = None,
77
+ ) -> DeferredStreaming:
78
+ """Create a deferred streaming response that preserves headers.
79
+
80
+ This always returns a DeferredStreaming response which:
81
+ - Defers the actual HTTP request until FastAPI sends the response
82
+ - Captures all upstream headers correctly
83
+ - Supports SSE processing through handler_config
84
+ - Provides request tracing and metrics
85
+ """
86
+
87
+ # Use provided client or create a short-lived one
88
+ owns_client = False
89
+ if client is None:
90
+ client = httpx.AsyncClient(**(client_config or {}))
91
+ owns_client = True
92
+
93
+ # Log that we're creating a deferred response
94
+ logger.debug(
95
+ "streaming_handler_creating_deferred_response",
96
+ url=url,
97
+ method=method,
98
+ has_sse_adapter=bool(handler_config.response_adapter),
99
+ adapter_type=type(handler_config.response_adapter).__name__
100
+ if handler_config.response_adapter
101
+ else None,
102
+ )
103
+
104
+ # Return the deferred response with format adapter from handler config
105
+ return DeferredStreaming(
106
+ method=method,
107
+ url=url,
108
+ headers=headers,
109
+ body=body,
110
+ client=client,
111
+ media_type="text/event-stream; charset=utf-8",
112
+ handler_config=handler_config, # Contains format adapter if needed
113
+ request_context=request_context,
114
+ hook_manager=self.hook_manager,
115
+ on_headers=on_headers,
116
+ close_client_on_finish=owns_client,
117
+ )
@@ -0,0 +1,77 @@
1
+ """Streaming interfaces for provider implementations.
2
+
3
+ This module defines interfaces that providers can implement to extend
4
+ streaming functionality without coupling core code to specific providers.
5
+ """
6
+
7
+ from typing import Protocol
8
+
9
+ from typing_extensions import TypedDict
10
+
11
+
12
+ class StreamingMetrics(TypedDict, total=False):
13
+ """Standard streaming metrics structure."""
14
+
15
+ tokens_input: int | None
16
+ tokens_output: int | None
17
+ cache_read_tokens: int | None
18
+ cache_write_tokens: int | None
19
+ cost_usd: float | None
20
+
21
+
22
+ class IStreamingMetricsCollector(Protocol):
23
+ """Interface for provider-specific streaming metrics collection.
24
+
25
+ Providers implement this interface to extract token usage and other
26
+ metrics from their specific streaming response formats.
27
+ """
28
+
29
+ def process_chunk(self, chunk_str: str) -> bool:
30
+ """Process a streaming chunk to extract metrics.
31
+
32
+ Args:
33
+ chunk_str: Raw chunk string from streaming response
34
+
35
+ Returns:
36
+ True if this was the final chunk with complete metrics, False otherwise
37
+ """
38
+ ...
39
+
40
+ def process_raw_chunk(self, chunk_str: str) -> bool:
41
+ """Process a raw provider chunk before any format conversion.
42
+
43
+ This method is called with chunks in the provider's native format,
44
+ before any OpenAI/Anthropic format conversion happens.
45
+
46
+ Args:
47
+ chunk_str: Raw chunk string in provider's native format
48
+
49
+ Returns:
50
+ True if this was the final chunk with complete metrics, False otherwise
51
+ """
52
+ ...
53
+
54
+ def process_converted_chunk(self, chunk_str: str) -> bool:
55
+ """Process a chunk after format conversion.
56
+
57
+ This method is called with chunks after they've been converted
58
+ to a different format (e.g., OpenAI format).
59
+
60
+ Args:
61
+ chunk_str: Chunk string after format conversion
62
+
63
+ Returns:
64
+ True if this was the final chunk with complete metrics, False otherwise
65
+ """
66
+ ...
67
+
68
+ def get_metrics(self) -> StreamingMetrics:
69
+ """Get the collected metrics.
70
+
71
+ Returns:
72
+ Dictionary with provider-specific metrics (tokens, costs, etc.)
73
+ """
74
+ ...
75
+
76
+
77
+ # Moved StreamingConfigurable to ccproxy.core.interfaces to avoid circular imports
@@ -0,0 +1,39 @@
1
+ """Simplified streaming adapter that bypasses complex type conversions.
2
+
3
+ This adapter provides a direct dict-based interface for streaming without
4
+ the complexity of the shim layer.
5
+ """
6
+
7
+ from collections.abc import AsyncGenerator, AsyncIterator
8
+ from typing import Any
9
+
10
+
11
+ class SimpleStreamingAdapter:
12
+ """Simple adapter for streaming responses that works directly with dicts."""
13
+
14
+ def __init__(self, name: str = "simple_streaming"):
15
+ """Initialize the simple adapter."""
16
+ self.name = name
17
+
18
+ async def adapt_request(self, request: dict[str, Any]) -> dict[str, Any]:
19
+ """Pass through request - no adaptation needed for streaming."""
20
+ return request
21
+
22
+ async def adapt_response(self, response: dict[str, Any]) -> dict[str, Any]:
23
+ """Pass through response - no adaptation needed for streaming."""
24
+ return response
25
+
26
+ def adapt_stream(
27
+ self, stream: AsyncIterator[dict[str, Any]]
28
+ ) -> AsyncGenerator[dict[str, Any], None]:
29
+ """Pass through stream - no adaptation needed for simple streaming."""
30
+
31
+ async def passthrough_stream() -> AsyncGenerator[dict[str, Any], None]:
32
+ async for chunk in stream:
33
+ yield chunk
34
+
35
+ return passthrough_stream()
36
+
37
+ async def adapt_error(self, error: dict[str, Any]) -> dict[str, Any]:
38
+ """Pass through error - no adaptation needed."""
39
+ return error
@@ -0,0 +1,109 @@
1
+ """Shared helpers for serializing JSON streams into SSE messages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import AsyncGenerator, AsyncIterator
7
+ from typing import Any
8
+
9
+ from ccproxy.core.logging import get_logger
10
+ from ccproxy.llms.streaming import AnthropicSSEFormatter
11
+
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ async def serialize_json_to_sse_stream(
17
+ json_stream: AsyncIterator[Any],
18
+ *,
19
+ include_done: bool = True,
20
+ request_context: Any | None = None,
21
+ ) -> AsyncGenerator[bytes, None]:
22
+ """Serialize JSON-like stream items into SSE-compliant bytes.
23
+
24
+ This matches the behaviour previously implemented in
25
+ ``DeferredStreaming._serialize_json_to_sse_stream`` and is shared by
26
+ SDK and HTTP-based providers alike.
27
+
28
+ Args:
29
+ json_stream: Async iterator yielding dict-like SSE payloads (or
30
+ objects with ``model_dump``/``dict``).
31
+ include_done: Whether to append the ``data: [DONE]`` sentinel at
32
+ the end of the stream.
33
+ request_context: Optional request context for logging (expects a
34
+ ``request_id`` attribute when present).
35
+ """
36
+
37
+ formatter = AnthropicSSEFormatter()
38
+ request_id = None
39
+ if request_context and hasattr(request_context, "request_id"):
40
+ request_id = request_context.request_id
41
+
42
+ chunk_count = 0
43
+ anthropic_chunks = 0
44
+ openai_chunks = 0
45
+
46
+ async for json_obj in json_stream:
47
+ chunk_count += 1
48
+
49
+ # Normalise the payload to a dict
50
+ if hasattr(json_obj, "model_dump") and callable(json_obj.model_dump):
51
+ json_obj = json_obj.model_dump()
52
+ elif hasattr(json_obj, "dict") and callable(json_obj.dict):
53
+ json_obj = json_obj.dict()
54
+
55
+ if not isinstance(json_obj, dict):
56
+ # Skip unsupported payloads
57
+ logger.debug(
58
+ "sse_serialization_skipped_non_dict",
59
+ chunk_number=chunk_count,
60
+ payload_type=type(json_obj).__name__,
61
+ request_id=request_id,
62
+ category="sse_format",
63
+ )
64
+ continue
65
+
66
+ event_type = json_obj.get("type")
67
+ if isinstance(event_type, str) and event_type:
68
+ anthropic_chunks += 1
69
+ if event_type == "ping":
70
+ sse_event = formatter.format_ping()
71
+ else:
72
+ sse_event = formatter.format_event(event_type, json_obj)
73
+
74
+ logger.trace(
75
+ "sse_serialization_anthropic_format",
76
+ event_type=event_type,
77
+ chunk_number=chunk_count,
78
+ request_id=request_id,
79
+ category="sse_format",
80
+ )
81
+ else:
82
+ openai_chunks += 1
83
+ json_str = json.dumps(json_obj, ensure_ascii=False)
84
+ sse_event = f"data: {json_str}\n\n"
85
+
86
+ logger.trace(
87
+ "sse_serialization_openai_format",
88
+ chunk_number=chunk_count,
89
+ has_choices=bool(json_obj.get("choices")),
90
+ request_id=request_id,
91
+ category="sse_format",
92
+ )
93
+
94
+ yield sse_event.encode("utf-8")
95
+
96
+ logger.debug(
97
+ "sse_serialization_complete",
98
+ total_chunks=chunk_count,
99
+ anthropic_chunks=anthropic_chunks,
100
+ openai_chunks=openai_chunks,
101
+ request_id=request_id,
102
+ category="sse_format",
103
+ )
104
+
105
+ if include_done:
106
+ yield b"data: [DONE]\n\n"
107
+
108
+
109
+ __all__ = ["serialize_json_to_sse_stream"]
@@ -0,0 +1,127 @@
1
+ """Helpers for incrementally parsing server-sent events (SSE)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+
9
+ class SSEStreamParser:
10
+ """Accumulate SSE fragments and yield decoded ``data:`` payloads.
11
+
12
+ The parser keeps track of partial lines and events across ``feed`` calls so
13
+ callers can push raw provider chunks (``str`` or ``bytes``) and only receive
14
+ payloads when a full SSE event has been received. ``data: [DONE]`` sentinel
15
+ events are filtered out automatically.
16
+ """
17
+
18
+ __slots__ = ("_line_remainder", "_event_lines", "_errors")
19
+
20
+ def __init__(self) -> None:
21
+ self._line_remainder: str = ""
22
+ self._event_lines: list[str] = []
23
+ self._errors: list[tuple[str, Exception]] = []
24
+
25
+ def feed(self, chunk: str | bytes | None) -> list[Any]:
26
+ """Process a streaming fragment and return decoded JSON payloads.
27
+
28
+ Args:
29
+ chunk: Raw chunk from the provider. ``bytes`` inputs are decoded
30
+ using UTF-8. ``None`` or empty values yield no events.
31
+
32
+ Returns:
33
+ List of decoded JSON payloads for completed events. ``[DONE]``
34
+ sentinels are omitted.
35
+ """
36
+
37
+ if not chunk:
38
+ return []
39
+
40
+ if isinstance(chunk, bytes):
41
+ chunk = chunk.decode("utf-8", errors="ignore")
42
+
43
+ if not chunk:
44
+ return []
45
+
46
+ chunk = chunk.replace("\r\n", "\n").replace("\r", "\n")
47
+ buffered = f"{self._line_remainder}{chunk}"
48
+
49
+ lines = buffered.split("\n")
50
+ if buffered.endswith("\n"):
51
+ self._line_remainder = ""
52
+ else:
53
+ self._line_remainder = lines.pop()
54
+
55
+ completed: list[Any] = []
56
+
57
+ for line in lines:
58
+ if line == "":
59
+ payload = self._finalize_event()
60
+ if payload:
61
+ completed.append(payload)
62
+ continue
63
+
64
+ self._event_lines.append(line)
65
+
66
+ return completed
67
+
68
+ def flush(self) -> list[Any]:
69
+ """Return any buffered payload when the stream ends."""
70
+
71
+ if self._line_remainder:
72
+ self._event_lines.append(self._line_remainder)
73
+ self._line_remainder = ""
74
+
75
+ payload = self._finalize_event()
76
+ return [payload] if payload else []
77
+
78
+ def consume_errors(self) -> list[tuple[str, Exception]]:
79
+ """Return and clear parsing errors captured since the last call."""
80
+
81
+ errors = self._errors
82
+ self._errors = []
83
+ return errors
84
+
85
+ def _finalize_event(self) -> Any | None:
86
+ if not self._event_lines:
87
+ return None
88
+
89
+ event_lines, self._event_lines = self._event_lines, []
90
+
91
+ data_fields: list[str] = []
92
+ for line in event_lines:
93
+ if line.startswith("data:"):
94
+ data_fields.append(line[5:].lstrip(" "))
95
+
96
+ if not data_fields:
97
+ return None
98
+
99
+ # Try newline-preserving join first (Anthropic style), then a collapsed
100
+ # join for providers that stream JSON without explicit newlines.
101
+ candidates = ["\n".join(data_fields).strip()]
102
+ collapsed = "".join(data_fields).strip()
103
+ if collapsed and collapsed != candidates[0]:
104
+ candidates.append(collapsed)
105
+
106
+ last_exception: json.JSONDecodeError | None = None
107
+
108
+ for candidate in candidates:
109
+ if not candidate or candidate == "[DONE]":
110
+ return None
111
+
112
+ try:
113
+ return json.loads(candidate)
114
+ except json.JSONDecodeError as exc:
115
+ last_exception = exc
116
+ continue
117
+
118
+ if last_exception:
119
+ # Reconstruct the raw event so we can retry when more data arrives.
120
+ raw_event = "\n".join(event_lines) + "\n\n"
121
+ self._line_remainder = f"{raw_event}{self._line_remainder}"
122
+ self._errors.append((raw_event, last_exception))
123
+
124
+ return None
125
+
126
+
127
+ __all__ = ["SSEStreamParser"]
@@ -0,0 +1,6 @@
1
+ """Template helpers for generating CCProxy plugin scaffolds."""
2
+
3
+ from .plugin_scaffold import PluginTemplateType, build_plugin_scaffold
4
+
5
+
6
+ __all__ = ["PluginTemplateType", "build_plugin_scaffold"]