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,416 @@
1
+ """Abstract interfaces for the plugin system.
2
+
3
+ This module contains all abstract base classes and protocols to avoid
4
+ circular dependencies between factory and runtime modules.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast
9
+
10
+ import structlog
11
+ from fastapi import FastAPI
12
+
13
+ from ccproxy.auth import TokenStorage
14
+ from ccproxy.auth.managers import BaseTokenManager, TokenSnapshot
15
+ from ccproxy.auth.oauth import OAuthProviderProtocol
16
+ from ccproxy.models.detection import DetectedHeaders, DetectedPrompts
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ from ccproxy.services.container import ServiceContainer
21
+ from ccproxy.services.interfaces import StreamingMetrics
22
+
23
+ from .declaration import PluginContext, PluginManifest
24
+
25
+
26
+ # Type variable for service type checking
27
+ T = TypeVar("T")
28
+
29
+ logger = structlog.get_logger(__name__)
30
+
31
+ # --- Adapter protocol helpers -------------------------------------------------
32
+
33
+
34
+ class DetectionServiceProtocol(Protocol):
35
+ """Common capabilities shared by detection services."""
36
+
37
+ def get_detected_headers(self) -> DetectedHeaders: ...
38
+
39
+ def get_detected_prompts(self) -> DetectedPrompts: ...
40
+
41
+ def get_cached_data(self) -> object: ...
42
+
43
+ def get_system_prompt(self, mode: str | None = None) -> dict[str, object]: ...
44
+
45
+ def get_ignored_headers(self) -> list[str]: ...
46
+
47
+ def get_redacted_headers(self) -> list[str]: ...
48
+
49
+
50
+ class TokenManagerProtocol(Protocol):
51
+ """Minimal async token manager contract used by adapters."""
52
+
53
+ async def get_access_token(self) -> str | None: ...
54
+
55
+ async def get_access_token_with_refresh(self) -> str | None: ...
56
+
57
+ async def load_credentials(self) -> object: ...
58
+
59
+ async def get_token_snapshot(self) -> TokenSnapshot | None: ...
60
+
61
+ def should_refresh(
62
+ self, credentials: object, grace_seconds: float | None = None
63
+ ) -> bool: ...
64
+
65
+
66
+ class ProfiledTokenManagerProtocol(TokenManagerProtocol, Protocol):
67
+ """Token manager that can return a lightweight profile snapshot."""
68
+
69
+ async def get_profile_quick(self) -> object | None: ...
70
+
71
+
72
+ class PluginFactory(ABC):
73
+ """Abstract factory for creating plugin runtime instances.
74
+
75
+ Each plugin must provide a factory that knows how to create
76
+ its runtime instance from its manifest.
77
+ """
78
+
79
+ @abstractmethod
80
+ def get_manifest(self) -> PluginManifest:
81
+ """Get the plugin manifest with static declarations.
82
+
83
+ Returns:
84
+ Plugin manifest
85
+ """
86
+ ...
87
+
88
+ @abstractmethod
89
+ def create_runtime(self) -> Any:
90
+ """Create a runtime instance for this plugin.
91
+
92
+ Returns:
93
+ Plugin runtime instance
94
+ """
95
+ ...
96
+
97
+ @abstractmethod
98
+ def create_context(self, core_services: "ServiceContainer") -> PluginContext:
99
+ """Create the context for plugin initialization.
100
+
101
+ Args:
102
+ core_services: Core services container
103
+
104
+ Returns:
105
+ Plugin context with required services
106
+ """
107
+ ...
108
+
109
+
110
+ class BasePluginFactory(PluginFactory):
111
+ """Base implementation of plugin factory.
112
+
113
+ This class provides common functionality for creating plugin
114
+ runtime instances from manifests.
115
+ """
116
+
117
+ def __init__(self, manifest: PluginManifest, runtime_class: type[Any]):
118
+ """Initialize factory with manifest and runtime class.
119
+
120
+ Args:
121
+ manifest: Plugin manifest
122
+ runtime_class: Runtime class to instantiate
123
+ """
124
+ self.manifest = manifest
125
+ self.runtime_class = runtime_class
126
+
127
+ def get_manifest(self) -> PluginManifest:
128
+ """Get the plugin manifest."""
129
+ return self.manifest
130
+
131
+ def create_runtime(self) -> Any:
132
+ """Create a runtime instance."""
133
+ return self.runtime_class(self.manifest)
134
+
135
+ def create_context(self, service_container: "ServiceContainer") -> PluginContext:
136
+ """Create base context for plugin initialization.
137
+
138
+ Args:
139
+ service_container: Service container with all available services
140
+
141
+ Returns:
142
+ Plugin context with base services
143
+ """
144
+ context = PluginContext()
145
+
146
+ # Set core services
147
+ context.settings = service_container.settings
148
+ context.http_pool_manager = service_container.get_pool_manager()
149
+ context.logger = structlog.get_logger().bind(plugin=self.manifest.name)
150
+
151
+ # Add explicit dependency injection services
152
+ context.request_tracer = service_container.get_request_tracer()
153
+ context.streaming_handler = cast(
154
+ "StreamingMetrics", service_container.get_streaming_handler()
155
+ )
156
+ context.metrics = None # Will be set by plugins if needed
157
+
158
+ # Add CLI detection service
159
+ context.cli_detection_service = service_container.get_cli_detection_service()
160
+
161
+ # Add scheduler - not available in ServiceContainer, get from app state
162
+ context.scheduler = None # Will be set from app.state if needed
163
+
164
+ # Add plugin registry - not directly in ServiceContainer, get from app state
165
+ context.plugin_registry = None # Will be set from app.state
166
+
167
+ # Add OAuth registry for auth providers
168
+ context.oauth_registry = service_container.get_oauth_registry()
169
+
170
+ # Add hook registry and manager
171
+ context.hook_registry = service_container.get_hook_registry()
172
+
173
+ # Provide runtime helpers when available in the container
174
+ try:
175
+ from ccproxy.core.plugins.hooks.manager import HookManager
176
+
177
+ context.hook_manager = service_container.get_service(HookManager)
178
+ except (ValueError, ImportError):
179
+ context.hook_manager = None
180
+
181
+ try:
182
+ context.app = service_container.get_service(FastAPI)
183
+ except ValueError:
184
+ context.app = None
185
+
186
+ # Add service container directly
187
+ context.service_container = service_container
188
+
189
+ # Add plugin-specific config if available
190
+ # ServiceContainer doesn't have get_plugin_config, so we'll get it from settings directly
191
+ if self.manifest.config_class:
192
+ plugin_config = service_container.settings.plugins.get(self.manifest.name)
193
+
194
+ try:
195
+ if plugin_config is None:
196
+ # No explicit config provided; instantiate defaults
197
+ context.config = self.manifest.config_class()
198
+ else:
199
+ # Validate (even if empty dict) to honor model defaults
200
+ validated_config = self.manifest.config_class.model_validate(
201
+ plugin_config
202
+ )
203
+ context.config = validated_config
204
+ except Exception as exc: # pragma: no cover - defensive safety
205
+ logger.warning(
206
+ "plugin_config_initialization_failed",
207
+ plugin=self.manifest.name,
208
+ error=str(exc),
209
+ )
210
+ raise
211
+
212
+ # Add format registry
213
+ context.format_registry = service_container.get_format_registry()
214
+
215
+ return context
216
+
217
+
218
+ class ProviderPluginFactory(BasePluginFactory):
219
+ """Factory for provider plugins.
220
+
221
+ Provider plugins require additional components like adapters
222
+ and detection services that must be created during initialization.
223
+ """
224
+
225
+ def __init__(self, manifest: PluginManifest):
226
+ """Initialize provider plugin factory.
227
+
228
+ Args:
229
+ manifest: Plugin manifest
230
+ """
231
+ # Local import to avoid circular dependency at module load time
232
+ from .runtime import ProviderPluginRuntime
233
+
234
+ super().__init__(manifest, ProviderPluginRuntime)
235
+
236
+ # Validate this is a provider plugin
237
+ if not manifest.is_provider:
238
+ raise ValueError(
239
+ f"Plugin {manifest.name} is not marked as provider but using ProviderPluginFactory"
240
+ )
241
+
242
+ def create_context(self, service_container: "ServiceContainer") -> PluginContext:
243
+ """Create context with provider-specific components.
244
+
245
+ Args:
246
+ core_services: Core services container
247
+
248
+ Returns:
249
+ Plugin context with provider components
250
+ """
251
+ # Start with base context
252
+ context = super().create_context(service_container)
253
+
254
+ # Provider plugins need to create their own adapter and detection service
255
+ # This is typically done in the specific plugin factory implementation
256
+ # Here we just ensure the structure is correct
257
+
258
+ return context
259
+
260
+ @abstractmethod
261
+ async def create_adapter(self, context: PluginContext) -> Any:
262
+ """Create the adapter for this provider.
263
+
264
+ Args:
265
+ context: Plugin context
266
+
267
+ Returns:
268
+ Provider adapter instance
269
+ """
270
+ ...
271
+
272
+ @abstractmethod
273
+ def create_detection_service(
274
+ self, context: PluginContext
275
+ ) -> DetectionServiceProtocol | None:
276
+ """Create the detection service for this provider.
277
+
278
+ Args:
279
+ context: Plugin context
280
+
281
+ Returns:
282
+ Detection service instance or None
283
+ """
284
+ ...
285
+
286
+ @abstractmethod
287
+ async def create_credentials_manager(
288
+ self, context: PluginContext
289
+ ) -> BaseTokenManager[Any] | None:
290
+ """Create the credentials manager for this provider.
291
+
292
+ Args:
293
+ context: Plugin context
294
+
295
+ Returns:
296
+ Credentials manager instance or None
297
+ """
298
+ ...
299
+
300
+
301
+ class SystemPluginFactory(BasePluginFactory):
302
+ """Factory for system plugins."""
303
+
304
+ def __init__(self, manifest: PluginManifest):
305
+ """Initialize system plugin factory.
306
+
307
+ Args:
308
+ manifest: Plugin manifest
309
+ """
310
+ # Local import to avoid circular dependency at module load time
311
+ from .runtime import SystemPluginRuntime
312
+
313
+ super().__init__(manifest, SystemPluginRuntime)
314
+
315
+ # Validate this is a system plugin
316
+ if manifest.is_provider:
317
+ raise ValueError(
318
+ f"Plugin {manifest.name} is marked as provider but using SystemPluginFactory"
319
+ )
320
+
321
+
322
+ class AuthProviderPluginFactory(BasePluginFactory):
323
+ """Factory for authentication provider plugins.
324
+
325
+ Auth provider plugins provide OAuth authentication flows and token management
326
+ without directly proxying requests to API providers.
327
+ """
328
+
329
+ auth_manager_class: type[Any] | None = None
330
+
331
+ def __init__(self, manifest: PluginManifest):
332
+ """Initialize auth provider plugin factory.
333
+
334
+ Args:
335
+ manifest: Plugin manifest
336
+ """
337
+ # Local import to avoid circular dependency at module load time
338
+ from .runtime import AuthProviderPluginRuntime
339
+
340
+ super().__init__(manifest, AuthProviderPluginRuntime)
341
+
342
+ # Validate this is marked as a provider plugin (auth providers are a type of provider)
343
+ if not manifest.is_provider:
344
+ raise ValueError(
345
+ f"Plugin {manifest.name} must be marked as provider for AuthProviderPluginFactory"
346
+ )
347
+
348
+ def create_context(self, service_container: "ServiceContainer") -> PluginContext:
349
+ """Create context with auth provider-specific components.
350
+
351
+ Args:
352
+ core_services: Core services container
353
+
354
+ Returns:
355
+ Plugin context with auth provider components
356
+ """
357
+ # Start with base context
358
+ context = super().create_context(service_container)
359
+
360
+ # Auth provider plugins need to create their auth components
361
+ # This is typically done in the specific plugin factory implementation
362
+
363
+ return context
364
+
365
+ def get_auth_manager_registry_name(self) -> str:
366
+ """Return registry key used for this auth manager."""
367
+
368
+ name = getattr(self, "auth_manager_name", None)
369
+ return name or self.manifest.name
370
+
371
+ @abstractmethod
372
+ def create_auth_provider(
373
+ self, context: PluginContext | None = None
374
+ ) -> OAuthProviderProtocol:
375
+ """Create the OAuth provider for this auth plugin.
376
+
377
+ Args:
378
+ context: Optional plugin context for initialization
379
+
380
+ Returns:
381
+ OAuth provider instance implementing OAuthProviderProtocol
382
+ """
383
+ ...
384
+
385
+ def create_token_manager(self) -> BaseTokenManager[Any] | None:
386
+ """Create the token manager for this auth plugin.
387
+
388
+ Returns:
389
+ Token manager instance or None if not needed
390
+ """
391
+ return None
392
+
393
+ def create_storage(self) -> TokenStorage[Any] | None:
394
+ """Create the storage implementation for this auth plugin.
395
+
396
+ Returns:
397
+ Storage instance or None if using default
398
+ """
399
+ return None
400
+
401
+
402
+ def factory_type_name(factory: PluginFactory) -> str:
403
+ """Return a stable type name for a plugin factory.
404
+
405
+ Returns one of: "auth_provider", "provider", "system", or "plugin" (fallback).
406
+ """
407
+ try:
408
+ if isinstance(factory, AuthProviderPluginFactory):
409
+ return "auth_provider"
410
+ if isinstance(factory, ProviderPluginFactory):
411
+ return "provider"
412
+ if isinstance(factory, SystemPluginFactory):
413
+ return "system"
414
+ except Exception:
415
+ pass
416
+ return "plugin"
@@ -0,0 +1,166 @@
1
+ """Centralized plugin loader.
2
+
3
+ Provides a single entry to discover factories, build a `PluginRegistry`, and
4
+ prepare `MiddlewareManager` based on settings. This isolates loader usage to
5
+ one place and reinforces import boundaries (core should not import concrete
6
+ plugin modules directly).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ import structlog
14
+
15
+ from ccproxy.config.settings import Settings
16
+ from ccproxy.core.plugins.discovery import discover_and_load_plugins
17
+ from ccproxy.core.plugins.factories import PluginRegistry
18
+ from ccproxy.core.plugins.interfaces import (
19
+ AuthProviderPluginFactory,
20
+ PluginFactory,
21
+ )
22
+ from ccproxy.core.plugins.middleware import MiddlewareManager
23
+
24
+
25
+ logger = structlog.get_logger(__name__)
26
+
27
+
28
+ def load_plugin_system(settings: Settings) -> tuple[PluginRegistry, MiddlewareManager]:
29
+ """Discover plugins and build a registry + middleware manager.
30
+
31
+ This function is the single entry point to set up the plugin layer for
32
+ the application factory. It avoids scattering discovery/registry logic.
33
+
34
+ Args:
35
+ settings: Application settings (with plugin config)
36
+
37
+ Returns:
38
+ Tuple of (PluginRegistry, MiddlewareManager)
39
+ """
40
+ # Discover factories (filesystem + entry points) with existing helper
41
+ factories: dict[str, PluginFactory] = discover_and_load_plugins(settings)
42
+
43
+ # Create registry and register all factories
44
+ registry = PluginRegistry()
45
+ for _name, factory in factories.items():
46
+ registry.register_factory(factory)
47
+
48
+ # Prepare middleware manager; plugins will populate via manifests during
49
+ # app creation (manifest population stage) and at runtime as needed
50
+ middleware_manager = MiddlewareManager()
51
+
52
+ logger.debug(
53
+ "plugin_system_loaded",
54
+ factory_count=len(factories),
55
+ plugins=list(factories.keys()),
56
+ category="plugin",
57
+ )
58
+
59
+ return registry, middleware_manager
60
+
61
+
62
+ def load_cli_plugins(
63
+ settings: Any,
64
+ auth_provider: str | None = None,
65
+ allow_plugins: list[str] | None = None,
66
+ ) -> PluginRegistry:
67
+ """Load filtered plugins for CLI operations.
68
+
69
+ This function creates a lightweight plugin registry for CLI commands that:
70
+ - Includes only CLI-safe plugins (marked with cli_safe = True)
71
+ - Optionally includes a specific auth provider plugin if requested
72
+ - Excludes heavy provider plugins that cause DuckDB locks, task manager errors, etc.
73
+
74
+ Args:
75
+ settings: Application settings
76
+ auth_provider: Name of auth provider to include (e.g., "codex", "claude-api")
77
+ allow_plugins: Additional plugins to explicitly allow (beyond cli_safe ones)
78
+
79
+ Returns:
80
+ Filtered PluginRegistry containing only CLI-appropriate plugins
81
+ """
82
+ # Discover all available factories
83
+ all_factories: dict[str, PluginFactory] = discover_and_load_plugins(settings)
84
+
85
+ # Start with CLI-safe plugins
86
+ cli_factories: dict[str, PluginFactory] = {}
87
+
88
+ for name, factory in all_factories.items():
89
+ # Include plugins explicitly marked as CLI-safe
90
+ if getattr(factory, "cli_safe", False):
91
+ cli_factories[name] = factory
92
+
93
+ # Add specific auth provider if requested
94
+ if auth_provider:
95
+ auth_plugin_name = _resolve_auth_provider_plugin_name(auth_provider)
96
+ if auth_plugin_name and auth_plugin_name in all_factories:
97
+ cli_factories[auth_plugin_name] = all_factories[auth_plugin_name]
98
+ else:
99
+ logger.warning(
100
+ "auth_provider_not_found",
101
+ provider=auth_provider,
102
+ resolved_name=auth_plugin_name,
103
+ available_auth_providers=[
104
+ name
105
+ for name, factory in all_factories.items()
106
+ if isinstance(factory, AuthProviderPluginFactory)
107
+ ],
108
+ )
109
+
110
+ # Add explicitly allowed plugins
111
+ if allow_plugins:
112
+ for plugin_name in allow_plugins:
113
+ if plugin_name in all_factories and plugin_name not in cli_factories:
114
+ cli_factories[plugin_name] = all_factories[plugin_name]
115
+
116
+ # Create filtered registry
117
+ registry = PluginRegistry()
118
+ for _name, factory in cli_factories.items():
119
+ registry.register_factory(factory)
120
+
121
+ logger.debug(
122
+ "cli_plugin_system_loaded",
123
+ total_available=len(all_factories),
124
+ cli_safe_count=len(
125
+ [f for f in all_factories.values() if getattr(f, "cli_safe", False)]
126
+ ),
127
+ loaded_count=len(cli_factories),
128
+ loaded_plugins=list(cli_factories.keys()),
129
+ auth_provider=auth_provider,
130
+ allow_plugins=allow_plugins or [],
131
+ category="plugin",
132
+ )
133
+
134
+ return registry
135
+
136
+
137
+ def _resolve_auth_provider_plugin_name(provider: str) -> str | None:
138
+ """Map CLI provider name to auth plugin name.
139
+
140
+ Args:
141
+ provider: CLI provider name (e.g., "codex", "claude-api")
142
+
143
+ Returns:
144
+ Plugin name (e.g., "oauth_codex", "oauth_claude") or None
145
+ """
146
+ provider_key = provider.strip().lower().replace("_", "-")
147
+
148
+ mapping: dict[str, str] = {
149
+ "codex": "oauth_codex",
150
+ "openai": "oauth_codex",
151
+ "openai-api": "oauth_codex",
152
+ "claude": "oauth_claude",
153
+ "claude-api": "oauth_claude",
154
+ "claude_api": "oauth_claude",
155
+ "copilot": "copilot",
156
+ }
157
+
158
+ resolved = mapping.get(provider_key)
159
+ if resolved:
160
+ return resolved
161
+ # Fallback: build dynamically as oauth_<provider>
162
+ fallback = "oauth_" + provider_key.replace("-", "_")
163
+ return fallback
164
+
165
+
166
+ __all__ = ["load_plugin_system", "load_cli_plugins"]