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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +439 -212
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +145 -176
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +402 -530
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +558 -0
  97. ccproxy/data/codex_headers_fallback.json +121 -0
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +63 -107
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +346 -314
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +95 -342
  387. ccproxy/utils/version_checker.py +279 -6
  388. ccproxy_api-0.2.0.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1231
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -269
  458. ccproxy/services/codex_detection_service.py +0 -263
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.6.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.6.dist-info/RECORD +0 -189
  473. ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,27 +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.observability import get_metrics
19
-
20
- # Note: get_claude_cli_info is imported locally to avoid circular imports
21
- from ccproxy.observability.storage.duckdb_simple import SimpleDuckDBStorage
22
15
  from ccproxy.scheduler.errors import SchedulerError
23
16
  from ccproxy.scheduler.manager import start_scheduler, stop_scheduler
24
- from ccproxy.services.claude_detection_service import ClaudeDetectionService
25
- from ccproxy.services.claude_sdk_service import ClaudeSDKService
26
- from ccproxy.services.codex_detection_service import CodexDetectionService
27
- from ccproxy.services.credentials.manager import CredentialsManager
28
17
 
29
18
 
30
- # 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
31
23
 
32
24
  if TYPE_CHECKING:
33
25
  from ccproxy.config.settings import Settings
@@ -35,173 +27,72 @@ if TYPE_CHECKING:
35
27
  logger = structlog.get_logger(__name__)
36
28
 
37
29
 
38
- async def validate_authentication_startup(app: FastAPI, settings: Settings) -> None:
39
- """Validate authentication credentials at startup.
30
+ async def check_version_updates_startup(app: FastAPI, settings: Settings) -> None:
31
+ """Trigger version update check at startup.
32
+
33
+ Manually runs the version check task once during application startup,
34
+ before the scheduler starts managing periodic checks.
40
35
 
41
36
  Args:
42
37
  app: FastAPI application instance
43
38
  settings: Application settings
