ccproxy-api 0.1.6__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 +439 -212
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +145 -176
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +402 -530
  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 +558 -0
  97. ccproxy/data/codex_headers_fallback.json +121 -0
  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 +63 -107
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +346 -314
  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 +95 -342
  387. ccproxy/utils/version_checker.py +279 -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.6.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 -1231
  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 -269
  458. ccproxy/services/codex_detection_service.py +0 -263
  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.6.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.6.dist-info/RECORD +0 -189
  473. ccproxy_api-0.1.6.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.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,164 +1,13 @@
1
- """Pydantic models for Claude Proxy API Server."""
1
+ """Pydantic models for Claude Proxy API Server.
2
2
 
3
- from .claude_sdk import (
4
- AssistantMessage,
5
- ContentBlock,
6
- ExtendedContentBlock,
7
- ResultMessage,
8
- ResultMessageBlock,
9
- SDKContentBlock,
10
- SDKMessageMode,
11
- TextBlock,
12
- ToolResultBlock,
13
- ToolResultSDKBlock,
14
- ToolUseBlock,
15
- ToolUseSDKBlock,
16
- UserMessage,
17
- convert_sdk_result_message,
18
- convert_sdk_system_message,
19
- convert_sdk_text_block,
20
- convert_sdk_tool_result_block,
21
- convert_sdk_tool_use_block,
22
- to_sdk_variant,
23
- )
24
- from .messages import (
25
- MessageContentBlock,
26
- MessageCreateParams,
27
- MessageResponse,
28
- MetadataParams,
29
- SystemMessage,
30
- ThinkingConfig,
31
- ToolChoiceParams,
32
- )
33
- from .requests import (
34
- ImageContent,
35
- Message,
36
- MessageContent,
37
- TextContent,
38
- ToolDefinition,
39
- Usage,
40
- )
41
- from .responses import (
42
- APIError,
43
- AuthenticationError,
44
- ChatCompletionResponse,
45
- Choice,
46
- ErrorResponse,
47
- InternalServerError,
48
- InvalidRequestError,
49
- NotFoundError,
50
- OverloadedError,
51
- RateLimitError,
52
- ResponseContent,
53
- StreamingChatCompletionResponse,
54
- StreamingChoice,
55
- TextResponse,
56
- ToolCall,
57
- ToolUse,
58
- )
59
- from .types import (
60
- ContentBlockType,
61
- ErrorType,
62
- ImageSourceType,
63
- MessageRole,
64
- ModalityType,
65
- OpenAIFinishReason,
66
- PermissionBehavior,
67
- ResponseFormatType,
68
- ServiceTier,
69
- StopReason,
70
- StreamEventType,
71
- ToolChoiceType,
72
- ToolType,
73
- )
3
+ This package now re-exports Anthropic models from ccproxy.llms.models.anthropic
4
+ for backward compatibility, while keeping provider-agnostic models here.
5
+ """
6
+
7
+ from .provider import ProviderConfig
74
8
 
75
9
 
