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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0a4.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0a4.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1251
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -1,48 +1,20 @@
1
1
  """Serve command for CCProxy API server - consolidates server-related commands."""
2
2
 
3
- import json
4
- import os
5
3
  from pathlib import Path
6
4
  from typing import Annotated, Any
7
5
 
8
6
  import typer
9
7
  import uvicorn
10
8
  from click import get_current_context
11
- from structlog import get_logger
9
+ from rich.console import Console
10
+ from rich.syntax import Syntax
12
11
 
13
- from ccproxy._version import __version__
14
- from ccproxy.cli.helpers import (
15
- get_rich_toolkit,
16
- is_running_in_docker,
17
- warning,
18
- )
19
- from ccproxy.config.settings import (
20
- ConfigurationError,
21
- Settings,
22
- config_manager,
23
- )
24
- from ccproxy.core.async_utils import get_root_package_name
25
- from ccproxy.docker import (
26
- create_docker_adapter,
27
- )
12
+ from ccproxy.cli.helpers import get_rich_toolkit
13
+ from ccproxy.config.settings import ConfigurationError, Settings
14
+ from ccproxy.core.logging import get_logger, setup_logging
28
15
 
29
- from ..docker import (
30
- _create_docker_adapter_from_settings,
31
- )
32
- from ..options.claude_options import (
33
- ClaudeOptions,
34
- validate_claude_cli_path,
35
- validate_cwd,
36
- validate_max_thinking_tokens,
37
- validate_max_turns,
38
- validate_permission_mode,
39
- validate_pool_size,
40
- validate_sdk_message_mode,
41
- validate_system_prompt_injection_mode,
42
- )
43
- from ..options.security_options import SecurityOptions, validate_auth_token
16
+ from ..options.security_options import validate_auth_token
44
17
  from ..options.server_options import (
45
- ServerOptions,
46
18
  validate_log_level,
47
19
  validate_port,
48
20
  )
@@ -56,203 +28,42 @@ def get_config_path_from_context() -> Path | None:
56
28
  config_path = ctx.obj["config_path"]
57
29
  return config_path if config_path is None else Path(config_path)
58
30
  except RuntimeError:
59
- # No active click context (e.g., in tests)
60
31
  pass
61
32
  return None
62
33
 
63
34
 
64
35
  def _show_api_usage_info(toolkit: Any, settings: Settings) -> None:
65
36
  """Show API usage information when auth token is configured."""
66
- from rich.console import Console
67
- from rich.syntax import Syntax
68
37
 
69
38
  toolkit.print_title("API Client Configuration", tag="config")
70
39
 
71
- # Determine the base URLs
72
- anthropic_base_url = f"http://{settings.server.host}:{settings.server.port}"
73
- openai_base_url = f"http://{settings.server.host}:{settings.server.port}/openai"
40
+ anthropic_base_url = f"http://{settings.server.host}:{settings.server.port}/claude"
41
+ openai_base_url = f"http://{settings.server.host}:{settings.server.port}/codex"
74
42
 
75
- # Show environment variable exports using code blocks
76
43
  toolkit.print("Environment Variables for API Clients:", tag="info")
77
44
  toolkit.print_line()
78
45
 
79
- # Use rich console for code blocks
80
46
  console = Console()
81
47
 
82
- exports = f"""export ANTHROPIC_API_KEY={settings.security.auth_token}
48
+ auth_token = "YOUR_AUTH_TOKEN" if settings.security.auth_token else "NOT_SET"
49
+ exports = f"""export ANTHROPIC_API_KEY={auth_token}
83
50
  export ANTHROPIC_BASE_URL={anthropic_base_url}