44
39
  """
45
- try:
46
- credentials_manager = CredentialsManager()
47
- validation = await credentials_manager.validate()
48
-
49
- if validation.valid and not validation.expired:
50
- credentials = validation.credentials
51
- oauth_token = credentials.claude_ai_oauth if credentials else None
52
-
53
- if oauth_token and oauth_token.expires_at_datetime:
54
- hours_until_expiry = int(
55
- (
56
- oauth_token.expires_at_datetime - datetime.now(UTC)
57
- ).total_seconds()
58
- / 3600
59
- )
60
- logger.debug(
61
- "auth_token_valid",
62
- expires_in_hours=hours_until_expiry,
63
- subscription_type=oauth_token.subscription_type,
64
- credentials_path=str(validation.path) if validation.path else None,
65
- )
66
- else:
67
- logger.debug("auth_token_valid", credentials_path=str(validation.path))
68
- elif validation.expired:
69
- logger.warning(
70
- "auth_token_expired",
71
- message="Authentication token has expired. Please run 'ccproxy auth login' to refresh.",
72
- credentials_path=str(validation.path) if validation.path else None,
73
- )
74
- else:
75
- logger.warning(
76
- "auth_token_invalid",
77
- message="Authentication token is invalid. Please run 'ccproxy auth login'.",
78
- credentials_path=str(validation.path) if validation.path else None,
79
- )
80
- except CredentialsNotFoundError:
81
- logger.warning(
82
- "auth_token_not_found",
83
- message="No authentication credentials found. Please run 'ccproxy auth login' to authenticate.",
84
- searched_paths=settings.auth.storage.storage_paths,
85
- )
86
- except Exception as e:
87
- logger.error(
88
- "auth_token_validation_error",
89
- error=str(e),
90
- message="Failed to validate authentication token. The server will continue without authentication.",
91
- exc_info=True,
92
- )
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
93
44
 
45
+ if not settings.scheduler.version_check_enabled:
46
+ logger.debug("version_check_startup_disabled")
47
+ return
94
48
 
95
- async def check_claude_cli_startup(app: FastAPI, settings: Settings) -> None:
96
- """Check Claude CLI availability at startup.
97
-
98
- Args:
99
- app: FastAPI application instance
100
- settings: Application settings
101
- """
102
49
  try:
103
- from ccproxy.api.routes.health import get_claude_cli_info
50
+ # Import locally to avoid circular imports and create task instance
51
+ from ccproxy.scheduler.tasks import VersionUpdateCheckTask
52
+
53
+ # Create a temporary task instance for startup check
54
+ version_task = VersionUpdateCheckTask(
55
+ name="version_check_startup",
56
+ interval_seconds=settings.scheduler.version_check_interval_hours * 3600,
57
+ enabled=True,
58
+ version_check_cache_ttl_hours=settings.scheduler.version_check_cache_ttl_hours,
59
+ skip_first_scheduled_run=False,
60
+ )
104
61
 
105
- claude_info = await get_claude_cli_info()
62
+ # Run the version check once and wait for it to complete
63
+ success = await version_task.run()
106
64
 
107
- if claude_info.status == "available":
108
- logger.info(
109
- "claude_cli_available",
110
- status=claude_info.status,
111
- version=claude_info.version,
112
- binary_path=claude_info.binary_path,
113
- )
65
+ if success:
66
+ logger.debug("version_check_startup_completed")
114
67
  else:
115
- logger.warning(
116
- "claude_cli_unavailable",
117
- status=claude_info.status,
118
- error=claude_info.error,
119
- binary_path=claude_info.binary_path,
120
- message=f"Claude CLI status: {claude_info.status}",
121
- )
122
- except Exception as e:
123
- logger.error(
124
- "claude_cli_check_failed",
68
+ logger.debug("version_check_startup_failed")
69
+
70
+ except (ImportError, ModuleNotFoundError) as e:
71
+ logger.debug(
72
+ "version_check_startup_import_error",
125
73
  error=str(e),
126
- message="Failed to check Claude CLI status during startup",
74
+ error_type=type(e).__name__,
127
75
  )
128
-
129
-
130
- async def check_codex_cli_startup(app: FastAPI, settings: Settings) -> None:
131
- """Check Codex CLI availability at startup.
132
-
133
- Args:
134
- app: FastAPI application instance
135
- settings: Application settings
136
- """
137
- try:
138
- from ccproxy.api.routes.health import get_codex_cli_info
139
-
140
- codex_info = await get_codex_cli_info()
141
-
142
- if codex_info.status == "available":
143
- logger.info(
144
- "codex_cli_available",
145
- status=codex_info.status,
146
- version=codex_info.version,
147
- binary_path=codex_info.binary_path,
148
- )
149
- else:
150
- logger.warning(
151
- "codex_cli_unavailable",
152
- status=codex_info.status,
153
- error=codex_info.error,
154
- binary_path=codex_info.binary_path,
155
- message=f"Codex CLI status: {codex_info.status}",
156
- )
157
76
  except Exception as e:
158
- logger.error(
159
- "codex_cli_check_failed",
77
+ logger.debug(
78
+ "version_check_startup_unexpected_error",
160
79
  error=str(e),
161
- message="Failed to check Codex CLI status during startup",
80
+ error_type=type(e).__name__,
162
81
  )
163
82
 
164
83
 
165
- async def initialize_log_storage_startup(app: FastAPI, settings: Settings) -> None:
166
- """Initialize log storage if needed and backend is DuckDB.
84
+ async def check_claude_cli_startup(app: FastAPI, settings: Settings) -> None:
85
+ """Check Claude CLI availability at startup.
167
86
 
168
87
  Args:
169
88
  app: FastAPI application instance
170
89
  settings: Application settings
171
90
  """
172
- if (
173
- settings.observability.needs_storage_backend
174
- and settings.observability.log_storage_backend == "duckdb"
175
- ):
176
- try:
177
- storage = SimpleDuckDBStorage(
178
- database_path=settings.observability.duckdb_path
179
- )
180
- await storage.initialize()
181
- app.state.log_storage = storage
182
- logger.debug(
183
- "log_storage_initialized",
184
- backend="duckdb",
185
- path=str(settings.observability.duckdb_path),
186
- collection_enabled=settings.observability.logs_collection_enabled,
187
- )
188
- except Exception as e:
189
- logger.error("log_storage_initialization_failed", error=str(e))
190
- # Continue without log storage (graceful degradation)
91
+ # Claude CLI check is now handled by the plugin
92
+ pass
191
93
 
192
94
 
193
- async def initialize_log_storage_shutdown(app: FastAPI) -> None:
194
- """Close log storage if initialized.
195
-
196
- Args:
197
- app: FastAPI application instance
198
- """
199
- if hasattr(app.state, "log_storage") and app.state.log_storage:
200
- try:
201
- await app.state.log_storage.close()
202
- logger.debug("log_storage_closed")
203
- except Exception as e:
204
- logger.error("log_storage_close_failed", error=str(e))
95
+ # DuckDB storage startup/shutdown handled by plugin
205
96
 
