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
@@ -1,284 +0,0 @@
1
- """Simple request logging utility for content logging across all service layers."""
2
-
3
- import asyncio
4
- import json
5
- import os
6
- from datetime import UTC, datetime
7
- from pathlib import Path
8
- from typing import Any
9
-
10
- import structlog
11
-
12
-
13
- logger = structlog.get_logger(__name__)
14
-
15
- # Global batching settings for streaming logs
16
- _STREAMING_BATCH_SIZE = 8192 # Batch chunks until we have 8KB
17
- _STREAMING_BATCH_TIMEOUT = 0.1 # Or flush after 100ms
18
- _streaming_batches: dict[str, dict[str, Any]] = {} # request_id -> batch info
19
-
20
-
21
- def should_log_requests() -> bool:
22
- """Check if request logging is enabled via environment variable.
23
-
24
- Returns:
25
- True if CCPROXY_LOG_REQUESTS is set to 'true' (case-insensitive)
26
- """
27
- return os.environ.get("CCPROXY_LOG_REQUESTS", "false").lower() == "true"
28
-
29
-
30
- def get_request_log_dir() -> Path | None:
31
- """Get the request log directory from environment variable.
32
-
33
- Returns:
34
- Path object if CCPROXY_REQUEST_LOG_DIR is set and valid, None otherwise
35
- """
36
- log_dir = os.environ.get("CCPROXY_REQUEST_LOG_DIR")
37
- if not log_dir:
38
- return None
39
-
40
- path = Path(log_dir)
41
- try:
42
- path.mkdir(parents=True, exist_ok=True)
43
- return path
44
- except Exception as e:
45
- logger.error(
46
- "failed_to_create_request_log_dir",
47
- log_dir=log_dir,
48
- error=str(e),
49
- )
50
- return None
51
-
52
-
53
- def get_timestamp_prefix() -> str:
54
- """Generate timestamp prefix in YYYYMMDDhhmmss format.
55
-
56
- Returns:
57
- Timestamp string in YYYYMMDDhhmmss format (UTC)
58
- """
59
- return datetime.now(UTC).strftime("%Y%m%d%H%M%S")
60
-
61
-
62
- async def write_request_log(
63
- request_id: str,
64
- log_type: str,
65
- data: dict[str, Any],
66
- timestamp: str | None = None,
67
- ) -> None:
68
- """Write request/response data to JSON file.
69
-
70
- Args:
71
- request_id: Unique request identifier
72
- log_type: Type of log (e.g., 'middleware_request', 'upstream_response')
73
- data: Data to log as JSON
74
- timestamp: Optional timestamp prefix (defaults to current time)
75
- """
76
- if not should_log_requests():
77
- return
78
-
79
- log_dir = get_request_log_dir()
80
- if not log_dir:
81
- return
82
-
83
- timestamp = timestamp or get_timestamp_prefix()
84
- filename = f"{timestamp}_{request_id}_{log_type}.json"
85
- file_path = log_dir / filename
86
-
87
- try:
88
- # Write JSON data to file asynchronously
89
- def write_file() -> None:
90
- with file_path.open("w", encoding="utf-8") as f:
91
- json.dump(data, f, indent=2, default=str, ensure_ascii=False)
92
-
93
- # Run in thread pool to avoid blocking
94
- await asyncio.get_event_loop().run_in_executor(None, write_file)
95
-
96
- logger.debug(
97
- "request_log_written",
98
- request_id=request_id,
99
- log_type=log_type,
100
- file_path=str(file_path),
101
- )
102
-
103
- except Exception as e:
104
- logger.error(
105
- "failed_to_write_request_log",
106
- request_id=request_id,
107
- log_type=log_type,
108
- file_path=str(file_path),
109
- error=str(e),
110
- )
111
-
112
-
113
- async def write_streaming_log(
114
- request_id: str,
115
- log_type: str,
116
- data: bytes,
117
- timestamp: str | None = None,
118
- ) -> None:
119
- """Write streaming data to raw file.
120
-
121
- Args:
122
- request_id: Unique request identifier
123
- log_type: Type of log (e.g., 'middleware_streaming', 'upstream_streaming')
124
- data: Raw bytes to log
125
- timestamp: Optional timestamp prefix (defaults to current time)
126
- """
127
- if not should_log_requests():
128
- return
129
-
130
- log_dir = get_request_log_dir()
131
- if not log_dir:
132
- return
133
-
134
- timestamp = timestamp or get_timestamp_prefix()
135
- filename = f"{timestamp}_{request_id}_{log_type}.raw"
136
- file_path = log_dir / filename
137
-
138
- try:
139
- # Write raw data to file asynchronously
140
- def write_file() -> None:
141
- with file_path.open("wb") as f:
142
- f.write(data)
143
-
144
- # Run in thread pool to avoid blocking
145
- await asyncio.get_event_loop().run_in_executor(None, write_file)
146
-
147
- logger.debug(
148
- "streaming_log_written",
149
- request_id=request_id,
150
- log_type=log_type,
151
- file_path=str(file_path),
152
- data_size=len(data),
153
- )
154
-
155
- except Exception as e:
156
- logger.error(
157
- "failed_to_write_streaming_log",
158
- request_id=request_id,
159
- log_type=log_type,
160
- file_path=str(file_path),
161
- error=str(e),
162
- )
163
-
164
-
165
- async def append_streaming_log(
166
- request_id: str,
167
- log_type: str,
168
- data: bytes,
169
- timestamp: str | None = None,
170
- ) -> None:
171
- """Append streaming data using batching for performance.
172
-
173
- Args:
174
- request_id: Unique request identifier
175
- log_type: Type of log (e.g., 'middleware_streaming', 'upstream_streaming')
176
- data: Raw bytes to append
177
- timestamp: Optional timestamp prefix (defaults to current time)
178
- """
179
- if not should_log_requests():
180
- return
181
-
182
- log_dir = get_request_log_dir()
183
- if not log_dir:
184
- return
185
-
186
- timestamp = timestamp or get_timestamp_prefix()
187
- batch_key = f"{request_id}_{log_type}"
188
-
189
- # Get or create batch for this request/log_type combination
190
- if batch_key not in _streaming_batches:
191
- _streaming_batches[batch_key] = {
192
- "request_id": request_id,
193
- "log_type": log_type,
194
- "timestamp": timestamp,
195
- "data": bytearray(),
196
- "chunk_count": 0,
197
- "first_chunk_time": asyncio.get_event_loop().time(),
198
- "last_flush_task": None,
199
- }
200
-
201
- batch = _streaming_batches[batch_key]
202
- batch["data"].extend(data)
203
- batch["chunk_count"] += 1
204
-
205
- # Cancel previous flush task if it exists
206
- if batch["last_flush_task"] and not batch["last_flush_task"].done():
207
- batch["last_flush_task"].cancel()
208
-
209
- # Check if we should flush now
210
- should_flush = (
211
- len(batch["data"]) >= _STREAMING_BATCH_SIZE
212
- or batch["chunk_count"] >= 50 # Max 50 chunks per batch
213
- )
214
-
215
- if should_flush:
216
- await _flush_streaming_batch(batch_key)
217
- else:
218
- # Schedule a delayed flush
219
- batch["last_flush_task"] = asyncio.create_task(
220
- _delayed_flush_streaming_batch(batch_key, _STREAMING_BATCH_TIMEOUT)
221
- )
222
-
223
-
224
- async def _delayed_flush_streaming_batch(batch_key: str, delay: float) -> None:
225
- """Flush a streaming batch after a delay."""
226
- try:
227
- await asyncio.sleep(delay)
228
- if batch_key in _streaming_batches:
229
- await _flush_streaming_batch(batch_key)
230
- except asyncio.CancelledError:
231
- # Task was cancelled, don't flush
232
- pass
233
-
234
-
235
- async def _flush_streaming_batch(batch_key: str) -> None:
236
- """Flush a streaming batch to disk."""
237
- if batch_key not in _streaming_batches:
238
- return
239
-
240
- batch = _streaming_batches.pop(batch_key)
241
-
242
- if not batch["data"]:
243
- return # Nothing to flush
244
-
245
- log_dir = get_request_log_dir()
246
- if not log_dir:
247
- return
248
-
249
- filename = f"{batch['timestamp']}_{batch['request_id']}_{batch['log_type']}.raw"
250
- file_path = log_dir / filename
251
-
252
- try:
253
- # Append batched data to file asynchronously
254
- def append_file() -> None:
255
- with file_path.open("ab") as f:
256
- f.write(batch["data"])
257
-
258
- # Run in thread pool to avoid blocking
259
- await asyncio.get_event_loop().run_in_executor(None, append_file)
260
-
261
- logger.debug(
262
- "streaming_batch_flushed",
263
- request_id=batch["request_id"],
264
- log_type=batch["log_type"],
265
- file_path=str(file_path),
266
- batch_size=len(batch["data"]),
267
- chunk_count=batch["chunk_count"],
268
- )
269
-
270
- except Exception as e:
271
- logger.error(
272
- "failed_to_flush_streaming_batch",
273
- request_id=batch["request_id"],
274
- log_type=batch["log_type"],
275
- file_path=str(file_path),
276
- error=str(e),
277
- )
278
-
279
-
280
- async def flush_all_streaming_batches() -> None:
281
- """Flush all pending streaming batches. Call this on shutdown."""
282
- batch_keys = list(_streaming_batches.keys())
283
- for batch_key in batch_keys:
284
- await _flush_streaming_batch(batch_key)
@@ -1,199 +0,0 @@
1
- """Streaming metrics extraction utilities.
2
-
3
- This module provides utilities for extracting token usage and calculating costs
4
- from Anthropic streaming responses in a testable, modular way.
5
- """
6
-
7
- import json
8
- from typing import Any
9
-
10
- import structlog
11
-
12
- from ccproxy.models.types import StreamingTokenMetrics, UsageData
13
- from ccproxy.utils.cost_calculator import calculate_token_cost
14
-
15
-
16
- logger = structlog.get_logger(__name__)
17
-
18
-
19
- def extract_usage_from_streaming_chunk(chunk_data: Any) -> UsageData | None:
20
- """Extract usage information from Anthropic streaming response chunk.
21
-
22
- This function looks for usage information in both message_start and message_delta events
23
- from Anthropic's streaming API responses. message_start contains initial input tokens,
24
- message_delta contains final output tokens.
25
-
26
- Args:
27
- chunk_data: Streaming response chunk dictionary
28
-
29
- Returns:
30
- UsageData with token counts or None if no usage found
31
- """
32
- if not isinstance(chunk_data, dict):
33
- return None
34
-
35
- chunk_type = chunk_data.get("type")
36
-
37
- # Look for message_start events with initial usage (input tokens)
38
- if chunk_type == "message_start" and "message" in chunk_data:
39
- message = chunk_data["message"]
40
- if "usage" in message:
41
- usage = message["usage"]
42
- return UsageData(
43
- input_tokens=usage.get("input_tokens"),
44
- output_tokens=usage.get(
45
- "output_tokens"
46
- ), # Initial output tokens (usually small)
47
- cache_read_input_tokens=usage.get("cache_read_input_tokens"),
48
- cache_creation_input_tokens=usage.get("cache_creation_input_tokens"),
49
- event_type="message_start",
50
- )
51
-
52
- # Look for message_delta events with final usage (output tokens)
53
- elif chunk_type == "message_delta" and "usage" in chunk_data:
54
- usage = chunk_data["usage"]
55
- return UsageData(
56
- input_tokens=usage.get("input_tokens"), # Usually None in delta
57
- output_tokens=usage.get("output_tokens"), # Final output token count
58
- cache_read_input_tokens=usage.get("cache_read_input_tokens"),
59
- cache_creation_input_tokens=usage.get("cache_creation_input_tokens"),
60
- event_type="message_delta",
61
- )
62
-
63
- return None
64
-
65
-
66
- class StreamingMetricsCollector:
67
- """Collects and manages token metrics during streaming responses."""
68
-
69
- def __init__(self, request_id: str | None = None) -> None:
70
- """Initialize the metrics collector.
71
-
72
- Args:
73
- request_id: Optional request ID for logging context
74
- """
75
- self.request_id = request_id
76
- self.metrics = StreamingTokenMetrics(
77
- tokens_input=None,
78
- tokens_output=None,
79
- cache_read_tokens=None,
80
- cache_write_tokens=None,
81
- cost_usd=None,
82
- )
83
-
84
- def process_chunk(self, chunk_str: str) -> bool:
85
- """Process a streaming chunk to extract token metrics.
86
-
87
- Args:
88
- chunk_str: Raw chunk string from streaming response
89
-
90
- Returns:
91
- True if this was the final chunk with complete metrics, False otherwise
92
- """
93
- # Check if this chunk contains usage information
94
- # Look for usage data in any chunk - the event type will be determined from the JSON
95
- if "usage" not in chunk_str:
96
- return False
97
-
98
- logger.debug(
99
- "Processing chunk with usage",
100
- chunk_preview=chunk_str[:300],
101
- request_id=self.request_id,
102
- )
103
-
104
- try:
105
- # Parse SSE data lines to find usage information
106
- for line in chunk_str.split("\n"):
107
- if line.startswith("data: "):
108
- data_str = line[6:].strip()
109
- if data_str and data_str != "[DONE]":
110
- event_data = json.loads(data_str)
111
- usage_data = extract_usage_from_streaming_chunk(event_data)
112
-
113
- if usage_data:
114
- event_type = usage_data.get("event_type")
115
-
116
- # Handle message_start: get input tokens and initial cache tokens
117
- if event_type == "message_start":
118
- self.metrics["tokens_input"] = usage_data.get(
119
- "input_tokens"
120
- )
121
- self.metrics["cache_read_tokens"] = (
122
- usage_data.get("cache_read_input_tokens")
123
- or self.metrics["cache_read_tokens"]
124
- )
125
- self.metrics["cache_write_tokens"] = (
126
- usage_data.get("cache_creation_input_tokens")
127
- or self.metrics["cache_write_tokens"]
128
- )
129
- logger.debug(
130
- "Extracted input tokens from message_start",
131
- tokens_input=self.metrics["tokens_input"],
132
- cache_read_tokens=self.metrics["cache_read_tokens"],
133
- cache_write_tokens=self.metrics[
134
- "cache_write_tokens"
135
- ],
136
- request_id=self.request_id,
137
- )
138
- return False # Not final yet
139
-
140
- # Handle message_delta: get final output tokens
141
- elif event_type == "message_delta":
142
- self.metrics["tokens_output"] = usage_data.get(
143
- "output_tokens"
144
- )
145
- logger.debug(
146
- "Extracted output tokens from message_delta",
147
- tokens_output=self.metrics["tokens_output"],
148
- request_id=self.request_id,
149
- )
150
- return True # This is the final event
151
-
152
- break # Only process first valid data line
153
-
154
- except (json.JSONDecodeError, KeyError) as e:
155
- logger.debug(
156
- "Failed to parse streaming token metrics",
157
- error=str(e),
158
- request_id=self.request_id,
159
- )
160
-
161
- return False
162
-
163
- def calculate_final_cost(self, model: str | None) -> float | None:
164
- """Calculate the final cost based on collected metrics.
165
-
166
- Args:
167
- model: Model name for pricing lookup
168
-
169
- Returns:
170
- Final cost in USD or None if calculation fails
171
- """
172
- cost_usd = calculate_token_cost(
173
- self.metrics["tokens_input"],
174
- self.metrics["tokens_output"],
175
- model,
176
- self.metrics["cache_read_tokens"],
177
- self.metrics["cache_write_tokens"],
178
- )
179
- self.metrics["cost_usd"] = cost_usd
180
-
181
- logger.debug(
182
- "Final streaming token metrics",
183
- tokens_input=self.metrics["tokens_input"],
184
- tokens_output=self.metrics["tokens_output"],
185
- cache_read_tokens=self.metrics["cache_read_tokens"],
186
- cache_write_tokens=self.metrics["cache_write_tokens"],
187
- cost_usd=cost_usd,
188
- request_id=self.request_id,
189
- )
190
-
191
- return cost_usd
192
-
193
- def get_metrics(self) -> StreamingTokenMetrics:
194
- """Get the current collected metrics.
195
-
196
- Returns:
197
- Current token metrics
198
- """
199
- return self.metrics.copy()