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
@@ -3,6 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import os
7
+ import re
6
8
  from datetime import datetime
7
9
  from pathlib import Path
8
10
  from typing import Any
@@ -13,18 +15,26 @@ import structlog
13
15
  from packaging import version as pkg_version
14
16
  from pydantic import BaseModel
15
17
 
16
- from ccproxy._version import __version__
17
- from ccproxy.config.discovery import get_ccproxy_config_dir
18
+ from ccproxy.config.utils import get_ccproxy_config_dir
19
+ from ccproxy.core._version import __version__
18
20
 
19
21
 
20
22
  logger = structlog.get_logger(__name__)
21
23
 
22
24
 
25
+ BRANCH_OVERRIDE_ENV_VAR = "CCPROXY_VERSION_BRANCH"
26
+ GITHUB_API_BASE = "https://api.github.com/repos/CaddyGlow/ccproxy-api"
27
+
28
+
23
29
  class VersionCheckState(BaseModel):
24
30
  """State tracking for version checks."""
25
31
 
26
32
  last_check_at: datetime
27
33
  latest_version_found: str | None = None
34
+ latest_branch_name: str | None = None
35
+ latest_branch_commit: str | None = None
36
+ running_version: str | None = None
37
+ running_commit: str | None = None
28
38
 
29
39
 
30
40
  async def fetch_latest_github_version() -> str | None:
@@ -61,9 +71,23 @@ async def fetch_latest_github_version() -> str | None:
61
71
  except httpx.HTTPStatusError as e:
62
72
  logger.warning("github_version_http_error", status_code=e.response.status_code)
63
73
  return None
74
+ except httpx.RequestError as e:
75
+ logger.warning(
76
+ "github_version_fetch_http_error",
77
+ error=str(e),
78
+ error_type=type(e).__name__,
79
+ )
80
+ return None
81
+ except (json.JSONDecodeError, KeyError, TypeError) as e:
82
+ logger.warning(
83
+ "github_version_parse_error",
84
+ error=str(e),
85
+ error_type=type(e).__name__,
86
+ )
87
+ return None
64
88
  except Exception as e:
65
89
  logger.warning(
66
- "github_version_fetch_failed",
90
+ "github_version_fetch_unexpected_error",
67
91
  error=str(e),
68
92
  error_type=type(e).__name__,
69
93
  )
@@ -80,6 +104,38 @@ def get_current_version() -> str:
80
104
  return __version__
81
105
 
82
106
 
107
+ def extract_commit_from_version(version: str) -> str | None:
108
+ """Extract a git commit SHA from a setuptools-scm formatted version."""
109
+
110
+ match = re.search(r"\+g(?P<sha>[0-9a-f]{7,40})", version)
111
+ if match:
112
+ return match.group("sha")
113
+ return None
114
+
115
+
116
+ def commit_refs_match(current: str | None, latest: str | None) -> bool:
117
+ """Return True when two commit references identify the same commit."""
118
+
119
+ if not current or not latest:
120
+ return current == latest
121
+
122
+ current_lower = current.lower()
123
+ latest_lower = latest.lower()
124
+
125
+ # Normalize shorter/longer pair for prefix comparison
126
+ if len(current_lower) <= len(latest_lower):
127
+ return latest_lower.startswith(current_lower)
128
+
129
+ return current_lower.startswith(latest_lower)
130
+
131
+
132
+ def get_branch_override() -> str | None:
133
+ """Return branch override from environment if provided."""
134
+
135
+ env_branch = os.getenv(BRANCH_OVERRIDE_ENV_VAR, "").strip()
136
+ return env_branch or None
137
+
138
+
83
139
  def compare_versions(current: str, latest: str) -> bool:
