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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1251
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,146 @@
1
+ """Plugin entry point for the credential balancer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ccproxy.core.logging import get_plugin_logger
8
+ from ccproxy.core.plugins import (
9
+ PluginContext,
10
+ PluginManifest,
11
+ SystemPluginFactory,
12
+ SystemPluginRuntime,
13
+ )
14
+ from ccproxy.services.auth_registry import AuthManagerRegistry
15
+
16
+ from .config import CredentialBalancerSettings
17
+ from .hook import CredentialBalancerHook
18
+ from .manager import CredentialBalancerTokenManager
19
+
20
+
21
+ logger = get_plugin_logger()
22
+
23
+
24
+ class CredentialBalancerRuntime(SystemPluginRuntime):
25
+ """Runtime responsible for registering auth managers and hooks."""
26
+
27
+ def __init__(self, manifest: PluginManifest):
28
+ super().__init__(manifest)
29
+ self._registrations: list[tuple[str, CredentialBalancerTokenManager]] = []
30
+ self._hook: CredentialBalancerHook | None = None
31
+ self._registry: AuthManagerRegistry | None = None
32
+
33
+ async def _on_initialize(self) -> None:
34
+ await super()._on_initialize()
35
+ if not self.context:
36
+ raise RuntimeError("Context not set")
37
+
38
+ config = self.context.get("config")
39
+ if not isinstance(config, CredentialBalancerSettings):
40
+ logger.debug("credential_balancer_using_default_config")
41
+ config = CredentialBalancerSettings()
42
+
43
+ if not config.enabled:
44
+ logger.info("credential_balancer_disabled")
45
+ return
46
+
47
+ if not config.providers:
48
+ logger.warning("credential_balancer_no_providers_configured")
49
+ return
50
+
51
+ service_container = self.context.get("service_container")
52
+ if not service_container:
53
+ raise RuntimeError("Service container unavailable for credential balancer")
54
+
55
+ registry = service_container.get_auth_manager_registry()
56
+ self._registry = registry
57
+
58
+ base_logger = self.context.get("logger") or get_plugin_logger(__name__)
59
+ managers: list[CredentialBalancerTokenManager] = []
60
+
61
+ for pool in config.providers:
62
+ manager_name = pool.manager_name
63
+ if manager_name is None:
64
+ raise ValueError(
65
+ f"Credential balancer pool '{pool.provider}' missing manager name"
66
+ )
67
+ manager_logger = base_logger.bind(pool=manager_name)
68
+ # Use async factory to create manager with composed AuthManagers
69
+ manager = await CredentialBalancerTokenManager.create(
70
+ pool, logger=manager_logger
71
+ )
72
+ registry.register_instance(manager_name, manager)
73
+ managers.append(manager)
74
+ self._registrations.append((manager_name, manager))
75
+ logger.info(
76
+ "credential_balancer_manager_registered",
77
+ manager=manager_name,
78
+ provider=pool.provider,
79
+ strategy=pool.strategy.value,
80
+ credentials=len(pool.credentials),
81
+ )
82
+
83
+ if managers:
84
+ hook_registry = self.context.get("hook_registry")
85
+ if not hook_registry:
86
+ app = self.context.get("app")
87
+ if app and hasattr(app.state, "hook_registry"):
88
+ hook_registry = app.state.hook_registry
89
+
90
+ if hook_registry:
91
+ hook = CredentialBalancerHook(managers)
92
+ hook_registry.register(hook)
93
+ self._hook = hook
94
+ logger.debug("credential_balancer_hook_registered")
95
+ else:
96
+ logger.warning("credential_balancer_hook_registry_missing")
97
+
98
+ async def _on_shutdown(self) -> None:
99
+ await super()._on_shutdown()
100
+ if self.context and self._hook:
101
+ hook_registry = self.context.get("hook_registry")
102
+ if not hook_registry:
103
+ app = self.context.get("app")
104
+ if app and hasattr(app.state, "hook_registry"):
105
+ hook_registry = app.state.hook_registry
106
+ if hook_registry:
107
+ hook_registry.unregister(self._hook)
108
+ logger.debug("credential_balancer_hook_unregistered")
109
+ self._hook = None
110
+
111
+ if self._registry:
112
+ for name, _ in self._registrations:
113
+ try:
114
+ self._registry.unregister(name)
115
+ except Exception:
116
+ logger.debug(
117
+ "credential_balancer_registry_unregistration_failed",
118
+ manager=name,
119
+ )
120
+ self._registrations.clear()
121
+
122
+
123
+ class CredentialBalancerFactory(SystemPluginFactory):
124
+ """Factory for the credential balancer plugin."""
125
+
126
+ def __init__(self) -> None:
127
+ manifest = PluginManifest(
128
+ name="credential_balancer",
129
+ version="0.1.0",
130
+ description="Rotate across multiple credential files for upstream providers",
131
+ is_provider=False,
132
+ config_class=CredentialBalancerSettings,
133
+ )
134
+ super().__init__(manifest)
135
+
136
+ def create_runtime(self) -> CredentialBalancerRuntime:
137
+ return CredentialBalancerRuntime(self.manifest)
138
+
139
+ def create_context(self, core_services: Any) -> PluginContext:
140
+ context = super().create_context(core_services)
141
+ return context
142
+
143
+
144
+ factory = CredentialBalancerFactory()
145
+
146
+ __all__ = ["CredentialBalancerFactory", "CredentialBalancerRuntime", "factory"]
@@ -0,0 +1,25 @@
1
+ # Dashboard Plugin
2
+
3
+ Serves the CCProxy dashboard SPA and supporting APIs.
4
+
5
+ ## Highlights
6
+ - Mounts static assets for the dashboard when available on disk
7
+ - Registers dashboard routes for health, session, and telemetry views
8
+ - Integrates with FastAPI app mounting during plugin initialization
9
+
10
+ ## Configuration
11
+ - `DashboardPluginConfig` toggles static asset mounting and route exposure
12
+ - Defaults to auto-mounting assets under `/dashboard/assets` when present
13
+ - Generate defaults with `python3 scripts/generate_config_from_model.py \
14
+ --format toml --plugin dashboard --config-class DashboardPluginConfig`
15
+
16
+ ```toml
17
+ [plugins.dashboard]
18
+ # enabled = true
19
+ # mount_static = true
20
+ ```
21
+
22
+ ## Related Components
23
+ - `plugin.py`: runtime for mounting static files
24
+ - `routes.py`: FastAPI router for dashboard APIs
25
+ - `config.py`: settings model for plugin toggles
@@ -0,0 +1 @@
1
+ """Dashboard plugin (serves SPA and favicon; mounts assets)."""
@@ -0,0 +1,8 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class DashboardPluginConfig(BaseModel):
5
+ enabled: bool = Field(default=True, description="Enable dashboard routes")
6
+ mount_static: bool = Field(
7
+ default=True, description="Mount /dashboard/assets static files if present"
8
+ )
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from fastapi.staticfiles import StaticFiles
6
+
7
+ from ccproxy.core.logging import get_plugin_logger
8
+ from ccproxy.core.plugins import (
9
+ PluginManifest,
10
+ RouteSpec,
11
+ SystemPluginFactory,
12
+ SystemPluginRuntime,
13
+ )
14
+
15
+ from .config import DashboardPluginConfig
16
+
17
+
18
+ logger = get_plugin_logger()
19
+
20
+
21
+ class DashboardRuntime(SystemPluginRuntime):
22
+ async def _on_initialize(self) -> None:
23
+ if not self.context:
24
+ raise RuntimeError("Context not set")
25
+ from typing import cast
26
+
27
+ cfg = cast(DashboardPluginConfig | None, self.context.get("config"))
28
+ app = self.context.get("app")
29
+ if not app or not hasattr(app, "mount"):
30
+ return
31
+
32
+ # Optionally mount static assets for the SPA
33
+ cfg = cfg or DashboardPluginConfig()
34
+ if cfg.mount_static:
35
+ current_file = Path(__file__)
36
+ project_root = current_file.parent.parent.parent
37
+ dashboard_static_path = project_root / "ccproxy" / "static" / "dashboard"
38
+ if dashboard_static_path.exists():
39
+ try:
40
+ app.mount(
41
+ "/dashboard/assets",
42
+ StaticFiles(directory=str(dashboard_static_path)),
43
+ name="dashboard-static",
44
+ )
45
+ logger.debug(
46
+ "dashboard_static_files_mounted",
47
+ path=str(dashboard_static_path),
48
+ )
49
+ except Exception as e: # pragma: no cover
50
+ logger.warning("dashboard_static_mount_failed", error=str(e))
51
+
52
+
53
+ class DashboardFactory(SystemPluginFactory):
54
+ def __init__(self) -> None:
55
+ from .routes import router as dashboard_router
56
+
57
+ manifest = PluginManifest(
58
+ name="dashboard",
59
+ version="0.1.0",
60
+ description="Dashboard SPA routes and static asset mounting",
61
+ is_provider=False,
62
+ config_class=DashboardPluginConfig,
63
+ routes=[RouteSpec(router=dashboard_router, prefix="", tags=["dashboard"])],
64
+ )
65
+ super().__init__(manifest)
66
+
67
+ def create_runtime(self) -> DashboardRuntime:
68
+ return DashboardRuntime(self.manifest)
69
+
70
+
71
+ factory = DashboardFactory()
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from fastapi import APIRouter, HTTPException
6
+ from fastapi.responses import FileResponse, HTMLResponse
7
+
8
+
9
+ router = APIRouter()
10
+
11
+
12
+ @router.get("/dashboard")
13
+ async def get_metrics_dashboard() -> HTMLResponse:
14
+ current_file = Path(__file__)
15
+ project_root = current_file.parent.parent.parent
16
+ dashboard_folder = project_root / "ccproxy" / "static" / "dashboard"
17
+ dashboard_index = dashboard_folder / "index.html"
18
+
19
+ if not dashboard_folder.exists():
20
+ raise HTTPException(
21
+ status_code=404,
22
+ detail="Dashboard not found. Build it with 'cd dashboard && bun run build:prod'",
23
+ )
24
+ if not dashboard_index.exists():
25
+ raise HTTPException(
26
+ status_code=404,
27
+ detail="Dashboard index.html not found. Rebuild with 'cd dashboard && bun run build:prod'",
28
+ )
29
+
30
+ try:
31
+ html_content = dashboard_index.read_text(encoding="utf-8")
32
+ return HTMLResponse(
33
+ content=html_content,
34
+ status_code=200,
35
+ headers={
36
+ "Cache-Control": "no-cache, no-store, must-revalidate",
37
+ "Pragma": "no-cache",
38
+ "Expires": "0",
39
+ "Content-Type": "text/html; charset=utf-8",
40
+ },
41
+ )
42
+ except (OSError, PermissionError) as e:
43
+ raise HTTPException(
44
+ status_code=500, detail=f"Dashboard file access error: {str(e)}"
45
+ ) from e
46
+ except UnicodeDecodeError as e:
47
+ raise HTTPException(
48
+ status_code=500, detail=f"Dashboard file encoding error: {str(e)}"
49
+ ) from e
50
+ except Exception as e:
51
+ raise HTTPException(
52
+ status_code=500, detail=f"Failed to serve dashboard: {str(e)}"
53
+ ) from e
54
+
55
+
56
+ @router.get("/dashboard/favicon.svg")
57
+ async def get_dashboard_favicon() -> FileResponse:
58
+ current_file = Path(__file__)
59
+ project_root = current_file.parent.parent.parent
60
+ favicon_path = project_root / "ccproxy" / "static" / "dashboard" / "favicon.svg"
61
+ if not favicon_path.exists():
62
+ raise HTTPException(status_code=404, detail="Favicon not found")
63
+ return FileResponse(
64
+ path=str(favicon_path),
65
+ media_type="image/svg+xml",
66
+ headers={"Cache-Control": "public, max-age=3600"},
67
+ )
@@ -0,0 +1,32 @@
1
+ # Docker Plugin
2
+
3
+ Provides Docker-backed execution for CCProxy via CLI extensions.
4
+
5
+ ## Highlights
6
+ - Wraps requests with `DockerAdapter` to run providers inside containers
7
+ - Extends the `ccproxy serve` CLI with Docker-specific arguments
8
+ - Applies CLI overrides to runtime configuration before adapter startup
9
+
10
+ ## Configuration
11
+ - `DockerConfig` controls image, workspace, env vars, and volume mounts
12
+ - CLI flags override the configuration and are declared via `cli_arguments`
13
+ - Generate defaults with `python3 scripts/generate_config_from_model.py \
14
+ --format toml --plugin docker --config-class DockerConfig`
15
+
16
+ ```toml
17
+ [plugins.docker]
18
+ # enabled = true
19
+ # docker_image = "anthropics/claude-cli:latest"
20
+ # docker_home_directory = "/home/user"
21
+ # docker_workspace_directory = "/workspace"
22
+ # docker_volumes = []
23
+ # docker_environment = []
24
+ # user_mapping_enabled = true
25
+ # user_uid = 1000
26
+ # user_gid = 1000
27
+ ```
28
+
29
+ ## Related Components
30
+ - `adapter.py`: executor that launches Docker containers
31
+ - `plugin.py`: runtime handling CLI context and overrides
32
+ - `config.py`: settings model for Docker execution
@@ -10,6 +10,7 @@ This module provides a comprehensive Docker integration system with support for:
10
10
  """
