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
@@ -0,0 +1,415 @@
1
+ """Factory for creating AuthManager instances from credential sources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from ccproxy.auth.exceptions import AuthenticationError
10
+ from ccproxy.auth.manager import AuthManager
11
+ from ccproxy.core.logging import TraceBoundLogger, get_plugin_logger
12
+
13
+ from .config import CredentialManager
14
+
15
+
16
+ if TYPE_CHECKING:
17
+ from ccproxy.services.auth_registry import AuthManagerRegistry
18
+
19
+
20
+ logger = get_plugin_logger(__name__)
21
+
22
+
23
+ class AuthManagerFactory:
24
+ """Creates AuthManager instances from credential source configurations."""
25
+
26
+ def __init__(
27
+ self,
28
+ auth_registry: AuthManagerRegistry | None = None,
29
+ *,
30
+ logger: TraceBoundLogger | None = None,
31
+ ) -> None:
32
+ """Initialize auth manager factory.
33
+
34
+ Args:
35
+ auth_registry: Auth manager registry for resolving manager keys
36
+ logger: Optional logger for this factory
37
+ """
38
+ self._auth_registry = auth_registry
39
+ self._logger = logger or get_plugin_logger(__name__)
40
+
41
+ async def create_from_source(
42
+ self,
43
+ source: CredentialManager,
44
+ provider: str,
45
+ ) -> AuthManager:
46
+ """Create AuthManager instance from credential source configuration.
47
+
48
+ Args:
49
+ source: Manager credential configuration
50
+ provider: Provider name for this credential (unused, kept for compatibility)
51
+
52
+ Returns:
53
+ AuthManager instance
54
+
55
+ Raises:
56
+ AuthenticationError: If manager creation fails
57
+ """
58
+ return await self._create_provider_manager(source)
59
+
60
+ async def _create_provider_manager(
61
+ self,
62
+ source: CredentialManager,
63
+ ) -> AuthManager:
64
+ """Create provider-specific auth manager.
65
+
66
+ Args:
67
+ source: Manager credential configuration
68
+
69
+ Returns:
70
+ AuthManager instance
71
+
72
+ Raises:
73
+ AuthenticationError: If manager creation fails
74
+ """
75
+ # Check if custom file path is specified (already expanded by validator)
76
+ custom_file = str(source.file.resolve()) if source.file else None
77
+
78
+ # Direct class specification approach
79
+ if source.manager_class:
80
+ return await self._create_manager_from_class_name(
81
+ source.manager_class,
82
+ source.storage_class,
83
+ custom_file,
84
+ source.resolved_label,
85
+ source.config,
86
+ )
87
+
88
+ # Registry lookup approach
89
+ if source.manager_key:
90
+ return await self._create_manager_from_registry(
91
+ source.manager_key,
92
+ custom_file,
93
+ source.resolved_label,
94
+ )
95
+
96
+ raise AuthenticationError(
97
+ "Neither manager_class nor manager_key specified in credential source"
98
+ )
99
+
100
+ async def _create_manager_from_registry(
101
+ self,
102
+ manager_key: str,
103
+ custom_file: str | None,
104
+ label: str,
105
+ ) -> AuthManager:
106
+ """Create manager using registry lookup.
107
+
108
+ Args:
109
+ manager_key: Registry key
110
+ custom_file: Optional custom file path
111
+ label: Label for logging
112
+
113
+ Returns:
114
+ AuthManager instance
115
+
116
+ Raises:
117
+ AuthenticationError: If manager not found or creation fails
118
+ """
119
+ if self._auth_registry is None:
120
+ raise AuthenticationError(
121
+ f"Auth registry not available for manager key: {manager_key}"
122
+ )
123
+
124
+ if custom_file:
125
+ # Create manager with custom storage
126
+ return await self._create_manager_with_custom_file(
127
+ manager_key,
128
+ custom_file,
129
+ label,
130
+ )
131
+
132
+ # Standard registry lookup
133
+ self._logger.debug(
134
+ "creating_provider_manager_from_registry",
135
+ manager_key=manager_key,
136
+ label=label,
137
+ )
138
+
139
+ manager = await self._auth_registry.get(manager_key)
140
+ if manager is None:
141
+ raise AuthenticationError(
142
+ f"Auth manager not found in registry: {manager_key}"
143
+ )
144
+
145
+ self._logger.info(
146
+ "provider_manager_created_from_registry",
147
+ manager_key=manager_key,
148
+ label=label,
149
+ manager_type=type(manager).__name__,
150
+ )
151
+ return manager # type: ignore[no-any-return]
152
+
153
+ async def _create_manager_from_class_name(
154
+ self,
155
+ manager_class_name: str,
156
+ storage_class_name: str | None,
157
+ custom_file: str | None,
158
+ label: str,
159
+ config: dict[str, Any] | None = None,
160
+ ) -> AuthManager:
161
+ """Create manager by dynamically importing class.
162
+
163
+ Args:
164
+ manager_class_name: Fully qualified manager class name
165
+ storage_class_name: Fully qualified storage class name (required if custom_file specified)
166
+ custom_file: Optional custom file path
167
+ label: Label for logging
168
+ config: Additional configuration options for storage and manager
169
+
170
+ Returns:
171
+ AuthManager instance
172
+
173
+ Raises:
174
+ AuthenticationError: If class cannot be imported or instantiated
175
+ """
176
+ config = config or {}
177
+
178
+ self._logger.debug(
179
+ "creating_manager_from_class_name",
180
+ manager_class=manager_class_name,
181
+ storage_class=storage_class_name,
182
+ custom_file=custom_file,
183
+ label=label,
184
+ config_keys=list(config.keys()),
185
+ )
186
+
187
+ # Import manager class
188
+ try:
189
+ manager_class = self._import_class(manager_class_name)
190
+ except Exception as exc:
191
+ raise AuthenticationError(
192
+ f"Failed to import manager class '{manager_class_name}': {exc}"
193
+ ) from exc
194
+
195
+ # Create storage if custom file specified
196
+ storage = None
197
+ if custom_file:
198
+ if not storage_class_name:
199
+ raise AuthenticationError(
200
+ "storage_class is required when using custom file with manager_class"
201
+ )
202
+
203
+ try:
204
+ storage_class = self._import_class(storage_class_name)
205
+ # custom_file is already expanded and resolved by config validator
206
+ custom_path = Path(custom_file)
207
+
208
+ # Extract storage-specific config options
209
+ storage_kwargs: dict[str, Any] = {"storage_path": custom_path}
210
+ if "enable_backups" in config:
211
+ storage_kwargs["enable_backups"] = bool(config["enable_backups"])
212
+
213
+ storage = storage_class(**storage_kwargs)
214
+ except Exception as exc:
215
+ raise AuthenticationError(
216
+ f"Failed to create storage from '{storage_class_name}': {exc}"
217
+ ) from exc
218
+
219
+ # Create manager instance with config options
220
+ try:
221
+ # Check if we have advanced config options that need direct __init__ call
222
+ has_advanced_config = (
223
+ "credentials_ttl" in config or "refresh_grace_seconds" in config
224
+ )
225
+
226
+ if has_advanced_config:
227
+ # Use direct __init__ to pass ttl/grace parameters
228
+ # These are supported by BaseTokenManager but not exposed in create() methods
229
+ init_kwargs: dict[str, Any] = {"storage": storage}
230
+ if "credentials_ttl" in config:
231
+ init_kwargs["credentials_ttl"] = float(config["credentials_ttl"])
232
+ if "refresh_grace_seconds" in config:
233
+ init_kwargs["refresh_grace_seconds"] = float(
234
+ config["refresh_grace_seconds"]
235
+ )
236
+
237
+ manager = manager_class(**init_kwargs)
238
+ elif hasattr(manager_class, "create"):
239
+ # Use async create() method for standard instantiation
240
+ manager = (
241
+ await manager_class.create(storage=storage)
242
+ if storage
243
+ else await manager_class.create()
244
+ )
245
+ else:
246
+ raise AuthenticationError(
247
+ f"Manager class {manager_class.__name__} does not have 'create' method"
248
+ )
249
+ except FileNotFoundError as exc:
250
+ # Clean warning for missing credential files
251
+ file_path = custom_file or "default location"
252
+ self._logger.warning(
253
+ "credential_file_not_found",
254
+ label=label,
255
+ file_path=file_path,
256
+ manager_class=manager_class_name,
257
+ )
258
+ raise AuthenticationError(f"Credential file not found: {file_path}")
259
+ except Exception as exc:
260
+ # Log the full error for debugging but raise a clean message
261
+ self._logger.error(
262
+ "manager_creation_failed",
263
+ label=label,
264
+ manager_class=manager_class_name,
265
+ error=str(exc),
266
+ error_type=type(exc).__name__,
267
+ )
268
+ raise AuthenticationError(
269
+ f"Failed to create manager from class '{manager_class_name}': {exc}"
270
+ )
271
+
272
+ self._logger.info(
273
+ "provider_manager_created_from_class",
274
+ manager_class=manager_class_name,
275
+ storage_class=storage_class_name,
276
+ custom_file=custom_file,
277
+ label=label,
278
+ manager_type=type(manager).__name__,
279
+ )
280
+
281
+ return manager # type: ignore[no-any-return]
282
+
283
+ def _import_class(self, class_path: str) -> type:
284
+ """Dynamically import a class from a fully qualified path.
285
+
286
+ Args:
287
+ class_path: Fully qualified class path (e.g., 'module.submodule.ClassName')
288
+
289
+ Returns:
290
+ Imported class
291
+
292
+ Raises:
293
+ ValueError: If class path is invalid
294
+ ImportError: If module cannot be imported
295
+ AttributeError: If class not found in module
296
+ """
297
+ if "." not in class_path:
298
+ raise ValueError(
299
+ f"Invalid class path (must be fully qualified): {class_path}"
300
+ )
301
+
302
+ module_path, class_name = class_path.rsplit(".", 1)
303
+
304
+ try:
305
+ module = importlib.import_module(module_path)
306
+ cls = getattr(module, class_name)
307
+
308
+ if not isinstance(cls, type):
309
+ raise ValueError(f"'{class_path}' is not a class")
310
+
311
+ return cls
312
+ except ImportError as exc:
313
+ raise ImportError(f"Cannot import module '{module_path}': {exc}") from exc
314
+ except AttributeError as exc:
315
+ raise AttributeError(
316
+ f"Module '{module_path}' has no class '{class_name}'"
317
+ ) from exc
318
+
319
+ async def _create_manager_with_custom_file(
320
+ self,
321
+ manager_key: str,
322
+ file_path: str,
323
+ label: str,
324
+ ) -> AuthManager:
325
+ """Create auth manager with custom file storage.
326
+
327
+ Args:
328
+ manager_key: Manager registry key
329
+ file_path: Custom file path for storage
330
+ label: Label for logging
331
+
332
+ Returns:
333
+ AuthManager instance with custom storage
334
+
335
+ Raises:
336
+ AuthenticationError: If manager class not found or creation fails
337
+ """
338
+ if self._auth_registry is None:
339
+ raise AuthenticationError("Auth registry not available")
340
+
341
+ # Get manager class from registry
342
+ manager_class = self._auth_registry.get_class(manager_key)
343
+ if manager_class is None:
344
+ raise AuthenticationError(
345
+ f"Manager class not found for key: {manager_key}. "
346
+ "Only managers registered via register_class support custom file paths."
347
+ )
348
+
349
+ self._logger.debug(
350
+ "creating_manager_with_custom_storage",
351
+ manager_key=manager_key,
352
+ file_path=file_path,
353
+ label=label,
354
+ manager_class=manager_class.__name__,
355
+ )
356
+
357
+ # Create custom storage based on manager type
358
+ # file_path is already expanded and resolved by config validator
359
+ custom_path = Path(file_path)
360
+ storage = await self._create_storage_for_manager(
361
+ manager_key, manager_class, custom_path
362
+ )
363
+
364
+ # Create manager with custom storage
365
+ if hasattr(manager_class, "create"):
366
+ manager = await manager_class.create(storage=storage)
367
+ else:
368
+ raise AuthenticationError(
369
+ f"Manager class {manager_class.__name__} does not support async creation"
370
+ )
371
+
372
+ self._logger.info(
373
+ "provider_manager_created_with_custom_storage",
374
+ manager_key=manager_key,
375
+ file_path=str(custom_path),
376
+ label=label,
377
+ manager_type=type(manager).__name__,
378
+ )
379
+
380
+ return manager # type: ignore[no-any-return]
381
+
382
+ async def _create_storage_for_manager(
383
+ self,
384
+ manager_key: str,
385
+ manager_class: type,
386
+ storage_path: Path,
387
+ ) -> Any:
388
+ """Create appropriate storage instance for the manager type.
389
+
390
+ Args:
391
+ manager_key: Manager registry key
392
+ manager_class: Manager class
393
+ storage_path: Path to storage file
394
+
395
+ Returns:
396
+ Storage instance
397
+
398
+ Raises:
399
+ AuthenticationError: If storage type cannot be determined
400
+ """
401
+ # Map manager keys to their storage classes
402
+ # This could be made more dynamic by having managers expose their storage class
403
+ if manager_key == "codex":
404
+ from ccproxy.plugins.oauth_codex.storage import CodexTokenStorage
405
+
406
+ return CodexTokenStorage(storage_path=storage_path)
407
+ else:
408
+ raise AuthenticationError(
409
+ f"Custom file storage not yet supported for manager: {manager_key}. "
410
+ f"Supported managers: codex. "
411
+ f"Either use type='file' or add storage mapping for {manager_key}."
412
+ )
413
+
414
+
415
+ __all__ = ["AuthManagerFactory"]
@@ -0,0 +1,51 @@
1
+ """Hook implementation that monitors provider responses for credential failures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+
7
+ from ccproxy.core.plugins.hooks import Hook
8
+ from ccproxy.core.plugins.hooks.base import HookContext
9
+ from ccproxy.core.plugins.hooks.events import HookEvent
10
+
11
+ from .manager import CredentialBalancerTokenManager
12
+
13
+
14
+ class CredentialBalancerHook(Hook):
15
+ """Hook that routes HTTP lifecycle events to the balancer managers."""
16
+
17
+ name = "credential_balancer"
18
+ events = [HookEvent.HTTP_RESPONSE, HookEvent.HTTP_ERROR]
19
+ priority = 550
20
+
21
+ def __init__(self, managers: Iterable[CredentialBalancerTokenManager]):
22
+ self._managers: list[CredentialBalancerTokenManager] = list(managers)
23
+
24
+ def add_manager(self, manager: CredentialBalancerTokenManager) -> None:
25
+ if manager not in self._managers:
26
+ self._managers.append(manager)
27
+
28
+ def remove_manager(self, manager: CredentialBalancerTokenManager) -> None:
29
+ if manager in self._managers:
30
+ self._managers.remove(manager)
31
+
32
+ async def __call__(self, context: HookContext) -> None:
33
+ if not self._managers:
34
+ return
35
+
36
+ request_id = context.data.get("request_id")
37
+ is_provider = bool(
38
+ context.data.get("is_provider_response")
39
+ or context.data.get("is_provider_request")
40
+ )
41
+ if not request_id or not is_provider:
42
+ return
43
+
44
+ status_code = context.data.get("status_code")
45
+ for manager in list(self._managers):
46
+ handled = await manager.handle_response_event(request_id, status_code)
47
+ if handled:
48
+ break
49
+
50
+
51
+ __all__ = ["CredentialBalancerHook"]