76
10
  __all__ = [
77
- # Type aliases
78
- "ContentBlockType",
79
- "ErrorType",
80
- "ImageSourceType",
81
- "MessageRole",
82
- "ModalityType",
83
- "OpenAIFinishReason",
84
- "PermissionBehavior",
85
- "ResponseFormatType",
86
- "ServiceTier",
87
- "StopReason",
88
- "StreamEventType",
89
- "ToolChoiceType",
90
- "ToolType",
91
- # Claude SDK models
92
- "AssistantMessage",
93
- "ContentBlock",
94
- "ExtendedContentBlock",
95
- "ResultMessage",
96
- "ResultMessageBlock",
97
- "SDKContentBlock",
98
- "SDKMessageMode",
99
- "TextBlock",
100
- "ToolResultBlock",
101
- "ToolResultSDKBlock",
102
- "ToolUseBlock",
103
- "ToolUseSDKBlock",
104
- "UserMessage",
105
- "convert_sdk_result_message",
106
- "convert_sdk_system_message",
107
- "convert_sdk_text_block",
108
- "convert_sdk_tool_result_block",
109
- "convert_sdk_tool_use_block",
110
- "to_sdk_variant",
111
- # Message models
112
- "MessageContentBlock",
113
- "MessageCreateParams",
114
- "MessageResponse",
115
- "MetadataParams",
116
- "SystemMessage",
117
- "ThinkingConfig",
118
- "ToolChoiceParams",
119
- # Request models
120
- "ImageContent",
121
- "Message",
122
- "MessageContent",
123
- "TextContent",
124
- "ToolDefinition",
125
- "Usage",
126
- # Response models
127
- "APIError",
128
- "AuthenticationError",
129
- "ChatCompletionResponse",
130
- "Choice",
131
- "ErrorResponse",
132
- "InternalServerError",
133
- "InvalidRequestError",
134
- "NotFoundError",
135
- "OverloadedError",
136
- "RateLimitError",
137
- "ResponseContent",
138
- "StreamingChatCompletionResponse",
139
- "StreamingChoice",
140
- "TextResponse",
141
- "ToolCall",
142
- "ToolUse",
143
- # OpenAI-compatible models
144
- "OpenAIChatCompletionRequest",
145
- "OpenAIChatCompletionResponse",
146
- "OpenAIChoice",
147
- "OpenAIErrorDetail",
148
- "OpenAIErrorResponse",
149
- "OpenAIFunction",
150
- "OpenAILogprobs",
151
- "OpenAIMessage",
152
- "OpenAIMessageContent",
153
- "OpenAIModelInfo",
154
- "OpenAIModelsResponse",
155
- "OpenAIResponseFormat",
156
- "OpenAIResponseMessage",
157
- "OpenAIStreamingChatCompletionResponse",
158
- "OpenAIStreamingChoice",
159
- "OpenAIStreamOptions",
160
- "OpenAITool",
161
- "OpenAIToolCall",
162
- "OpenAIToolChoice",
163
- "OpenAIUsage",
11
+ # Provider models
12
+ "ProviderConfig",
164
13
  ]
