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,154 @@
1
+ """Options handling for Claude SDK interactions."""
2
+
3
+ from typing import Any
4
+
5
+ from claude_agent_sdk import ClaudeAgentOptions
6
+
7
+ from .config import ClaudeSDKSettings
8
+
9
+
10
+ class OptionsHandler:
11
+ """
12
+ Handles creation and management of Claude SDK options.
13
+ """
14
+
15
+ def __init__(self, config: ClaudeSDKSettings) -> None:
16
+ """
17
+ Initialize options handler.
18
+
19
+ Args:
20
+ config: Plugin-specific configuration for Claude SDK
21
+ """
22
+ self.config = config
23
+
24
+ def create_options(
25
+ self,
26
+ model: str,
27
+ temperature: float | None = None,
28
+ max_tokens: int | None = None,
29
+ system_message: str | None = None,
30
+ **additional_options: Any,
31
+ ) -> ClaudeAgentOptions:
32
+ """
33
+ Create Claude SDK options from API parameters.
34
+
35
+ Args:
36
+ model: The model name
37
+ temperature: Temperature for response generation
38
+ max_tokens: Maximum tokens in response
39
+ system_message: System message to include
40
+ **additional_options: Additional options to set on the ClaudeAgentOptions instance
41
+
42
+ Returns:
43
+ Configured ClaudeAgentOptions instance
44
+ """
45
+ # Start with configured defaults if available, otherwise create fresh instance
46
+ if self.config and self.config.code_options:
47
+ configured_opts = self.config.code_options
48
+ options = ClaudeAgentOptions()
49
+
50
+ # Copy all attributes from configured defaults
51
+ for attr in dir(configured_opts):
52
+ if not attr.startswith("_"):
53
+ configured_value = getattr(configured_opts, attr)
54
+ if configured_value is not None and hasattr(options, attr):
55
+ # Special handling for mcp_servers to ensure we copy the dict
56
+ if attr == "mcp_servers" and isinstance(configured_value, dict):
57
+ setattr(options, attr, configured_value.copy())
58
+ else:
59
+ setattr(options, attr, configured_value)
60
+ else:
61
+ options = ClaudeAgentOptions()
62
+
63
+ # Override the model (API parameter takes precedence)
64
+ options.model = model
65
+
66
+ # Apply system message if provided (this is supported by ClaudeAgentOptions)
67
+ if system_message is not None:
68
+ options.system_prompt = system_message
69
+
70
+ # If session_id is provided via additional_options, enable continue_conversation
71
+ if additional_options.get("session_id"):
72
+ options.continue_conversation = True
73
+
74
+ # Automatically map additional_options to ClaudeAgentOptions attributes
75
+ for key, value in additional_options.items():
76
+ if hasattr(options, key):
77
+ try:
78
+ # Attempt type conversion if the attribute already exists
79
+ attr_type = type(getattr(options, key))
80
+ # Only convert if the attribute is not None
81
+ if getattr(options, key) is not None:
82
+ setattr(options, key, attr_type(value))
83
+ else:
84
+ setattr(options, key, value)
85
+ except Exception:
86
+ # Fallback to direct assignment if conversion fails
87
+ setattr(options, key, value)
88
+
89
+ return options
90
+
91
+ @staticmethod
92
+ def extract_system_message(messages: list[dict[str, Any]]) -> str | None:
93
+ """
94
+ Extract system message from Anthropic messages format.
95
+
96
+ Args:
97
+ messages: List of messages in Anthropic format
98
+
99
+ Returns:
100
+ System message content if found, None otherwise
101
+ """
102
+ for message in messages:
103
+ if message.get("role") == "system":
104
+ content = message.get("content", "")
105
+ if isinstance(content, list):
106
+ # Handle content blocks
107
+ text_parts = []
108
+ for block in content:
109
+ if block.get("type") == "text":
110
+ text_parts.append(block.get("text", ""))
111
+ return " ".join(text_parts)
112
+ return str(content)
113
+ return None
114
+
115
+ @staticmethod
116
+ def get_supported_models() -> list[str]:
117
+ """
118
+ Get list of supported Claude models.
119
+
120
+ Returns:
121
+ List of supported model names
122
+ """
123
+ from ccproxy.plugins.claude_shared.model_defaults import (
124
+ DEFAULT_CLAUDE_MODEL_CARDS,
125
+ )
126
+
127
+ return [card.id for card in DEFAULT_CLAUDE_MODEL_CARDS]
128
+
129
+ @staticmethod
130
+ def validate_model(model: str) -> bool:
131
+ """
132
+ Validate if a model is supported.
133
+
134
+ Args:
135
+ model: The model name to validate
136
+
137
+ Returns:
138
+ True if supported, False otherwise
139
+ """
140
+ return model in OptionsHandler.get_supported_models()
141
+
142
+ @staticmethod
143
+ def get_default_options() -> dict[str, Any]:
144
+ """
145
+ Get default options for API parameters.
146
+
147
+ Returns:
148
+ Dictionary of default API parameter values
149
+ """
150
+ return {
151
+ "model": "claude-3-5-sonnet-20241022",
152
+ "temperature": 0.7,
153
+ "max_tokens": 4000,
154
+ }
@@ -14,7 +14,25 @@ import json
14
14
  import re
