ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0a4__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.0a4.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0a4.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.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,170 @@
1
+ """Storage implementation for GitHub Copilot OAuth credentials."""
2
+
3
+ from pathlib import Path
4
+
5
+ from ccproxy.auth.storage.base import BaseJsonStorage
6
+ from ccproxy.core.logging import get_plugin_logger
7
+
8
+ from .models import CopilotCredentials, CopilotOAuthToken, CopilotTokenResponse
9
+
10
+
11
+ logger = get_plugin_logger()
12
+
13
+
14
+ class CopilotOAuthStorage(BaseJsonStorage[CopilotCredentials]):
15
+ """Storage implementation for Copilot OAuth credentials."""
16
+
17
+ def __init__(self, credentials_path: Path | None = None) -> None:
18
+ """Initialize storage with credentials path.
19
+
20
+ Args:
21
+ credentials_path: Path to credentials file (uses default if None)
22
+ """
23
+ if credentials_path is None:
24
+ # Use standard GitHub Copilot storage location
25
+ credentials_path = Path.home() / ".config" / "copilot" / "credentials.json"
26
+
27
+ super().__init__(credentials_path)
28
+
29
+ async def save(self, credentials: CopilotCredentials) -> bool:
30
+ """Store Copilot credentials to file.
31
+
32
+ Args:
33
+ credentials: Credentials to store
34
+ """
35
+ try:
36
+ # Update timestamp
37
+ credentials.refresh_updated_at()
38
+
39
+ # Convert to dict for storage
40
+ data = credentials.model_dump(mode="json", exclude_none=True)
41
+
42
+ # Use parent class's atomic write with backup
43
+ await self._write_json(data)
44
+
45
+ logger.debug(
46
+ "credentials_stored",
47
+ path=str(self.file_path),
48
+ account_type=credentials.account_type,
49
+ )
50
+ return True
51
+ except Exception as e:
52
+ logger.error("credentials_save_failed", error=str(e), exc_info=e)
53
+ return False
54
+
55
+ async def load(self) -> CopilotCredentials | None:
56
+ """Load Copilot credentials from file.
57
+
58
+ Returns:
59
+ Credentials if found and valid, None otherwise
60
+ """
61
+ try:
62
+ # Use parent class's read method
63
+ data = await self._read_json()
64
+ if not data:
65
+ logger.debug(
66
+ "credentials_not_found",
67
+ path=str(self.file_path),
68
+ )
69
+ return None
70
+
71
+ credentials = CopilotCredentials.model_validate(data)
72
+ logger.debug(
73
+ "credentials_loaded",
74
+ path=str(self.file_path),
75
+ account_type=credentials.account_type,
76
+ is_expired=credentials.is_expired(),
77
+ )
78
+ return credentials
79
+ except Exception as e:
80
+ logger.error(
81
+ "credentials_load_failed",
82
+ error=str(e),
83
+ exc_info=e,
84
+ )
85
+ return None
86
+
87
+ async def delete(self) -> bool:
88
+ """Clear stored credentials."""
89
+ result = await super().delete()
90
+
91
+ logger.debug(
92
+ "credentials_cleared",
93
+ path=str(self.file_path),
94
+ )
95
+ return result
96
+
97
+ async def update_oauth_token(self, oauth_token: CopilotOAuthToken) -> None:
98
+ """Update OAuth token in stored credentials.
99
+
100
+ Args:
101
+ oauth_token: New OAuth token to store
102
+ """
103
+ credentials = await self.load()
104
+ if not credentials:
105
+ # Create new credentials with just the OAuth token
106
+ credentials = CopilotCredentials(
107
+ oauth_token=oauth_token, copilot_token=None
108
+ )
109
+ else:
110
+ # Update existing credentials
111
+ credentials.oauth_token = oauth_token
112
+
113
+ await self.save(credentials)
114
+
115
+ async def update_copilot_token(self, copilot_token: CopilotTokenResponse) -> None:
116
+ """Update Copilot service token in stored credentials.
117
+
118
+ Args:
119
+ copilot_token: New Copilot token to store
120
+ """
121
+ credentials = await self.load()
122
+ if not credentials:
123
+ logger.warning(
124
+ "no_oauth_credentials_for_copilot_token",
125
+ message="Cannot store Copilot token without OAuth credentials",
126
+ )
127
+ raise ValueError(
128
+ "OAuth credentials must exist before storing Copilot token"
129
+ )
130
+
131
+ credentials.copilot_token = copilot_token
132
+ await self.save(credentials)
133
+
134
+ async def get_oauth_token(self) -> CopilotOAuthToken | None:
135
+ """Get OAuth token from stored credentials.
136
+
137
+ Returns:
138
+ OAuth token if available, None otherwise
139
+ """
140
+ credentials = await self.load()
141
+ return credentials.oauth_token if credentials else None
142
+
143
+ async def get_copilot_token(self) -> CopilotTokenResponse | None:
144
+ """Get Copilot service token from stored credentials.
145
+
146
+ Returns:
147
+ Copilot token if available, None otherwise
148
+ """
149
+ credentials = await self.load()
150
+ return credentials.copilot_token if credentials else None
151
+
152
+ # BaseOAuthStorage protocol methods
153
+
154
+ # Additional convenience methods for Copilot-specific functionality
155
+
156
+ async def load_credentials(self) -> CopilotCredentials | None:
157
+ """Legacy method name for backward compatibility."""
158
+ return await self.load()
159
+
160
+ async def store_credentials(self, credentials: CopilotCredentials) -> None:
161
+ """Legacy method name for backward compatibility."""
162
+ await self.save(credentials)
163
+
164
+ async def save_credentials(self, credentials: CopilotCredentials) -> None:
165
+ """Save credentials method for OAuth provider compatibility."""
166
+ await self.save(credentials)
167
+
168
+ async def clear_credentials(self) -> None:
169
+ """Legacy method name for backward compatibility."""
170
+ await self.delete()
@@ -0,0 +1,360 @@
1
+ """GitHub Copilot plugin factory and runtime implementation."""
2
+
3
+ import contextlib
4
+ from typing import Any, cast
5
+
6
+ from ccproxy.auth.oauth import OAuthProviderProtocol
7
+ from ccproxy.core.constants import (
8
+ FORMAT_ANTHROPIC_MESSAGES,
9
+ FORMAT_OPENAI_CHAT,
10
+ FORMAT_OPENAI_RESPONSES,
11
+ )
12
+ from ccproxy.core.logging import get_plugin_logger
13
+ from ccproxy.core.plugins import (
14
+ AuthProviderPluginFactory,
15
+ AuthProviderPluginRuntime,
16
+ BaseProviderPluginFactory,
17
+ PluginContext,
18
+ PluginManifest,
19
+ ProviderPluginRuntime,
20
+ )
21
+ from ccproxy.core.plugins.declaration import FormatPair, RouterSpec
22
+ from ccproxy.core.plugins.interfaces import DetectionServiceProtocol
23
+ from ccproxy.llms.streaming.accumulators import OpenAIAccumulator
24
+ from ccproxy.services.adapters.base import BaseAdapter
25
+ from ccproxy.services.interfaces import (
26
+ NullMetricsCollector,
27
+ NullRequestTracer,
28
+ NullStreamingHandler,
29
+ )
30
+
31
+ from .adapter import CopilotAdapter
32
+ from .config import CopilotConfig
33
+ from .detection_service import CopilotDetectionService
34
+ from .manager import CopilotTokenManager
35
+ from .oauth.provider import CopilotOAuthProvider
36
+ from .routes import router_github, router_v1
37
+
38
+
39
+ logger = get_plugin_logger()
40
+
41
+
42
+ class CopilotPluginRuntime(ProviderPluginRuntime, AuthProviderPluginRuntime):
43
+ """Runtime for GitHub Copilot plugin."""
44
+
45
+ def __init__(self, manifest: PluginManifest):
46
+ """Initialize runtime."""
47
+ super().__init__(manifest)
48
+ self.config: CopilotConfig | None = None
49
+ self.adapter: CopilotAdapter | None = None
50
+ self.credential_manager: CopilotTokenManager | None = None
51
+ self.oauth_provider: CopilotOAuthProvider | None = None
52
+ self.detection_service: CopilotDetectionService | None = None
53
+
54
+ async def _on_initialize(self) -> None:
55
+ """Initialize the Copilot plugin."""
56
+ logger.debug(
57
+ "copilot_initializing",
58
+ context_keys=list(self.context.keys()) if self.context else [],
59
+ )
60
+
61
+ # Get configuration
62
+ if self.context:
63
+ config = self.context.get("config")
64
+ if not isinstance(config, CopilotConfig):
65
+ config = CopilotConfig()
66
+ logger.info("copilot_using_default_config")
67
+ self.config = config
68
+
69
+ # Get services from context
70
+ self.oauth_provider = self.context.get("oauth_provider")
71
+ self.detection_service = self.context.get("detection_service")
72
+ self.adapter = self.context.get("adapter")
73
+ with contextlib.suppress(Exception):
74
+ self.credential_manager = self.context.get("credentials_manager")
75
+
76
+ # Call parent initialization - explicitly call both parent classes
77
+ await ProviderPluginRuntime._on_initialize(self)
78
+ await AuthProviderPluginRuntime._on_initialize(self)
79
+
80
+ # Note: BaseHTTPAdapter doesn't have an initialize() method
81
+ # Initialization is handled through dependency injection
82
+
83
+ logger.debug(
84
+ "copilot_plugin_initialized",
85
+ status="initialized",
86
+ has_oauth=bool(self.oauth_provider),
87
+ has_detection=bool(self.detection_service),
88
+ has_adapter=bool(self.adapter),
89
+ category="plugin",
90
+ )
91
+
92
+ async def cleanup(self) -> None:
93
+ """Cleanup plugin resources."""
94
+ errors = []
95
+
96
+ # Cleanup adapter
97
+ if self.adapter:
98
+ try:
99
+ await self.adapter.cleanup()
100
+ except Exception as e:
101
+ errors.append(f"Adapter cleanup failed: {e}")
102
+ finally:
103
+ self.adapter = None
104
+
105
+ # Cleanup OAuth provider
106
+ if self.oauth_provider:
107
+ try:
108
+ await self.oauth_provider.cleanup()
109
+ except Exception as e:
110
+ errors.append(f"OAuth provider cleanup failed: {e}")
111
+ finally:
112
+ self.oauth_provider = None
113
+
114
+ if self.credential_manager:
115
+ try:
116
+ await self.credential_manager.aclose()
117
+ except Exception as e:
118
+ errors.append(f"Token manager cleanup failed: {e}")
119
+ finally:
120
+ self.credential_manager = None
121
+
122
+ if errors:
123
+ logger.error(
124
+ "copilot_plugin_cleanup_failed",
125
+ errors=errors,
126
+ )
127
+ else:
128
+ logger.debug("copilot_plugin_cleanup_completed")
129
+
130
+
131
+ class CopilotPluginFactory(BaseProviderPluginFactory, AuthProviderPluginFactory):
132
+ """Factory for GitHub Copilot plugin."""
133
+
134
+ cli_safe = False # Heavy provider - not for CLI use
135
+
136
+ # Plugin configuration via class attributes
137
+ plugin_name = "copilot"
138
+ plugin_description = "GitHub Copilot provider plugin with OAuth authentication"
139
+ runtime_class = CopilotPluginRuntime
140
+ adapter_class = CopilotAdapter
141
+ detection_service_class = CopilotDetectionService
142
+ config_class = CopilotConfig
143
+ auth_manager_name = "oauth_copilot"
144
+ # credentials_manager_class = CopilotTokenManager
145
+ routers = [
146
+ RouterSpec(router=router_v1, prefix="/copilot/v1", tags=["copilot-api-v1"]),
147
+ RouterSpec(router=router_github, prefix="/copilot", tags=["copilot-github"]),
148
+ ]
149
+ dependencies = []
150
+ optional_requires = []
151
+
152
+ # # Define format adapter dependencies (Anthropic ↔ OpenAI provided by core)
153
+ # requires_format_adapters: list[FormatPair] = [
154
+ # (
155
+ # "anthropic",
156
+ # "openai",
157
+ # ), # Provided by core OpenAI adapter for /v1/messages endpoint
158
+ # ]
159
+
160
+ # Define format adapter requirements (all provided by core)
161
+ requires_format_adapters: list[FormatPair] = [
162
+ # Primary format conversion for Copilot endpoints
163
+ (FORMAT_ANTHROPIC_MESSAGES, FORMAT_OPENAI_CHAT),
164
+ (FORMAT_OPENAI_CHAT, FORMAT_ANTHROPIC_MESSAGES),
165
+ # OpenAI Responses API support
166
+ (FORMAT_OPENAI_RESPONSES, FORMAT_ANTHROPIC_MESSAGES),
167
+ (FORMAT_ANTHROPIC_MESSAGES, FORMAT_OPENAI_RESPONSES),
168
+ (FORMAT_OPENAI_RESPONSES, FORMAT_OPENAI_CHAT),
169
+ (FORMAT_OPENAI_CHAT, FORMAT_OPENAI_RESPONSES),
170
+ ]
171
+ tool_accumulator_class = OpenAIAccumulator
172
+
173
+ def create_context(self, core_services: Any) -> PluginContext:
174
+ """Create context with all plugin components.
175
+
176
+ Args:
177
+ core_services: Core services container
178
+
179
+ Returns:
180
+ Plugin context with all components
181
+ """
182
+ # Start with base context
183
+ context = super().create_context(core_services)
184
+
185
+ # Get or create configuration
186
+ config = context.get("config")
187
+ if not isinstance(config, CopilotConfig):
188
+ config = CopilotConfig()
189
+ context["config"] = config
190
+
191
+ # Create OAuth provider
192
+ oauth_provider = self.create_oauth_provider(context)
193
+ context["oauth_provider"] = oauth_provider
194
+ # Also set as auth_provider for AuthProviderPluginRuntime compatibility
195
+ context["auth_provider"] = oauth_provider
196
+
197
+ # Create detection service
198
+ detection_service = self.create_detection_service(context)
199
+ context["detection_service"] = detection_service
200
+
201
+ # Note: adapter creation is handled asynchronously by create_runtime
202
+ # in factories.py, so we don't create it here in the synchronous context creation
203
+
204
+ return context
205
+
206
+ def create_runtime(self) -> CopilotPluginRuntime:
207
+ """Create runtime instance."""
208
+ return CopilotPluginRuntime(self.manifest)
209
+
210
+ def create_oauth_provider(
211
+ self, context: PluginContext | None = None
212
+ ) -> CopilotOAuthProvider:
213
+ """Create OAuth provider instance.
214
+
215
+ Args:
216
+ context: Plugin context containing shared resources
217
+
218
+ Returns:
219
+ CopilotOAuthProvider instance
220
+ """
221
+ if context and isinstance(context.get("config"), CopilotConfig):
222
+ cfg = cast(CopilotConfig, context.get("config"))
223
+ else:
224
+ cfg = CopilotConfig()
225
+
226
+ config: CopilotConfig = cfg
227
+ http_client = context.get("http_client") if context else None
228
+ hook_manager = context.get("hook_manager") if context else None
229
+ cli_detection_service = (
230
+ context.get("cli_detection_service") if context else None
231
+ )
232
+
233
+ return CopilotOAuthProvider(
234
+ config.oauth,
235
+ http_client=http_client,
236
+ hook_manager=hook_manager,
237
+ detection_service=cli_detection_service,
238
+ )
239
+
240
+ def create_detection_service(
241
+ self, context: PluginContext
242
+ ) -> DetectionServiceProtocol:
243
+ """Create detection service instance.
244
+
245
+ Args:
246
+ context: Plugin context
247
+
248
+ Returns:
249
+ CopilotDetectionService instance
250
+ """
251
+ settings = context.get("settings")
252
+ cli_service = context.get("cli_detection_service")
253
+
254
+ if not settings or not cli_service:
255
+ raise ValueError("Settings and CLI detection service required")
256
+
257
+ service = CopilotDetectionService(settings, cli_service)
258
+ return cast(DetectionServiceProtocol, service)
259
+
260
+ async def create_adapter(self, context: PluginContext) -> BaseAdapter:
261
+ """Create main adapter instance.
262
+
263
+ Args:
264
+ context: Plugin context
265
+
266
+ Returns:
267
+ CopilotAdapter instance
268
+ """
269
+ if not context:
270
+ raise ValueError("Context required for adapter")
271
+
272
+ config = context.get("config")
273
+ if not isinstance(config, CopilotConfig):
274
+ config = CopilotConfig()
275
+
276
+ # Get required dependencies following BaseHTTPAdapter pattern
277
+ oauth_provider = context.get("oauth_provider")
278
+ detection_service = context.get("detection_service")
279
+ http_pool_manager = context.get("http_pool_manager")
280
+ auth_manager = context.get("credentials_manager")
281
+
282
+ # Optional dependencies
283
+ request_tracer = context.get("request_tracer") or NullRequestTracer()
284
+ metrics = context.get("metrics") or NullMetricsCollector()
285
+ streaming_handler = context.get("streaming_handler") or NullStreamingHandler()
286
+ hook_manager = context.get("hook_manager")
287
+
288
+ # Get format_registry from service container
289
+ service_container = context.get("service_container")
290
+ format_registry = None
291
+ if service_container:
292
+ format_registry = service_container.get_format_registry()
293
+
294
+ # Debug: Log what we actually have in the context
295
+ logger.debug(
296
+ "copilot_adapter_dependencies_debug",
297
+ context_keys=list(context.keys()) if context else [],
298
+ has_auth_manager=bool(auth_manager),
299
+ has_detection_service=bool(detection_service),
300
+ has_http_pool_manager=bool(http_pool_manager),
301
+ has_oauth_provider=bool(oauth_provider),
302
+ has_format_registry=bool(format_registry),
303
+ )
304
+
305
+ if not all([detection_service, http_pool_manager, oauth_provider]):
306
+ missing = []
307
+ if not detection_service:
308
+ missing.append("detection_service")
309
+ if not http_pool_manager:
310
+ missing.append("http_pool_manager")
311
+ if not oauth_provider:
312
+ missing.append("oauth_provider")
313
+
314
+ raise ValueError(
315
+ f"Required dependencies missing for CopilotAdapter: {missing}"
316
+ )
317
+
318
+ if auth_manager is None:
319
+ configured_override = None
320
+ if hasattr(context, "config") and context.config is not None:
321
+ with contextlib.suppress(AttributeError):
322
+ configured_override = getattr(context.config, "auth_manager", None)
323
+
324
+ logger.debug(
325
+ "copilot_adapter_missing_auth_manager",
326
+ reason="unresolved_override",
327
+ configured_override=configured_override,
328
+ )
329
+
330
+ return CopilotAdapter(
331
+ config=config,
332
+ auth_manager=auth_manager,
333
+ detection_service=detection_service,
334
+ http_pool_manager=http_pool_manager,
335
+ oauth_provider=oauth_provider,
336
+ request_tracer=request_tracer,
337
+ metrics=metrics,
338
+ streaming_handler=streaming_handler,
339
+ hook_manager=hook_manager,
340
+ format_registry=format_registry,
341
+ context=context,
342
+ )
343
+
344
+ def create_auth_provider(
345
+ self, context: PluginContext | None = None
346
+ ) -> OAuthProviderProtocol:
347
+ """Create OAuth provider instance for AuthProviderPluginFactory interface.
348
+
349
+ Args:
350
+ context: Plugin context containing shared resources
351
+
352
+ Returns:
353
+ CopilotOAuthProvider instance
354
+ """
355
+ provider = self.create_oauth_provider(context)
356
+ return cast(OAuthProviderProtocol, provider)
357
+
358
+
359
+ # Export the factory instance
360
+ factory = CopilotPluginFactory()