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
ccproxy/config/claude.py DELETED
@@ -1,348 +0,0 @@
1
- """Claude-specific configuration settings."""
2
-
3
- import os
4
- import shutil
5
- from enum import Enum
6
- from pathlib import Path
7
- from typing import Any
8
-
9
- import structlog
10
- from pydantic import BaseModel, Field, field_validator, model_validator
11
-
12
- from ccproxy.core.async_utils import get_package_dir, patched_typing
13
-
14
-
15
- # For further information visit https://errors.pydantic.dev/2.11/u/typed-dict-version
16
- with patched_typing():
17
- from claude_code_sdk import ClaudeCodeOptions # noqa: E402
18
-
19
- logger = structlog.get_logger(__name__)
20
-
21
-
22
- def _create_default_claude_code_options(
23
- builtin_permissions: bool = True,
24
- continue_conversation: bool = False,
25
- ) -> ClaudeCodeOptions:
26
- """Create ClaudeCodeOptions with default values.
27
-
28
- Args:
29
- builtin_permissions: Whether to include built-in permission handling defaults
30
- """
31
- if builtin_permissions:
32
- return ClaudeCodeOptions(
33
- continue_conversation=continue_conversation,
34
- mcp_servers={
35
- "confirmation": {"type": "sse", "url": "http://127.0.0.1:8000/mcp"}
36
- },
37
- permission_prompt_tool_name="mcp__confirmation__check_permission",
38
- )
39
- else:
40
- return ClaudeCodeOptions(
41
- mcp_servers={},
42
- permission_prompt_tool_name=None,
43
- continue_conversation=continue_conversation,
44
- )
45
-
46
-
47
- class SDKMessageMode(str, Enum):
48
- """Modes for handling SDK messages from Claude SDK.
49
-
50
- - forward: Forward SDK content blocks directly with original types and metadata
51
- - ignore: Skip SDK messages and blocks completely
52
- - formatted: Format as XML tags with JSON data in text deltas
53
- """
54
-
55
- FORWARD = "forward"
56
- IGNORE = "ignore"
57
- FORMATTED = "formatted"
58
-
59
-
60
- class SystemPromptInjectionMode(str, Enum):
61
- """Modes for system prompt injection.
62
-
63
- - minimal: Only inject Claude Code identification prompt
64
- - full: Inject all detected system messages from Claude CLI
65
- """
66
-
67
- MINIMAL = "minimal"
68
- FULL = "full"
69
-
70
-
71
- class SessionPoolSettings(BaseModel):
72
- """Session pool configuration settings."""
73
-
74
- enabled: bool = Field(
75
- default=True, description="Enable session-aware persistent pooling"
76
- )
77
-
78
- session_ttl: int = Field(
79
- default=3600,
80
- ge=60,
81
- le=86400,
82
- description="Session time-to-live in seconds (1 minute to 24 hours)",
83
- )
84
-
85
- max_sessions: int = Field(
86
- default=1000,
87
- ge=1,
88
- le=10000,
89
- description="Maximum number of concurrent sessions",
90
- )
91
-
92
- cleanup_interval: int = Field(
93
- default=300,
94
- ge=30,
95
- le=3600,
96
- description="Session cleanup interval in seconds (30 seconds to 1 hour)",
97
- )
98
-
99
- idle_threshold: int = Field(
100
- default=600,
101
- ge=60,
102
- le=7200,
103
- description="Session idle threshold in seconds (1 minute to 2 hours)",
104
- )
105
-
106
- connection_recovery: bool = Field(
107
- default=True,
108
- description="Enable automatic connection recovery for unhealthy sessions",
109
- )
110
-
111
- stream_first_chunk_timeout: int = Field(
112
- default=3,
113
- ge=1,
114
- le=30,
115
- description="Stream first chunk timeout in seconds (1-30 seconds)",
116
- )
117
-
118
- stream_ongoing_timeout: int = Field(
119
- default=60,
120
- ge=10,
121
- le=600,
122
- description="Stream ongoing timeout in seconds after first chunk (10 seconds to 10 minutes)",
123
- )
124
-
125
- stream_interrupt_timeout: int = Field(
126
- default=10,
127
- ge=2,
128
- le=60,
129
- description="Stream interrupt timeout in seconds for SDK and worker operations (2-60 seconds)",
130
- )
131
-
132
- @model_validator(mode="after")
133
- def validate_timeout_hierarchy(self) -> "SessionPoolSettings":
134
- """Ensure stream timeouts are less than session TTL."""
135
- if self.stream_ongoing_timeout >= self.session_ttl:
136
- raise ValueError(
137
- f"stream_ongoing_timeout ({self.stream_ongoing_timeout}s) must be less than session_ttl ({self.session_ttl}s)"
138
- )
139
-
140
- if self.stream_first_chunk_timeout >= self.stream_ongoing_timeout:
141
- raise ValueError(
142
- f"stream_first_chunk_timeout ({self.stream_first_chunk_timeout}s) must be less than stream_ongoing_timeout ({self.stream_ongoing_timeout}s)"
143
- )
144
-
145
- return self
146
-
147
-
148
- class ClaudeSettings(BaseModel):
149
- """Claude-specific configuration settings."""
150
-
151
- cli_path: str | None = Field(
152
- default=None,
153
- description="Path to Claude CLI executable",
154
- )
155
-
156
- builtin_permissions: bool = Field(
157
- default=True,
158
- description="Whether to enable built-in permission handling infrastructure (MCP server and SSE endpoints). When disabled, users can still configure custom MCP servers and permission tools.",
159
- )
160
-
161
- code_options: ClaudeCodeOptions | None = Field(
162
- default=None,
163
- description="Claude Code SDK options configuration",
164
- )
165
-
166
- sdk_message_mode: SDKMessageMode = Field(
167
- default=SDKMessageMode.FORWARD,
168
- description="Mode for handling SDK messages from Claude SDK. Options: forward (direct SDK blocks), ignore (skip blocks), formatted (XML tags with JSON data)",
169
- )
170
-
171
- system_prompt_injection_mode: SystemPromptInjectionMode = Field(
172
- default=SystemPromptInjectionMode.MINIMAL,
173
- description="Mode for system prompt injection. Options: minimal (Claude Code ID only), full (all detected system messages)",
174
- )
175
-
176
- pretty_format: bool = Field(
177
- default=True,
178
- description="Whether to use pretty formatting (indented JSON, newlines after XML tags, unescaped content). When false: compact JSON, no newlines, escaped content between XML tags",
179
- )
180
-
181
- sdk_session_pool: SessionPoolSettings = Field(
182
- default_factory=SessionPoolSettings,
183
- description="Configuration settings for session-aware SDK client pooling",
184
- )
185
-
186
- @field_validator("cli_path")
187
- @classmethod
188
- def validate_claude_cli_path(cls, v: str | None) -> str | None:
189
- """Validate Claude CLI path if provided."""
190
- if v is not None:
191
- path = Path(v)
192
- if not path.exists():
193
- raise ValueError(f"Claude CLI path does not exist: {v}")
194
- if not path.is_file():
195
- raise ValueError(f"Claude CLI path is not a file: {v}")
196
- if not os.access(path, os.X_OK):
197
- raise ValueError(f"Claude CLI path is not executable: {v}")
198
- return v
199
-
200
- @field_validator("code_options", mode="before")
201
- @classmethod
202
- def validate_claude_code_options(cls, v: Any, info: Any) -> Any:
203
- """Validate and convert Claude Code options."""
204
- # Get builtin_permissions setting from the model data
205
- builtin_permissions = True # default
206
- if info.data and "builtin_permissions" in info.data:
207
- builtin_permissions = info.data["builtin_permissions"]
208
-
209
- if v is None:
210
- # Create instance with default values based on builtin_permissions
211
- return _create_default_claude_code_options(builtin_permissions)
212
-
213
- # If it's already a ClaudeCodeOptions instance, return as-is
214
- if isinstance(v, ClaudeCodeOptions):
215
- return v
216
-
217
- # If it's an empty dict, treat it like None and use defaults
218
- if isinstance(v, dict) and not v:
219
- return _create_default_claude_code_options(builtin_permissions)
220
-
221
- # For non-empty dicts, merge with defaults instead of replacing them
222
- if isinstance(v, dict):
223
- # Start with default values based on builtin_permissions
224
- defaults = _create_default_claude_code_options(builtin_permissions)
225
-
226
- # Extract default values as a dict for merging
227
- default_values = {
228
- "mcp_servers": dict(defaults.mcp_servers)
229
- if isinstance(defaults.mcp_servers, dict)
230
- else {},
231
- "permission_prompt_tool_name": defaults.permission_prompt_tool_name,
232
- }
233
-
234
- # Add other default attributes if they exist
235
- for attr in [
236
- "max_thinking_tokens",
237
- "allowed_tools",
238
- "disallowed_tools",
239
- "cwd",
240
- "append_system_prompt",
241
- "max_turns",
242
- "continue_conversation",
243
- "permission_mode",
244
- "model",
245
- "system_prompt",
246
- ]:
247
- if hasattr(defaults, attr):
248
- default_value = getattr(defaults, attr, None)
249
- if default_value is not None:
250
- default_values[attr] = default_value
251
-
252
- # Handle MCP server merging when builtin_permissions is enabled
253
- if builtin_permissions and "mcp_servers" in v:
254
- user_mcp_servers = v["mcp_servers"]
255
- if isinstance(user_mcp_servers, dict):
256
- # Merge user MCP servers with built-in ones (user takes precedence)
257
- default_mcp = default_values["mcp_servers"]
258
- if isinstance(default_mcp, dict):
259
- merged_mcp_servers = {
260
- **default_mcp,
261
- **user_mcp_servers,
262
- }
263
- v = {**v, "mcp_servers": merged_mcp_servers}
264
-
265
- # Merge CLI overrides with defaults (CLI overrides take precedence)
266
- merged_values = {**default_values, **v}
267
-
268
- return ClaudeCodeOptions(**merged_values)
269
-
270
- # Try to convert to ClaudeCodeOptions if possible
271
- if hasattr(v, "model_dump"):
272
- return ClaudeCodeOptions(**v.model_dump())
273
- elif hasattr(v, "__dict__"):
274
- return ClaudeCodeOptions(**v.__dict__)
275
-
276
- # Fallback: use default values
277
- return _create_default_claude_code_options(builtin_permissions)
278
-
279
- @model_validator(mode="after")
280
- def validate_code_options_after(self) -> "ClaudeSettings":
281
- """Ensure code_options is properly initialized after field validation."""
282
- if self.code_options is None:
283
- self.code_options = _create_default_claude_code_options(
284
- self.builtin_permissions
285
- )
286
- return self
287
-
288
- def find_claude_cli(self) -> tuple[str | None, bool]:
289
- """Find Claude CLI executable in PATH or specified location.
290
-
291
- Returns:
292
- tuple: (path_to_claude, found_in_path)
293
- """
294
- if self.cli_path:
295
- return self.cli_path, False
296
-
297
- # Try to find claude in PATH
298
- claude_path = shutil.which("claude")
299
- if claude_path:
300
- return claude_path, True
301
-
302
- # Common installation paths (in order of preference)
303
- common_paths = [
304
- # User-specific Claude installation
305
- Path.home() / ".claude" / "local" / "claude",
306
- # User's global node_modules (npm install -g)
307
- Path.home() / "node_modules" / ".bin" / "claude",
308
- # Package installation directory node_modules
309
- get_package_dir() / "node_modules" / ".bin" / "claude",
310
- # Current working directory node_modules
311
- Path.cwd() / "node_modules" / ".bin" / "claude",
312
- # System-wide installations
313
- Path("/usr/local/bin/claude"),
314
- Path("/opt/homebrew/bin/claude"),
315
- ]
316
-
317
- for path in common_paths:
318
- if path.exists() and path.is_file() and os.access(path, os.X_OK):
319
- return str(path), False
320
-
321
- return None, False
322
-
323
- def get_searched_paths(self) -> list[str]:
324
- """Get list of paths that would be searched for Claude CLI auto-detection."""
325
- paths = []
326
-
327
- # PATH search
328
- paths.append("PATH environment variable")
329
-
330
- # Common installation paths (in order of preference)
331
- common_paths = [
332
- # User-specific Claude installation
333
- Path.home() / ".claude" / "local" / "claude",
334
- # User's global node_modules (npm install -g)
335
- Path.home() / "node_modules" / ".bin" / "claude",
336
- # Package installation directory node_modules
337
- get_package_dir() / "node_modules" / ".bin" / "claude",
338
- # Current working directory node_modules
339
- Path.cwd() / "node_modules" / ".bin" / "claude",
340
- # System-wide installations
341
- Path("/usr/local/bin/claude"),
342
- Path("/opt/homebrew/bin/claude"),
343
- ]
344
-
345
- for path in common_paths:
346
- paths.append(str(path))
347
-
348
- return paths
ccproxy/config/cors.py DELETED
@@ -1,79 +0,0 @@
1
- """CORS configuration settings."""
2
-
3
- from pydantic import BaseModel, Field, field_validator
4
-
5
-
6
- class CORSSettings(BaseModel):
7
- """CORS-specific configuration settings."""
8
-
9
- origins: list[str] = Field(
10
- default_factory=lambda: ["*"],
11
- description="CORS allowed origins",
12
- )
13
-
14
- credentials: bool = Field(
15
- default=True,
16
- description="CORS allow credentials",
17
- )
18
-
19
- methods: list[str] = Field(
20
- default_factory=lambda: ["*"],
21
- description="CORS allowed methods",
22
- )
23
-
24
- headers: list[str] = Field(
25
- default_factory=lambda: ["*"],
26
- description="CORS allowed headers",
27
- )
28
-
29
- origin_regex: str | None = Field(
30
- default=None,
31
- description="CORS origin regex pattern",
32
- )
33
-
34
- expose_headers: list[str] = Field(
35
- default_factory=list,
36
- description="CORS exposed headers",
37
- )
38
-
39
- max_age: int = Field(
40
- default=600,
41
- description="CORS preflight max age in seconds",
42
- ge=0,
43
- )
44
-
45
- @field_validator("origins", mode="before")
46
- @classmethod
47
- def validate_cors_origins(cls, v: str | list[str]) -> list[str]:
48
- """Parse CORS origins from string or list."""
49
- if isinstance(v, str):
50
- # Split comma-separated string
51
- return [origin.strip() for origin in v.split(",") if origin.strip()]
52
- return v
53
-
54
- @field_validator("methods", mode="before")
55
- @classmethod
56
- def validate_cors_methods(cls, v: str | list[str]) -> list[str]:
57
- """Parse CORS methods from string or list."""
58
- if isinstance(v, str):
59
- # Split comma-separated string
60
- return [method.strip().upper() for method in v.split(",") if method.strip()]
61
- return [method.upper() for method in v]
62
-
63
- @field_validator("headers", mode="before")
64
- @classmethod
65
- def validate_cors_headers(cls, v: str | list[str]) -> list[str]:
66
- """Parse CORS headers from string or list."""
67
- if isinstance(v, str):
68
- # Split comma-separated string
69
- return [header.strip() for header in v.split(",") if header.strip()]
70
- return v
71
-
72
- @field_validator("expose_headers", mode="before")
73
- @classmethod
74
- def validate_cors_expose_headers(cls, v: str | list[str]) -> list[str]:
75
- """Parse CORS expose headers from string or list."""
76
- if isinstance(v, str):
77
- # Split comma-separated string
78
- return [header.strip() for header in v.split(",") if header.strip()]
79
- return v
@@ -1,95 +0,0 @@
1
- from pathlib import Path
2
-
3
- from ccproxy.core.system import get_xdg_cache_home, get_xdg_config_home
4
-
5
-
6
- def find_toml_config_file() -> Path | None:
7
- """Find the TOML configuration file for ccproxy.
8
-
9
- Searches in the following order:
10
- 1. .ccproxy.toml in current directory
11
- 2. ccproxy.toml in git repository root (if in a git repo)
12
- 3. config.toml in XDG_CONFIG_HOME/ccproxy/
13
- """
14
- # Check current directory first
15
- candidates = [
16
- Path(".ccproxy.toml").resolve(),
17
- Path("ccproxy.toml").resolve(),
18
- ]
19
-
20
- # Check git repo root
21
- git_root = find_git_root()
22
- if git_root:
23
- candidates.extend(
24
- [
25
- git_root / ".ccproxy.toml",
26
- git_root / "ccproxy.toml",
27
- ]
28
- )
29
-
30
- # Check XDG config directory
31
- config_dir = get_ccproxy_config_dir()
32
- candidates.append(config_dir / "config.toml")
33
-
34
- # Return first existing file
35
- for candidate in candidates:
36
- if candidate.exists() and candidate.is_file():
37
- return candidate
38
-
39
- return None
40
-
41
-
42
- def find_git_root(path: Path | None = None) -> Path | None:
43
- """Find the root directory of a git repository."""
44
- import subprocess
45
-
46
- if path is None:
47
- path = Path.cwd()
48
-
49
- try:
50
- result = subprocess.run(
51
- ["git", "rev-parse", "--show-toplevel"],
52
- cwd=path,
53
- capture_output=True,
54
- text=True,
55
- check=True,
56
- )
57
- return Path(result.stdout.strip())
58
- except (subprocess.CalledProcessError, FileNotFoundError):
59
- return None
60
-
61
-
62
- def get_ccproxy_config_dir() -> Path:
63
- """Get the ccproxy configuration directory.
64
-
65
- Returns:
66
- Path to the ccproxy configuration directory within XDG_CONFIG_HOME.
67
- """
68
- return get_xdg_config_home() / "ccproxy"
69
-
70
-
71
- def get_claude_cli_config_dir() -> Path:
72
- """Get the Claude CLI configuration directory.
73
-
74
- Returns:
75
- Path to the Claude CLI configuration directory within XDG_CONFIG_HOME.
76
- """
77
- return get_xdg_config_home() / "claude"
78
-
79
-
80
- def get_claude_docker_home_dir() -> Path:
81
- """Get the Claude Docker home directory.
82
-
83
- Returns:
84
- Path to the Claude Docker home directory within XDG_DATA_HOME.
85
- """
86
- return get_ccproxy_config_dir() / "home"
87
-
88
-
89
- def get_ccproxy_cache_dir() -> Path:
90
- """Get the ccproxy cache directory.
91
-
92
- Returns:
93
- Path to the ccproxy cache directory within XDG_CACHE_HOME.
94
- """
95
- return get_xdg_cache_home() / "ccproxy"