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
@@ -7,28 +7,19 @@ the KISS principle and avoiding overengineering.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from datetime import UTC, datetime
11
10
  from typing import TYPE_CHECKING
12
11
 
13
12
  import structlog
14
13
  from fastapi import FastAPI
15
14
 
16
- from ccproxy.auth.credentials_adapter import CredentialsAuthManager
17
- from ccproxy.auth.exceptions import CredentialsNotFoundError
18
- from ccproxy.auth.openai.credentials import OpenAITokenManager
19
- from ccproxy.observability import get_metrics
20
-
21
- # Note: get_claude_cli_info is imported locally to avoid circular imports
22
- from ccproxy.observability.storage.duckdb_simple import SimpleDuckDBStorage
23
15
  from ccproxy.scheduler.errors import SchedulerError
24
16
  from ccproxy.scheduler.manager import start_scheduler, stop_scheduler
25
- from ccproxy.services.claude_detection_service import ClaudeDetectionService
26
- from ccproxy.services.claude_sdk_service import ClaudeSDKService
27
- from ccproxy.services.codex_detection_service import CodexDetectionService
28
- from ccproxy.services.credentials.manager import CredentialsManager
29
17
 
30
18
 
31
- # Note: get_permission_service is imported locally to avoid circular imports
19
+ # DuckDB storage initialization is handled by the duckdb_storage plugin.
20
+
21
+
22
+ # get_permission_service is imported locally to avoid circular imports
32
23
 
33
24
  if TYPE_CHECKING:
34
25
  from ccproxy.config.settings import Settings
@@ -36,126 +27,6 @@ if TYPE_CHECKING:
36
27
  logger = structlog.get_logger(__name__)
37
28
 
38
29
 
39
- async def validate_claude_authentication_startup(
40
- app: FastAPI, settings: Settings
41
- ) -> None:
42
- """Validate Claude authentication credentials at startup.
43
-
44
- Args:
45
- app: FastAPI application instance
46
- settings: Application settings
47
- """
48
- try:
49
- credentials_manager = CredentialsManager()
50
- validation = await credentials_manager.validate()
51
-
52
- if validation.valid and not validation.expired:
53
- credentials = validation.credentials
54
- oauth_token = credentials.claude_ai_oauth if credentials else None
55
-
56
- if oauth_token and oauth_token.expires_at_datetime:
57
- hours_until_expiry = int(
58
- (
59
- oauth_token.expires_at_datetime - datetime.now(UTC)
60
- ).total_seconds()
61
- / 3600
62
- )
63
- logger.debug(
64
- "claude_token_valid",
65
- expires_in_hours=hours_until_expiry,
66
- subscription_type=oauth_token.subscription_type,
67
- credentials_path=str(validation.path) if validation.path else None,
68
- )
69
- else:
70
- logger.debug(
71
- "claude_token_valid", credentials_path=str(validation.path)
72
- )
73
- elif validation.expired:
74
- logger.warning(
75
- "claude_token_expired",
76
- message="Claude authentication token has expired. Please run 'ccproxy auth login' to refresh.",
77
- credentials_path=str(validation.path) if validation.path else None,
78
- )
79
- else:
80
- logger.warning(
81
- "claude_token_invalid",
82
- message="Claude authentication token is invalid. Please run 'ccproxy auth login'.",
83
- credentials_path=str(validation.path) if validation.path else None,
84
- )
85
- except CredentialsNotFoundError:
86
- logger.warning(
87
- "claude_token_not_found",
88
- message="No Claude authentication credentials found. Please run 'ccproxy auth login' to authenticate.",
89
- searched_paths=settings.auth.storage.storage_paths,
90
- )
91
- except Exception as e:
92
- logger.error(
93
- "claude_token_validation_error",
94
- error=str(e),
95
- message="Failed to validate Claude authentication token. The server will continue without Claude authentication.",
96
- exc_info=True,
97
- )
98
-
99
-
100
- async def validate_codex_authentication_startup(
101
- app: FastAPI, settings: Settings
102
- ) -> None:
103
- """Validate Codex (OpenAI) authentication credentials at startup.
104
-
105
- Args:
106
- app: FastAPI application instance
107
- settings: Application settings
108
- """
109
- # Skip codex authentication validation if codex is disabled
110
- if not settings.codex.enabled:
111
- logger.debug("codex_token_validation_skipped", reason="codex_disabled")
112
- return
113
-
114
- try:
115
- token_manager = OpenAITokenManager()
116
- credentials = await token_manager.load_credentials()
117
-
118
- if not credentials:
119
- logger.warning(
120
- "codex_token_not_found",
121
- message="No Codex authentication credentials found. Please run 'ccproxy auth login-openai' to authenticate.",
122
- location=token_manager.get_storage_location(),
123
- )
124
- return
125
-
126
- if not credentials.active:
127
- logger.warning(
128
- "codex_token_inactive",
129
- message="Codex authentication credentials are inactive. Please run 'ccproxy auth login-openai' to refresh.",
130
- location=token_manager.get_storage_location(),
131
- )
132
- return
133
-
134
- if credentials.is_expired():
135
- logger.warning(
136
- "codex_token_expired",
137
- message="Codex authentication token has expired. Please run 'ccproxy auth login-openai' to refresh.",
138
- location=token_manager.get_storage_location(),
139
- expires_at=credentials.expires_at.isoformat(),
140
- )
141
- else:
142
- hours_until_expiry = int(credentials.expires_in_seconds() / 3600)
143
- logger.debug(
144
- "codex_token_valid",
145
- expires_in_hours=hours_until_expiry,
146
- account_id=credentials.account_id,
147
- location=token_manager.get_storage_location(),
148
- )
149
-
150
- except Exception as e:
151
- logger.error(
152
- "codex_token_validation_error",
153
- error=str(e),
154
- message="Failed to validate Codex authentication token. The server will continue without Codex authentication.",
155
- exc_info=True,
156
- )
157
-
158
-
159
30
  async def check_version_updates_startup(app: FastAPI, settings: Settings) -> None:
