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
@@ -1,18 +1,19 @@
1
1
  """Request ID middleware for generating and tracking request IDs."""
2
2
 
3
- import uuid
3
+ from collections.abc import Awaitable, Callable, MutableMapping
4
4
  from datetime import UTC, datetime
5
5
  from typing import Any
6
6
 
7
- import structlog
8
7
  from fastapi import Request, Response
9
8
  from starlette.middleware.base import BaseHTTPMiddleware
10
- from starlette.types import ASGIApp
9
+ from starlette.types import ASGIApp, Receive, Send
11
10
 
12
- from ccproxy.observability.context import request_context
11
+ from ccproxy.core.id_utils import generate_short_id
12
+ from ccproxy.core.logging import get_logger
13
+ from ccproxy.core.request_context import request_context
13
14
 
14
15
 
15
- logger = structlog.get_logger(__name__)
16
+ logger = get_logger(__name__)
16
17
 
17
18
 
18
19
  class RequestIDMiddleware(BaseHTTPMiddleware):
@@ -26,7 +27,33 @@ class RequestIDMiddleware(BaseHTTPMiddleware):
26
27
  """
27
28
  super().__init__(app)
28
29
 
29
- async def dispatch(self, request: Request, call_next: Any) -> Response:
30
+ async def __call__(
31
+ self, scope: MutableMapping[str, Any], receive: Receive, send: Send
32
+ ) -> None:
33
+ """ASGI interface to inject request ID early."""
34
+ if scope["type"] == "http":
35
+ # Generate or extract request ID
36
+ headers_dict = dict(scope.get("headers", []))
37
+ request_id = (
38
+ headers_dict.get(b"x-request-id", b"").decode("utf-8")
39
+ or generate_short_id()
40
+ )
41
+
42
+ # Store in ASGI extensions for other middleware
43
+ if "extensions" not in scope:
44
+ scope["extensions"] = {}
45
+ scope["extensions"]["request_id"] = request_id
46
+
47
+ # If not in headers, add it
48
+ if b"x-request-id" not in headers_dict:
49
+ scope["headers"] = list(scope.get("headers", []))
50
+ scope["headers"].append((b"x-request-id", request_id.encode("utf-8")))
51
+
52
+ return await super().__call__(scope, receive, send)
53
+
54
+ async def dispatch(
55
+ self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
56
+ ) -> Response:
30
57
  """Process the request and add request ID/context.
31
58
 
32
59
  Args:
@@ -37,18 +64,14 @@ class RequestIDMiddleware(BaseHTTPMiddleware):
37
64
  The HTTP response
