ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0a4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0a4.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0a4.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1251
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,266 @@
1
+ """Claude-specific authentication models."""
2
+
3
+ import json
4
+ from datetime import UTC, datetime
5
+ from pathlib import Path
6
+ from typing import Any, Literal
7
+
8
+ from pydantic import (
9
+ BaseModel,
10
+ ConfigDict,
11
+ Field,
12
+ SecretStr,
13
+ computed_field,
14
+ field_serializer,
15
+ field_validator,
16
+ )
17
+
18
+ from ccproxy.auth.models.base import BaseProfileInfo, BaseTokenInfo
19
+
20
+
21
+ class ClaudeOAuthToken(BaseModel):
22
+ """OAuth token information from Claude credentials."""
23
+
24
+ model_config = ConfigDict(
25
+ populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True
26
+ )
27
+
28
+ access_token: SecretStr = Field(..., alias="accessToken")
29
+ refresh_token: SecretStr = Field(..., alias="refreshToken")
30
+ expires_at: int | None = Field(None, alias="expiresAt")
31
+ scopes: list[str] = Field(default_factory=list)
32
+ subscription_type: str | None = Field(None, alias="subscriptionType")
33
+
34
+ @field_serializer("access_token", "refresh_token")
35
+ def serialize_secret(self, value: SecretStr) -> str:
36
+ """Serialize SecretStr to plain string for JSON output."""
37
+ return value.get_secret_value() if value else ""
38
+
39
+ @field_validator("access_token", "refresh_token", mode="before")
40
+ @classmethod
41
+ def validate_tokens(cls, v: str | SecretStr | None) -> SecretStr | None:
42
+ """Convert string values to SecretStr."""
43
+ if v is None:
44
+ return None
45
+ if isinstance(v, str):
46
+ return SecretStr(v)
47
+ return v
48
+
49
+ def __repr__(self) -> str:
50
+ """Safe string representation that masks sensitive tokens."""
51
+ access_token_str = self.access_token.get_secret_value()
52
+ refresh_token_str = self.refresh_token.get_secret_value()
53
+
54
+ access_preview = (
55
+ f"{access_token_str[:8]}...{access_token_str[-8:]}"
56
+ if len(access_token_str) > 16
57
+ else "***"
58
+ )
59
+ refresh_preview = (
60
+ f"{refresh_token_str[:8]}...{refresh_token_str[-8:]}"
61
+ if len(refresh_token_str) > 16
62
+ else "***"
63
+ )
64
+
65
+ expires_at = (
66
+ datetime.fromtimestamp(self.expires_at / 1000, tz=UTC).isoformat()
67
+ if self.expires_at is not None
68
+ else "None"
69
+ )
70
+ return (
71
+ f"OAuthToken(access_token='{access_preview}', "
72
+ f"refresh_token='{refresh_preview}', "
73
+ f"expires_at={expires_at}, "
74
+ f"scopes={self.scopes}, "
75
+ f"subscription_type='{self.subscription_type}')"
76
+ )
77
+
78
+ @property
79
+ def is_expired(self) -> bool:
80
+ """Check if the token is expired."""
81
+ if self.expires_at is None:
82
+ return False
83
+ now = datetime.now(UTC).timestamp() * 1000 # Convert to milliseconds
84
+ return now >= self.expires_at
85
+
86
+ @property
87
+ def expires_at_datetime(self) -> datetime:
88
+ """Get expiration as datetime object."""
89
+ if self.expires_at is None:
90
+ # Return a far future date if no expiration info
91
+ return datetime.fromtimestamp(2147483647, tz=UTC) # Year 2038
92
+ return datetime.fromtimestamp(self.expires_at / 1000, tz=UTC)
93
+
94
+
95
+ class ClaudeCredentials(BaseModel):
96
+ """Claude credentials from the credentials file."""
97
+
98
+ model_config = ConfigDict(
99
+ populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True
100
+ )
101
+
102
+ claude_ai_oauth: ClaudeOAuthToken = Field(..., alias="claudeAiOauth")
103
+
104
+ def __repr__(self) -> str:
105
+ """Safe string representation that masks sensitive tokens."""
106
+ return f"ClaudeCredentials(claude_ai_oauth={repr(self.claude_ai_oauth)})"
107
+
108
+ def is_expired(self) -> bool:
109
+ """Check if the credentials are expired.
110
+
111
+ Returns:
112
+ True if expired, False otherwise
113
+ """
114
+ return self.claude_ai_oauth.is_expired
115
+
116
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
117
+ """Override model_dump to use by_alias=True by default."""
118
+ kwargs.setdefault("by_alias", True)
119
+ return super().model_dump(**kwargs)
120
+
121
+ def to_dict(self) -> dict[str, Any]:
122
+ """Convert to dictionary for storage.
123
+
124
+ Returns:
125
+ Dictionary representation
126
+ """
127
+ return self.model_dump(mode="json", exclude_none=True)
128
+
129
+ @classmethod
130
+ def from_dict(cls, data: dict[str, Any]) -> "ClaudeCredentials":
131
+ """Create from dictionary.
132
+
133
+ Args:
134
+ data: Dictionary containing credential data
135
+
136
+ Returns:
137
+ ClaudeCredentials instance
138
+ """
139
+ return cls.model_validate(data)
140
+
141
+
142
+ class ClaudeTokenWrapper(BaseTokenInfo):
143
+ """Wrapper for Claude credentials that adds computed properties.
144
+
145
+ This wrapper maintains the original ClaudeCredentials structure
146
+ while providing a unified interface through BaseTokenInfo.
147
+ """
148
+
149
+ # Embed the original credentials to preserve JSON schema
150
+ credentials: ClaudeCredentials
151
+
152
+ @computed_field
153
+ def access_token_value(self) -> str:
154
+ """Extract access token from Claude OAuth structure."""
155
+ return self.credentials.claude_ai_oauth.access_token.get_secret_value()
156
+
157
+ @property
158
+ def refresh_token_value(self) -> str | None:
159
+ """Extract refresh token from Claude OAuth structure."""
160
+ token = self.credentials.claude_ai_oauth.refresh_token
161
+ return token.get_secret_value() if token else None
162
+
163
+ @property
164
+ def expires_at_datetime(self) -> datetime:
165
+ """Convert Claude's millisecond timestamp to datetime."""
166
+ expires_at = self.credentials.claude_ai_oauth.expires_at
167
+ if not expires_at:
168
+ # No expiration means token doesn't expire
169
+ return datetime.max.replace(tzinfo=UTC)
170
+ # Claude stores expires_at in milliseconds
171
+ return datetime.fromtimestamp(expires_at / 1000, tz=UTC)
172
+
173
+ @property
174
+ def subscription_type(self) -> str | None:
175
+ """Compute subscription type from stored profile info.
176
+
177
+ Attempts to read the Claude profile file ("~/.claude/.account.json")
178
+ and derive the subscription from account flags:
179
+ - "max" if has_claude_max is true
180
+ - "pro" if has_claude_pro is true
181
+ - "free" otherwise
182
+
183
+ Falls back to the token's own subscription_type if profile is unavailable.
184
+ """
185
+ # Lazy, best-effort read of local profile data; keep this non-fatal.
186
+ try:
187
+ profile_path = Path.home() / ".claude" / ".account.json"
188
+ if profile_path.exists():
189
+ with profile_path.open("r") as f:
190
+ data = json.load(f)
191
+ account = data.get("account", {})
192
+ if account.get("has_claude_max") is True:
193
+ return "max"
194
+ if account.get("has_claude_pro") is True:
195
+ return "pro"
196
+ # If account is present but neither flag set, assume free tier
197
+ if account:
198
+ return "free"
199
+ except Exception:
200
+ # Ignore any profile read/parse errors and fall back
201
+ pass
202
+
203
+ return self.credentials.claude_ai_oauth.subscription_type
204
+
205
+ @property
206
+ def scopes(self) -> list[str]:
207
+ """Get OAuth scopes."""
208
+ return self.credentials.claude_ai_oauth.scopes
209
+
210
+
211
+ class ClaudeProfileInfo(BaseProfileInfo):
212
+ """Claude-specific profile information from API.
213
+
214
+ Created from the /api/organizations/me endpoint response.
215
+ """
216
+
217
+ provider_type: Literal["claude-api"] = "claude-api"
218
+
219
+ @classmethod
220
+ def from_api_response(cls, data: dict[str, Any]) -> "ClaudeProfileInfo":
221
+ """Create profile from Claude API response.
222
+
223
+ Args:
224
+ data: Response from /api/organizations/me endpoint
225
+
226
+ Returns:
227
+ ClaudeProfileInfo instance with all data preserved
228
+ """
229
+ # Extract account information if present
230
+ account = data.get("account", {})
231
+ organization = data.get("organization", {})
232
+
233
+ # Extract common fields for easy access
234
+ account_id = account.get("uuid", "")
235
+ email = account.get("email", "")
236
+ display_name = account.get("full_name")
237
+
238
+ # Store entire response in extras for complete information
239
+ # This includes: has_claude_pro, has_claude_max, organization details, etc.
240
+ return cls(
241
+ account_id=account_id,
242
+ email=email,
243
+ display_name=display_name,
244
+ extras=data, # Preserve complete API response
245
+ )
246
+
247
+ @property
248
+ def has_claude_pro(self) -> bool | None:
249
+ """Check if user has Claude Pro subscription."""
250
+ account = self.extras.get("account", {})
251
+ value = account.get("has_claude_pro")
252
+ return bool(value) if value is not None else None
253
+
254
+ @property
255
+ def has_claude_max(self) -> bool | None:
256
+ """Check if user has Claude Max subscription."""
257
+ account = self.extras.get("account", {})
258
+ value = account.get("has_claude_max")
259
+ return bool(value) if value is not None else None
260
+
261
+ @property
262
+ def organization_name(self) -> str | None:
263
+ """Get organization name if available."""
264
+ org = self.extras.get("organization", {})
265
+ name = org.get("name")
266
+ return str(name) if name is not None else None
@@ -0,0 +1,149 @@
1
+ """OAuth Claude plugin v2 implementation."""
2
+
3
+ from typing import Any, cast
4
+
5
+ from ccproxy.auth.oauth import OAuthProviderProtocol
6
+ from ccproxy.core.logging import get_plugin_logger
7
+ from ccproxy.core.plugins import (
8
+ AuthProviderPluginFactory,
9
+ AuthProviderPluginRuntime,
10
+ PluginContext,
11
+ PluginManifest,
12
+ )
13
+
14
+ from .config import ClaudeOAuthConfig
15
+ from .provider import ClaudeOAuthProvider
16
+
17
+
18
+ logger = get_plugin_logger()
19
+
20
+
21
+ class OAuthClaudeRuntime(AuthProviderPluginRuntime):
22
+ """Runtime for OAuth Claude plugin."""
23
+
24
+ def __init__(self, manifest: PluginManifest):
25
+ """Initialize runtime."""
26
+ super().__init__(manifest)
27
+ self.config: ClaudeOAuthConfig | None = None
28
+
29
+ async def _on_initialize(self) -> None:
30
+ """Initialize the OAuth Claude plugin."""
31
+ logger.debug(
32
+ "oauth_claude_initializing",
33
+ context_keys=list(self.context.keys()) if self.context else [],
34
+ )
35
+
36
+ # Get configuration
37
+ if self.context:
38
+ config = self.context.get("config")
39
+ if not isinstance(config, ClaudeOAuthConfig):
40
+ # Use default config if none provided
41
+ config = ClaudeOAuthConfig()
42
+ logger.debug("oauth_claude_using_default_config")
43
+ self.config = config
44
+
45
+ # Call parent initialization which handles provider registration
46
+ await super()._on_initialize()
47
+
48
+ logger.debug(
49
+ "oauth_claude_plugin_initialized",
50
+ status="initialized",
51
+ provider_name=self.auth_provider.provider_name
52
+ if self.auth_provider
53
+ else "unknown",
54
+ category="plugin",
55
+ )
56
+
57
+
58
+ class OAuthClaudeFactory(AuthProviderPluginFactory):
59
+ """Factory for OAuth Claude plugin."""
60
+
61
+ cli_safe = True # Safe for CLI - provides auth only
62
+
63
+ def __init__(self) -> None:
64
+ """Initialize factory with manifest."""
65
+ # Create manifest with static declarations
66
+ manifest = PluginManifest(
67
+ name="oauth_claude",
68
+ version="0.1.0",
69
+ description="Standalone Claude OAuth authentication provider plugin",
70
+ is_provider=True, # It's a provider plugin but focused on OAuth
71
+ config_class=ClaudeOAuthConfig,
72
+ dependencies=[],
73
+ routes=[], # No HTTP routes needed
74
+ tasks=[], # No scheduled tasks needed
75
+ )
76
+
77
+ # Initialize with manifest
78
+ super().__init__(manifest)
79
+
80
+ def create_context(self, core_services: Any) -> PluginContext:
81
+ """Create context with auth provider components.
82
+
83
+ Args:
84
+ core_services: Core services container
85
+
86
+ Returns:
87
+ Plugin context with auth provider components
88
+ """
89
+ # Start with base context
90
+ context = super().create_context(core_services)
91
+
92
+ # Create auth provider for this plugin
93
+ auth_provider = self.create_auth_provider(context)
94
+ context["auth_provider"] = auth_provider
95
+
96
+ # Add other auth-specific components if needed
97
+ storage = self.create_storage()
98
+ if storage:
99
+ context["storage"] = storage
100
+
101
+ return context
102
+
103
+ def create_runtime(self) -> OAuthClaudeRuntime:
104
+ """Create runtime instance."""
105
+ return OAuthClaudeRuntime(self.manifest)
106
+
107
+ def create_auth_provider(
108
+ self, context: PluginContext | None = None
109
+ ) -> OAuthProviderProtocol:
110
+ """Create OAuth provider instance.
111
+
112
+ Args:
113
+ context: Plugin context containing shared resources
114
+
115
+ Returns:
116
+ ClaudeOAuthProvider instance
117
+ """
118
+ # Prefer validated config from context when available
119
+ if context and isinstance(context.get("config"), ClaudeOAuthConfig):
120
+ cfg = cast(ClaudeOAuthConfig, context.get("config"))
121
+ else:
122
+ cfg = ClaudeOAuthConfig()
123
+ config: ClaudeOAuthConfig = cfg
124
+ http_client = context.get("http_client") if context else None
125
+ hook_manager = context.get("hook_manager") if context else None
126
+ # CLIDetectionService is injected under 'cli_detection_service' in base context
127
+ detection_service = context.get("cli_detection_service") if context else None
128
+ settings = context.get("settings") if context else None
129
+ provider = ClaudeOAuthProvider(
130
+ config,
131
+ http_client=http_client,
132
+ hook_manager=hook_manager,
133
+ detection_service=detection_service,
134
+ settings=settings,
135
+ )
136
+ return cast(OAuthProviderProtocol, provider)
137
+
138
+ def create_storage(self) -> Any | None:
139
+ """Create storage for OAuth credentials.
140
+
141
+ Returns:
142
+ Storage instance or None to use provider's default
143
+ """
144
+ # ClaudeOAuthProvider manages its own storage internally
145
+ return None
146
+
147
+
148
+ # Export the factory instance
149
+ factory = OAuthClaudeFactory()