@@ -1,208 +1,107 @@
1
- """Detection models for Claude Code CLI headers and system prompt extraction."""
1
+ """Shared helper dataclasses for plugin detection caches."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from datetime import UTC, datetime
6
- from typing import Annotated, Any
7
-
8
- from pydantic import BaseModel, ConfigDict, Field
9
-
10
-
11
- class ClaudeCodeHeaders(BaseModel):
12
- """Pydantic model for Claude CLI headers extraction with field aliases."""
13
-
14
- anthropic_beta: str = Field(
15
- alias="anthropic-beta",
16
- description="Anthropic beta features",
17
- default="claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
18
- )
19
- anthropic_version: str = Field(
20
- alias="anthropic-version",
21
- description="Anthropic API version",
22
- default="2023-06-01",
23
- )
24
- anthropic_dangerous_direct_browser_access: str = Field(
25
- alias="anthropic-dangerous-direct-browser-access",
26
- description="Browser access flag",
27
- default="true",
28
- )
29
- x_app: str = Field(
30
- alias="x-app", description="Application identifier", default="cli"
31
- )
32
- user_agent: str = Field(
33
- alias="user-agent",
34
- description="User agent string",
35
- default="claude-cli/1.0.60 (external, cli)",
36
- )
37
- x_stainless_lang: str = Field(
38
- alias="x-stainless-lang", description="SDK language", default="js"
39
- )
40
- x_stainless_retry_count: str = Field(
41
- alias="x-stainless-retry-count", description="Retry count", default="0"
42
- )
43
- x_stainless_timeout: str = Field(
44
- alias="x-stainless-timeout", description="Request timeout", default="60"
45
- )
46
- x_stainless_package_version: str = Field(
47
- alias="x-stainless-package-version",
48
- description="Package version",
49
- default="0.55.1",
50
- )
51
- x_stainless_os: str = Field(
52
- alias="x-stainless-os", description="Operating system", default="Linux"
53
- )
54
- x_stainless_arch: str = Field(
55
- alias="x-stainless-arch", description="Architecture", default="x64"
56
- )
57
- x_stainless_runtime: str = Field(
58
- alias="x-stainless-runtime", description="Runtime", default="node"
59
- )
60
- x_stainless_runtime_version: str = Field(
61
- alias="x-stainless-runtime-version",
62
- description="Runtime version",
63
- default="v24.3.0",
64
- )
65
-
66
- model_config = ConfigDict(extra="ignore", populate_by_name=True)
67
-
68
- def to_headers_dict(self) -> dict[str, str]:
69
- """Convert to headers dictionary for HTTP forwarding with proper case."""
70
- headers = {}
71
-
72
- # Map field names to proper HTTP header names
73
- header_mapping = {
74
- "anthropic_beta": "anthropic-beta",
75
- "anthropic_version": "anthropic-version",
76
- "anthropic_dangerous_direct_browser_access": "anthropic-dangerous-direct-browser-access",
77
- "x_app": "x-app",
78
- "user_agent": "User-Agent",
79
- "x_stainless_lang": "X-Stainless-Lang",
80
- "x_stainless_retry_count": "X-Stainless-Retry-Count",
81
- "x_stainless_timeout": "X-Stainless-Timeout",
82
- "x_stainless_package_version": "X-Stainless-Package-Version",
83
- "x_stainless_os": "X-Stainless-OS",
84
- "x_stainless_arch": "X-Stainless-Arch",
85
- "x_stainless_runtime": "X-Stainless-Runtime",
86
- "x_stainless_runtime_version": "X-Stainless-Runtime-Version",
5
+ from collections.abc import ItemsView, Iterable
6
+ from copy import deepcopy
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class DetectedHeaders:
13
+ """Normalized, lowercase HTTP headers captured during CLI detection."""
14
+
15
+ values: dict[str, str] = field(default_factory=dict)
16
+
17
+ def __post_init__(self) -> None:
18
+ normalized: dict[str, str] = {}
19
+ for key, raw_value in (self.values or {}).items():
20
+ if key is None:
21
+ continue
22
+ normalized_key = str(key).lower()
23
+ normalized[normalized_key] = "" if raw_value is None else str(raw_value)
24
+ self.values = normalized
25
+
26
+ def as_dict(self) -> dict[str, str]:
27
+ """Return a copy of the detected headers as a plain dict."""
28
+
29
+ return dict(self.values)
30
+
31
+ def filtered(
32
+ self,
33
+ ignores: Iterable[str] | None = None,
34
+ redacted: Iterable[str] | None = None,
35
+ ) -> dict[str, str]:
36
+ """Return headers filtered for safe forwarding."""
37
+
38
+ ignore_set = {item.lower() for item in ignores or ()}
39
+ redacted_set = {item.lower() for item in redacted or ()}
40
+ return {
41
+ key: value
42
+ for key, value in self.values.items()
43
+ if value and key not in ignore_set and key not in redacted_set
87
44
  }
88
45
 
89
- for field_name, header_name in header_mapping.items():
90
- value = getattr(self, field_name, None)
91
- if value is not None:
92
- headers[header_name] = value
93
-
94
- return headers
95
-
96
-
97
- class SystemPromptData(BaseModel):
98
- """Extracted system prompt information."""
99
-
100
- system_field: Annotated[
101
- str | list[dict[str, Any]],
102
- Field(
103
- description="Complete system field as detected from Claude CLI, preserving exact structure including type, text, and cache_control"
104
- ),
105
- ]
106
-
107
- model_config = ConfigDict(extra="forbid")
108
-
109
-
110
- class ClaudeCacheData(BaseModel):
111
- """Cached Claude CLI detection data with version tracking."""
112
-
113
- claude_version: Annotated[str, Field(description="Claude CLI version")]
114
- headers: Annotated[ClaudeCodeHeaders, Field(description="Extracted headers")]
115
- system_prompt: Annotated[
116
- SystemPromptData, Field(description="Extracted system prompt")
117
- ]
118
- cached_at: Annotated[
119
- datetime,
120
- Field(
121
- description="Cache timestamp",
122
- default_factory=lambda: datetime.now(UTC),
123
- ),
124
- ] = None # type: ignore # Pydantic handles this via default_factory
125
-
126
- model_config = ConfigDict(extra="forbid")
127
-
128
-
129
- class CodexHeaders(BaseModel):
130
- """Pydantic model for Codex CLI headers extraction with field aliases."""
131
-
132
- session_id: str = Field(
133
- alias="session_id",
134
- description="Codex session identifier",
135
- default="",
136
- )
137
- originator: str = Field(
138
- description="Codex originator identifier",
139
- default="codex_cli_rs",
140
- )
141
- openai_beta: str = Field(
142
- alias="openai-beta",
143
- description="OpenAI beta features",
144
- default="responses=experimental",
145
- )
146
- version: str = Field(
147
- description="Codex CLI version",
148
- default="0.21.0",
149
- )
150
- chatgpt_account_id: str = Field(
151
- alias="chatgpt-account-id",
152
- description="ChatGPT account identifier",
153
- default="",
154
- )
155
-
156
- model_config = ConfigDict(extra="ignore", populate_by_name=True)
157
-
158
- def to_headers_dict(self) -> dict[str, str]:
159
- """Convert to headers dictionary for HTTP forwarding with proper case."""
160
- headers = {}
161
-
162
- # Map field names to proper HTTP header names
163
- header_mapping = {
164
- "session_id": "session_id",
165
- "originator": "originator",
166
- "openai_beta": "openai-beta",
167
- "version": "version",
168
- "chatgpt_account_id": "chatgpt-account-id",
169
- }
46
+ def get(self, key: str, default: str | None = None) -> str | None:
47
+ """Lookup a header by key (case-insensitive)."""
48
+
49
+ return self.values.get(key.lower(), default)
50
+
51
+ def items(self) -> ItemsView[str, str]:
52
+ """Iterate over header key/value pairs."""
53
+
54
+ return self.values.items()
55
+
56
+ def __bool__(self) -> bool:
57
+ return bool(self.values)
58
+
59
+
60
+ @dataclass(slots=True)
61
+ class DetectedPrompts:
62
+ """Structured prompt metadata extracted from CLI detection payloads."""
63
+
64
+ instructions: str | None = None
65
+ system: Any | None = None
66
+ raw: dict[str, Any] = field(default_factory=dict)
67
+
68
+ @classmethod
69
+ def from_body(cls, body: Any | None) -> DetectedPrompts:
70
+ """Build a DetectedPrompts instance from a captured request body."""
71
+
72
+ if not isinstance(body, dict):
73
+ return cls(raw={} if body is None else {"__raw__": body})
74
+
75
+ body_copy = deepcopy(body)
76
+
77
+ instructions = body_copy.get("instructions")
78
+ if not isinstance(instructions, str) or not instructions.strip():
79
+ instructions = None
170
80
 
171
- for field_name, header_name in header_mapping.items():
172
- value = getattr(self, field_name, None)
173
- if value is not None and value != "":
174
- headers[header_name] = value
81
+ system_value = body_copy.get("system")
175
82
 
176
- return headers
83
+ return cls(instructions=instructions, system=system_value, raw=body_copy)
177
84
 
85
+ def instructions_payload(self) -> dict[str, Any]:
86
+ """Return a payload suitable for injecting Codex-style instructions."""
178
87
 
179
- class CodexInstructionsData(BaseModel):
180
- """Extracted Codex instructions information."""
88
+ if self.instructions:
89
+ return {"instructions": self.instructions}
90
+ return {}
181
91
 
182
- instructions_field: Annotated[
183
- str,
184
- Field(
185
- description="Complete instructions field as detected from Codex CLI, preserving exact text content"
186
- ),
187
- ]
92
+ def system_payload(self, mode: str = "full") -> dict[str, Any]:
93
+ """Return anthropic-style system data respecting the requested mode."""
188
94
 
189
- model_config = ConfigDict(extra="forbid")
95
+ if self.system is None or mode == "none":
96
+ return {}
190
97
 
98
+ if mode == "minimal" and isinstance(self.system, list):
99
+ return {"system": self.system[:1]} if self.system else {}
191
100
 
192
- class CodexCacheData(BaseModel):
193
- """Cached Codex CLI detection data with version tracking."""
101
+ return {"system": self.system}
194
102
 
195
- codex_version: Annotated[str, Field(description="Codex CLI version")]
196
- headers: Annotated[CodexHeaders, Field(description="Extracted headers")]
197
- instructions: Annotated[
198
- CodexInstructionsData, Field(description="Extracted instructions")
199
- ]
200
- cached_at: Annotated[
201
- datetime,
202
- Field(
203
- description="Cache timestamp",
204
- default_factory=lambda: datetime.now(UTC),
205
- ),
206
- ] = None # type: ignore # Pydantic handles this via default_factory
103
+ def has_system(self) -> bool:
104
+ return bool(self.system)
207
105
 
208
- model_config = ConfigDict(extra="forbid")
106
+ def has_instructions(self) -> bool:
107
+ return bool(self.instructions)
@@ -0,0 +1,75 @@
1
+ """Provider configuration models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class ModelMappingRule(BaseModel):
11
+ """Ordered mapping rule for translating client model identifiers."""
12
+
13
+ match: str = Field(..., description="Client-facing model identifier or pattern")
14
+ target: str = Field(..., description="Upstream model identifier to use")
15
+ kind: Literal["exact", "regex", "prefix", "suffix"] = Field(
16
+ default="exact",
17
+ description="Type of match to apply for this rule",
18
+ )
19
+ flags: list[Literal["IGNORECASE"]] = Field(
20
+ default_factory=list,
21
+ description="Optional regex flags to apply when kind=regex",
22
+ )
23
+ notes: str | None = Field(
24
+ default=None,
25
+ description="Optional human-readable description of this rule",
26
+ )
27
+
28
+
29
+ class ModelCard(BaseModel):
30
+ """Representation of a model entry returned by /models."""
31
+
32
+ id: str = Field(..., description="Unique model identifier exposed to clients")
33
+ object: str = Field(default="model", description="OpenAI-compatible object type")
34
+ created: int | None = Field(
35
+ default=None, description="Unix timestamp when the model became available"
36
+ )
37
+ owned_by: str | None = Field(
38
+ default=None, description="Provider or organization that owns the model"
39
+ )
40
+ permission: list[dict[str, Any]] | None = Field(
41
+ default=None,
42
+ description="Optional list of permission descriptors",
43
+ )
44
+ root: str | None = Field(
45
+ default=None, description="Root model identifier for fine-tuned variants"
46
+ )
47
+ parent: str | None = Field(
48
+ default=None, description="Parent model identifier, if any"
49
+ )
50
+
51
+ model_config = ConfigDict(extra="allow")
52
+
53
+
54
+ class ProviderConfig(BaseModel):
55
+ """Configuration for a provider plugin."""
56
+
57
+ name: str = Field(..., description="Provider name")
58
+ base_url: str = Field(..., description="Base URL for the provider API")
59
+ supports_streaming: bool = Field(
60
+ default=False, description="Whether the provider supports streaming"
61
+ )
62
+ requires_auth: bool = Field(
63
+ default=True, description="Whether the provider requires authentication"
64
+ )
65
+ auth_type: str | None = Field(
66
+ default=None, description="Authentication type (bearer, api_key, etc.)"
67
+ )
68
+ model_mappings: list[ModelMappingRule] = Field(
69
+ default_factory=list,
70
+ description="Ordered list of client→upstream model mapping rules",
71
+ )
72
+ models_endpoint: list[ModelCard] = Field(
73
+ default_factory=list,
74
+ description="Model metadata returned by the provider's /models endpoint",
75
+ )
@@ -0,0 +1,32 @@
1
+ # Access Log Plugin
2
+
3
+ Captures client and provider traffic and writes structured access logs.
4
+
5
+ ## Highlights
6
+ - Hooks into request lifecycle events to log Common, Combined, or JSON formats
7
+ - Supports per-channel enablement and configurable log destinations
8
+ - Optionally forwards records to the analytics ingest service for persistence
9
+
10
+ ## Configuration
11
+ - `AccessLogConfig` controls enable flags, formats, and file paths
12
+ - Generate defaults with `python3 scripts/generate_config_from_model.py \
13
+ --format toml --plugin access_log`
14
+
15
+ ```toml
16
+ [plugins.access_log]
17
+ # enabled = true
18
+ # client_enabled = true
19
+ # client_format = "structured"
20
+ # client_log_file = "/tmp/ccproxy/access.log"
21
+ # provider_enabled = false
22
+ # provider_format = "structured"
23
+ # provider_log_file = "/tmp/ccproxy/provider_access.log"
24
+ # exclude_paths = ["/health", "/metrics", "/readyz", "/livez"]
25
+ # buffer_size = 100
26
+ # flush_interval = 1.0
27
+ ```
28
+
29
+ ## Related Components
30
+ - `plugin.py`: runtime lifecycle and hook registration
31
+ - `hook.py`: event handler that formats and writes log entries
32
+ - `formatter.py` / `writer.py`: formatting helpers and async file writer
@@ -0,0 +1,20 @@
1
+ """Access log plugin for CCProxy.
2
+
3
+ Provides structured access logging for both client and provider requests
4
+ using the hook system.
5
+ """
6
+
7
+ from .config import AccessLogConfig
8
+ from .hook import AccessLogHook
9
+ from .plugin import AccessLogFactory, AccessLogRuntime
10
+
11
+
12
+ __all__ = [
13
+ "AccessLogConfig",
14
+ "AccessLogFactory",
15
+ "AccessLogHook",
16
+ "AccessLogRuntime",
17
+ ]
18
+
19
+ # Export the factory instance for plugin loading
20
+ factory = AccessLogFactory()
@@ -0,0 +1,33 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+
6
+ class AccessLogConfig(BaseModel):
7
+ """Configuration for access logging.
8
+
9
+ Supports logging at both client and provider levels with
10
+ different formats for each.
11
+ """
12
+
13
+ # Global enable/disable
14
+ enabled: bool = True
15
+
16
+ # Client-level access logging
17
+ client_enabled: bool = True
18
+ client_format: Literal["combined", "common", "structured"] = "structured"
19
+ client_log_file: str = "/tmp/ccproxy/access.log"
20
+
21
+ # Provider-level access logging (optional)
22
+ provider_enabled: bool = False
23
+ provider_format: Literal["structured"] = "structured"
24
+ provider_log_file: str = "/tmp/ccproxy/provider_access.log"
25
+
26
+ # Path filters (only for client level)
27
+ exclude_paths: list[str] = ["/health", "/metrics", "/readyz", "/livez"]
28
+
29
+ # Performance options
30
+ buffer_size: int = 100 # Buffer this many log entries before writing
31
+ flush_interval: float = 1.0 # Flush buffer every N seconds
32
+
33
+ model_config = ConfigDict()