38
65
  """
39
66
  # Generate or extract request ID
40
- request_id = request.headers.get("x-request-id") or str(uuid.uuid4())
67
+ request_id = request.headers.get("x-request-id") or generate_short_id()
41
68
 
42
69
  # Generate datetime for consistent logging across all layers
43
70
  log_timestamp = datetime.now(UTC)
44
71
 
45
- # Get DuckDB storage from app state if available
46
- storage = getattr(request.app.state, "duckdb_storage", None)
47
-
48
72
  # Use the proper request context manager to ensure __aexit__ is called
49
73
  async with request_context(
50
74
  request_id=request_id,
51
- storage=storage,
52
75
  log_timestamp=log_timestamp,
53
76
  method=request.method,
54
77
  path=str(request.url.path),
@@ -61,14 +84,10 @@ class RequestIDMiddleware(BaseHTTPMiddleware):
61
84
  request.state.request_id = request_id
62
85
  request.state.context = ctx
63
86
 
64
- # Add DuckDB storage to context if available
65
- if hasattr(request.state, "duckdb_storage"):
66
- ctx.storage = request.state.duckdb_storage
67
-
68
87
  # Process the request
69
88
  response = await call_next(request)
70
89
 
71
90
  # Add request ID to response headers
72
91
  response.headers["x-request-id"] = request_id
73
92
 
74
- return response # type: ignore[no-any-return]
93
+ return response
@@ -0,0 +1,292 @@
1
+ """Streaming response wrapper for hook emission.
2
+
3
+ This module provides a wrapper for streaming responses that emits
4
+ REQUEST_COMPLETED hook event when the stream actually completes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import json
11
+ import time
12
+ from collections.abc import AsyncGenerator, AsyncIterator
13
+ from datetime import datetime
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from fastapi.responses import StreamingResponse
17
+
18
+ from ccproxy.core.logging import get_logger
19
+ from ccproxy.core.plugins.hooks import HookContext, HookEvent
20
+ from ccproxy.utils.headers import extract_response_headers
21
+
22
+
23
+ if TYPE_CHECKING:
24
+ from ccproxy.core.plugins.hooks import HookManager
25
+
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ class StreamingResponseWithHooks(StreamingResponse):
31
+ """Streaming response wrapper that emits hooks on completion.
32
+
33
+ This wrapper ensures REQUEST_COMPLETED is emitted when streaming
34
+ actually finishes, not when the response is initially created.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ content: AsyncGenerator[bytes, None] | AsyncIterator[bytes],
40
+ hook_manager: HookManager | None,
41
+ request_id: str,
42
+ request_data: dict[str, Any],
43
+ start_time: float,
44
+ status_code: int = 200,
45
+ request_metadata: dict[str, Any] | None = None,
46
+ origin: str = "client",
47
+ is_sse: bool = False,
48
+ **kwargs: Any,
49
+ ) -> None:
50
+ """Initialize streaming response with hook emission.
51
+
52
+ Args:
53
+ content: The async generator producing streaming content
54
+ hook_manager: Hook manager for emitting events
55
+ request_id: Request ID for correlation
56
+ request_data: Original request data for context
57
+ start_time: Request start timestamp
58
+ status_code: HTTP status code for the response
59
+ request_metadata: Metadata from RequestContext (includes tokens, cost, etc.)
60
+ **kwargs: Additional arguments passed to StreamingResponse
61
+ """
62
+ self.hook_manager = hook_manager
63
+ self.request_id = request_id
64
+ self.request_data = request_data
65
+ self.request_metadata = request_metadata or {}
66
+ self.start_time = start_time
67
+ self.origin = origin
68
+ self.is_sse = is_sse
69
+
70
+ # Wrap the content generator to add hook emission
71
+ wrapped_content = self._wrap_with_hooks(content, status_code)
72
+
73
+ super().__init__(wrapped_content, status_code=status_code, **kwargs)
74
+
75
+ async def _wrap_with_hooks(
76
+ self,
77
+ content: AsyncGenerator[bytes, None] | AsyncIterator[bytes],
78
+ status_code: int,
79
+ ) -> AsyncGenerator[bytes, None]:
80
+ """Wrap content generator with hook emission on completion.
81
+
82
+ Args:
83
+ content: The original content generator
84
+ status_code: HTTP status code
85
+
86
+ Yields:
87
+ bytes: Content chunks from the original generator
88
+ """
89
+ error_occurred: str | None = None
90
+ error_type_name: str | None = None
91
+ final_status = status_code
92
+ # Collect chunks for HTTP_RESPONSE hook
93
+ collected_chunks: list[bytes] = []
94
+
95
+ closed_by: str | None = None
96
+
97
+ try:
98
+ # Stream all content from the original generator
99
+ async for chunk in content:
100
+ collected_chunks.append(chunk) # Collect for HTTP hook
101
+ yield chunk
102
+
103
+ except GeneratorExit:
104
+ # Client disconnected - still emit completion hook
105
+ error_occurred = "client_disconnected"
106
+ error_type_name = "GeneratorExit"
107
+ closed_by = "client"
108
+ return
109
+
110
+ except asyncio.CancelledError as e:
111
+ error_occurred = "client_cancelled"
112
+ error_type_name = type(e).__name__
113
+ closed_by = "client"
114
+ return
115
+
116
+ except Exception as e:
117
+ # Error during streaming
118
+ error_occurred = str(e)
119
+ error_type_name = type(e).__name__
120
+ final_status = 500
121
+ closed_by = "server_error"
122
+ raise
123
+
124
+ finally:
125
+ end_time = time.time()
126
+ duration = end_time - self.start_time
127
+
128
+ # Emit HTTP_RESPONSE hook first with collected body, then REQUEST_COMPLETED
129
+ if self.hook_manager:
130
+ try:
131
+ # First emit HTTP_RESPONSE hook with collected streaming body
132
+ await self._emit_http_response_hook(
133
+ collected_chunks, final_status, end_time
134
+ )
135
+
136
+ # Then emit REQUEST_COMPLETED hook (existing behavior)
137
+ completion_data = {
138
+ "request_id": self.request_id,
139
+ "duration": duration,
140
+ "response_status": final_status,
141
+ "streaming_completed": True,
142
+ }
143
+
144
+ # Include original request data
145
+ if self.request_data:
146
+ completion_data.update(
147
+ {
148
+ "method": self.request_data.get("method"),
149
+ "url": self.request_data.get("url"),
150
+ "headers": self.request_data.get("headers"),
151
+ }
152
+ )
153
+
154
+ # Add error info if an error occurred
155
+ if error_occurred:
156
+ completion_data["error"] = error_occurred
157
+ event = HookEvent.REQUEST_FAILED
158
+ else:
159
+ event = HookEvent.REQUEST_COMPLETED
160
+
161
+ # Merge request metadata (tokens, cost, etc.) into hook metadata
162
+ hook_metadata = {"request_id": self.request_id}
163
+ hook_metadata.update(self.request_metadata)
164
+
165
+ hook_context = HookContext(
166
+ event=event,
167
+ timestamp=datetime.fromtimestamp(end_time),
168
+ data=completion_data,
169
+ metadata=hook_metadata,
170
+ )
171
+
172
+ await self.hook_manager.emit_with_context(hook_context)
173
+
174
+ except Exception:
175
+ # Silently ignore hook emission errors to avoid breaking the stream
176
+ pass
177
+
178
+ success = error_occurred is None
179
+ body_size = sum(len(chunk) for chunk in collected_chunks)
180
+ log_fields: dict[str, Any] = {
181
+ "request_id": self.request_id,
182
+ "method": self.request_data.get("method"),
183
+ "url": self.request_data.get("url"),
184
+ "origin": self.origin,
185
+ "status_code": final_status,
186
+ "duration_ms": round(duration * 1000, 3),
187
+ "streaming": True,
188
+ "success": success,
189
+ "category": "http",
190
+ "has_body": body_size > 0,
191
+ "body_size": body_size,
192
+ }
193
+ if not success and error_type_name:
194
+ log_fields["error_type"] = error_type_name
195
+ if not closed_by:
196
+ closed_by = "server"
197
+ log_fields["closed_by"] = closed_by
198
+ if not success and error_occurred:
199
+ log_fields["error_reason"] = error_occurred
200
+
201
+ # Propagate metadata for downstream consumers (e.g., upstream logging)
202
+ try:
203
+ self.request_metadata["stream_closed_by"] = closed_by
204
+ if not success and error_occurred:
205
+ self.request_metadata["stream_error_reason"] = error_occurred
206
+ if error_type_name:
207
+ self.request_metadata["stream_error_type"] = error_type_name
208
+ except Exception:
209
+ # Metadata propagation is best-effort
210
+ pass
211
+
212
+ if self.is_sse:
213
+ logger.info("sse_connection_ended", **log_fields)
214
+ else:
215
+ logger.info("request_completed", **log_fields)
216
+
217
+ async def _emit_http_response_hook(
218
+ self, collected_chunks: list[bytes], status_code: int, end_time: float
219
+ ) -> None:
220
+ """Emit HTTP_RESPONSE hook with collected streaming response body.
221
+
222
+ Args:
223
+ collected_chunks: All chunks collected from the stream
224
+ status_code: Final HTTP status code
225
+ end_time: Timestamp when streaming completed
226
+ """
227
+ try:
228
+ # Combine all chunks to get full response body
229
+ full_response_body = b"".join(collected_chunks)
230
+
231
+ # Build HTTP response context
232
+ http_response_context = {
233
+ "request_id": self.request_id,
234
+ "status_code": status_code,
235
+ "is_client_response": True, # Distinguish from provider responses
236
+ }
237
+
238
+ # Include request data for context
239
+ if self.request_data:
240
+ http_response_context.update(
241
+ {
242
+ "method": self.request_data.get("method"),
243
+ "url": self.request_data.get("url"),
244
+ "headers": self.request_data.get("headers"),
245
+ }
246
+ )
247
+
248
+ # Add response headers if available, preserving order and case
249
+ try:
250
+ http_response_context["response_headers"] = extract_response_headers(
251
+ self
252
+ )
253
+ except Exception:
254
+ if hasattr(self, "headers"):
255
+ http_response_context["response_headers"] = dict(self.headers)
256
+
257
+ # Parse response body
258
+ if full_response_body:
259
+ try:
260
+ # For streaming responses, try to parse as text first
261
+ response_text = full_response_body.decode("utf-8", errors="replace")
262
+
263
+ # Check if it looks like JSON
264
+ headers_obj = http_response_context.get("response_headers")
265
+ content_type = ""
266
+ if headers_obj is not None and isinstance(headers_obj, dict):
267
+ content_type = headers_obj.get("content-type", "")
268
+
269
+ if "application/json" in content_type:
270
+ try:
271
+ http_response_context["response_body"] = json.loads(
272
+ response_text
273
+ )
274
+ except json.JSONDecodeError:
275
+ http_response_context["response_body"] = response_text
276
+ else:
277
+ # For streaming responses (like SSE), include as text
278
+ http_response_context["response_body"] = response_text
279
+
280
+ except UnicodeDecodeError:
281
+ # If decode fails, include as bytes
282
+ http_response_context["response_body"] = full_response_body
283
+
284
+ # Emit HTTP_RESPONSE hook
285
+ if self.hook_manager:
286
+ await self.hook_manager.emit(
287
+ HookEvent.HTTP_RESPONSE, http_response_context
288
+ )
289
+
290
+ except Exception:
291
+ # Silently ignore HTTP hook emission errors
292
+ pass
@@ -1,24 +1,15 @@
1
1
  """API routes for CCProxy API Server."""
2
2
 
3
3
  # from ccproxy.api.routes.auth import router as auth_router # Module doesn't exist
4
- from ccproxy.api.routes.claude import router as claude_router
5
4
  from ccproxy.api.routes.health import router as health_router
6
- from ccproxy.api.routes.metrics import (
7
- dashboard_router,
8
- logs_router,
9
- )
10
- from ccproxy.api.routes.metrics import (
11
- prometheus_router as metrics_router,
12
- )
13
- from ccproxy.api.routes.proxy import router as proxy_router
5
+
6
+
7
+ # proxy routes are now handled by plugin system
14
8
 
15
9
 
16
10
  __all__ = [
17
11
  # "auth_router", # Module doesn't exist
18
- "claude_router",
19
12
  "health_router",
20
- "metrics_router",
21
- "logs_router",
22
- "dashboard_router",
23
- "proxy_router",
13
+ # Metrics, logs, and dashboard routes are provided by plugins now
14
+ # "proxy_router", # Removed - handled by plugin system
24
15
  ]