160
31
  """Trigger version update check at startup.
161
32
 
@@ -167,6 +38,10 @@ async def check_version_updates_startup(app: FastAPI, settings: Settings) -> Non
167
38
  settings: Application settings
168
39
  """
169
40
  # Skip version check if disabled by settings
41
+ if not settings.scheduler.enabled:
42
+ logger.debug("version_check_startup_skipped_scheduler_disabled")
43
+ return
44
+
170
45
  if not settings.scheduler.version_check_enabled:
171
46
  logger.debug("version_check_startup_disabled")
172
47
  return
@@ -192,124 +67,32 @@ async def check_version_updates_startup(app: FastAPI, settings: Settings) -> Non
192
67
  else:
193
68
  logger.debug("version_check_startup_failed")
194
69
 
195
- except Exception as e:
70
+ except (ImportError, ModuleNotFoundError) as e:
196
71
  logger.debug(
197
- "version_check_startup_error",
72
+ "version_check_startup_import_error",
198
73
  error=str(e),
199
74
  error_type=type(e).__name__,
200
75
  )
201
-
202
-
203
- async def check_claude_cli_startup(app: FastAPI, settings: Settings) -> None:
204
- """Check Claude CLI availability at startup.
205
-
206
- Args:
207
- app: FastAPI application instance
208
- settings: Application settings
209
- """
210
- try:
211
- from ccproxy.api.routes.health import get_claude_cli_info
212
-
213
- claude_info = await get_claude_cli_info()
214
-
215
- if claude_info.status == "available":
216
- logger.info(
217
- "claude_cli_available",
218
- status=claude_info.status,
219
- version=claude_info.version,
220
- binary_path=claude_info.binary_path,
221
- )
222
- else:
223
- logger.warning(
224
- "claude_cli_unavailable",
225
- status=claude_info.status,
226
- error=claude_info.error,
227
- binary_path=claude_info.binary_path,
228
- message=f"Claude CLI status: {claude_info.status}",
229
- )
230
76
  except Exception as e:
