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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1251
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,256 @@
1
+ """Dependency injection container for all services.
2
+
3
+ This module provides a clean, testable dependency injection container that
4
+ manages service lifecycles and dependencies without singleton anti-patterns.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ from collections.abc import Callable
11
+ from contextvars import ContextVar
12
+ from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast
13
+
14
+ import httpx
15
+ import structlog
16
+
17
+ from ccproxy.config.settings import Settings
18
+ from ccproxy.core.plugins.hooks.registry import HookRegistry
19
+ from ccproxy.core.plugins.hooks.thread_manager import BackgroundHookThreadManager
20
+ from ccproxy.http.pool import HTTPPoolManager
21
+ from ccproxy.scheduler.registry import TaskRegistry
22
+ from ccproxy.services.adapters.format_registry import FormatRegistry
23
+ from ccproxy.services.auth_registry import AuthManagerRegistry
24
+ from ccproxy.services.cache import ResponseCache
25
+ from ccproxy.services.cli_detection import CLIDetectionService
26
+ from ccproxy.services.config import ProxyConfiguration
27
+ from ccproxy.services.factories import ConcreteServiceFactory
28
+ from ccproxy.services.interfaces import (
29
+ IRequestTracer,
30
+ NullMetricsCollector,
31
+ NullRequestTracer,
32
+ )
33
+ from ccproxy.services.mocking import MockResponseHandler
34
+ from ccproxy.streaming import StreamingHandler
35
+ from ccproxy.utils.binary_resolver import BinaryResolver
36
+
37
+
38
+ if TYPE_CHECKING:
39
+ from ccproxy.core.async_task_manager import AsyncTaskManager
40
+
41
+
42
+ logger = structlog.get_logger(__name__)
43
+
44
+ T = TypeVar("T")
45
+
46
+
47
+ class ServiceContainer:
48
+ """Dependency injection container for all services."""
49
+
50
+ _current_container: ClassVar[ContextVar[ServiceContainer | None]] = ContextVar(
51
+ "ccproxy_service_container",
52
+ default=None,
53
+ )
54
+ _default_container: ClassVar[ServiceContainer | None] = None
55
+
56
+ def __init__(self, settings: Settings) -> None:
57
+ """Initialize the service container."""
58
+ self.settings = settings
59
+ self._services: dict[object, Any] = {}
60
+ self._factories: dict[object, Callable[[], Any]] = {}
61
+
62
+ self.register_service(Settings, self.settings)
63
+ self.register_service(ServiceContainer, self)
64
+
65
+ factory = ConcreteServiceFactory(self)
66
+ factory.register_services()
67
+
68
+ # Ensure a request tracer is always available for early consumers
69
+ # Plugins may override this with a real tracer at runtime
70
+ # Register a default tracer using the protocol as key
71
+ self.register_service(IRequestTracer, instance=NullRequestTracer())
72
+
73
+ # Make this container available for modules that resolve services globally
74
+ self.activate()
75
+
76
+ def activate(self, *, set_default: bool = True) -> None:
77
+ """Mark this container as the current active container."""
78
+ self.__class__._current_container.set(self)
79
+ if set_default:
80
+ self.__class__._default_container = self
81
+
82
+ @classmethod
83
+ def get_current(cls, *, strict: bool = True) -> ServiceContainer | None:
84
+ """Return the currently active container.
85
+
86
+ Args:
87
+ strict: When True, raise an error if no container is active.
88
+
89
+ Returns:
90
+ Active service container or None.
91
+ """
92
+
93
+ container = cls._current_container.get()
94
+ if container is None:
95
+ container = cls._default_container
96
+ if container is None and strict:
97
+ raise RuntimeError("ServiceContainer is not available")
98
+ return container
99
+
100
+ def register_service(
101
+ self,
102
+ service_type: object,
103
+ instance: Any | None = None,
104
+ factory: Callable[[], Any] | None = None,
105
+ ) -> None:
106
+ """Register a service instance or factory."""
107
+ if instance is not None:
108
+ self._services[service_type] = instance
109
+ elif factory is not None:
110
+ self._factories[service_type] = factory
111
+ else:
112
+ raise ValueError("Either instance or factory must be provided")
113
+
114
+ def get_service(self, service_type: type[T]) -> T:
115
+ """Get a service instance by key (type or protocol)."""
116
+ if service_type not in self._services:
117
+ if service_type in self._factories:
118
+ self._services[service_type] = self._factories[service_type]()
119
+ else:
120
+ # Best-effort name for error messages
121
+ type_name = getattr(service_type, "__name__", str(service_type))
122
+ raise ValueError(f"Service {type_name} not registered")
123
+ return cast(T, self._services[service_type])
124
+
125
+ def get_request_tracer(self) -> IRequestTracer:
126
+ """Get request tracer service instance."""
127
+ service = self._services.get(IRequestTracer)
128
+ if service is None:
129
+ raise ValueError("Service IRequestTracer not registered")
130
+ return cast(IRequestTracer, service)
131
+
132
+ def set_request_tracer(self, tracer: IRequestTracer) -> None:
133
+ """Set the request tracer (called by plugin)."""
134
+ self.register_service(IRequestTracer, instance=tracer)
135
+
136
+ def get_mock_handler(self) -> MockResponseHandler:
137
+ """Get mock handler service instance."""
138
+ return self.get_service(MockResponseHandler)
139
+
140
+ def get_streaming_handler(self) -> StreamingHandler:
141
+ """Get streaming handler service instance."""
142
+ return self.get_service(StreamingHandler)
143
+
144
+ def get_binary_resolver(self) -> BinaryResolver:
145
+ """Get binary resolver service instance."""
146
+ return self.get_service(BinaryResolver)
147
+
148
+ def get_cli_detection_service(self) -> CLIDetectionService:
149
+ """Get CLI detection service instance."""
150
+ return self.get_service(CLIDetectionService)
151
+
152
+ def get_proxy_config(self) -> ProxyConfiguration:
153
+ """Get proxy configuration service instance."""
154
+ return self.get_service(ProxyConfiguration)
155
+
156
+ def get_http_client(self) -> httpx.AsyncClient:
157
+ """Get container-managed HTTP client instance."""
158
+ return self.get_service(httpx.AsyncClient)
159
+
160
+ def get_pool_manager(self) -> HTTPPoolManager:
161
+ """Get HTTP connection pool manager instance."""
162
+ return self.get_service(HTTPPoolManager)
163
+
164
+ def get_response_cache(self) -> ResponseCache:
165
+ """Get response cache service instance."""
166
+ return self.get_service(ResponseCache)
167
+
168
+ # Use HTTPPoolManager for pooling
169
+
170
+ def get_format_registry(self) -> FormatRegistry:
171
+ """Get format adapter registry service instance."""
172
+ return self.get_service(FormatRegistry)
173
+
174
+ # FormatterRegistry removed; use FormatRegistry exclusively.
175
+
176
+ def get_oauth_registry(self) -> Any:
177
+ """Get OAuth provider registry instance."""
178
+ # Import lazily to avoid circular imports through auth package
179
+ from ccproxy.auth.oauth.registry import OAuthRegistry
180
+
181
+ return self.get_service(OAuthRegistry)
182
+
183
+ def get_hook_registry(self) -> HookRegistry:
184
+ """Get hook registry instance."""
185
+ return self.get_service(HookRegistry)
186
+
187
+ def get_task_registry(self) -> TaskRegistry:
188
+ """Get scheduled task registry instance."""
189
+ return self.get_service(TaskRegistry)
190
+
191
+ def get_auth_manager_registry(self) -> AuthManagerRegistry:
192
+ """Get auth manager registry instance."""
193
+ return self.get_service(AuthManagerRegistry)
194
+
195
+ def get_background_hook_thread_manager(self) -> BackgroundHookThreadManager:
196
+ """Get background hook thread manager instance."""
197
+ return self.get_service(BackgroundHookThreadManager)
198
+
199
+ def get_async_task_manager(self) -> AsyncTaskManager:
200
+ """Get async task manager instance."""
201
+ from ccproxy.core.async_task_manager import AsyncTaskManager
202
+
203
+ return self.get_service(AsyncTaskManager)
204
+
205
+ def get_adapter_dependencies(self, metrics: Any | None = None) -> dict[str, Any]:
206
+ """Get all services an adapter might need."""
207
+ return {
208
+ "http_client": self.get_http_client(),
209
+ "request_tracer": self.get_request_tracer(),
210
+ "metrics": metrics or NullMetricsCollector(),
211
+ "streaming_handler": self.get_streaming_handler(),
212
+ "logger": structlog.get_logger(),
213
+ "config": self.get_proxy_config(),
214
+ "cli_detection_service": self.get_cli_detection_service(),
215
+ "format_registry": self.get_format_registry(),
216
+ }
217
+
218
+ async def close(self) -> None:
219
+ """Close all managed resources during shutdown."""
220
+ for service in list(self._services.values()):
221
+ # Avoid recursive self-close
222
+ if service is self:
223
+ continue
224
+
225
+ try:
226
+ # Prefer aclose() if available (e.g., httpx.AsyncClient)
227
+ if hasattr(service, "aclose") and callable(service.aclose):
228
+ maybe_coro = service.aclose()
229
+ if inspect.isawaitable(maybe_coro):
230
+ await maybe_coro
231
+ elif hasattr(service, "close") and callable(service.close):
232
+ maybe_coro = service.close()
233
+ if inspect.isawaitable(maybe_coro):
234
+ await maybe_coro
235
+ elif hasattr(service, "stop") and callable(service.stop):
236
+ stop_result = service.stop()
237
+ if inspect.isawaitable(stop_result):
238
+ await stop_result
239
+ # else: nothing to close
240
+ except Exception as e:
241
+ logger.error(
242
+ "service_close_failed",
243
+ service=type(service).__name__,
244
+ error=str(e),
245
+ exc_info=e,
246
+ category="lifecycle",
247
+ )
248
+ self._services.clear()
249
+ logger.debug("service_container_resources_closed", category="lifecycle")
250
+
251
+ async def shutdown(self) -> None:
252
+ """Shutdown all services in the container."""
253
+ await self.close()
254
+ if self.__class__._default_container is self:
255
+ self.__class__._default_container = None
256
+ self.__class__._current_container.set(None)
@@ -0,0 +1,380 @@
1
+ """Concrete service factory implementations.
2
+
3
+ This module provides concrete implementations of service factories that
4
+ create and configure service instances according to their interfaces.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any, TypedDict
10
+
11
+ import httpx
12
+ import structlog
13
+
14
+ from ccproxy.auth.oauth import registry as oauth_registry_module
15
+ from ccproxy.config.settings import Settings
16
+ from ccproxy.core.plugins.hooks import HookManager
17
+ from ccproxy.core.plugins.hooks.registry import HookRegistry
18
+ from ccproxy.core.plugins.hooks.thread_manager import BackgroundHookThreadManager
19
+ from ccproxy.http.client import HTTPClientFactory
20
+ from ccproxy.http.pool import HTTPPoolManager
21
+ from ccproxy.scheduler.registry import TaskRegistry
22
+ from ccproxy.services.adapters.format_adapter import DictFormatAdapter
23
+ from ccproxy.services.adapters.format_registry import FormatRegistry
24
+ from ccproxy.services.adapters.simple_converters import (
25
+ convert_anthropic_to_openai_response,
26
+ )
27
+ from ccproxy.services.auth_registry import AuthManagerRegistry
28
+ from ccproxy.services.cache import ResponseCache
29
+ from ccproxy.services.cli_detection import CLIDetectionService
30
+ from ccproxy.services.config import ProxyConfiguration
31
+ from ccproxy.services.mocking import MockResponseHandler
32
+ from ccproxy.streaming import StreamingHandler
33
+ from ccproxy.testing import RealisticMockResponseGenerator
34
+ from ccproxy.utils.binary_resolver import BinaryResolver
35
+
36
+
37
+ if TYPE_CHECKING:
38
+ from ccproxy.core.async_task_manager import AsyncTaskManager
39
+ from ccproxy.services.container import ServiceContainer
40
+
41
+ logger = structlog.get_logger(__name__)
42
+
43
+
44
+ class _CoreAdapterSpec(TypedDict):
45
+ """Type definition for core adapter specification dictionary."""
46
+
47
+ from_format: str
48
+ to_format: str
49
+ request: Any # Format converter function
50
+ response: Any # Format converter function
51
+ stream: Any # Format converter function
52
+ error: Any # Error converter function
53
+ name: str
54
+
55
+
56
+ class ConcreteServiceFactory:
57
+ """Concrete implementation of service factory."""
58
+
59
+ def __init__(self, container: ServiceContainer) -> None:
60
+ """Initialize the service factory."""
61
+ self._container = container
62
+
63
+ def register_services(self) -> None:
64
+ """Register all services with the container."""
65
+ self._container.register_service(
66
+ MockResponseHandler, factory=self.create_mock_handler
67
+ )
68
+ self._container.register_service(
69
+ StreamingHandler, factory=self.create_streaming_handler
70
+ )
71
+ self._container.register_service(
72
+ ProxyConfiguration, factory=self.create_proxy_config
73
+ )
74
+ self._container.register_service(
75
+ httpx.AsyncClient, factory=self.create_http_client
76
+ )
77
+ self._container.register_service(
78
+ CLIDetectionService, factory=self.create_cli_detection_service
79
+ )
80
+ self._container.register_service(
81
+ HTTPPoolManager, factory=self.create_http_pool_manager
82
+ )
83
+ self._container.register_service(
84
+ ResponseCache, factory=self.create_response_cache
85
+ )
86
+ self._container.register_service(
87
+ BinaryResolver, factory=self.create_binary_resolver
88
+ )
89
+
90
+ self._container.register_service(
91
+ FormatRegistry, factory=self.create_format_registry
92
+ )
93
+
94
+ # Registries
95
+ self._container.register_service(
96
+ HookRegistry, factory=self.create_hook_registry
97
+ )
98
+
99
+ self._container.register_service(
100
+ oauth_registry_module.OAuthRegistry, factory=self.create_oauth_registry
101
+ )
102
+ self._container.register_service(
103
+ TaskRegistry, factory=self.create_task_registry
104
+ )
105
+ self._container.register_service(
106
+ AuthManagerRegistry, factory=self.create_auth_manager_registry
107
+ )
108
+
109
+ # Register background thread manager for hooks
110
+ self._container.register_service(
111
+ BackgroundHookThreadManager,
112
+ factory=self.create_background_hook_thread_manager,
113
+ )
114
+ from ccproxy.core.async_task_manager import AsyncTaskManager
115
+
116
+ self._container.register_service(
117
+ AsyncTaskManager,
118
+ factory=self.create_async_task_manager,
119
+ )
120
+
121
+ def create_mock_handler(self) -> MockResponseHandler:
122
+ """Create mock handler instance."""
123
+ mock_generator = RealisticMockResponseGenerator()
124
+ settings = self._container.get_service(Settings)
125
+ # Create simple format adapter for anthropic->openai conversion (for mock responses)
126
+ openai_adapter = DictFormatAdapter(
127
+ response=convert_anthropic_to_openai_response,
128
+ name="mock_anthropic_to_openai",
129
+ )
130
+ # Configure streaming settings if needed
131
+ openai_thinking_xml = getattr(
132
+ getattr(settings, "llm", object()), "openai_thinking_xml", True
133
+ )
134
+ if hasattr(openai_adapter, "configure_streaming"):
135
+ openai_adapter.configure_streaming(openai_thinking_xml=openai_thinking_xml)
136
+
137
+ handler = MockResponseHandler(
138
+ mock_generator=mock_generator,
139
+ openai_adapter=openai_adapter,
140
+ error_rate=0.05,
141
+ latency_range=(0.5, 2.0),
142
+ )
143
+ return handler
144
+
145
+ def create_streaming_handler(self) -> StreamingHandler:
146
+ """Create streaming handler instance.
147
+
148
+ Requires HookManager to be registered before resolution to avoid
149
+ post-hoc patching of the handler.
150
+ """
151
+ hook_manager = self._container.get_service(HookManager)
152
+ handler = StreamingHandler(hook_manager=hook_manager)
153
+ return handler
154
+
155
+ def create_proxy_config(self) -> ProxyConfiguration:
156
+ """Create proxy configuration instance."""
157
+ config = ProxyConfiguration()
158
+ return config
159
+
160
+ def create_http_client(self) -> httpx.AsyncClient:
161
+ """Create HTTP client instance."""
162
+ settings = self._container.get_service(Settings)
163
+ hook_manager = self._container.get_service(HookManager)
164
+ client = HTTPClientFactory.create_client(
165
+ settings=settings, hook_manager=hook_manager
166
+ )
167
+ logger.debug("http_client_created", category="lifecycle")
168
+ return client
169
+
170
+ def create_cli_detection_service(self) -> CLIDetectionService:
171
+ """Create CLI detection service instance."""
172
+ settings = self._container.get_service(Settings)
173
+ return CLIDetectionService(settings)
174
+
175
+ def create_http_pool_manager(self) -> HTTPPoolManager:
176
+ """Create HTTP pool manager instance."""
177
+ settings = self._container.get_service(Settings)
178
+ hook_manager = self._container.get_service(HookManager)
179
+ logger.debug(
180
+ "http_pool_manager_created",
181
+ has_hook_manager=hook_manager is not None,
182
+ hook_manager_type=type(hook_manager).__name__ if hook_manager else "None",
183
+ category="lifecycle",
184
+ )
185
+ return HTTPPoolManager(settings, hook_manager)
186
+
187
+ def create_response_cache(self) -> ResponseCache:
188
+ """Create response cache instance."""
189
+ return ResponseCache()
190
+
191
+ # ConnectionPoolManager is no longer used; HTTPPoolManager only
192
+
193
+ def create_binary_resolver(self) -> BinaryResolver:
194
+ """Create a BinaryResolver from settings."""
195
+ settings = self._container.get_service(Settings)
196
+ return BinaryResolver.from_settings(settings)
197
+
198
+ def create_format_registry(self) -> FormatRegistry:
199
+ """Create format adapter registry with core adapters pre-registered.
200
+
201
+ Pre-registers common format conversions to prevent plugin conflicts.
202
+ Plugins can still register their own plugin-specific adapters.
203
+ """
204
+ settings = self._container.get_service(Settings)
205
+
206
+ # Always use priority mode (latest behavior)
207
+ registry = FormatRegistry()
208
+
209
+ # Pre-register core format adapters
210
+ self._register_core_format_adapters(registry, settings)
211
+ registry.flush_all_logs()
212
+
213
+ logger.debug(
214
+ "format_registry_created",
215
+ category="format",
216
+ )
217
+
218
+ return registry
219
+
220
+ def create_hook_registry(self) -> HookRegistry:
221
+ """Create a HookRegistry instance."""
222
+ return HookRegistry()
223
+
224
+ def create_oauth_registry(self) -> Any:
225
+ """Create an OAuthRegistry instance (imported lazily to avoid cycles)."""
226
+ from ccproxy.auth.oauth.registry import OAuthRegistry
227
+
228
+ return OAuthRegistry()
229
+
230
+ def create_task_registry(self) -> TaskRegistry:
231
+ """Create a TaskRegistry instance."""
232
+ return TaskRegistry()
233
+
234
+ def create_auth_manager_registry(self) -> AuthManagerRegistry:
235
+ """Create an AuthManagerRegistry instance.
236
+
237
+ Note: Auth managers are registered by their respective plugins during initialization.
238
+ """
239
+ return AuthManagerRegistry()
240
+
241
+ def _register_core_format_adapters(
242
+ self, registry: FormatRegistry, settings: Settings | None = None
243
+ ) -> None:
244
+ """Register essential format adapters provided by core.
245
+
246
+ Registers commonly-needed format conversions to prevent plugin duplication
247
+ and ensure required adapters are available for plugin dependencies.
248
+ """
249
+ from ccproxy.core.constants import (
250
+ FORMAT_ANTHROPIC_MESSAGES,
251
+ FORMAT_OPENAI_CHAT,
252
+ FORMAT_OPENAI_RESPONSES,
253
+ )
254
+ from ccproxy.services.adapters.simple_converters import (
255
+ convert_anthropic_to_openai_error,
256
+ convert_anthropic_to_openai_request,
257
+ convert_anthropic_to_openai_response,
258
+ convert_anthropic_to_openai_responses_error,
259
+ convert_anthropic_to_openai_responses_request,
260
+ convert_anthropic_to_openai_responses_response,
261
+ convert_anthropic_to_openai_responses_stream,
262
+ convert_anthropic_to_openai_stream,
263
+ convert_openai_chat_to_openai_responses_error,
264
+ convert_openai_chat_to_openai_responses_request,
265
+ convert_openai_chat_to_openai_responses_response,
266
+ convert_openai_chat_to_openai_responses_stream,
267
+ convert_openai_responses_to_anthropic_error,
268
+ convert_openai_responses_to_anthropic_request,
269
+ convert_openai_responses_to_anthropic_response,
270
+ convert_openai_responses_to_anthropic_stream,
271
+ convert_openai_responses_to_openai_chat_error,
272
+ convert_openai_responses_to_openai_chat_request,
273
+ convert_openai_responses_to_openai_chat_response,
274
+ convert_openai_responses_to_openai_chat_stream,
275
+ convert_openai_to_anthropic_error,
276
+ convert_openai_to_anthropic_request,
277
+ convert_openai_to_anthropic_response,
278
+ convert_openai_to_anthropic_stream,
279
+ )
280
+
281
+ # Define core format adapter specifications
282
+ core_adapter_specs: list[_CoreAdapterSpec] = [
283
+ # Most commonly required: Anthropic ↔ OpenAI Responses
284
+ {
285
+ "from_format": FORMAT_ANTHROPIC_MESSAGES,
286
+ "to_format": FORMAT_OPENAI_RESPONSES,
287
+ "request": convert_anthropic_to_openai_responses_request,
288
+ "response": convert_anthropic_to_openai_responses_response,
289
+ "stream": convert_anthropic_to_openai_responses_stream,
290
+ "error": convert_anthropic_to_openai_responses_error,
291
+ "name": "core_anthropic_to_openai_responses",
292
+ },
293
+ {
294
+ "from_format": FORMAT_OPENAI_RESPONSES,
295
+ "to_format": FORMAT_ANTHROPIC_MESSAGES,
296
+ "request": convert_openai_responses_to_anthropic_request,
297
+ "response": convert_openai_responses_to_anthropic_response,
298
+ "stream": convert_openai_responses_to_anthropic_stream,
299
+ "error": convert_openai_responses_to_anthropic_error,
300
+ "name": "core_openai_responses_to_anthropic",
301
+ },
302
+ # OpenAI Chat ↔ Responses (needed by Codex plugin)
303
+ {
304
+ "from_format": FORMAT_OPENAI_CHAT,
305
+ "to_format": FORMAT_OPENAI_RESPONSES,
306
+ "request": convert_openai_chat_to_openai_responses_request,
307
+ "response": convert_openai_chat_to_openai_responses_response,
308
+ "stream": convert_openai_chat_to_openai_responses_stream,
309
+ "error": convert_openai_chat_to_openai_responses_error,
310
+ "name": "core_openai_chat_to_responses",
311
+ },
312
+ # Reverse: OpenAI Responses -> OpenAI Chat
313
+ {
314
+ "from_format": FORMAT_OPENAI_RESPONSES,
315
+ "to_format": FORMAT_OPENAI_CHAT,
316
+ "request": convert_openai_responses_to_openai_chat_request,
317
+ "response": convert_openai_responses_to_openai_chat_response,
318
+ "stream": convert_openai_responses_to_openai_chat_stream,
319
+ "error": convert_openai_responses_to_openai_chat_error,
320
+ "name": "core_openai_responses_to_chat",
321
+ },
322
+ # Anthropic ↔ OpenAI Chat (commonly needed for proxying)
323
+ {
324
+ "from_format": FORMAT_ANTHROPIC_MESSAGES,
325
+ "to_format": FORMAT_OPENAI_CHAT,
326
+ "request": convert_anthropic_to_openai_request,
327
+ "response": convert_anthropic_to_openai_response,
328
+ "stream": convert_anthropic_to_openai_stream,
329
+ "error": convert_anthropic_to_openai_error,
330
+ "name": "core_anthropic_to_openai_chat",
331
+ },
332
+ # Reverse: OpenAI Chat -> Anthropic
333
+ {
334
+ "from_format": FORMAT_OPENAI_CHAT,
335
+ "to_format": FORMAT_ANTHROPIC_MESSAGES,
336
+ "request": convert_openai_to_anthropic_request,
337
+ "response": convert_openai_to_anthropic_response,
338
+ "stream": convert_openai_to_anthropic_stream,
339
+ "error": convert_openai_to_anthropic_error,
340
+ "name": "core_openai_chat_to_anthropic",
341
+ },
342
+ ]
343
+
344
+ # Register each core adapter
345
+ for spec in core_adapter_specs:
346
+ adapter = DictFormatAdapter(
347
+ request=spec["request"],
348
+ response=spec["response"],
349
+ stream=spec["stream"],
350
+ error=spec["error"],
351
+ name=spec["name"],
352
+ )
353
+ registry.register(
354
+ from_format=spec["from_format"],
355
+ to_format=spec["to_format"],
356
+ adapter=adapter,
357
+ plugin_name="core",
358
+ )
359
+
360
+ logger.debug(
361
+ "core_format_adapters_registered",
362
+ count=len(core_adapter_specs),
363
+ adapters=[
364
+ f"{spec['from_format']}->{spec['to_format']}"
365
+ for spec in core_adapter_specs
366
+ ],
367
+ category="format",
368
+ )
369
+
370
+ def create_background_hook_thread_manager(self) -> BackgroundHookThreadManager:
371
+ """Create background hook thread manager instance."""
372
+ manager = BackgroundHookThreadManager()
373
+ logger.debug("background_hook_thread_manager_created", category="lifecycle")
374
+ return manager
375
+
376
+ def create_async_task_manager(self) -> AsyncTaskManager:
377
+ """Create async task manager instance."""
378
+ from ccproxy.core.async_task_manager import AsyncTaskManager
379
+
380
+ return AsyncTaskManager()