206
97
 
207
98
  async def setup_scheduler_startup(app: FastAPI, settings: Settings) -> None:
@@ -212,9 +103,14 @@ async def setup_scheduler_startup(app: FastAPI, settings: Settings) -> None:
212
103
  settings: Application settings
213
104
  """
214
105
  try:
215
- 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)
216
109
  app.state.scheduler = scheduler
217
- logger.debug("scheduler_initialized")
110
+ if scheduler:
111
+ logger.debug("scheduler_initialized")
112
+ else:
113
+ logger.debug("scheduler_skipped_initialization")
218
114
 
219
115
  # Add session pool stats task if session manager is available
220
116
  if (
@@ -232,11 +128,19 @@ async def setup_scheduler_startup(app: FastAPI, settings: Settings) -> None:
232
128
  pool_manager=app.state.session_manager,
233
129
  )
234
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
+ )
235
138
  except Exception as e:
236
139
  logger.error(
237
- "session_pool_stats_task_add_failed",
140
+ "session_pool_stats_task_add_unexpected_error",
238
141
  error=str(e),
239
142
  error_type=type(e).__name__,
143
+ exc_info=e,
240
144
  )
241
145
  except SchedulerError as e:
242
146
  logger.error("scheduler_initialization_failed", error=str(e))
@@ -267,204 +171,53 @@ async def setup_session_manager_shutdown(app: FastAPI) -> None:
267
171
  try:
268
172
  await app.state.session_manager.shutdown()
269
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
+ )
270
180
  except Exception as e:
271
- logger.error("claude_sdk_session_manager_shutdown_failed", error=str(e))
272
-
273
-
274
- async def initialize_claude_detection_startup(app: FastAPI, settings: Settings) -> None:
275
- """Initialize Claude detection service.
276
-
277
- Args:
278
- app: FastAPI application instance
279
- settings: Application settings
280
- """
281
- try:
282
- logger.debug("initializing_claude_detection")
283
- detection_service = ClaudeDetectionService(settings)
284
- claude_data = await detection_service.initialize_detection()
285
- app.state.claude_detection_data = claude_data
286
- app.state.claude_detection_service = detection_service
287
- logger.debug(
288
- "claude_detection_completed",
289
- version=claude_data.claude_version,
290
- cached_at=claude_data.cached_at.isoformat(),
291
- )
292
- except Exception as e:
293
- logger.error("claude_detection_startup_failed", error=str(e))
294
- # Continue startup with fallback - detection service will provide fallback data
295
- detection_service = ClaudeDetectionService(settings)
296
- app.state.claude_detection_data = detection_service._get_fallback_data()
297
- app.state.claude_detection_service = detection_service
298
-
299
-
300
- async def initialize_codex_detection_startup(app: FastAPI, settings: Settings) -> None:
301
- """Initialize Codex detection service.
302
-
303
- Args:
304
- app: FastAPI application instance
305
- settings: Application settings
306
- """
307
- try:
308
- logger.debug("initializing_codex_detection")
309
- detection_service = CodexDetectionService(settings)
310
- codex_data = await detection_service.initialize_detection()
311
- app.state.codex_detection_data = codex_data
312
- app.state.codex_detection_service = detection_service
313
- logger.debug(
314
- "codex_detection_completed",
315
- version=codex_data.codex_version,
316
- cached_at=codex_data.cached_at.isoformat(),
317
- )
318
- except Exception as e:
319
- logger.error("codex_detection_startup_failed", error=str(e))
320
- # Continue startup with fallback - detection service will provide fallback data
321
- detection_service = CodexDetectionService(settings)
322
- app.state.codex_detection_data = detection_service._get_fallback_data()
323
- app.state.codex_detection_service = detection_service
324
-
325
-
326
- async def initialize_claude_sdk_startup(app: FastAPI, settings: Settings) -> None:
327
- """Initialize ClaudeSDKService and store in app state.
328
-
329
- Args:
330
- app: FastAPI application instance
331
- settings: Application settings
332
- """
333
- try:
334
- # Create auth manager with settings
335
- auth_manager = CredentialsAuthManager()
336
-
337
- # Get global metrics instance
338
- metrics = get_metrics()
339
-
340
- # Check if session pool should be enabled from settings configuration
341
- use_session_pool = settings.claude.sdk_session_pool.enabled
342
-
343
- # Initialize session manager if session pool is enabled
344
- session_manager = None
345
- if use_session_pool:
346
- from ccproxy.claude_sdk.manager import SessionManager
347
-
348
- # Create SessionManager with dependency injection
349
- session_manager = SessionManager(
350
- 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,
351
185
  )
352
186
 
353
- # Start the session manager (initializes session pool if enabled)
354
- await session_manager.start()
355
-
356
- # Create ClaudeSDKService instance
357
- claude_service = ClaudeSDKService(
358
- auth_manager=auth_manager,
359
- metrics=metrics,
360
- settings=settings,
361
- session_manager=session_manager,
362
- )
363
187
 
364
- # Store in app state for reuse in dependencies
365
- app.state.claude_service = claude_service
366
- app.state.session_manager = (
367
- session_manager # Store session_manager for shutdown
368
- )
369
- logger.debug("claude_sdk_service_initialized")
370
- except Exception as e:
371
- logger.error("claude_sdk_service_initialization_failed", error=str(e))
372
- # Continue startup even if ClaudeSDKService fails (graceful degradation)
373
-
374
-
375
- async def initialize_permission_service_startup(
188
+ async def initialize_service_container_startup(
376
189
  app: FastAPI, settings: Settings
377
190
  ) -> None:
378
- """Initialize permission service (conditional on builtin_permissions).
191
+ """Initialize service container and proxy client.
379
192
 
380
193
  Args:
381
194
  app: FastAPI application instance
382
195
  settings: Application settings
383
196
  """