84
140
  """
85
141
  Compare version strings to determine if an update is available.
@@ -101,9 +157,18 @@ def compare_versions(current: str, latest: str) -> bool:
101
157
  return latest_parsed > current_base
102
158
 
103
159
  return latest_parsed > current_parsed
160
+ except (ValueError, TypeError, AttributeError) as e:
161
+ logger.error(
162
+ "version_comparison_parse_error",
163
+ current=current,
164
+ latest=latest,
165
+ error=str(e),
166
+ error_type=type(e).__name__,
167
+ )
168
+ return False
104
169
  except Exception as e:
105
170
  logger.error(
106
- "version_comparison_failed",
171
+ "version_comparison_unexpected_error",
107
172
  current=current,
108
173
  latest=latest,
109
174
  error=str(e),
@@ -112,6 +177,172 @@ def compare_versions(current: str, latest: str) -> bool:
112
177
  return False
113
178
 
114
179
 
180
+ async def fetch_latest_branch_commit(branch: str) -> str | None:
181
+ """Fetch the latest commit SHA for a given branch from GitHub."""
182
+
183
+ url = f"{GITHUB_API_BASE}/branches/{branch}"
184
+ headers = {
185
+ "User-Agent": f"ccproxy-api/{__version__}",
186
+ "Accept": "application/vnd.github.v3+json",
187
+ }
188
+
189
+ try:
190
+ async with httpx.AsyncClient(timeout=15.0) as client:
191
+ response = await client.get(url, headers=headers)
192
+ response.raise_for_status()
193
+
194
+ data: dict[str, Any] = response.json()
195
+ commit_info = data.get("commit", {})
196
+ latest_sha = commit_info.get("sha")
197
+
198
+ if isinstance(latest_sha, str) and latest_sha:
199
+ logger.debug(
200
+ "github_branch_commit_fetched",
201
+ branch=branch,
202
+ latest_sha=latest_sha,
203
+ )
204
+ return latest_sha
205
+
206
+ logger.warning(
207
+ "github_branch_commit_missing_sha",
208
+ branch=branch,
209
+ )
210
+ return None
211
+
212
+ except httpx.TimeoutException:
213
+ logger.warning("github_branch_commit_timeout", branch=branch)
214
+ return None
215
+ except httpx.HTTPStatusError as e:
216
+ logger.warning(
217
+ "github_branch_commit_http_error",
218
+ branch=branch,
219
+ status_code=e.response.status_code,
220
+ )
221
+ return None
222
+ except httpx.RequestError as e:
223
+ logger.warning(
224
+ "github_branch_commit_http_request_error",
225
+ branch=branch,
226
+ error=str(e),
227
+ error_type=type(e).__name__,
228
+ )
229
+ return None
230
+ except (json.JSONDecodeError, KeyError, TypeError) as e:
231
+ logger.warning(
232
+ "github_branch_commit_parse_error",
233
+ branch=branch,
234
+ error=str(e),
235
+ error_type=type(e).__name__,
236
+ )
237
+ return None
238
+ except Exception as e:
239
+ logger.warning(
240
+ "github_branch_commit_unexpected_error",
241
+ branch=branch,
242
+ error=str(e),
243
+ error_type=type(e).__name__,
244
+ )
245
+ return None
246
+
247
+
248
+ async def fetch_branch_names_for_commit(commit: str) -> list[str]:
249
+ """Fetch branch names for which the given commit is the HEAD."""
250
+
251
+ url = f"{GITHUB_API_BASE}/commits/{commit}/branches-where-head"
252
+ headers = {
253
+ "User-Agent": f"ccproxy-api/{__version__}",
254
+ "Accept": "application/vnd.github.v3+json",
255
+ }
256
+
257
+ try:
258
+ async with httpx.AsyncClient(timeout=15.0) as client:
259
+ response = await client.get(url, headers=headers)
260
+ response.raise_for_status()
261
+
262
+ data = response.json()
263
+ if not isinstance(data, list):
264
+ logger.warning(
265
+ "github_commit_branches_unexpected_payload",
266
+ commit=commit,
267
+ payload_type=type(data).__name__,
268
+ )
269
+ return []
270
+
271
+ branch_names: list[str] = []
272
+ for entry in data:
273
+ if not isinstance(entry, dict):
274
+ continue
275
+ name = entry.get("name")
276
+ if isinstance(name, str) and name:
277
+ branch_names.append(name)
278
+
279
+ logger.debug(
280
+ "github_commit_branches_fetched",
281
+ commit=commit,
282
+ branch_count=len(branch_names),
283
+ )
284
+ return branch_names
285
+
286
+ except httpx.TimeoutException:
287
+ logger.warning("github_commit_branches_timeout", commit=commit)
288
+ return []
289
+ except httpx.HTTPStatusError as e:
290
+ logger.warning(
291
+ "github_commit_branches_http_error",
292
+ commit=commit,
293
+ status_code=e.response.status_code,
294
+ )
295
+ return []
296
+ except httpx.RequestError as e:
297
+ logger.warning(
298
+ "github_commit_branches_http_request_error",
299
+ commit=commit,
300
+ error=str(e),
301
+ error_type=type(e).__name__,
302
+ )
303
+ return []
304
+ except (json.JSONDecodeError, ValueError, TypeError) as e:
305
+ logger.warning(
306
+ "github_commit_branches_parse_error",
307
+ commit=commit,
308
+ error=str(e),
309
+ error_type=type(e).__name__,
310
+ )
311
+ return []
312
+ except Exception as e:
313
+ logger.warning(
314
+ "github_commit_branches_unexpected_error",
315
+ commit=commit,
316
+ error=str(e),
317
+ error_type=type(e).__name__,
318
+ )
319
+ return []
320
+
321
+
322
+ async def resolve_branch_for_commit(commit: str) -> str | None:
323
+ """Resolve the branch name associated with the provided commit hash."""
324
+
325
+ override = get_branch_override()
326
+ if override:
327
+ return override
328
+
329
+ if not commit:
330
+ return None
331
+
332
+ branch_candidates = await fetch_branch_names_for_commit(commit)
333
+ if not branch_candidates:
334
+ return None
335
+
336
+ # Prefer mainline branches if available
337
+ preferred_order = ("main", "master", "develop", "dev")
338
+ for preferred in preferred_order:
339
+ if preferred in branch_candidates:
340
+ return preferred
341
+
342
+ # Otherwise return the first branch reported by GitHub
343
+ return branch_candidates[0]
344
+
345
+
115
346
  async def load_check_state(path: Path) -> VersionCheckState | None:
116
347
  """
