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,481 +0,0 @@
1
- """OAuth client implementation for Anthropic OAuth flow."""
2
-
3
- import asyncio
4
- import base64
5
- import hashlib
6
- import secrets
7
- import urllib.parse
8
- import webbrowser
9
- from datetime import UTC, datetime
10
- from http.server import BaseHTTPRequestHandler, HTTPServer
11
- from threading import Thread
12
- from typing import Any
13
- from urllib.parse import parse_qs, urlparse
14
-
15
- import httpx
16
- from structlog import get_logger
17
-
18
- from ccproxy.auth.exceptions import OAuthCallbackError, OAuthLoginError
19
- from ccproxy.auth.models import ClaudeCredentials, OAuthToken, UserProfile
20
- from ccproxy.auth.oauth.models import OAuthTokenRequest, OAuthTokenResponse
21
- from ccproxy.config.auth import OAuthSettings
22
- from ccproxy.services.credentials.config import OAuthConfig
23
-
24
-
25
- logger = get_logger(__name__)
26
-
27
-
28
- def _log_http_error_compact(operation: str, response: httpx.Response) -> None:
29
- """Log HTTP error response in compact format.
30
-
31
- Args:
32
- operation: Description of the operation that failed
33
- response: HTTP response object
34
- """
35
- import os
36
-
37
- # Check if verbose API logging is enabled
38
- verbose_api = os.environ.get("CCPROXY_VERBOSE_API", "false").lower() == "true"
39
-
40
- if verbose_api:
41
- # Full verbose logging
42
- logger.error(
43
- "http_operation_failed",
44
- operation=operation,
45
- status_code=response.status_code,
46
- response_text=response.text,
47
- )
48
- else:
49
- # Compact logging - truncate response body
50
- response_text = response.text
51
- if len(response_text) > 200:
52
- response_preview = f"{response_text[:100]}...{response_text[-50:]}"
53
- elif len(response_text) > 100:
54
- response_preview = f"{response_text[:100]}..."
55
- else:
56
- response_preview = response_text
57
-
58
- logger.error(
59
- "http_operation_failed_compact",
60
- operation=operation,
61
- status_code=response.status_code,
62
- response_preview=response_preview,
63
- verbose_hint="use CCPROXY_VERBOSE_API=true for full response",
64
- )
65
-
66
-
67
- class OAuthClient:
68
- """OAuth client for handling Anthropic OAuth flows."""
69
-
70
- def __init__(self, config: OAuthSettings | None = None):
71
- """Initialize OAuth client.
72
-
73
- Args:
74
- config: OAuth configuration, uses default if not provided
75
- """
76
- self.config = config or OAuthConfig()
77
-
78
- def generate_pkce_pair(self) -> tuple[str, str]:
79
- """Generate PKCE code verifier and challenge pair.
80
-
81
- Returns:
82
- Tuple of (code_verifier, code_challenge)
83
- """
84
- # Generate code verifier (43-128 characters, URL-safe)
85
- code_verifier = secrets.token_urlsafe(96) # 128 base64url chars
86
-
87
- # For now, use plain method (Anthropic supports this)
88
- # In production, should use SHA256 method
89
- code_challenge = code_verifier
90
-
91
- return code_verifier, code_challenge
92
-
93
- def build_authorization_url(self, state: str, code_challenge: str) -> str:
94
- """Build authorization URL for OAuth flow.
95
-
96
- Args:
97
- state: State parameter for CSRF protection
98
- code_challenge: PKCE code challenge
99
-
100
- Returns:
101
- Authorization URL
102
- """
103
- params = {
104
- "response_type": "code",
105
- "client_id": self.config.client_id,
106
- "redirect_uri": self.config.redirect_uri,
107
- "scope": " ".join(self.config.scopes),
108
- "state": state,
109
- "code_challenge": code_challenge,
110
- "code_challenge_method": "plain", # Using plain for simplicity
111
- }
112
-
113
- query_string = urllib.parse.urlencode(params)
114
- return f"{self.config.authorize_url}?{query_string}"
115
-
116
- async def exchange_code_for_tokens(
117
- self,
118
- authorization_code: str,
119
- code_verifier: str,
120
- ) -> OAuthTokenResponse:
121
- """Exchange authorization code for access tokens.
122
-
123
- Args:
124
- authorization_code: Authorization code from callback
125
- code_verifier: PKCE code verifier
126
-
127
- Returns:
128
- Token response
129
-
130
- Raises:
131
- httpx.HTTPError: If token exchange fails
132
- """
133
- token_request = OAuthTokenRequest(
134
- code=authorization_code,
135
- redirect_uri=self.config.redirect_uri,
136
- client_id=self.config.client_id,
137
- code_verifier=code_verifier,
138
- )
139
-
140
- headers = {
141
- "Content-Type": "application/json",
142
- "anthropic-beta": self.config.beta_version,
143
- "User-Agent": self.config.user_agent,
144
- }
145
-
146
- async with httpx.AsyncClient() as client:
147
- response = await client.post(
148
- self.config.token_url,
149
- headers=headers,
150
- json=token_request.model_dump(),
151
- timeout=self.config.request_timeout,
152
- )
153
-
154
- if response.status_code != 200:
155
- _log_http_error_compact("Token exchange", response)
156
- response.raise_for_status()
157
-
158
- data = response.json()
159
- return OAuthTokenResponse.model_validate(data)
160
-
161
- async def refresh_access_token(self, refresh_token: str) -> OAuthTokenResponse:
162
- """Refresh access token using refresh token.
163
-
164
- Args:
165
- refresh_token: Refresh token
166
-
167
- Returns:
168
- New token response
169
-
170
- Raises:
171
- httpx.HTTPError: If token refresh fails
172
- """
173
- refresh_request = {
174
- "grant_type": "refresh_token",
175
- "refresh_token": refresh_token,
176
- "client_id": self.config.client_id,
177
- }
178
-
179
- headers = {
180
- "Content-Type": "application/json",
181
- "anthropic-beta": self.config.beta_version,
182
- "User-Agent": self.config.user_agent,
183
- }
184
-
185
- async with httpx.AsyncClient() as client:
186
- response = await client.post(
187
- self.config.token_url,
188
- headers=headers,
189
- json=refresh_request,
190
- timeout=self.config.request_timeout,
191
- )
192
-
193
- if response.status_code != 200:
194
- _log_http_error_compact("Token refresh", response)
195
- response.raise_for_status()
196
-
197
- data = response.json()
198
- return OAuthTokenResponse.model_validate(data)
199
-
200
- async def refresh_token(self, refresh_token: str) -> "OAuthToken":
201
- """Refresh token using refresh token - compatibility method for tests.
202
-
203
- Args:
204
- refresh_token: Refresh token
205
-
206
- Returns:
207
- New OAuth token
208
-
209
- Raises:
210
- OAuthTokenRefreshError: If token refresh fails
211
- """
212
- from datetime import UTC, datetime
213
-
214
- from ccproxy.auth.exceptions import OAuthTokenRefreshError
215
- from ccproxy.auth.models import OAuthToken
216
-
217
- try:
218
- token_response = await self.refresh_access_token(refresh_token)
219
-
220
- expires_in = (
221
- token_response.expires_in if token_response.expires_in else 3600
222
- )
223
-
224
- # Convert to OAuthToken format expected by tests
225
- expires_at_ms = int((datetime.now(UTC).timestamp() + expires_in) * 1000)
226
-
227
- return OAuthToken(
228
- accessToken=token_response.access_token,
229
- refreshToken=token_response.refresh_token or refresh_token,
230
- expiresAt=expires_at_ms,
231
- scopes=token_response.scope.split() if token_response.scope else [],
232
- subscriptionType="pro", # Default value
233
- )
234
- except Exception as e:
235
- raise OAuthTokenRefreshError(f"Token refresh failed: {e}") from e
236
-
237
- async def fetch_user_profile(self, access_token: str) -> UserProfile | None:
238
- """Fetch user profile information using access token.
239
-
240
- Args:
241
- access_token: Valid OAuth access token
242
-
243
- Returns:
244
- User profile information
245
-
246
- Raises:
247
- httpx.HTTPError: If profile fetch fails
248
- """
249
- from ccproxy.auth.models import UserProfile
250
-
251
- headers = {
252
- "Authorization": f"Bearer {access_token}",
253
- "anthropic-beta": self.config.beta_version,
254
- "User-Agent": self.config.user_agent,
255
- "Content-Type": "application/json",
256
- }
257
-
258
- # Use the profile url
259
- async with httpx.AsyncClient() as client:
260
- response = await client.get(
261
- self.config.profile_url,
262
- headers=headers,
263
- timeout=self.config.request_timeout,
264
- )
265
-
266
- if response.status_code == 404:
267
- # Userinfo endpoint not available - this is expected for some OAuth providers
268
- logger.debug(
269
- "userinfo_endpoint_unavailable", endpoint=self.config.profile_url
270
- )
271
- return None
272
- elif response.status_code != 200:
273
- _log_http_error_compact("Profile fetch", response)
274
- response.raise_for_status()
275
-
276
- data = response.json()
277
- logger.debug("user_profile_fetched", endpoint=self.config.profile_url)
278
- return UserProfile.model_validate(data)
279
-
280
- async def login(self) -> ClaudeCredentials:
281
- """Perform OAuth login flow.
282
-
283
- Returns:
284
- ClaudeCredentials with OAuth token
285
-
286
- Raises:
287
- OAuthLoginError: If login fails
288
- OAuthCallbackError: If callback processing fails
289
- """
290
- # Generate state parameter for security
291
- state = secrets.token_urlsafe(32)
292
-
293
- # Generate PKCE parameters
294
- code_verifier = secrets.token_urlsafe(32)
295
- code_challenge = (
296
- base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
297
- .decode()
298
- .rstrip("=")
299
- )
300
-
301
- authorization_code = None
302
- error = None
303
-
304
- class OAuthCallbackHandler(BaseHTTPRequestHandler):
305
- def do_GET(self) -> None: # noqa: N802
306
- nonlocal authorization_code, error
307
-
308
- # Ignore favicon requests
309
- if self.path == "/favicon.ico":
310
- self.send_response(404)
311
- self.end_headers()
312
- return
313
-
314
- parsed_url = urlparse(self.path)
315
- query_params = parse_qs(parsed_url.query)
316
-
317
- # Check state parameter
318
- received_state = query_params.get("state", [None])[0]
319
-
320
- if received_state != state:
321
- error = "Invalid state parameter"
322
- self.send_response(400)
323
- self.end_headers()
324
- self.wfile.write(b"Error: Invalid state parameter")
325
- return
326
-
327
- # Check for authorization code
328
- if "code" in query_params:
329
- authorization_code = query_params["code"][0]
330
- self.send_response(200)
331
- self.end_headers()
332
- self.wfile.write(b"Login successful! You can close this window.")
333
- elif "error" in query_params:
334
- error = query_params.get("error_description", ["Unknown error"])[0]
335
- self.send_response(400)
336
- self.end_headers()
337
- self.wfile.write(f"Error: {error}".encode())
338
- else:
339
- error = "No authorization code received"
340
- self.send_response(400)
341
- self.end_headers()
342
- self.wfile.write(b"Error: No authorization code received")
343
-
344
- def log_message(self, format: str, *args: Any) -> None:
345
- # Suppress HTTP server logs
346
- pass
347
-
348
- # Start local HTTP server for OAuth callback
349
- server = HTTPServer(
350
- ("localhost", self.config.callback_port), OAuthCallbackHandler
351
- )
352
- server_thread = Thread(target=server.serve_forever)
353
- server_thread.daemon = True
354
- server_thread.start()
355
-
356
- try:
357
- # Build authorization URL
358
- auth_params = {
359
- "response_type": "code",
360
- "client_id": self.config.client_id,
361
- "redirect_uri": self.config.redirect_uri,
362
- "scope": " ".join(self.config.scopes),
363
- "state": state,
364
- "code_challenge": code_challenge,
365
- "code_challenge_method": "S256",
366
- }
367
-
368
- auth_url = (
369
- f"{self.config.authorize_url}?{urllib.parse.urlencode(auth_params)}"
370
- )
371
-
372
- logger.info("oauth_browser_opening", auth_url=auth_url)
373
- logger.info(
374
- "oauth_manual_url",
375
- message="If browser doesn't open, visit this URL",
376
- auth_url=auth_url,
377
- )
378
-
379
- # Open browser
380
- webbrowser.open(auth_url)
381
-
382
- # Wait for callback (with timeout)
383
- import time
384
-
385
- start_time = time.time()
386
-
387
- while authorization_code is None and error is None:
388
- if time.time() - start_time > self.config.callback_timeout:
389
- error = "Login timeout"
390
- break
391
- await asyncio.sleep(0.1)
392
-
393
- if error:
394
- raise OAuthCallbackError(f"OAuth callback failed: {error}")
395
-
396
- if not authorization_code:
397
- raise OAuthLoginError("No authorization code received")
398
-
399
- # Exchange authorization code for tokens
400
- token_data = {
401
- "grant_type": "authorization_code",
402
- "code": authorization_code,
403
- "redirect_uri": self.config.redirect_uri,
404
- "client_id": self.config.client_id,
405
- "code_verifier": code_verifier,
406
- "state": state,
407
- }
408
-
409
- headers = {
410
- "Content-Type": "application/json",
411
- "anthropic-beta": self.config.beta_version,
412
- "User-Agent": self.config.user_agent,
413
- }
414
-
415
- async with httpx.AsyncClient() as client:
416
- response = await client.post(
417
- self.config.token_url,
418
- headers=headers,
419
- json=token_data,
420
- timeout=30.0,
421
- )
422
-
423
- if response.status_code == 200:
424
- result = response.json()
425
-
426
- # Calculate expires_at from expires_in
427
- expires_in = result.get("expires_in")
428
- expires_at = None
429
- if expires_in:
430
- expires_at = int(
431
- (datetime.now(UTC).timestamp() + expires_in) * 1000
432
- )
433
-
434
- # Create credentials object
435
- oauth_data = {
436
- "accessToken": result.get("access_token"),
437
- "refreshToken": result.get("refresh_token"),
438
- "expiresAt": expires_at,
439
- "scopes": result.get("scope", "").split()
440
- if result.get("scope")
441
- else self.config.scopes,
442
- "subscriptionType": result.get("subscription_type", "unknown"),
443
- }
444
-
445
- credentials = ClaudeCredentials(claudeAiOauth=OAuthToken(**oauth_data))
446
-
447
- logger.info("oauth_login_completed", client_id=self.config.client_id)
448
- return credentials
449
-
450
- else:
451
- # Use compact logging for the error message
452
- import os
453
-
454
- verbose_api = (
455
- os.environ.get("CCPROXY_VERBOSE_API", "false").lower() == "true"
456
- )
457
-
458
- if verbose_api:
459
- error_detail = response.text
460
- else:
461
- response_text = response.text
462
- if len(response_text) > 200:
463
- error_detail = f"{response_text[:100]}...{response_text[-50:]}"
464
- elif len(response_text) > 100:
465
- error_detail = f"{response_text[:100]}..."
466
- else:
467
- error_detail = response_text
468
-
469
- raise OAuthLoginError(
470
- f"Token exchange failed: {response.status_code} - {error_detail}"
471
- )
472
-
473
- except Exception as e:
474
- if isinstance(e, OAuthLoginError | OAuthCallbackError):
475
- raise
476
- raise OAuthLoginError(f"OAuth login failed: {e}") from e
477
-
478
- finally:
479
- # Stop the HTTP server
480
- server.shutdown()
481
- server_thread.join(timeout=1)