231
- logger.error(
232
- "claude_cli_check_failed",
77
+ logger.debug(
78
+ "version_check_startup_unexpected_error",
233
79
  error=str(e),
234
- message="Failed to check Claude CLI status during startup",
80
+ error_type=type(e).__name__,
235
81
  )
236
82
 
237
83
 
238
- async def check_codex_cli_startup(app: FastAPI, settings: Settings) -> None:
239
- """Check Codex CLI availability at startup.
84
+ async def check_claude_cli_startup(app: FastAPI, settings: Settings) -> None:
85
+ """Check Claude CLI availability at startup.
240
86
 
241
87
  Args:
242
88
  app: FastAPI application instance
243
89
  settings: Application settings
244
90
  """
245
- try:
246
- from ccproxy.api.routes.health import get_codex_cli_info
247
-
248
- codex_info = await get_codex_cli_info()
249
-
250
- if codex_info.status == "available":
251
- logger.info(
252
- "codex_cli_available",
253
- status=codex_info.status,
254
- version=codex_info.version,
255
- binary_path=codex_info.binary_path,
256
- )
257
- else:
258
- logger.warning(
259
- "codex_cli_unavailable",
260
- status=codex_info.status,
261
- error=codex_info.error,
262
- binary_path=codex_info.binary_path,
263
- message=f"Codex CLI status: {codex_info.status}",
264
- )
265
- except Exception as e:
266
- logger.error(
267
- "codex_cli_check_failed",
268
- error=str(e),
269
- message="Failed to check Codex CLI status during startup",
270
- )
271
-
272
-
273
- async def initialize_log_storage_startup(app: FastAPI, settings: Settings) -> None:
274
- """Initialize log storage if needed and backend is DuckDB.
91
+ # Claude CLI check is now handled by the plugin
92
+ pass
275
93
 
276
- Args:
277
- app: FastAPI application instance
278
- settings: Application settings
279
- """
280
- if (
281
- settings.observability.needs_storage_backend
282
- and settings.observability.log_storage_backend == "duckdb"
283
- ):
284
- try:
285
- storage = SimpleDuckDBStorage(
286
- database_path=settings.observability.duckdb_path
287
- )
288
- await storage.initialize()
289
- app.state.log_storage = storage
290
- logger.debug(
291
- "log_storage_initialized",
292
- backend="duckdb",
293
- path=str(settings.observability.duckdb_path),
294
- collection_enabled=settings.observability.logs_collection_enabled,
295
- )
296
- except Exception as e:
297
- logger.error("log_storage_initialization_failed", error=str(e))
298
- # Continue without log storage (graceful degradation)
299
94
 
300
-
301
- async def initialize_log_storage_shutdown(app: FastAPI) -> None:
302
- """Close log storage if initialized.
303
-
304
- Args:
305
- app: FastAPI application instance
306
- """
307
- if hasattr(app.state, "log_storage") and app.state.log_storage:
308
- try:
309
- await app.state.log_storage.close()
310
- logger.debug("log_storage_closed")
311
- except Exception as e:
312
- logger.error("log_storage_close_failed", error=str(e))
95
+ # DuckDB storage startup/shutdown handled by plugin
313
96
 
314
97
 
315
98
  async def setup_scheduler_startup(app: FastAPI, settings: Settings) -> None:
@@ -320,9 +103,14 @@ async def setup_scheduler_startup(app: FastAPI, settings: Settings) -> None:
320
103
  settings: Application settings
321
104
  """
322
105
  try:
323
- scheduler = await start_scheduler(settings)
106
+ # Use DI container to resolve registry and dependencies
107
+ container = app.state.service_container
108
+ scheduler = await start_scheduler(settings, container)
324
109
  app.state.scheduler = scheduler
325
- logger.debug("scheduler_initialized")
110
+ if scheduler:
111
+ logger.debug("scheduler_initialized")
112
+ else:
113
+ logger.debug("scheduler_skipped_initialization")
326
114
 
327
115
  # Add session pool stats task if session manager is available
328
116
  if (
@@ -340,11 +128,19 @@ async def setup_scheduler_startup(app: FastAPI, settings: Settings) -> None:
340
128
  pool_manager=app.state.session_manager,
341
129
  )
342
130
  logger.debug("session_pool_stats_task_added", interval_seconds=60)
131
+ except (ImportError, ModuleNotFoundError) as e:
132
+ logger.error(
133
+ "session_pool_stats_task_add_import_error",
134
+ error=str(e),
135
+ error_type=type(e).__name__,
136
+ exc_info=e,
137
+ )
343
138
  except Exception as e:
344
139
  logger.error(
345
- "session_pool_stats_task_add_failed",
140
+ "session_pool_stats_task_add_unexpected_error",
346
141
  error=str(e),
347
142
  error_type=type(e).__name__,
143
+ exc_info=e,
348
144
  )
349
145
  except SchedulerError as e:
350
146
  logger.error("scheduler_initialization_failed", error=str(e))
@@ -375,227 +171,53 @@ async def setup_session_manager_shutdown(app: FastAPI) -> None:
375
171
  try:
376
172
  await app.state.session_manager.shutdown()
377
173
  logger.debug("claude_sdk_session_manager_shutdown")
174
+ except (ImportError, ModuleNotFoundError) as e:
175
+ logger.error(
176
+ "claude_sdk_session_manager_shutdown_import_error",
177
+ error=str(e),
178
+ exc_info=e,
179
+ )
378
180
  except Exception as e:
379
- logger.error("claude_sdk_session_manager_shutdown_failed", error=str(e))
380
-
381
-
382
- async def initialize_claude_detection_startup(app: FastAPI, settings: Settings) -> None:
383
- """Initialize Claude detection service.
384
-
385
- Args:
386
- app: FastAPI application instance
387
- settings: Application settings
388
- """
389
- try:
390
- logger.debug("initializing_claude_detection")
391
- detection_service = ClaudeDetectionService(settings)
392
- claude_data = await detection_service.initialize_detection()
393
- app.state.claude_detection_data = claude_data
394
- app.state.claude_detection_service = detection_service
395
- logger.debug(
396
- "claude_detection_completed",
397
- version=claude_data.claude_version,
398
- cached_at=claude_data.cached_at.isoformat(),
399
- )
400
- except Exception as e:
401
- logger.error("claude_detection_startup_failed", error=str(e))
402
- # Continue startup with fallback - detection service will provide fallback data
403
- detection_service = ClaudeDetectionService(settings)
404
- app.state.claude_detection_data = detection_service._get_fallback_data()
405
- app.state.claude_detection_service = detection_service
406
-
407
-
408
- async def initialize_codex_detection_startup(app: FastAPI, settings: Settings) -> None:
409
- """Initialize Codex detection service.
410
-
411
- Args:
412
- app: FastAPI application instance
413
- settings: Application settings
414
- """
415
- # Skip codex detection if codex is disabled
416
- if not settings.codex.enabled:
417
- logger.debug("codex_detection_skipped", reason="codex_disabled")
418
- detection_service = CodexDetectionService(settings)
419
- app.state.codex_detection_data = detection_service._get_fallback_data()
420
- app.state.codex_detection_service = detection_service
421
- return
422
-
423
- # Check if Codex CLI is available before attempting header detection
424
- from ccproxy.api.routes.health import get_codex_cli_info
425
-
426
- codex_info = await get_codex_cli_info()
427
- if codex_info.status != "available":
428
- logger.debug(
429
- "codex_detection_skipped",
430
- reason="codex_cli_not_available",
431
- status=codex_info.status,
432
- )
433
- detection_service = CodexDetectionService(settings)
434
- app.state.codex_detection_data = detection_service._get_fallback_data()
435
- app.state.codex_detection_service = detection_service
436
- return
437
-
438
- try:
439
- logger.debug("initializing_codex_detection")
440
- detection_service = CodexDetectionService(settings)
441
- codex_data = await detection_service.initialize_detection()
442
- app.state.codex_detection_data = codex_data
443
- app.state.codex_detection_service = detection_service
444
- logger.debug(
445
- "codex_detection_completed",
446
- version=codex_data.codex_version,
447
- cached_at=codex_data.cached_at.isoformat(),
448
- )
449
- except Exception as e:
450
- logger.error("codex_detection_startup_failed", error=str(e))
451
- # Continue startup with fallback - detection service will provide fallback data
452
- detection_service = CodexDetectionService(settings)
453
- app.state.codex_detection_data = detection_service._get_fallback_data()
454
- app.state.codex_detection_service = detection_service
455
-
456
-
457
- async def initialize_claude_sdk_startup(app: FastAPI, settings: Settings) -> None:
458
- """Initialize ClaudeSDKService and store in app state.
459
-
460
- Args:
461
- app: FastAPI application instance
462
- settings: Application settings
463
- """
464
- try:
465
- # Create auth manager with settings
466
- auth_manager = CredentialsAuthManager()
467
-
468
- # Get global metrics instance
469
- metrics = get_metrics()
470
-
471
- # Check if session pool should be enabled from settings configuration
472
- use_session_pool = settings.claude.sdk_session_pool.enabled
473
-
474
- # Initialize session manager if session pool is enabled
475
- session_manager = None
476
- if use_session_pool:
477
- from ccproxy.claude_sdk.manager import SessionManager
478
-
479
- # Create SessionManager with dependency injection
480
- session_manager = SessionManager(
481
- settings=settings, metrics_factory=lambda: metrics
181
+ logger.error(
182
+ "claude_sdk_session_manager_shutdown_unexpected_error",
183
+ error=str(e),
184
+ exc_info=e,
482
185
  )
483
186
 
484
- # Start the session manager (initializes session pool if enabled)
485
- await session_manager.start()
486
-
487
- # Create ClaudeSDKService instance
488
- claude_service = ClaudeSDKService(
489
- auth_manager=auth_manager,
490
- metrics=metrics,
491
- settings=settings,
492
- session_manager=session_manager,
493
- )
494
-
495
- # Store in app state for reuse in dependencies
496
- app.state.claude_service = claude_service
497
- app.state.session_manager = (
498
- session_manager # Store session_manager for shutdown
499
- )
500
- logger.debug("claude_sdk_service_initialized")
501
- except Exception as e:
502
- logger.error("claude_sdk_service_initialization_failed", error=str(e))
503
- # Continue startup even if ClaudeSDKService fails (graceful degradation)
504
-
505
187
 
506
- async def initialize_permission_service_startup(
188
+ async def initialize_service_container_startup(
507
189
  app: FastAPI, settings: Settings
508
190
  ) -> None:
509
- """Initialize permission service (conditional on builtin_permissions).
191
+ """Initialize service container and proxy client.
510
192
 
511
193
  Args:
512
194
  app: FastAPI application instance
513
195
  settings: Application settings
514
196
  """
515
- if settings.claude.builtin_permissions:
516
- try:
517
- from ccproxy.api.services.permission_service import get_permission_service
518
-
519
- permission_service = get_permission_service()
520
-
521
- # Only connect terminal handler if not using external handler
522
- if settings.server.use_terminal_permission_handler:
523
- # terminal_handler = TerminalPermissionHandler()
524
-
525
- # TODO: Terminal handler should subscribe to events from the service
526
- # instead of trying to set a handler directly
527
- # The service uses an event-based architecture, not direct handlers
528
-
529
- # logger.info(
530
- # "permission_handler_configured",
531
- # handler_type="terminal",
532
- # message="Connected terminal handler to permission service",
533
- # )
534
- # app.state.terminal_handler = terminal_handler
535
- pass
536
- else:
537
- logger.debug(
538
- "permission_handler_configured",
539
- handler_type="external_sse",
540
- message="Terminal permission handler disabled - use 'ccproxy permission-handler connect' to handle permissions",
541
- )
542
- logger.warning(
543
- "permission_handler_required",
544
- message="Start external handler with: ccproxy permission-handler connect",
545
- )
197
+ try:
198
+ # Create HTTP client for proxy
199
+ from ccproxy.services.container import ServiceContainer
546
200
 
547
- # Start the permission service
548
- await permission_service.start()
201
+ # Reuse ServiceContainer from app state or create new one
202
+ if hasattr(app.state, "service_container"):
203
+ container = app.state.service_container
204
+ else:
205
+ logger.debug("creating_new_service_container")
206
+ container = ServiceContainer(settings)
207
+ app.state.service_container = container
549
208
 
550
- # Store references in app state
551
- app.state.permission_service = permission_service
209
+ # Metrics are now handled by the metrics plugin
210
+ app.state.metrics = None
552
211
 
553
- logger.debug(
554
- "permission_service_initialized",
555
- timeout_seconds=permission_service._timeout_seconds,
556
- terminal_handler_enabled=settings.server.use_terminal_permission_handler,
557
- builtin_permissions_enabled=True,
558
- )
559
- except Exception as e:
560
- logger.error("permission_service_initialization_failed", error=str(e))
561
- # Continue without permission service (API will work but without prompts)
562
- else:
563
- logger.debug(
564
- "permission_service_skipped",
565
- builtin_permissions_enabled=False,
566
- message="Built-in permission handling disabled - users can configure custom MCP servers and permission tools",
212
+ logger.debug("service_container_initialized")
213
+ except (ImportError, ModuleNotFoundError) as e:
214
+ logger.error(
215
+ "service_container_initialization_import_error", error=str(e), exc_info=e
567
216
  )
568
-
569
-
570
- async def setup_permission_service_shutdown(app: FastAPI, settings: Settings) -> None:
571
- """Stop permission service (if it was initialized).
572
-
573
- Args:
574
- app: FastAPI application instance
575
- settings: Application settings
576
- """
577
- if (
578
- hasattr(app.state, "permission_service")
579
- and app.state.permission_service
580
- and settings.claude.builtin_permissions
581
- ):
582
- try:
583
- await app.state.permission_service.stop()
584
- logger.debug("permission_service_stopped")
585
- except Exception as e:
586
- logger.error("permission_service_stop_failed", error=str(e))
587
-
588
-
589
- async def flush_streaming_batches_shutdown(app: FastAPI) -> None:
590
- """Flush any remaining streaming log batches.
591
-
592
- Args:
593
- app: FastAPI application instance
594
- """
595
- try:
596
- from ccproxy.utils.simple_request_logger import flush_all_streaming_batches
597
-
598
- await flush_all_streaming_batches()
599
- logger.debug("streaming_batches_flushed")
600
217
  except Exception as e:
601
- logger.error("streaming_batches_flush_failed", error=str(e))
218
+ logger.error(
219
+ "service_container_initialization_unexpected_error",
220
+ error=str(e),
221
+ exc_info=e,
222
+ )
223
+ # Continue startup even if service container fails (graceful degradation)