117
348
  Load version check state from file.
@@ -130,9 +361,25 @@ async def load_check_state(path: Path) -> VersionCheckState | None:
130
361
  content = await f.read()
131
362
  data = json.loads(content)
132
363
  return VersionCheckState(**data)
364
+ except (OSError, FileNotFoundError, PermissionError) as e:
365
+ logger.warning(
366
+ "version_check_state_load_file_error",
367
+ path=str(path),
368
+ error=str(e),
369
+ error_type=type(e).__name__,
370
+ )
371
+ return None
372
+ except (json.JSONDecodeError, ValueError, TypeError) as e:
373
+ logger.warning(
374
+ "version_check_state_load_parse_error",
375
+ path=str(path),
376
+ error=str(e),
377
+ error_type=type(e).__name__,
378
+ )
379
+ return None
133
380
  except Exception as e:
134
381
  logger.warning(
135
- "version_check_state_load_failed",
382
+ "version_check_state_load_unexpected_error",
136
383
  path=str(path),
137
384
  error=str(e),
138
385
  error_type=type(e).__name__,
@@ -160,9 +407,23 @@ async def save_check_state(path: Path, state: VersionCheckState) -> None:
160
407
  await f.write(json.dumps(state_dict, indent=2))
161
408
 
162
409
  logger.debug("version_check_state_saved", path=str(path))
410
+ except (OSError, FileNotFoundError, PermissionError) as e:
411
+ logger.warning(
412
+ "version_check_state_save_file_error",
413
+ path=str(path),
414
+ error=str(e),
415
+ error_type=type(e).__name__,
416
+ )
417
+ except (TypeError, ValueError) as e:
418
+ logger.warning(
419
+ "version_check_state_save_serialize_error",
420
+ path=str(path),
421
+ error=str(e),
422
+ error_type=type(e).__name__,
423
+ )
163
424
  except Exception as e:
164
425
  logger.warning(
165
- "version_check_state_save_failed",
426
+ "version_check_state_save_unexpected_error",
166
427
  path=str(path),
167
428
  error=str(e),
168
429
  error_type=type(e).__name__,
@@ -182,7 +443,13 @@ def get_version_check_state_path() -> Path:
182
443
  __all__ = [
183
444
  "VersionCheckState",
184
445
  "fetch_latest_github_version",
446
+ "fetch_latest_branch_commit",
447
+ "fetch_branch_names_for_commit",
448
+ "resolve_branch_for_commit",
185
449
  "get_current_version",
450
+ "extract_commit_from_version",
451
+ "commit_refs_match",
452
+ "get_branch_override",
186
453
  "compare_versions",
187
454
  "load_check_state",
188
455
  "save_check_state",
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccproxy-api
3
+ Version: 0.2.0
4
+ Summary: API server that provides an Anthropic and OpenAI compatible interface over Claude Code, allowing to use your Claude OAuth account or over the API.
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aiofiles>=24.1.0
8
+ Requires-Dist: fastapi[standard]>=0.115.14
9
+ Requires-Dist: httpx[http2]>=0.28.1
10
+ Requires-Dist: packaging>=25.0
11
+ Requires-Dist: pydantic-settings>=2.4.0
12
+ Requires-Dist: pydantic>=2.8.0
13
+ Requires-Dist: pyjwt>=2.10.1
14
+ Requires-Dist: rich-toolkit>=0.14.8
15
+ Requires-Dist: rich>=13.0.0
16
+ Requires-Dist: sortedcontainers>=2.4.0
17
+ Requires-Dist: structlog>=25.4.0
18
+ Requires-Dist: typer>=0.16.0
19
+ Requires-Dist: typing-extensions>=4.0.0
20
+ Requires-Dist: uvicorn>=0.34.0
21
+ Provides-Extra: plugins-claude
22
+ Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'plugins-claude'
23
+ Requires-Dist: qrcode>=8.2; extra == 'plugins-claude'
24
+ Provides-Extra: plugins-codex
25
+ Requires-Dist: pyjwt>=2.10.1; extra == 'plugins-codex'
26
+ Requires-Dist: qrcode>=8.2; extra == 'plugins-codex'
27
+ Provides-Extra: plugins-mcp
28
+ Requires-Dist: fastapi-mcp>=0.3.7; extra == 'plugins-mcp'
29
+ Provides-Extra: plugins-metrics
30
+ Requires-Dist: prometheus-client>=0.22.1; extra == 'plugins-metrics'
31
+ Provides-Extra: plugins-storage
32
+ Requires-Dist: duckdb-engine>=0.17.0; extra == 'plugins-storage'
33
+ Requires-Dist: duckdb<1.4.0,>=1.1.0; extra == 'plugins-storage'
34
+ Requires-Dist: sqlalchemy>=2.0.0; extra == 'plugins-storage'
35
+ Requires-Dist: sqlmodel>=0.0.24; extra == 'plugins-storage'
36
+ Provides-Extra: plugins-tui
37
+ Requires-Dist: aioconsole>=0.8.1; extra == 'plugins-tui'
38
+ Requires-Dist: textual>=3.7.1; extra == 'plugins-tui'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # CCProxy API Server
42
+
43
+ CCProxy is a local, plugin-based reverse proxy that unifies access to
44
+ multiple AI providers (e.g., Claude SDK/API and OpenAI Codex) behind a
45
+ consistent API. It ships with bundled plugins for providers, logging,
46
+ tracing, metrics, analytics, and more.
47
+
48
+ ## Supported Providers
49
+
50
+ - Anthropic Claude API/SDK (OAuth2 flow or Claude CLI/SDK token files)
51
+ - OpenAI Codex (ChatGPT backend Responses API using OAuth for paid/pro accounts)
52
+ - GitHub Copilot (chat and completions for free, paid, or business accounts)
53
+
54
+ Each provider adapter exposes the same surface area: OpenAI Chat
55
+ Completions, OpenAI Responses, and Anthropic Messages. The proxy maintains a
56
+ shared model-mapping layer so you can reuse the same `model` identifier
57
+ across providers without rewriting client code.
58
+
59
+ Authentication can reuse existing provider files (e.g., Claude CLI SDK
60
+ tokens and the Codex CLI credential store), or you can run
61
+ `ccproxy auth login <provider>` to complete the OAuth flow from the CLI;
62
+ stored secrets are picked up automatically by the proxy.
63
+
64
+ ## Extensibility
65
+
66
+ CCProxy's plugin system lets you add instrumentation and storage layers
67
+ without patching the core server. Bundled plugins currently include:
68
+
69
+ - [`access_log`](ccproxy/plugins/access_log/README.md): structured access
70
+ logging for client and provider traffic
71
+ - [`analytics`](ccproxy/plugins/analytics/README.md): DuckDB-backed analytics
72
+ APIs for captured request logs
73
+ - [`claude_api`](ccproxy/plugins/claude_api/README.md): Anthropic Claude HTTP
74
+ API adapter with health and metrics
75
+ - [`claude_sdk`](ccproxy/plugins/claude_sdk/README.md): local Claude CLI/SDK
76
+ adapter with session pooling
77
+ - [`codex`](ccproxy/plugins/codex/README.md): OpenAI Codex provider adapter
78
+ with OAuth support
79
+ - [`command_replay`](ccproxy/plugins/command_replay/README.md): generates
80
+ `curl`/`xh` commands for captured requests
81
+ - [`copilot`](ccproxy/plugins/copilot/README.md): GitHub Copilot provider
82
+ adapter with OAuth token management
83
+ - [`credential_balancer`](ccproxy/plugins/credential_balancer/README.md):
84
+ rotates upstream credentials based on health
85
+ - [`dashboard`](ccproxy/plugins/dashboard/README.md): serves the CCProxy
86
+ dashboard SPA and APIs
87
+ - [`docker`](ccproxy/plugins/docker/README.md): runs providers inside Docker via
88
+ CLI extensions
89
+ - [`duckdb_storage`](ccproxy/plugins/duckdb_storage/README.md): exposes
90
+ DuckDB-backed storage for logs and analytics
91
+ - [`max_tokens`](ccproxy/plugins/max_tokens/README.md): normalizes
92
+ `max_tokens` fields to provider limits
93
+ - [`metrics`](ccproxy/plugins/metrics/README.md): Prometheus-compatible metrics
94
+ with optional Pushgateway
95
+ - [`oauth_claude`](ccproxy/plugins/oauth_claude/README.md): standalone OAuth
96
+ provider for Claude integrations
97
+ - [`oauth_codex`](ccproxy/plugins/oauth_codex/README.md): standalone OAuth
98
+ provider for Codex integrations
99
+ - [`permissions`](ccproxy/plugins/permissions/README.md): interactive approval
100
+ flow for privileged tool actions
101
+ - [`pricing`](ccproxy/plugins/pricing/README.md): caches model pricing data for
102
+ cost-aware features
103
+ - [`request_tracer`](ccproxy/plugins/request_tracer/README.md): detailed
104
+ request/response tracing for debugging
105
+
106
+ Shared helpers such as
107
+ [`claude_shared`](ccproxy/plugins/claude_shared/README.md) provide metadata
108
+ consumed by the Claude plugins. Each plugin directory contains its own README
109
+ with configuration examples.
110
+
111
+ ## Quick Links
112
+
113
+ - Docs site entry: `docs/index.md`
114
+ - Getting started: `docs/getting-started/quickstart.md`
115
+ - Configuration reference: `docs/getting-started/configuration.md`
116
+ - Examples: `docs/examples.md`
117
+ - Migration (0.2): `docs/migration/0.2-plugin-first.md`
118
+
119
+ ## Plugin Config Quickstart
120
+
121
+ The plugin system is enabled by default (`enable_plugins = true`), and all
122
+ discovered plugins load automatically when no additional filters are set. Use
123
+ these knobs to adjust what runs:
124
+
125
+ - `enabled_plugins`: optional allow list; when set, only the listed plugins run.
126
+ - `disabled_plugins`: optional block list applied when `enabled_plugins` is not
127
+ set.
128
+ - `plugins.<name>.enabled`: per-plugin flag (defaults to `true`) that you can
129
+ override in TOML or environment variables. Any plugin set to `false` is added
130
+ to the deny list alongside `disabled_plugins` during startup.
131
+
132
+ During startup we merge `disabled_plugins` and any `plugins.<name>.enabled = false`
133
+ entries into a single deny list. At runtime the loader checks the allow list
134
+ first and then confirms the plugin is not deny listed. Configure plugins under
135
+ `plugins.<name>` in TOML or via nested environment variables.
136
+
137
+ Use `ccproxy plugins list` to inspect discovered plugins and
138
+ `ccproxy plugins settings <name>` to review configuration fields.
139
+
140
+ ### TOML example (`.ccproxy.toml`)
141
+
142
+ ```toml
143
+ enable_plugins = true
144
+ # enabled_plugins = ["metrics", "analytics"] # Optional allow list
145
+ disabled_plugins = ["duckdb_storage"] # Optional block list
146
+
147
+ [plugins.access_log]
148
+ client_enabled = true
149
+ client_format = "structured"
150
+ client_log_file = "/tmp/ccproxy/access.log"
151
+
152
+ [plugins.request_tracer]
153
+ json_logs_enabled = true
154
+ raw_http_enabled = true
155
+ log_dir = "/tmp/ccproxy/traces"
156
+
157
+ [plugins.duckdb_storage]
158
+ enabled = false
159
+
160
+ [plugins.analytics]
161
+ enabled = true
162
+
163
+ # Metrics plugin
164
+ [plugins.metrics]
165
+ enabled = true
166
+ # pushgateway_enabled = true
167
+ # pushgateway_url = "http://localhost:9091"
168
+ # pushgateway_job = "ccproxy"
169
+ # pushgateway_push_interval = 60
170
+ ```
171
+
172
+ ### Environment variables (nested with `__`)
173
+
174
+ ```bash
175
+ export DISABLED_PLUGINS="duckdb_storage" # Optional block list
176
+ export PLUGINS__ACCESS_LOG__ENABLED=true
177
+ export PLUGINS__ACCESS_LOG__CLIENT_ENABLED=true
178
+ export PLUGINS__ACCESS_LOG__CLIENT_FORMAT=structured
179
+ export PLUGINS__ACCESS_LOG__CLIENT_LOG_FILE=/tmp/ccproxy/access.log
180
+
181
+ export PLUGINS__REQUEST_TRACER__ENABLED=true
182
+ export PLUGINS__REQUEST_TRACER__JSON_LOGS_ENABLED=true
183
+ export PLUGINS__REQUEST_TRACER__RAW_HTTP_ENABLED=true
184
+ export PLUGINS__REQUEST_TRACER__LOG_DIR=/tmp/ccproxy/traces
185
+
186
+ export PLUGINS__DUCKDB_STORAGE__ENABLED=true
187
+ export PLUGINS__ANALYTICS__ENABLED=true
188
+ export PLUGINS__METRICS__ENABLED=true
189
+ # export PLUGINS__METRICS__PUSHGATEWAY_ENABLED=true
190
+ # export PLUGINS__METRICS__PUSHGATEWAY_URL=http://localhost:9091
191
+ ```
192
+
193
+ ## Running
194
+
195
+ To install the latest stable release without cloning the repository, use `uvx`
196
+ to grab the published wheel and launch the CLI:
197
+
198
+ ```bash
199
+ uvx --with "ccproxy-api[all]" ccproxy serve --port 8000
200
+ ```
201
+
202
+ If you prefer `pipx`, install the package (optionally with extras) and use the
203
+ local shim:
204
+
205
+ ```bash
206
+ pipx install "ccproxy-api[all]"
207
+ ccproxy serve # default on localhost:8000
208
+ ```
209
+
210
+ ## License
211
+
212
+ See `LICENSE`.