84
- export OPENAI_API_KEY={settings.security.auth_token}
51
+ export OPENAI_API_KEY={auth_token}
85
52
  export OPENAI_BASE_URL={openai_base_url}"""
86
53
 
87
54
  console.print(Syntax(exports, "bash", theme="monokai", background_color="default"))
88
55
  toolkit.print_line()
89
56
 
90
57
 
91
- def _run_docker_server(
92
- settings: Settings,
93
- docker_image: str | None = None,
94
- docker_env: list[str] | None = None,
95
- docker_volume: list[str] | None = None,
96
- docker_arg: list[str] | None = None,
97
- docker_home: str | None = None,
98
- docker_workspace: str | None = None,
99
- user_mapping_enabled: bool | None = None,
100
- user_uid: int | None = None,
101
- user_gid: int | None = None,
102
- ) -> None:
103
- """Run the server using Docker."""
104
- toolkit = get_rich_toolkit()
105
- logger = get_logger(__name__)
106
-
107
- docker_env = docker_env or []
108
- docker_volume = docker_volume or []
109
- docker_arg = docker_arg or []
110
-
111
- docker_env_dict = {}
112
- for env_var in docker_env:
113
- if "=" in env_var:
114
- key, value = env_var.split("=", 1)
115
- docker_env_dict[key] = value
116
-
117
- # Add server configuration to Docker environment
118
- if settings.server.reload:
119
- docker_env_dict["RELOAD"] = "true"
120
- docker_env_dict["PORT"] = str(settings.server.port)
121
- docker_env_dict["HOST"] = "0.0.0.0"
122
-
123
- # Display startup information
124
- # toolkit.print_title(
125
- # "Starting CCProxy API server with Docker", tag="docker"
126
- # )
127
- # toolkit.print(
128
- # f"Server will be available at: http://{settings.server.host}:{settings.server.port}",
129
- # tag="info",
130
- # )
131
- toolkit.print_line()
132
-
133
- # Show Docker configuration summary
134
- toolkit.print_title("Docker Configuration Summary", tag="config")
135
-
136
- # Determine effective directories for volume mapping
137
- home_dir = docker_home or settings.docker.docker_home_directory
138
- workspace_dir = docker_workspace or settings.docker.docker_workspace_directory
139
-
140
- # Show volume information
141
- toolkit.print("Volumes:", tag="config")
142
- if home_dir:
143
- toolkit.print(f" Home: {home_dir} → /data/home", tag="volume")
144
- if workspace_dir:
145
- toolkit.print(f" Workspace: {workspace_dir} → /data/workspace", tag="volume")
146
- if docker_volume:
147
- for vol in docker_volume:
148
- toolkit.print(f" Additional: {vol}", tag="volume")
149
- toolkit.print_line()
150
-
151
- # Show environment information
152
- toolkit.print("Environment Variables:", tag="config")
153
- key_env_vars = {
154
- "CLAUDE_HOME": "/data/home",
155
- "CLAUDE_WORKSPACE": "/data/workspace",
156
- "PORT": str(settings.server.port),
157
- "HOST": "0.0.0.0",
158
- }
159
- if settings.server.reload:
160
- key_env_vars["RELOAD"] = "true"
161
-
162
- for key, value in key_env_vars.items():
163
- toolkit.print(f" {key}={value}", tag="env")
164
-
165
- # Show additional environment variables from CLI
166
- for env_var in docker_env:
167
- toolkit.print(f" {env_var}", tag="env")
168
-
169
- # Show debug environment information if log level is DEBUG
170
- if settings.server.log_level == "DEBUG":
171
- toolkit.print_line()
172
- toolkit.print_title("Debug: All Environment Variables", tag="debug")
173
- all_env = {**docker_env_dict}
174
- for key, value in sorted(all_env.items()):
175
- toolkit.print(f" {key}={value}", tag="debug")
176
-
177
- toolkit.print_line()
178
-
179
- toolkit.print_line()
180
-
181
- # Show API usage information if auth token is configured
182
- if settings.security.auth_token:
183
- _show_api_usage_info(toolkit, settings)
184
-
185
- # Execute using the new Docker adapter
186
- image, volumes, environment, command, user_context, additional_args = (
187
- _create_docker_adapter_from_settings(
188
- settings,
189
- command=["ccproxy", "serve"],
190
- docker_image=docker_image,
191
- docker_env=[f"{k}={v}" for k, v in docker_env_dict.items()],
192
- docker_volume=docker_volume,
193
- docker_arg=docker_arg,
194
- docker_home=docker_home,
195
- docker_workspace=docker_workspace,
196
- user_mapping_enabled=user_mapping_enabled,
197
- user_uid=user_uid,
198
- user_gid=user_gid,
199
- )
200
- )
201
-
202
- logger.info(
203
- "docker_server_config",
204
- configured_image=settings.docker.docker_image,
205
- effective_image=image,
206
- )
207
-
208
- # Add port mapping
209
- ports = [f"{settings.server.port}:{settings.server.port}"]
210
-
211
- # Create Docker adapter and execute
212
- adapter = create_docker_adapter()
213
- adapter.exec_container(
214
- image=image,
215
- volumes=volumes,
216
- environment=environment,
217
- command=command,
218
- user_context=user_context,
219
- ports=ports,
220
- )
221
-
222
-
223
- def _run_local_server(settings: Settings, cli_overrides: dict[str, Any]) -> None:
58
+ def _run_local_server(settings: Settings) -> None:
224
59
  """Run the server locally."""
225
- in_docker = is_running_in_docker()
60
+ # in_docker = is_running_in_docker()
226
61
  toolkit = get_rich_toolkit()
227
62
  logger = get_logger(__name__)
228
63
 
229
- if in_docker:
230
- toolkit.print_title(
231
- f"Starting CCProxy API server in {warning('docker')}",
232
- tag="docker",
233
- )
234
- toolkit.print(
235
- f"uid={warning(str(os.getuid()))} gid={warning(str(os.getgid()))}"
236
- )
237
- toolkit.print(f"HOME={os.environ['HOME']}")
238
- # else:
239
- # toolkit.print_title("Starting CCProxy API server", tag="local")
240
-
241
- # toolkit.print(
242
- # f"Server will be available at: http://{settings.server.host}:{settings.server.port}",
243
- # tag="info",
244
- # )
245
-
246
- # toolkit.print_line()
247
-
248
- # Show API usage information if auth token is configured
249
64
  if settings.security.auth_token:
250
65
  _show_api_usage_info(toolkit, settings)
251
66
 
252
- # Set environment variables for server to access CLI overrides
253
- if cli_overrides:
254
- os.environ["CCPROXY_CONFIG_OVERRIDES"] = json.dumps(cli_overrides)
255
-
256
67
  logger.debug(
257
68
  "server_starting",
258
69
  host=settings.server.host,
@@ -262,26 +73,27 @@ def _run_local_server(settings: Settings, cli_overrides: dict[str, Any]) -> None
262
73
 
263
74
  reload_includes = None
264
75
  if settings.server.reload:
265
- reload_includes = ["ccproxy", "pyproject.toml", "uv.lock"]
76
+ reload_includes = ["ccproxy", "pyproject.toml", "uv.lock", "plugins"]
77
+
78
+ # container = create_service_container(settings)
266
79
 
267
- # Run uvicorn with our already configured logging
268
80
  uvicorn.run(
269
- app=f"{get_root_package_name()}.api.app:create_app",
81
+ # app=create_app(container),
82
+ app="ccproxy.api.app:create_app",
270
83
  factory=True,
271
84
  host=settings.server.host,
272
85
  port=settings.server.port,
273
86
  reload=settings.server.reload,
274
- workers=None, # ,settings.workers,
87
+ workers=settings.server.workers,
275
88
  log_config=None,
276
- access_log=False, # Disable uvicorn's default access logs
277
- server_header=False, # Disable uvicorn's server header to preserve upstream headers
89
+ access_log=False,
90
+ server_header=False,
91
+ date_header=False,
278
92
  reload_includes=reload_includes,
279
- # log_config=get_uvicorn_log_config(),
280
93
  )
281
94
 
282
95
 
283
96
  def api(
284
- # Configuration
285
97
  config: Annotated[
286
98
  Path | None,
287
99
  typer.Option(
@@ -295,7 +107,6 @@ def api(
295
107
  rich_help_panel="Configuration",
296
108
  ),
297
109
  ] = None,
298
- # Server options
299
110
  port: Annotated[
300
111
  int | None,
301
112
  typer.Option(
@@ -340,15 +151,6 @@ def api(
340
151
  rich_help_panel="Server Settings",
341
152
  ),
342
153
  ] = None,
343
- use_terminal_permission_handler: Annotated[
344
- bool,
345
- typer.Option(
346
- "--terminal-permission-handler",
347
- help="Enable terminal permission terminal handler",
348
- rich_help_panel="Server Settings",
349
- ),
350
- ] = False,
351
- # Security options
352
154
  auth_token: Annotated[
353
155
  str | None,
354
156
  typer.Option(
@@ -358,629 +160,92 @@ def api(
358
160
  rich_help_panel="Security Settings",
359
161
  ),
360
162
  ] = None,
361
- # Claude options
362
- max_thinking_tokens: Annotated[
363
- int | None,
364
- typer.Option(
365
- "--max-thinking-tokens",
366
- help="Maximum thinking tokens for Claude Code",
367
- callback=validate_max_thinking_tokens,
368
- rich_help_panel="Claude Settings",
369
- ),
370
- ] = None,
371
- allowed_tools: Annotated[
372
- str | None,
373
- typer.Option(
374
- "--allowed-tools",
375
- help="List of allowed tools (comma-separated)",
376
- rich_help_panel="Claude Settings",
377
- ),
378
- ] = None,
379
- disallowed_tools: Annotated[
380
- str | None,
381
- typer.Option(
382
- "--disallowed-tools",
383
- help="List of disallowed tools (comma-separated)",
384
- rich_help_panel="Claude Settings",
385
- ),
386
- ] = None,
387
- claude_cli_path: Annotated[
388
- str | None,
389
- typer.Option(
390
- "--claude-cli-path",
391
- help="Path to Claude CLI executable",
392
- callback=validate_claude_cli_path,
393
- rich_help_panel="Claude Settings",
394
- ),
395
- ] = None,
396
- append_system_prompt: Annotated[
397
- str | None,
398
- typer.Option(
399
- "--append-system-prompt",
400
- help="Additional system prompt to append",
401
- rich_help_panel="Claude Settings",
402
- ),
403
- ] = None,
404
- permission_mode: Annotated[
405
- str | None,
406
- typer.Option(
407
- "--permission-mode",
408
- help="Permission mode: default, acceptEdits, or bypassPermissions",
409
- callback=validate_permission_mode,
410
- rich_help_panel="Claude Settings",
411
- ),
412
- ] = None,
413
- max_turns: Annotated[
414
- int | None,
415
- typer.Option(
416
- "--max-turns",
417
- help="Maximum conversation turns",
418
- callback=validate_max_turns,
419
- rich_help_panel="Claude Settings",
420
- ),
421
- ] = None,
422
- cwd: Annotated[
423
- str | None,
424
- typer.Option(
425
- "--cwd",
426
- help="Working directory path",
427
- callback=validate_cwd,
428
- rich_help_panel="Claude Settings",
429
- ),
430
- ] = None,
431
- permission_prompt_tool_name: Annotated[
432
- str | None,
433
- typer.Option(
434
- "--permission-prompt-tool-name",
435
- help="Permission prompt tool name",
436
- rich_help_panel="Claude Settings",
437
- ),
438
- ] = None,
439
- sdk_message_mode: Annotated[
440
- str | None,
441
- typer.Option(
442
- "--sdk-message-mode",
443
- help="SDK message handling mode: forward (direct SDK blocks), ignore (skip blocks), formatted (XML tags with JSON data)",
444
- callback=validate_sdk_message_mode,
445
- rich_help_panel="Claude Settings",
446
- ),
447
- ] = None,
448
- sdk_pool: Annotated[
449
- bool,
450
- typer.Option(
451
- "--sdk-pool/--no-sdk-pool",
452
- help="Enable/disable general Claude SDK client connection pooling",
453
- rich_help_panel="Claude Settings",
454
- ),
455
- ] = False,
456
- sdk_pool_size: Annotated[
457
- int | None,
458
- typer.Option(
459
- "--sdk-pool-size",
460
- help="Number of clients to maintain in the general pool (1-20)",
461
- callback=validate_pool_size,
462
- rich_help_panel="Claude Settings",
463
- ),
464
- ] = None,
465
- sdk_session_pool: Annotated[
466
- bool,
467
- typer.Option(
468
- "--sdk-session-pool/--no-sdk-session-pool",
469
- help="Enable/disable session-aware Claude SDK client pooling",
470
- rich_help_panel="Claude Settings",
471
- ),
472
- ] = False,
473
- system_prompt_injection_mode: Annotated[
474
- str | None,
475
- typer.Option(
476
- "--system-prompt-injection-mode",
477
- help="System prompt injection mode: minimal (Claude Code ID only), full (all detected system messages)",
478
- callback=validate_system_prompt_injection_mode,
479
- rich_help_panel="Claude Settings",
480
- ),
481
- ] = None,
482
- builtin_permissions: Annotated[
483
- bool,
484
- typer.Option(
485
- "--builtin-permissions/--no-builtin-permissions",
486
- help="Enable built-in permission handling infrastructure (MCP server and SSE endpoints). When disabled, users can configure custom MCP servers and permission tools.",
487
- rich_help_panel="Claude Settings",
488
- ),
489
- ] = True,
490
- # Core settings
491
- docker: Annotated[
492
- bool,
493
- typer.Option(
494
- "--docker",
495
- "-d",
496
- help="Run API server using Docker instead of local execution",
497
- ),
498
- ] = False,
499
- # Docker settings using shared parameters
500
- docker_image: Annotated[
501
- str | None,
502
- typer.Option(
503
- "--docker-image",
504
- help="Docker image to use (overrides configuration)",
505
- rich_help_panel="Docker Settings",
506
- ),
507
- ] = None,
508
- docker_env: Annotated[
509
- list[str] | None,
510
- typer.Option(
511
- "--docker-env",
512
- "-e",
513
- help="Environment variables to pass to Docker container",
514
- rich_help_panel="Docker Settings",
515
- ),
516
- ] = None,
517
- docker_volume: Annotated[
163
+ enable_plugin: Annotated[
518
164
  list[str] | None,
519
165
  typer.Option(
520
- "--docker-volume",
521
- "-v",
522
- help="Volume mounts for Docker container",
523
- rich_help_panel="Docker Settings",
166
+ "--enable-plugin",
167
+ help="Enable a plugin by name (repeatable)",
168
+ rich_help_panel="Plugin Settings",
524
169
  ),
525
170
  ] = None,
526
- docker_arg: Annotated[
171
+ disable_plugin: Annotated[
527
172
  list[str] | None,
528
173
  typer.Option(
529
- "--docker-arg",
530
- help="Additional arguments to pass to docker run",
531
- rich_help_panel="Docker Settings",
532
- ),
533
- ] = None,
534
- docker_home: Annotated[
535
- str | None,
536
- typer.Option(
537
- "--docker-home",
538
- help="Override the home directory for Docker",
539
- rich_help_panel="Docker Settings",
174
+ "--disable-plugin",
175
+ help="Disable a plugin by name (repeatable)",
176
+ rich_help_panel="Plugin Settings",
540
177
  ),
541
178
  ] = None,
542
- docker_workspace: Annotated[
543
- str | None,
544
- typer.Option(
545
- "--docker-workspace",
546
- help="Override the workspace directory for Docker",
547
- rich_help_panel="Docker Settings",
548
- ),
549
- ] = None,
550
- user_mapping_enabled: Annotated[
551
- bool | None,
552
- typer.Option(
553
- "--user-mapping/--no-user-mapping",
554
- help="Enable user mapping for Docker",
555
- rich_help_panel="Docker Settings",
556
- ),
557
- ] = None,
558
- user_uid: Annotated[
559
- int | None,
560
- typer.Option(
561
- "--user-uid",
562
- help="User UID for Docker user mapping",
563
- rich_help_panel="Docker Settings",
564
- ),
565
- ] = None,
566
- user_gid: Annotated[
567
- int | None,
568
- typer.Option(
569
- "--user-gid",
570
- help="User GID for Docker user mapping",
571
- rich_help_panel="Docker Settings",
572
- ),
573
- ] = None,
574
- # Network control flags
575
- no_network_calls: Annotated[
576
- bool,
577
- typer.Option(
578
- "--no-network-calls",
579
- help="Disable all network calls (version checks and pricing updates)",
580
- rich_help_panel="Privacy Settings",
581
- ),
582
- ] = False,
583
- disable_version_check: Annotated[
584
- bool,
585
- typer.Option(
586
- "--disable-version-check",
587
- help="Disable version update checks (prevents calls to GitHub API)",
588
- rich_help_panel="Privacy Settings",
589
- ),
590
- ] = False,
591
- disable_pricing_updates: Annotated[
592
- bool,
593
- typer.Option(
594
- "--disable-pricing-updates",
595
- help="Disable pricing data updates (prevents downloads from GitHub)",
596
- rich_help_panel="Privacy Settings",
597
- ),
598
- ] = False,
179
+ # Removed unused flags: plugin_setting, no_network_calls,
180
+ # disable_version_check, disable_pricing_updates
599
181
  ) -> None:
600
- """
601
- Start the CCProxy API server.
602
-
603
- This command starts the API server either locally or in Docker.
604
- The server provides both Anthropic and OpenAI-compatible endpoints.
605
-
606
- All configuration options can be provided via CLI parameters,
607
- which override values from configuration files and environment variables.
608
-
609
- Examples:
610
- ccproxy serve
611
- ccproxy serve --port 8080 --reload
612
- ccproxy serve --docker
613
- ccproxy serve --docker --docker-image custom:latest --port 8080
614
- ccproxy serve --max-thinking-tokens 10000 --allowed-tools Read,Write,Bash
615
- ccproxy serve --port 8080 --workers 4
616
- """
182
+ """Start the CCProxy API server."""
617
183
  try:
618
- # Early logging - use basic print until logging is configured
619
- # We'll log this properly after logging is configured
620
-
621
- # Get config path from context if not provided directly
622
184
  if config is None:
623
185
  config = get_config_path_from_context()
624
186
 
625
- # Create option containers for better organization
626
- server_options = ServerOptions(
627
- port=port,
628
- host=host,
629
- reload=reload,
630
- log_level=log_level,
631
- log_file=log_file,
632
- use_terminal_confirmation_handler=use_terminal_permission_handler,
633
- )
634
-
635
- claude_options = ClaudeOptions(
636
- max_thinking_tokens=max_thinking_tokens,
637
- allowed_tools=allowed_tools,
638
- disallowed_tools=disallowed_tools,
639
- claude_cli_path=claude_cli_path,
640
- append_system_prompt=append_system_prompt,
641
- permission_mode=permission_mode,
642
- max_turns=max_turns,
643
- cwd=cwd,
644
- permission_prompt_tool_name=permission_prompt_tool_name,
645
- sdk_message_mode=sdk_message_mode,
646
- sdk_pool=sdk_pool,
647
- sdk_pool_size=sdk_pool_size,
648
- sdk_session_pool=sdk_session_pool,
649
- system_prompt_injection_mode=system_prompt_injection_mode,
650
- builtin_permissions=builtin_permissions,
651
- )
652
-
653
- security_options = SecurityOptions(auth_token=auth_token)
654
-
655
- # Handle network control flags
656
- scheduler_overrides = {}
657
- if no_network_calls:
658
- # Disable both network features
659
- scheduler_overrides["pricing_update_enabled"] = False
660
- scheduler_overrides["version_check_enabled"] = False
661
- else:
662
- # Handle individual flags
663
- if disable_pricing_updates:
664
- scheduler_overrides["pricing_update_enabled"] = False
665
- if disable_version_check:
666
- scheduler_overrides["version_check_enabled"] = False
667
-
668
- # Extract CLI overrides from structured option containers
669
- cli_overrides = config_manager.get_cli_overrides_from_args(
670
- # Server options
671
- host=server_options.host,
672
- port=server_options.port,
673
- reload=server_options.reload,
674
- log_level=server_options.log_level,
675
- log_file=server_options.log_file,
676
- use_terminal_confirmation_handler=server_options.use_terminal_confirmation_handler,
677
- # Security options
678
- auth_token=security_options.auth_token,
679
- # Claude options
680
- claude_cli_path=claude_options.claude_cli_path,
681
- max_thinking_tokens=claude_options.max_thinking_tokens,
682
- allowed_tools=claude_options.allowed_tools,
683
- disallowed_tools=claude_options.disallowed_tools,
684
- append_system_prompt=claude_options.append_system_prompt,
685
- permission_mode=claude_options.permission_mode,
686
- max_turns=claude_options.max_turns,
687
- permission_prompt_tool_name=claude_options.permission_prompt_tool_name,
688
- cwd=claude_options.cwd,
689
- sdk_message_mode=claude_options.sdk_message_mode,
690
- sdk_pool=claude_options.sdk_pool,
691
- sdk_pool_size=claude_options.sdk_pool_size,
692
- sdk_session_pool=claude_options.sdk_session_pool,
693
- system_prompt_injection_mode=claude_options.system_prompt_injection_mode,
694
- builtin_permissions=claude_options.builtin_permissions,
695
- )
696
-
697
- # Add scheduler overrides if any
698
- if scheduler_overrides:
699
- cli_overrides["scheduler"] = scheduler_overrides
700
-
701
- # Load settings with CLI overrides
702
- settings = config_manager.load_settings(
703
- config_path=config, cli_overrides=cli_overrides
704
- )
187
+ # Base CLI context; plugin-injected args merged below
188
+ cli_context = {
189
+ "port": port,
190
+ "host": host,
191
+ "reload": reload,
192
+ "log_level": log_level,
193
+ "log_file": log_file,
194
+ "auth_token": auth_token,
195
+ "enabled_plugins": enable_plugin,
196
+ "disabled_plugins": disable_plugin,
197
+ }
198
+
199
+ # Merge plugin-provided CLI args via helper
200
+ try:
201
+ from ccproxy.cli.helpers import get_plugin_cli_args
202
+
203
+ plugin_args = get_plugin_cli_args()
204
+ if plugin_args:
205
+ cli_context.update(plugin_args)
206
+ except Exception:
207
+ pass
208
+
209
+ # Pass CLI context to settings creation
210
+ settings = Settings.from_config(config_path=config, cli_context=cli_context)
705
211
 
706
- # Set up logging once with the effective log level
707
- # Import here to avoid circular import
708
-
709
- from ccproxy.core.logging import setup_logging
710
-
711
- # Always reconfigure logging to ensure log level changes are picked up
712
- # Use JSON logs if explicitly requested via env var
713
212
  setup_logging(
714
- json_logs=settings.server.log_format == "json",
715
- log_level_name=settings.server.log_level,
716
- log_file=settings.server.log_file,
213
+ json_logs=settings.logging.format == "json",
214
+ log_level_name=settings.logging.level,
215
+ log_file=settings.logging.file,
717
216
  )
718
217
 
719
- # Re-get logger after logging is configured
720
218
  logger = get_logger(__name__)
721
219
 
722
- # Test debug logging
723
- logger.debug(
724
- "Debug logging is enabled",
725
- effective_log_level=server_options.log_level or settings.server.log_level,
726
- )
727
-
728
- # Log CLI command that was deferred
729
- logger.info(
730
- "cli_command_starting",
731
- command="serve",
732
- version=__version__,
733
- docker=docker,
734
- port=server_options.port,
735
- host=server_options.host,
736
- config_path=str(config) if config else None,
737
- )
738
-
739
- # Log effective configuration
740
220
  logger.debug(
741
221
  "configuration_loaded",
742
222
  host=settings.server.host,
743
223
  port=settings.server.port,
744
- log_level=settings.server.log_level,
745
- log_file=settings.server.log_file,
746
- docker_mode=docker,
747
- docker_image=settings.docker.docker_image if docker else None,
224
+ log_level=settings.logging.level,
225
+ log_file=settings.logging.file,
748
226
  auth_enabled=bool(settings.security.auth_token),
749
- duckdb_enabled=settings.observability.duckdb_enabled,
750
- duckdb_path=settings.observability.duckdb_path
751
- if settings.observability.duckdb_enabled
752
- else None,
753
- claude_cli_path=settings.claude.cli_path,
227
+ duckdb_enabled=bool(
228
+ (settings.plugins.get("duckdb_storage") or {}).get("enabled", False)
229
+ ),
754
230
  )
755
231
 
756
- if docker:
757
- _run_docker_server(
758
- settings,
759
- docker_image=docker_image,
760
- docker_env=docker_env,
761
- docker_volume=docker_volume,
762
- docker_arg=docker_arg,
763
- docker_home=docker_home,
764
- docker_workspace=docker_workspace,
765
- user_mapping_enabled=user_mapping_enabled,
766
- user_uid=user_uid,
767
- user_gid=user_gid,
768
- )
769
- else:
770
- _run_local_server(settings, cli_overrides)
232
+ _run_local_server(settings)
771
233
 
772
234
  except ConfigurationError as e:
773
235
  toolkit = get_rich_toolkit()
774
236
  toolkit.print(f"Configuration error: {e}", tag="error")
775
237
  raise typer.Exit(1) from e
776
- except Exception as e:
238
+ except OSError as e:
777
239
  toolkit = get_rich_toolkit()
778
- toolkit.print(f"Error starting server: {e}", tag="error")
779
- raise typer.Exit(1) from e
780
-
781
-
782
- def claude(
783
- args: Annotated[
784
- list[str] | None,
785
- typer.Argument(
786
- help="Arguments to pass to claude CLI (e.g. --version, doctor, config)",
787
- ),
788
- ] = None,
789
- docker: Annotated[
790
- bool,
791
- typer.Option(
792
- "--docker",
793
- "-d",
794
- help="Run claude command from docker image instead of local CLI",
795
- ),
796
- ] = False,
797
- # Docker settings using shared parameters
798
- docker_image: Annotated[
799
- str | None,
800
- typer.Option(
801
- "--docker-image",
802
- help="Docker image to use (overrides configuration)",
803
- rich_help_panel="Docker Settings",
804
- ),
805
- ] = None,
806
- docker_env: Annotated[
807
- list[str] | None,
808
- typer.Option(
809
- "--docker-env",
810
- "-e",
811
- help="Environment variables to pass to Docker container",
812
- rich_help_panel="Docker Settings",
813
- ),
814
- ] = None,
815
- docker_volume: Annotated[
816
- list[str] | None,
817
- typer.Option(
818
- "--docker-volume",
819
- "-v",
820
- help="Volume mounts for Docker container",
821
- rich_help_panel="Docker Settings",
822
- ),
823
- ] = None,
824
- docker_arg: Annotated[
825
- list[str] | None,
826
- typer.Option(
827
- "--docker-arg",
828
- help="Additional arguments to pass to docker run",
829
- rich_help_panel="Docker Settings",
830
- ),
831
- ] = None,
832
- docker_home: Annotated[
833
- str | None,
834
- typer.Option(
835
- "--docker-home",
836
- help="Override the home directory for Docker",
837
- rich_help_panel="Docker Settings",
838
- ),
839
- ] = None,
840
- docker_workspace: Annotated[
841
- str | None,
842
- typer.Option(
843
- "--docker-workspace",
844
- help="Override the workspace directory for Docker",
845
- rich_help_panel="Docker Settings",
846
- ),
847
- ] = None,
848
- user_mapping_enabled: Annotated[
849
- bool | None,
850
- typer.Option(
851
- "--user-mapping/--no-user-mapping",
852
- help="Enable user mapping for Docker",
853
- rich_help_panel="Docker Settings",
854
- ),
855
- ] = None,
856
- user_uid: Annotated[
857
- int | None,
858
- typer.Option(
859
- "--user-uid",
860
- help="User UID for Docker user mapping",
861
- rich_help_panel="Docker Settings",
862
- ),
863
- ] = None,
864
- user_gid: Annotated[
865
- int | None,
866
- typer.Option(
867
- "--user-gid",
868
- help="User GID for Docker user mapping",
869
- rich_help_panel="Docker Settings",
870
- ),
871
- ] = None,
872
- ) -> None:
873
- """
874
- Execute claude CLI commands directly.
875
-
876
- This is a simple pass-through to the claude CLI executable
877
- found by the settings system or run from docker image.
878
-
879
- Examples:
880
- ccproxy claude -- --version
881
- ccproxy claude -- doctor
882
- ccproxy claude -- config
883
- ccproxy claude --docker -- --version
884
- ccproxy claude --docker --docker-image custom:latest -- --version
885
- ccproxy claude --docker --docker-env API_KEY=sk-... --docker-volume ./data:/data -- chat
886
- """
887
- # Handle None args case
888
- if args is None:
889
- args = []
890
-
891
- toolkit = get_rich_toolkit()
892
-
893
- try:
894
- # Logger will be configured by configuration manager
895
- logger = get_logger(__name__)
896
- # Log CLI command execution start
897
- logger.info(
898
- "cli_command_starting",
899
- command="claude",
900
- version=__version__,
901
- docker=docker,
902
- args=args if args else [],
903
- )
904
-
905
- # Load settings using configuration manager
906
- settings = config_manager.load_settings(
907
- config_path=get_config_path_from_context()
240
+ toolkit.print(
241
+ f"Server startup failed (port/permission issue): {e}", tag="error"
908
242
  )
909
-
910
- if docker:
911
- # Prepare Docker execution using new adapter
912
-
913
- toolkit.print_title(f"image {settings.docker.docker_image}", tag="docker")
914
- image, volumes, environment, command, user_context, additional_args = (
915
- _create_docker_adapter_from_settings(
916
- settings,
917
- docker_image=docker_image,
918
- docker_env=docker_env,
919
- docker_volume=docker_volume,
920
- docker_arg=docker_arg,
921
- docker_home=docker_home,
922
- docker_workspace=docker_workspace,
923
- user_mapping_enabled=user_mapping_enabled,
924
- user_uid=user_uid,
925
- user_gid=user_gid,
926
- command=["claude"],
927
- cmd_args=args,
928
- )
929
- )
930
-
931
- cmd_str = " ".join(command or [])
932
- logger.info(
933
- "docker_execution",
934
- image=image,
935
- command=" ".join(command or []),
936
- volumes_count=len(volumes),
937
- env_vars_count=len(environment),
938
- )
939
- toolkit.print(f"Executing: docker run ... {image} {cmd_str}", tag="docker")
940
- toolkit.print_line()
941
-
942
- # Execute using the new Docker adapter
943
- adapter = create_docker_adapter()
944
- adapter.exec_container(
945
- image=image,
946
- volumes=volumes,
947
- environment=environment,
948
- command=command,
949
- user_context=user_context,
950
- )
951
- else:
952
- # Get claude path from settings
953
- claude_path = settings.claude.cli_path
954
- if not claude_path:
955
- toolkit.print("Error: Claude CLI not found.", tag="error")
956
- toolkit.print(
957
- "Please install Claude CLI or configure claude_cli_path.",
958
- tag="error",
959
- )
960
- raise typer.Exit(1)
961
-
962
- # Resolve to absolute path
963
- if not Path(claude_path).is_absolute():
964
- claude_path = str(Path(claude_path).resolve())
965
-
966
- logger.info("local_claude_execution", claude_path=claude_path, args=args)
967
- toolkit.print(f"Executing: {claude_path} {' '.join(args)}", tag="claude")
968
- toolkit.print_line()
969
-
970
- # Execute command directly
971
- try:
972
- # Use os.execvp to replace current process with claude
973
- # This hands over full control to claude, including signal handling
974
- os.execvp(claude_path, [claude_path] + args)
975
- except OSError as e:
976
- toolkit.print(f"Failed to execute command: {e}", tag="error")
977
- raise typer.Exit(1) from e
978
-
979
- except ConfigurationError as e:
980
- logger.error("cli_configuration_error", error=str(e), command="claude")
981
- toolkit.print(f"Configuration error: {e}", tag="error")
243
+ raise typer.Exit(1) from e
244
+ except ImportError as e:
245
+ toolkit = get_rich_toolkit()
246
+ toolkit.print(f"Import error during server startup: {e}", tag="error")
982
247
  raise typer.Exit(1) from e
983
248
  except Exception as e:
984
- logger.error("cli_unexpected_error", error=str(e), command="claude")
985
- toolkit.print(f"Error executing claude command: {e}", tag="error")
249
+ toolkit = get_rich_toolkit()
250
+ toolkit.print(f"Error starting server: {e}", tag="error")
986
251
  raise typer.Exit(1) from e