384
- if settings.claude.builtin_permissions:
385
- try:
386
- from ccproxy.api.services.permission_service import get_permission_service
387
-
388
- permission_service = get_permission_service()
389
-
390
- # Only connect terminal handler if not using external handler
391
- if settings.server.use_terminal_permission_handler:
392
- # terminal_handler = TerminalPermissionHandler()
393
-
394
- # TODO: Terminal handler should subscribe to events from the service
395
- # instead of trying to set a handler directly
396
- # The service uses an event-based architecture, not direct handlers
397
-
398
- # logger.info(
399
- # "permission_handler_configured",
400
- # handler_type="terminal",
401
- # message="Connected terminal handler to permission service",
402
- # )
403
- # app.state.terminal_handler = terminal_handler
404
- pass
405
- else:
406
- logger.debug(
407
- "permission_handler_configured",
408
- handler_type="external_sse",
409
- message="Terminal permission handler disabled - use 'ccproxy permission-handler connect' to handle permissions",
410
- )
411
- logger.warning(
412
- "permission_handler_required",
413
- message="Start external handler with: ccproxy permission-handler connect",
414
- )
197
+ try:
198
+ # Create HTTP client for proxy
199
+ from ccproxy.services.container import ServiceContainer
415
200
 
416
- # Start the permission service
417
- 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
418
208
 
419
- # Store references in app state
420
- app.state.permission_service = permission_service
209
+ # Metrics are now handled by the metrics plugin
210
+ app.state.metrics = None
421
211
 
422
- logger.debug(
423
- "permission_service_initialized",
424
- timeout_seconds=permission_service._timeout_seconds,
425
- terminal_handler_enabled=settings.server.use_terminal_permission_handler,
426
- builtin_permissions_enabled=True,
427
- )
428
- except Exception as e:
429
- logger.error("permission_service_initialization_failed", error=str(e))
430
- # Continue without permission service (API will work but without prompts)
431
- else:
432
- logger.debug(
433
- "permission_service_skipped",
434
- builtin_permissions_enabled=False,
435
- 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
436
216
  )
437
-
438
-
439
- async def setup_permission_service_shutdown(app: FastAPI, settings: Settings) -> None:
440
- """Stop permission service (if it was initialized).
441
-
442
- Args:
443
- app: FastAPI application instance
444
- settings: Application settings
445
- """
446
- if (
447
- hasattr(app.state, "permission_service")
448
- and app.state.permission_service
449
- and settings.claude.builtin_permissions
450
- ):
451
- try:
452
- await app.state.permission_service.stop()
453
- logger.debug("permission_service_stopped")
454
- except Exception as e:
455
- logger.error("permission_service_stop_failed", error=str(e))
456
-
457
-
458
- async def flush_streaming_batches_shutdown(app: FastAPI) -> None:
459
- """Flush any remaining streaming log batches.
460
-
461
- Args:
462
- app: FastAPI application instance
463
- """
464
- try:
465
- from ccproxy.utils.simple_request_logger import flush_all_streaming_batches
466
-
467
- await flush_all_streaming_batches()
468
- logger.debug("streaming_batches_flushed")
469
217
  except Exception as e:
470
- 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)