15
15
  from typing import Any
16
16
 
17
- from ccproxy.adapters.openai.models import format_openai_tool_call
17
+ from ccproxy.llms.models import openai as openai_models
18
+
19
+
20
+ def format_openai_tool_call(tool_use: dict[str, Any]) -> openai_models.ToolCall:
21
+ """Convert Anthropic tool use to OpenAI tool call format."""
22
+ tool_input = tool_use.get("input", {})
23
+ if isinstance(tool_input, dict):
24
+ arguments_str = json.dumps(tool_input)
25
+ else:
26
+ arguments_str = str(tool_input)
27
+
28
+ return openai_models.ToolCall(
29
+ id=tool_use.get("id", ""),
30
+ type="function",
31
+ function=openai_models.FunctionCall(
32
+ name=tool_use.get("name", ""),
33
+ arguments=arguments_str,
34
+ ),
35
+ )
18
36
 
19
37
 
20
38
  def parse_system_message_tags(text: str) -> str:
@@ -31,7 +49,7 @@ def parse_system_message_tags(text: str) -> str:
31
49
  def replace_system_message(match: re.Match[str]) -> str:
32
50
  try:
33
51
  system_data = json.loads(match.group(1))
34
- source = system_data.get("source", "claude_code_sdk")
52
+ source = system_data.get("source", "claude_agent_sdk")
35
53
  system_text = system_data.get("text", "")
36
54
  return f"[{source}]: {system_text}"
37
55
  except json.JSONDecodeError:
@@ -75,7 +93,7 @@ def parse_tool_use_sdk_tags(
75
93
  tool_id = tool_data.get("id", "")
76
94
  tool_name = tool_data.get("name", "")
77
95
  tool_input = tool_data.get("input", {})
78
- return f"[claude_code_sdk tool_use {tool_id}]: {tool_name}({json.dumps(tool_input)})"
96
+ return f"[claude_agent_sdk tool_use {tool_id}]: {tool_name}({json.dumps(tool_input)})"
79
97
  except json.JSONDecodeError:
80
98
  # Keep original if parsing fails
81
99
  return match.group(0)
@@ -102,7 +120,7 @@ def parse_tool_result_sdk_tags(text: str) -> str:
102
120
  result_content = result_data.get("content", "")
103
121
  is_error = result_data.get("is_error", False)
104
122
  error_indicator = " (ERROR)" if is_error else ""
105
- return f"[claude_code_sdk tool_result {tool_use_id}{error_indicator}]: {result_content}"
123
+ return f"[claude_agent_sdk tool_result {tool_use_id}{error_indicator}]: {result_content}"
106
124
  except json.JSONDecodeError:
107
125
  # Keep original if parsing fails
108
126
  return match.group(0)
@@ -124,7 +142,7 @@ def parse_result_message_tags(text: str) -> str:
124
142
  def replace_result_message(match: re.Match[str]) -> str:
125
143
  try:
126
144
  result_data = json.loads(match.group(1))
127
- source = result_data.get("source", "claude_code_sdk")
145
+ source = result_data.get("source", "claude_agent_sdk")
128
146
  session_id = result_data.get("session_id", "")
129
147
  stop_reason = result_data.get("stop_reason", "")
130
148
  usage = result_data.get("usage", {})
@@ -0,0 +1,269 @@
1
+ """Claude SDK plugin v2 implementation."""
2
+
3
+ from typing import Any, cast
4
+
5
+ from ccproxy.core.logging import get_plugin_logger
6
+ from ccproxy.core.plugins import (
7
+ BaseProviderPluginFactory,
8
+ FormatAdapterSpec,
9
+ FormatPair,
10
+ PluginContext,
11
+ PluginManifest,
12
+ ProviderPluginRuntime,
13
+ TaskSpec,
14
+ )
15
+ from ccproxy.core.plugins.declaration import RouterSpec
16
+ from ccproxy.core.plugins.interfaces import DetectionServiceProtocol
17
+ from ccproxy.llms.streaming.accumulators import ClaudeAccumulator
18
+ from ccproxy.services.adapters.base import BaseAdapter
19
+
20
+ from .adapter import ClaudeSDKAdapter
21
+ from .config import ClaudeSDKSettings
22
+ from .detection_service import ClaudeSDKDetectionService
23
+ from .routes import router
24
+ from .tasks import ClaudeSDKDetectionRefreshTask
25
+
26
+
27
+ logger = get_plugin_logger()
28
+
29
+
30
+ class ClaudeSDKRuntime(ProviderPluginRuntime):
31
+ """Runtime for Claude SDK plugin."""
32
+
33
+ def __init__(self, manifest: PluginManifest):
34
+ """Initialize runtime."""
35
+ super().__init__(manifest)
36
+ self.session_manager: Any | None = None
37
+
38
+ async def _on_initialize(self) -> None:
39
+ """Initialize the Claude SDK plugin."""
40
+ # Call parent initialization to set up adapter, detection_service, etc.
41
+ await super()._on_initialize()
42
+
43
+ if not self.context:
44
+ raise RuntimeError("Context not set")
45
+
46
+ # Get configuration
47
+ config = self.context.get("config")
48
+ if not isinstance(config, ClaudeSDKSettings):
49
+ logger.debug("plugin_no_config")
50
+ # Use default config if none provided
51
+ config = ClaudeSDKSettings()
52
+ logger.debug("plugin_using_default_config")
53
+
54
+ # Initialize adapter with session manager if enabled
55
+ if self.adapter and hasattr(self.adapter, "session_manager"):
56
+ self.session_manager = self.adapter.session_manager
57
+ if self.session_manager:
58
+ await self.session_manager.start()
59
+ logger.debug("session_manager_started")
60
+
61
+ # Initialize detection service if present
62
+ if self.detection_service and hasattr(
63
+ self.detection_service, "initialize_detection"
64
+ ):
65
+ await self.detection_service.initialize_detection()
66
+
67
+ # Check CLI status
68
+ version = self.detection_service.get_version()
69
+ cli_path = self.detection_service.get_cli_path()
70
+
71
+ if cli_path:
72
+ # Single consolidated log message with both CLI detection and plugin initialization status
73
+ logger.debug(
74
+ "plugin_initialized",
75
+ plugin="claude_sdk",
76
+ version="0.1.0",
77
+ status="initialized",
78
+ has_credentials=True, # SDK handles its own auth
79
+ cli_available=True,
80
+ cli_version=version,
81
+ cli_path=cli_path,
82
+ cli_source="package_manager",
83
+ has_adapter=self.adapter is not None,
84
+ has_session_manager=self.session_manager is not None,
85
+ )
86
+ else:
87
+ error_msg = "Claude CLI not found in PATH or common locations - SDK plugin requires installed CLI"
88
+ logger.error(
89
+ "plugin_initialization_failed",
90
+ status="failed",
91
+ error=error_msg,
92
+ )
93
+ raise RuntimeError(error_msg)
94
+
95
+ async def _on_shutdown(self) -> None:
96
+ """Cleanup on shutdown."""
97
+ # Shutdown session manager first
98
+ if self.session_manager:
99
+ await self.session_manager.shutdown()
100
+ logger.debug("session_manager_shutdown")
101
+
102
+ # Call parent shutdown which handles adapter cleanup
103
+ await super()._on_shutdown()
104
+
105
+ async def _get_health_details(self) -> dict[str, Any]:
106
+ """Get health check details."""
107
+ details = await super()._get_health_details()
108
+
109
+ # Add SDK-specific health info
110
+ details.update(
111
+ {
112
+ "has_session_manager": self.session_manager is not None,
113
+ }
114
+ )
115
+
116
+ # Add CLI information if available
117
+ if self.detection_service:
118
+ details.update(
119
+ {
120
+ "cli_available": self.detection_service.is_claude_available(),
121
+ "cli_version": self.detection_service.get_version(),
122
+ "cli_path": self.detection_service.get_cli_path(),
123
+ }
124
+ )
125
+
126
+ return details
127
+
128
+
129
+ class ClaudeSDKFactory(BaseProviderPluginFactory):
130
+ """Factory for Claude SDK plugin."""
131
+
132
+ # Plugin configuration via class attributes
133
+ plugin_name = "claude_sdk"
134
+ plugin_description = (
135
+ "Claude SDK plugin providing access to Claude through the Claude Code SDK"
136
+ )
137
+ runtime_class = ClaudeSDKRuntime
138
+ adapter_class = ClaudeSDKAdapter
139
+ detection_service_class = ClaudeSDKDetectionService
140
+ config_class = ClaudeSDKSettings
141
+ routers = [
142
+ RouterSpec(router=router, prefix="/claude/sdk"),
143
+ ]
144
+ optional_requires = ["pricing"]
145
+
146
+ # No format adapters needed - core provides all required conversions
147
+ format_adapters: list[FormatAdapterSpec] = []
148
+
149
+ # Dependencies: All required adapters now provided by core
150
+ requires_format_adapters: list[FormatPair] = []
151
+
152
+ tasks = [
153
+ TaskSpec(
154
+ task_name="claude_sdk_detection_refresh",
155
+ task_type="claude_sdk_detection_refresh",
156
+ task_class=ClaudeSDKDetectionRefreshTask,
157
+ interval_seconds=3600,
158
+ enabled=True,
159
+ kwargs={"skip_initial_run": True},
160
+ )
161
+ ]
162
+ tool_accumulator_class = ClaudeAccumulator
163
+
164
+ async def create_adapter(self, context: PluginContext) -> BaseAdapter:
165
+ """Create the Claude SDK adapter.
166
+
167
+ This method overrides the base implementation because Claude SDK
168
+ has different dependencies than HTTP-based adapters.
169
+
170
+ Args:
171
+ context: Plugin context
172
+
173
+ Returns:
174
+ ClaudeSDKAdapter instance
175
+ """
176
+ config = context.get("config")
177
+ if not isinstance(config, ClaudeSDKSettings):
178
+ raise RuntimeError("No configuration provided for Claude SDK adapter")
179
+
180
+ # Get optional dependencies
181
+ metrics = context.get("metrics")
182
+
183
+ # Try to get hook_manager from context (provided by core services)
184
+ hook_manager = context.get("hook_manager")
185
+ if not hook_manager:
186
+ # Try to get from app state as fallback
187
+ app = context.get("app")
188
+ if app and hasattr(app, "state") and hasattr(app.state, "hook_manager"):
189
+ hook_manager = app.state.hook_manager
190
+
191
+ if hook_manager:
192
+ logger.debug("claude_sdk_hook_manager_found", source="context_or_app")
193
+
194
+ # Create adapter with config and optional dependencies
195
+ # Note: ClaudeSDKAdapter doesn't use an HTTP client, but it still
196
+ # needs access to the shared format registry so it can
197
+ # compose request/response converters declared by the core.
198
+ format_registry = None
199
+ service_container = context.get("service_container")
200
+ if service_container:
201
+ try:
202
+ format_registry = service_container.get_format_registry()
203
+ except Exception as exc: # pragma: no cover - defensive logging
204
+ logger.warning(
205
+ "claude_sdk_format_registry_unavailable",
206
+ error=str(exc),
207
+ category="format",
208
+ )
209
+
210
+ adapter = ClaudeSDKAdapter(
211
+ config=config,
212
+ metrics=metrics,
213
+ hook_manager=hook_manager,
214
+ format_registry=format_registry,
215
+ context=context,
216
+ )
217
+
218
+ return adapter
219
+
220
+ def create_detection_service(
221
+ self, context: PluginContext
222
+ ) -> DetectionServiceProtocol:
223
+ """Create the Claude SDK detection service with validation.
224
+
225
+ Args:
226
+ context: Plugin context
227
+
228
+ Returns:
229
+ ClaudeSDKDetectionService instance
230
+ """
231
+ settings = context.get("settings")
232
+ if not settings:
233
+ raise RuntimeError("No settings provided for Claude SDK detection service")
234
+
235
+ cli_service = context.get("cli_detection_service")
236
+ service = ClaudeSDKDetectionService(settings, cli_service)
237
+ return cast(DetectionServiceProtocol, service)
238
+
239
+ async def create_credentials_manager(self, context: PluginContext) -> None:
240
+ """Create the credentials manager for Claude SDK.
241
+
242
+ Args:
243
+ context: Plugin context
244
+
245
+ Returns:
246
+ None - Claude SDK uses its own authentication mechanism
247
+ """
248
+ # Claude SDK doesn't use a traditional credentials manager
249
+ # It uses the built-in CLI authentication
250
+ return None
251
+
252
+ def create_context(self, core_services: Any) -> PluginContext:
253
+ """Create context and set up detection service in tasks."""
254
+ # Get base context
255
+ context = super().create_context(core_services)
256
+
257
+ # Create detection service early so it can be passed to tasks
258
+ detection_service = self.create_detection_service(context)
259
+
260
+ # Update task kwargs with detection service
261
+ for task_spec in self.manifest.tasks:
262
+ if task_spec.task_name == "claude_sdk_detection_refresh":
263
+ task_spec.kwargs["detection_service"] = detection_service
264
+
265
+ return context
266
+
267
+
268
+ # Export the factory instance
269
+ factory = ClaudeSDKFactory()
@@ -0,0 +1,104 @@
1
+ """Routes for Claude SDK plugin."""
2
+
3
+ from typing import Annotated, Any
4
+
5
+ from fastapi import APIRouter, Depends, Request
6
+ from starlette.responses import Response, StreamingResponse
7
+
8
+ from ccproxy.api.decorators import with_format_chain
9
+ from ccproxy.api.dependencies import get_plugin_adapter
10
+ from ccproxy.auth.dependencies import ConditionalAuthDep
11
+ from ccproxy.core.constants import (
12
+ FORMAT_ANTHROPIC_MESSAGES,
13
+ FORMAT_OPENAI_CHAT,
14
+ FORMAT_OPENAI_RESPONSES,
15
+ )
16
+ from ccproxy.plugins.claude_sdk.adapter import ClaudeSDKAdapter
17
+ from ccproxy.streaming import DeferredStreaming
18
+
19
+
20
+ ClaudeSDKAdapterDep = Annotated[Any, Depends(get_plugin_adapter("claude_sdk"))]
21
+ router = APIRouter()
22
+
23
+ ResponseType = Response | StreamingResponse | DeferredStreaming
24
+
25
+
26
+ async def _handle_claude_sdk_request(
27
+ request: Request,
28
+ adapter: ClaudeSDKAdapter,
29
+ ) -> ResponseType:
30
+ return await adapter.handle_request(request)
31
+
32
+
33
+ @router.post("/v1/messages", response_model=None)
34
+ @with_format_chain([FORMAT_ANTHROPIC_MESSAGES])
35
+ async def claude_sdk_messages(
36
+ request: Request,
37
+ auth: ConditionalAuthDep,
38
+ adapter: ClaudeSDKAdapterDep,
39
+ ) -> ResponseType:
40
+ return await _handle_claude_sdk_request(request, adapter)
41
+
42
+
43
+ @router.post("/v1/chat/completions", response_model=None)
44
+ @with_format_chain(
45
+ [
46
+ FORMAT_OPENAI_CHAT,
47
+ FORMAT_ANTHROPIC_MESSAGES,
48
+ ]
49
+ )
50
+ async def claude_sdk_chat_completions(
51
+ request: Request,
52
+ auth: ConditionalAuthDep,
53
+ adapter: ClaudeSDKAdapterDep,
54
+ ) -> ResponseType:
55
+ return await _handle_claude_sdk_request(request, adapter)
56
+
57
+
58
+ @router.post("/v1/responses", response_model=None)
59
+ @with_format_chain([FORMAT_OPENAI_RESPONSES, FORMAT_ANTHROPIC_MESSAGES])
60
+ async def claude_sdk_responses(
61
+ request: Request,
62
+ auth: ConditionalAuthDep,
63
+ adapter: ClaudeSDKAdapterDep,
64
+ ) -> ResponseType:
65
+ return await _handle_claude_sdk_request(request, adapter)
66
+
67
+
68
+ @router.post("/{session_id}/v1/messages", response_model=None)
69
+ @with_format_chain([FORMAT_ANTHROPIC_MESSAGES])
70
+ async def claude_sdk_messages_with_session(
71
+ request: Request,
72
+ session_id: str,
73
+ auth: ConditionalAuthDep,
74
+ adapter: ClaudeSDKAdapterDep,
75
+ ) -> ResponseType:
76
+ request.state.session_id = session_id
77
+ return await _handle_claude_sdk_request(request, adapter)
78
+
79
+
80
+ @router.post("/{session_id}/v1/chat/completions", response_model=None)
81
+ @with_format_chain(
82
+ [
83
+ FORMAT_OPENAI_CHAT,
84
+ FORMAT_ANTHROPIC_MESSAGES,
85
+ ]
86
+ )
87
+ async def claude_sdk_chat_completions_with_session(
88
+ request: Request,
89
+ session_id: str,
90
+ auth: ConditionalAuthDep,
91
+ adapter: ClaudeSDKAdapterDep,
92
+ ) -> ResponseType:
93
+ request.state.session_id = session_id
94
+ return await _handle_claude_sdk_request(request, adapter)
95
+
96
+
97
+ @router.post("/{session_id}/v1/responses", response_model=None)
98
+ @with_format_chain([FORMAT_OPENAI_RESPONSES, FORMAT_ANTHROPIC_MESSAGES])
99
+ async def claude_sdk_responses_with_session(
100
+ request: Request,
101
+ auth: ConditionalAuthDep,
102
+ adapter: ClaudeSDKAdapterDep,
103
+ ) -> ResponseType:
104
+ return await _handle_claude_sdk_request(request, adapter)