11
11
 
12
12
  from .adapter import DockerAdapter, create_docker_adapter
13
+ from .config import DockerConfig
13
14
  from .docker_path import DockerPath, DockerPathSet
14
15
  from .middleware import (
15
16
  LoggerOutputMiddleware,
@@ -44,6 +45,8 @@ __all__ = [
44
45
  "DockerPathSet",
45
46
  # User context
46
47
  "DockerUserContext",
48
+ # Configuration
49
+ "DockerConfig",
47
50
  # Type aliases
48
51
  "DockerEnv",
49
52
  "DockerPortSpec",
@@ -3,11 +3,18 @@
3
3
  import asyncio
4
4
  import os
5
5
  import shlex
6
+ import subprocess
6
7
  from pathlib import Path
7
- from typing import cast
8
+ from typing import Any, cast
8
9
 
9
- from structlog import get_logger
10
+ from fastapi import Request
11
+ from starlette.responses import Response, StreamingResponse
10
12
 
13
+ from ccproxy.core.logging import get_plugin_logger
14
+ from ccproxy.services.adapters.base import BaseAdapter
15
+ from ccproxy.streaming import DeferredStreaming
16
+
17
+ from .config import DockerConfig
11
18
  from .middleware import LoggerOutputMiddleware
12
19
  from .models import DockerUserContext
13
20
  from .protocol import (
@@ -25,11 +32,19 @@ from .stream_process import (
25
32
  from .validators import create_docker_error, validate_port_spec
26
33
 
27
34
 
28
- logger = get_logger(__name__)
35
+ logger = get_plugin_logger(__name__)
36
+
37
+
38
+ class DockerAdapter(BaseAdapter, DockerAdapterProtocol):
39
+ """Docker adapter implementing both BaseAdapter and DockerAdapterProtocol."""
29
40
 
41
+ def __init__(self, config: DockerConfig | None = None):
42
+ """Initialize Docker adapter.
30
43
 
31
- class DockerAdapter:
32
- """Implementation of Docker adapter."""
44
+ Args:
45
+ config: Docker configuration
46
+ """
47
+ self.config = config or DockerConfig()
33
48
 
34
49
  async def _needs_sudo(self) -> bool:
35
50
  """Check if Docker requires sudo by testing docker info command."""
@@ -286,8 +301,6 @@ class DockerAdapter:
286
301
  # Note: We can't use await here since this method replaces the process
287
302
  # Use a simple check instead
288
303
  try:
289
- import subprocess
290
-
291
304
  subprocess.run(
292
305
  ["docker", "info"], check=True, capture_output=True, text=True
293
306
  )
@@ -441,7 +454,6 @@ class DockerAdapter:
441
454
 
442
455
  # Build the Docker command to check image existence
443
456
  docker_cmd = ["docker", "inspect", image_full_name]
444
- cmd_str = " ".join(shlex.quote(arg) for arg in docker_cmd)
445
457
 
446
458
  try:
447
459
  # Run Docker inspect command
@@ -559,6 +571,92 @@ class DockerAdapter:
559
571
  )
560
572
  raise error from e
561
573
 
574
+ # Legacy methods for backward compatibility with plugin system
575
+
576
+ def build_docker_run_args(
577
+ self,
578
+ settings: Any,
579
+ command: list[str] | None = None,
580
+ docker_image: str | None = None,
581
+ docker_env: list[str] | None = None,
582
+ docker_volume: list[str] | None = None,
583
+ docker_arg: list[str] | None = None,
584
+ docker_home: str | None = None,
585
+ docker_workspace: str | None = None,
586
+ user_mapping_enabled: bool | None = None,
587
+ user_uid: int | None = None,
588
+ user_gid: int | None = None,
589
+ ) -> tuple[str, list[str], list[str], list[str], dict[str, Any], dict[str, Any]]:
590
+ """Build Docker run arguments.
591
+
592
+ Returns:
593
+ Tuple of (image, volumes, environment, command, user_context, metadata)
594
+ """
595
+ # Use CLI overrides or config defaults
596
+ image = docker_image or self.config.docker_image
597
+ home_dir = docker_home or str(self.config.get_effective_home_directory())
598
+ workspace_dir = docker_workspace or str(
599
+ self.config.get_effective_workspace_directory()
600
+ )
601
+
602
+ # Build volumes
603
+ volumes = [
604
+ f"{home_dir}:/data/home",
605
+ f"{workspace_dir}:/data/workspace",
606
+ ]
607
+ volumes.extend(self.config.get_all_volumes(docker_volume))
608
+
609
+ # Build environment variables
610
+ env_vars = [
611
+ "CLAUDE_HOME=/data/home",
612
+ "CLAUDE_WORKSPACE=/data/workspace",
613
+ ]
614
+ env_vars.extend(self.config.get_all_environment_vars(docker_env))
615
+
616
+ # User mapping
617
+ user_context = {}
618
+ if user_mapping_enabled is None:
619
+ user_mapping_enabled = self.config.user_mapping_enabled
620
+
621
+ if user_mapping_enabled:
622
+ uid = user_uid or self.config.user_uid or os.getuid()
623
+ gid = user_gid or self.config.user_gid or os.getgid()
624
+ user_context = {"uid": uid, "gid": gid}
625
+
626
+ metadata = {
627
+ "config": self.config,
628
+ "cli_overrides": {
629
+ "docker_image": docker_image,
630
+ "docker_env": docker_env,
631
+ "docker_volume": docker_volume,
632
+ "docker_arg": docker_arg,
633
+ "docker_home": docker_home,
634
+ "docker_workspace": docker_workspace,
635
+ "user_mapping_enabled": user_mapping_enabled,
636
+ "user_uid": user_uid,
637
+ "user_gid": user_gid,
638
+ },
639
+ }
640
+
641
+ return image, volumes, env_vars, command or [], user_context, metadata
642
+
643
+ async def handle_request(
644
+ self, request: Request
645
+ ) -> Response | StreamingResponse | DeferredStreaming:
646
+ """Handle request (not used for Docker adapter)."""
647
+ raise NotImplementedError("Docker adapter does not handle HTTP requests")
648
+
649
+ async def handle_streaming(
650
+ self, request: Request, endpoint: str, **kwargs: Any
651
+ ) -> StreamingResponse | DeferredStreaming:
652
+ """Handle streaming request (not used for Docker adapter)."""
653
+ raise NotImplementedError("Docker adapter does not handle streaming requests")
654
+
655
+ async def cleanup(self) -> None:
656
+ """Cleanup Docker adapter resources."""
657
+ # No persistent resources to cleanup for Docker adapter
658
+ pass
659
+
562
660
 
563
661
  def create_docker_adapter(
564
662
  image: str | None = None,
@@ -582,7 +680,7 @@ def create_docker_adapter(
582
680
 
583
681
  Example:
584
682
  >>> adapter = create_docker_adapter()
585
- >>> if adapter.is_available():
586
- ... adapter.run_container("ubuntu:latest", [], {})
683
+ >>> if await adapter.is_available():
684
+ ... await adapter.run_container("ubuntu:latest", [], {})
587
685
  """
588
686
  return DockerAdapter()
@@ -0,0 +1,82 @@
1
+ """Docker plugin configuration."""
2
+
3
+ from pathlib import Path
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class DockerConfig(BaseModel):
9
+ """Configuration for Docker plugin."""
10
+
11
+ enabled: bool = Field(
12
+ default=True,
13
+ description="Enable Docker functionality",
14
+ )
15
+
16
+ docker_image: str = Field(
17
+ default="anthropics/claude-cli:latest",
18
+ description="Docker image to use for running commands",
19
+ )
20
+
21
+ docker_home_directory: str | None = Field(
22
+ default=None,
23
+ description="Home directory to mount in Docker container",
24
+ )
25
+
26
+ docker_workspace_directory: str | None = Field(
27
+ default=None,
28
+ description="Workspace directory to mount in Docker container",
29
+ )
30
+
31
+ docker_volumes: list[str] = Field(
32
+ default_factory=list,
33
+ description="Additional volume mounts for Docker container",
34
+ )
35
+
36
+ docker_environment: list[str] = Field(
37
+ default_factory=list,
38
+ description="Environment variables to pass to Docker container",
39
+ )
40
+
41
+ user_mapping_enabled: bool = Field(
42
+ default=True,
43
+ description="Enable user mapping for Docker containers",
44
+ )
45
+
46
+ user_uid: int | None = Field(
47
+ default=None,
48
+ description="User UID for Docker user mapping",
49
+ )
50
+
51
+ user_gid: int | None = Field(
52
+ default=None,
53
+ description="User GID for Docker user mapping",
54
+ )
55
+
56
+ def get_effective_home_directory(self) -> Path:
57
+ """Get the effective home directory for Docker mounting."""
58
+ if self.docker_home_directory:
59
+ return Path(self.docker_home_directory)
60
+ return Path.home()
61
+
62
+ def get_effective_workspace_directory(self) -> Path:
63
+ """Get the effective workspace directory for Docker mounting."""
64
+ if self.docker_workspace_directory:
65
+ return Path(self.docker_workspace_directory)
66
+ return Path.cwd()
67
+
68
+ def get_all_volumes(self, additional_volumes: list[str] | None = None) -> list[str]:
69
+ """Get all volume mounts including defaults and additional."""
70
+ volumes = self.docker_volumes.copy()
71
+ if additional_volumes:
72
+ volumes.extend(additional_volumes)
73
+ return volumes
74
+
75
+ def get_all_environment_vars(
76
+ self, additional_env: list[str] | None = None
77
+ ) -> list[str]:
78
+ """Get all environment variables including defaults and additional."""
79
+ env_vars = self.docker_environment.copy()
80
+ if additional_env:
81
+ env_vars.extend(additional_env)
82
+ return env_vars
@@ -4,10 +4,11 @@ from pathlib import Path
4
4
  from typing import Self
5
5
 
6
6
  from pydantic import BaseModel, field_validator
7
- from structlog import get_logger
8
7
 
8
+ from ccproxy.core.logging import get_plugin_logger
9
9
 
10
- logger = get_logger(__name__)
10
+
11
+ logger = get_plugin_logger(__name__)
11
12
 
12
13
 
13
14
  class DockerPath(BaseModel):
@@ -116,7 +117,7 @@ class DockerPathSet:
116
117
  """
117
118
  self.base_host_path = Path(base_host_path).resolve() if base_host_path else None
118
119
  self.paths: dict[str, DockerPath] = {}
119
- self.logger = get_logger(f"{__name__}.{self.__class__.__name__}")
120
+ self.logger = get_plugin_logger(f"{__name__}.{self.__class__.__name__}")
120
121
 
121
122
  def add(
122
123
  self, name: str, container_path: str, host_subpath: str | None = None
@@ -2,12 +2,12 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from structlog import get_logger
5
+ from ccproxy.core.logging import get_plugin_logger
6
6
 
7
7
  from .stream_process import OutputMiddleware, create_chained_middleware
8
8
 
9
9
 
10
- logger = get_logger(__name__)
10
+ logger = get_plugin_logger(__name__)
11
11
 
12
12
 
13
13
  class LoggerOutputMiddleware(OutputMiddleware[str]):