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
@@ -0,0 +1,30 @@
1
+ """
2
+ Application bootstrapping and dependency injection container setup.
3
+
4
+ This module is responsible for the initial setup of the application's core services,
5
+ including configuration loading and service container initialization. It acts as the
6
+ main entry point for assembling the application's components before the web server
7
+ starts.
8
+ """
9
+
10
+ from ccproxy.config.settings import Settings
11
+ from ccproxy.services.container import ServiceContainer
12
+
13
+
14
+ def create_service_container(settings: Settings | None = None) -> ServiceContainer:
15
+ """
16
+ Create and configure the service container.
17
+
18
+ Args:
19
+ settings: Optional pre-loaded settings instance. If not provided,
20
+ settings will be loaded from config files/environment.
21
+
22
+ Returns:
23
+ The initialized service container.
24
+ """
25
+ if settings is None:
26
+ settings = Settings.from_config()
27
+
28
+ container = ServiceContainer(settings)
29
+
30
+ return container
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ import uuid
5
+ from collections.abc import Awaitable, Callable
6
+ from functools import wraps
7
+ from typing import ParamSpec, TypeVar
8
+
9
+ from fastapi import Request
10
+
11
+ from ccproxy.core.logging import get_logger as _get_logger
12
+ from ccproxy.core.request_context import RequestContext
13
+
14
+
15
+ P = ParamSpec("P")
16
+ R = TypeVar("R")
17
+
18
+
19
+ def format_chain(
20
+ *formats: str,
21
+ ) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
22
+ """Existing simple decorator to attach a format chain to a route handler.
23
+
24
+ This attaches a __format_chain__ attribute used by validation and helpers.
25
+ """
26
+
27
+ def decorator(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
28
+ func.__format_chain__ = list(formats) # type: ignore[attr-defined]
29
+
30
+ @wraps(func)
31
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
32
+ return await func(*args, **kwargs)
33
+
34
+ return wrapper
35
+
36
+ return decorator
37
+
38
+
39
+ def with_format_chain(
40
+ formats: list[str], *, endpoint: str | None = None
41
+ ) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
42
+ """Decorator to set format chain and optional endpoint metadata on a route.
43
+
44
+ - Attaches __format_chain__ to the endpoint for upstream processing/validation
45
+ - Ensures request.state.context exists and sets context.format_chain
46
+ - Optionally sets context.metadata["endpoint"] to the provided upstream endpoint path
47
+ """
48
+
49
+ def decorator(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
50
+ func.__format_chain__ = list(formats) # type: ignore[attr-defined]
51
+
52
+ @wraps(func)
53
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
54
+ # Find Request in args/kwargs
55
+ request: Request | None = None
56
+ for arg in args:
57
+ if isinstance(arg, Request):
58
+ request = arg
59
+ break
60
+ if request is None:
61
+ req = kwargs.get("request")
62
+ if isinstance(req, Request):
63
+ request = req
64
+
65
+ if request is not None:
66
+ # Ensure a context exists
67
+ if (
68
+ not hasattr(request.state, "context")
69
+ or request.state.context is None
70
+ ):
71
+ request.state.context = RequestContext(
72
+ request_id=str(uuid.uuid4()),
73
+ start_time=time.perf_counter(),
74
+ logger=_get_logger(__name__),
75
+ )
76
+ # Set chain and endpoint metadata
77
+ request.state.context.format_chain = list(formats)
78
+ if endpoint:
79
+ request.state.context.metadata["endpoint"] = endpoint
80
+
81
+ return await func(*args, **kwargs)
82
+
83
+ return wrapper
84
+
85
+ return decorator
@@ -2,221 +2,190 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Annotated
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING, Annotated, Any, TypeVar
6
7
 
7
- from fastapi import Depends, Request
8
- from structlog import get_logger
8
+ import httpx
9
+ from fastapi import Depends, HTTPException, Request
9
10
 
10
- from ccproxy.auth.dependencies import AuthManagerDep
11
- from ccproxy.config.settings import Settings, get_settings
12
- from ccproxy.core.http import BaseProxyClient
13
- from ccproxy.observability import PrometheusMetrics, get_metrics
14
- from ccproxy.observability.storage.duckdb_simple import SimpleDuckDBStorage
15
- from ccproxy.services.claude_sdk_service import ClaudeSDKService
16
- from ccproxy.services.credentials.manager import CredentialsManager
17
- from ccproxy.services.proxy_service import ProxyService
11
+ from ccproxy.config.settings import Settings
12
+ from ccproxy.core.logging import get_logger
13
+ from ccproxy.core.plugins import PluginRegistry, ProviderPluginRuntime
14
+ from ccproxy.core.plugins.hooks import HookManager
15
+ from ccproxy.services.adapters.base import BaseAdapter
16
+ from ccproxy.services.container import ServiceContainer
18
17
 
19
18
 
19
+ if TYPE_CHECKING:
20
+ pass
21
+
20
22
  logger = get_logger(__name__)
21
23
 
24
+ T = TypeVar("T")
22
25
 
23
- def get_cached_settings(request: Request) -> Settings:
24
- """Get cached settings from app state.
25
26
 
26
- This avoids recomputing settings on every request by using the
27
- settings instance computed during application startup.
27
+ def get_service(service_type: type[T]) -> Callable[[Request], T]:
28
+ """Return a dependency callable that fetches a service from the container."""
28
29
 
29
- Args:
30
- request: FastAPI request object
30
+ def _get_service(request: Request) -> T:
31
+ """Get a service from the container."""
32
+ container: ServiceContainer | None = getattr(
33
+ request.app.state, "service_container", None
34
+ )
35
+ if container is None:
36
+ logger.error(
37
+ "service_container_missing_on_app_state",
38
+ category="lifecycle",
39
+ )
40
+ raise HTTPException(
41
+ status_code=503, detail="Service container not initialized"
42
+ )
43
+ return container.get_service(service_type)
31
44
 
32
- Returns:
33
- Settings instance from app state
45
+ return _get_service
34
46
 
35
- Raises:
36
- RuntimeError: If settings are not available in app state
37
- """
38
- settings = getattr(request.app.state, "settings", None)
39
- if settings is None:
40
- # Fallback to get_settings() for safety, but this should not happen
41
- # in normal operation after lifespan startup
42
- logger.warning(
43
- "Settings not found in app state, falling back to get_settings()"
44
- )
45
- settings = get_settings()
46
- return settings
47
47
 
48
+ def _resolve_service_container(request: Request) -> ServiceContainer | None:
49
+ """Resolve a service container from the request or global context."""
48
50
 
49
- def get_cached_claude_service(request: Request) -> ClaudeSDKService:
50
- """Get cached ClaudeSDKService from app state.
51
+ container: ServiceContainer | None = getattr(
52
+ request.app.state, "service_container", None
53
+ )
54
+ if container is not None:
55
+ return container
51
56
 
52
- This avoids recreating the ClaudeSDKService on every request by using the
53
- service instance created during application startup.
57
+ try:
58
+ return ServiceContainer.get_current(strict=False)
59
+ except RuntimeError:
60
+ # Should not happen with strict=False but guard defensively
61
+ return None
54
62
 
55
- Args:
56
- request: FastAPI request object
57
63
 
58
- Returns:
59
- ClaudeSDKService instance from app state
64
+ def get_cached_settings(request: Request) -> Settings:
65
+ """Get cached settings from app state.
60
66
 
61
- Raises:
62
- RuntimeError: If ClaudeSDKService is not available in app state
67
+ Raises a 503 HTTPException if no service container is available,
68
+ preserving the existing behaviour for required dependencies.
63
69
  """
64
- claude_service = getattr(request.app.state, "claude_service", None)
65
- if claude_service is None:
66
- # Fallback to get_claude_service() for safety, but this should not happen
67
- # in normal operation after lifespan startup
70
+ return get_service(Settings)(request)
71
+
72
+
73
+ def get_optional_settings(request: Request) -> Settings | None:
74
+ """Best-effort retrieval of settings for optional dependencies.
75
+
76
+ Returns a Settings instance if a service container is available.
77
+ Falls back to a new Settings object (with defaults) when running in
78
+ lightweight test contexts where the container is not initialised.
79
+ """
80
+
81
+ container = _resolve_service_container(request)
82
+ if container is not None:
83
+ try:
84
+ return container.get_service(Settings)
85
+ except ValueError:
86
+ logger.debug(
87
+ "settings_not_registered_in_container",
88
+ category="config",
89
+ )
90
+
91
+ try:
92
+ return Settings()
93
+ except Exception as exc: # pragma: no cover - defensive guard
68
94
  logger.warning(
69
- "ClaudeSDKService not found in app state, falling back to get_claude_service()"
95
+ "optional_settings_initialization_failed",
96
+ error=str(exc),
97
+ category="config",
70
98
  )
71
- # Get dependencies manually for fallback
72
- settings = get_cached_settings(request)
73
- # Create a simple auth manager for fallback
74
- from ccproxy.auth.credentials_adapter import CredentialsAuthManager
99
+ return None
75
100
 
76
- auth_manager = CredentialsAuthManager()
77
- claude_service = get_claude_service(settings, auth_manager)
78
- return claude_service
79
101
 
102
+ async def get_http_client(request: Request) -> httpx.AsyncClient:
103
+ """Get container-managed HTTP client from the service container."""
104
+ return get_service(httpx.AsyncClient)(request)
80
105
 
81
- # Type aliases for dependency injection
82
- SettingsDep = Annotated[Settings, Depends(get_cached_settings)]
83
106
 
107
+ def get_hook_manager(request: Request) -> HookManager:
108
+ """Get HookManager from the service container.
84
109
 
85
- def get_claude_service(
86
- settings: SettingsDep,
87
- auth_manager: AuthManagerDep,
88
- ) -> ClaudeSDKService:
89
- """Get Claude SDK service instance.
110
+ This dependency is required; if the hook system has not been initialized
111
+ the request will fail with 503 to reflect misconfigured startup order.
112
+ """
113
+ return get_service(HookManager)(request)
90
114
 
91
- Args:
92
- settings: Application settings dependency
93
- auth_manager: Authentication manager dependency
94
115
 
95
- Returns:
96
- Claude SDK service instance
97
- """
98
- logger.debug("Creating Claude SDK service instance")
99
- # Get global metrics instance
100
- metrics = get_metrics()
101
-
102
- # Check if pooling should be enabled from configuration
103
- use_pool = settings.claude.sdk_session_pool.enabled
104
- session_manager = None
105
-
106
- if use_pool:
107
- logger.info(
108
- "claude_sdk_pool_enabled",
109
- message="Using Claude SDK client pooling for improved performance",
110
- pool_size=settings.claude.sdk_session_pool.max_sessions,
111
- max_pool_size=settings.claude.sdk_session_pool.max_sessions,
112
- )
113
- # Note: Session manager should be created in the lifespan function, not here
114
- # This dependency function should not create stateful resources
115
-
116
- return ClaudeSDKService(
117
- auth_manager=auth_manager,
118
- metrics=metrics,
119
- settings=settings,
120
- session_manager=session_manager,
121
- )
116
+ def get_plugin_adapter(plugin_name: str) -> Any:
117
+ """Create a dependency function for a specific plugin's adapter."""
122
118
 
119
+ def _get_adapter(request: Request) -> BaseAdapter:
120
+ """Get adapter for the specified plugin."""
121
+ if not hasattr(request.app.state, "plugin_registry"):
122
+ raise HTTPException(
123
+ status_code=503, detail="Plugin registry not initialized"
124
+ )
123
125
 
124
- def get_credentials_manager(
125
- settings: SettingsDep,
126
- ) -> CredentialsManager:
127
- """Get credentials manager instance.
126
+ registry: PluginRegistry = request.app.state.plugin_registry
127
+ runtime = registry.get_runtime(plugin_name)
128
128
 
129
- Args:
130
- settings: Application settings dependency
129
+ if not runtime:
130
+ raise HTTPException(
131
+ status_code=503, detail=f"Plugin {plugin_name} not initialized"
132
+ )
131
133
 
132
- Returns:
133
- Credentials manager instance
134
- """
135
- logger.debug("Creating credentials manager instance")
136
- return CredentialsManager(config=settings.auth)
137
-
138
-
139
- def get_proxy_service(
140
- request: Request,
141
- settings: SettingsDep,
142
- credentials_manager: Annotated[
143
- CredentialsManager, Depends(get_credentials_manager)
144
- ],
145
- ) -> ProxyService:
146
- """Get proxy service instance.
147
-
148
- Args:
149
- request: FastAPI request object (for app state access)
150
- settings: Application settings dependency
151
- credentials_manager: Credentials manager dependency
152
-
153
- Returns:
154
- Proxy service instance
155
- """
156
- logger.debug("get_proxy_service")
157
- # Create HTTP client for proxy
158
- from ccproxy.core.http import HTTPXClient
159
-
160
- http_client = HTTPXClient()
161
- proxy_client = BaseProxyClient(http_client)
162
-
163
- # Get global metrics instance
164
- metrics = get_metrics()
165
-
166
- return ProxyService(
167
- proxy_client=proxy_client,
168
- credentials_manager=credentials_manager,
169
- settings=settings,
170
- proxy_mode="full",
171
- target_base_url=settings.reverse_proxy.target_url,
172
- metrics=metrics,
173
- app_state=request.app.state, # Pass app state for detection data access
174
- )
134
+ if not isinstance(runtime, ProviderPluginRuntime):
135
+ raise HTTPException(
136
+ status_code=503, detail=f"Plugin {plugin_name} is not a provider plugin"
137
+ )
175
138
 
139
+ if not runtime.adapter:
140
+ raise HTTPException(
141
+ status_code=503, detail=f"Plugin {plugin_name} adapter not available"
142
+ )
176
143
 
177
- def get_observability_metrics() -> PrometheusMetrics:
178
- """Get observability metrics instance.
144
+ adapter: BaseAdapter = runtime.adapter
145
+ return adapter
179
146
 
180
- Returns:
181
- PrometheusMetrics instance
182
- """
183
- logger.debug("get_observability_metrics")
184
- return get_metrics()
147
+ return _get_adapter
185
148
 
186
149
 
187
- async def get_log_storage(request: Request) -> SimpleDuckDBStorage | None:
188
- """Get log storage from app state.
150
+ def get_provider_config_dependency(
151
+ plugin_name: str, config_type: type[T]
152
+ ) -> Callable[[Request], T]:
153
+ """Return a dependency that fetches a provider plugin's validated config."""
189
154
 
190
- Args:
191
- request: FastAPI request object
155
+ def _get_config(request: Request) -> T:
156
+ if not hasattr(request.app.state, "plugin_registry"):
157
+ raise HTTPException(
158
+ status_code=503, detail="Plugin registry not initialized"
159
+ )
192
160
 
193
- Returns:
194
- SimpleDuckDBStorage instance if available, None otherwise
195
- """
196
- return getattr(request.app.state, "log_storage", None)
161
+ registry: PluginRegistry = request.app.state.plugin_registry
162
+ runtime = registry.get_runtime(plugin_name)
197
163
 
164
+ if not runtime or not isinstance(runtime, ProviderPluginRuntime):
165
+ raise HTTPException(
166
+ status_code=503, detail=f"Plugin {plugin_name} not initialized"
167
+ )
198
168
 
199
- async def get_duckdb_storage(request: Request) -> SimpleDuckDBStorage | None:
200
- """Get DuckDB storage from app state (backward compatibility).
169
+ context = getattr(runtime, "context", None)
170
+ if not context:
171
+ raise HTTPException(
172
+ status_code=503,
173
+ detail=f"Plugin {plugin_name} configuration unavailable",
174
+ )
201
175
 
202
- Args:
203
- request: FastAPI request object
176
+ try:
177
+ config = context.get(config_type)
178
+ return config # type: ignore[no-any-return]
179
+ except ValueError as exc: # pragma: no cover - defensive guard
180
+ raise HTTPException(
181
+ status_code=503,
182
+ detail=f"Plugin {plugin_name} configuration not loaded",
183
+ ) from exc
204
184
 
205
- Returns:
206
- SimpleDuckDBStorage instance if available, None otherwise
207
- """
208
- # Try new name first, then fall back to old name for backward compatibility
209
- storage = getattr(request.app.state, "log_storage", None)
210
- if storage is None:
211
- storage = getattr(request.app.state, "duckdb_storage", None)
212
- return storage
213
-
214
-
215
- # Type aliases for service dependencies
216
- ClaudeServiceDep = Annotated[ClaudeSDKService, Depends(get_cached_claude_service)]
217
- ProxyServiceDep = Annotated[ProxyService, Depends(get_proxy_service)]
218
- ObservabilityMetricsDep = Annotated[
219
- PrometheusMetrics, Depends(get_observability_metrics)
220
- ]
221
- LogStorageDep = Annotated[SimpleDuckDBStorage | None, Depends(get_log_storage)]
222
- DuckDBStorageDep = Annotated[SimpleDuckDBStorage | None, Depends(get_duckdb_storage)]
185
+ return _get_config
186
+
187
+
188
+ SettingsDep = Annotated[Settings, Depends(get_cached_settings)]
189
+ OptionalSettingsDep = Annotated[Settings | None, Depends(get_optional_settings)]
190
+ HTTPClientDep = Annotated[httpx.AsyncClient, Depends(get_http_client)]
191
+ HookManagerDep = Annotated[HookManager, Depends(get_hook_manager)]
@@ -0,0 +1,54 @@
1
+ """Utilities for validating format adapter chains during application startup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import FastAPI
6
+
7
+ from ccproxy.core.logging import TraceBoundLogger
8
+ from ccproxy.services.adapters.chain_validation import (
9
+ validate_chains,
10
+ validate_stream_pairs,
11
+ )
12
+ from ccproxy.services.adapters.format_registry import FormatRegistry
13
+
14
+
15
+ def collect_declared_format_chains(app: FastAPI) -> list[list[str]]:
16
+ """Collect declared format chains from FastAPI routes."""
17
+
18
+ chains: list[list[str]] = []
19
+ for route in app.router.routes:
20
+ endpoint = getattr(route, "endpoint", None)
21
+ chain = getattr(endpoint, "__format_chain__", None)
22
+ if chain:
23
+ chains.append(chain)
24
+ return chains
25
+
26
+
27
+ def validate_route_format_chains(
28
+ *,
29
+ app: FastAPI,
30
+ registry: FormatRegistry,
31
+ logger: TraceBoundLogger,
32
+ ) -> None:
33
+ """Validate format chains declared on routes against the format registry."""
34
+
35
+ try:
36
+ declared_chains = collect_declared_format_chains(app)
37
+ if not declared_chains:
38
+ return
39
+
40
+ missing = validate_chains(registry=registry, chains=declared_chains)
41
+ missing_stream = validate_stream_pairs(
42
+ registry=registry, chains=declared_chains
43
+ )
44
+ if missing or missing_stream:
45
+ logger.error(
46
+ "format_chain_validation_failed",
47
+ missing_adapters=missing,
48
+ missing_stream_adapters=missing_stream,
49
+ )
50
+ except Exception as exc: # pragma: no cover - defensive logging path
51
+ logger.warning("format_registry_setup_skipped", error=str(exc))
52
+
53
+
54
+ __all__ = ["validate_route_format_chains", "collect_declared_format_chains"]
@@ -4,9 +4,9 @@ from typing import Any
4
4
 
5
5
  from fastapi import FastAPI
6
6
  from fastapi.middleware.cors import CORSMiddleware
7
- from structlog import get_logger
8
7
 
9
8
  from ccproxy.config.settings import Settings
9
+ from ccproxy.core.logging import get_logger
10
10
 
11
11
 
12
12
  logger = get_logger(__name__)
@@ -19,7 +19,6 @@ def setup_cors_middleware(app: FastAPI, settings: Settings) -> None:
19
19
  app: FastAPI application instance
20
20
  settings: Application settings containing CORS configuration
21
21
  """
22
- logger.debug("cors_middleware_setup_start")
23
22
 
24
23
  app.add_middleware(
25
24
  CORSMiddleware,
@@ -32,7 +31,11 @@ def setup_cors_middleware(app: FastAPI, settings: Settings) -> None:
32
31
  max_age=settings.cors.max_age,
33
32
  )
34
33
 
35
- logger.debug("cors_middleware_configured", origins=settings.cors.origins)
34
+ logger.debug(
35
+ "cors_middleware_configured",
36
+ origins=settings.cors.origins,
37
+ category="middleware",
38
+ )
36
39
 
37
40
 
38
41
  def get_cors_config(settings: Settings) -> dict[str, Any]: