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,57 @@
1
+ """No-op auth manager for Claude SDK plugin."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import SecretStr
6
+
7
+ from ccproxy.auth.oauth.protocol import StandardProfileFields
8
+ from ccproxy.plugins.oauth_claude.models import ClaudeCredentials, ClaudeOAuthToken
9
+
10
+
11
+ class NoOpAuthManager:
12
+ """No-operation auth manager for Claude SDK.
13
+
14
+ The SDK handles authentication internally through the CLI,
15
+ so we don't need to manage auth headers.
16
+ """
17
+
18
+ async def get_access_token(self) -> str:
19
+ """Return empty token since SDK handles auth internally."""
20
+ return ""
21
+
22
+ async def get_credentials(self) -> ClaudeCredentials:
23
+ """Return dummy credentials since SDK handles auth internally."""
24
+ # Create minimal credentials object with OAuthToken
25
+
26
+ oauth_token = ClaudeOAuthToken(
27
+ accessToken=SecretStr("sdk-managed"),
28
+ refreshToken=SecretStr("sdk-managed"),
29
+ expiresAt=None,
30
+ scopes=[],
31
+ subscriptionType="sdk",
32
+ )
33
+ return ClaudeCredentials(claudeAiOauth=oauth_token)
34
+
35
+ async def is_authenticated(self) -> bool:
36
+ """Always return True since SDK handles auth internally."""
37
+ return True
38
+
39
+ async def get_user_profile(self) -> StandardProfileFields | None:
40
+ """Return ``None`` because the SDK does not surface profile metadata."""
41
+ return None
42
+
43
+ async def __aenter__(self) -> "NoOpAuthManager":
44
+ """Async context manager entry."""
45
+ return self
46
+
47
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
48
+ """No cleanup needed."""
49
+ pass
50
+
51
+ async def validate_credentials(self) -> bool:
52
+ """Always return True since SDK handles auth internally."""
53
+ return True
54
+
55
+ def get_provider_name(self) -> str:
56
+ """Get the provider name for logging."""
57
+ return "claude-sdk"
@@ -5,46 +5,47 @@ import contextlib
5
5
  from collections.abc import AsyncIterator
6
6
  from typing import Any, TypeVar, cast
7
7
 
8
- import structlog
9
8
  from pydantic import BaseModel
10
9
 
11
- from ccproxy.claude_sdk.exceptions import ClaudeSDKError, StreamTimeoutError
12
- from ccproxy.claude_sdk.manager import SessionManager
13
- from ccproxy.claude_sdk.stream_handle import StreamHandle
14
- from ccproxy.config.settings import Settings
15
10
  from ccproxy.core.async_utils import patched_typing
16
11
  from ccproxy.core.errors import ClaudeProxyError, ServiceUnavailableError
17
- from ccproxy.models import claude_sdk as sdk_models
18
- from ccproxy.models.claude_sdk import SDKMessage
19
- from ccproxy.observability import timed_operation
12
+ from ccproxy.core.logging import get_plugin_logger
13
+ from ccproxy.core.request_context import timed_operation
14
+
15
+ from . import models as sdk_models
16
+ from .config import ClaudeSDKSettings, SessionPoolSettings
17
+ from .exceptions import ClaudeSDKError, StreamTimeoutError
18
+ from .manager import SessionManager
19
+ from .models import SDKMessage
20
+ from .stream_handle import StreamHandle
20
21
 
21
22
 
22
23
  with patched_typing():
23
- from claude_code_sdk import (
24
+ from claude_agent_sdk import (
24
25
  AssistantMessage as SDKAssistantMessage,
25
26
  )
26
- from claude_code_sdk import (
27
- ClaudeCodeOptions,
27
+ from claude_agent_sdk import (
28
+ ClaudeAgentOptions,
28
29
  CLIConnectionError,
29
30
  CLIJSONDecodeError,
30
31
  CLINotFoundError,
31
32
  ProcessError,
32
33
  )
33
- from claude_code_sdk import (
34
+ from claude_agent_sdk import (
34
35
  ClaudeSDKClient as ImportedClaudeSDKClient,
35
36
  )
36
- from claude_code_sdk import (
37
+ from claude_agent_sdk import (
37
38
  ResultMessage as SDKResultMessage,
38
39
  )
39
- from claude_code_sdk import (
40
+ from claude_agent_sdk import (
40
41
  SystemMessage as SDKSystemMessage,
41
42
  )
42
- from claude_code_sdk import (
43
+ from claude_agent_sdk import (
43
44
  UserMessage as SDKUserMessage,
44
45
  )
45
46
 
46
47
 
47
- logger = structlog.get_logger(__name__)
48
+ logger = get_plugin_logger()
48
49
 
49
50
  T = TypeVar("T", bound=BaseModel)
50
51
 
@@ -69,17 +70,17 @@ class ClaudeSDKClient:
69
70
 
70
71
  def __init__(
71
72
  self,
72
- settings: Settings | None = None,
73
+ config: ClaudeSDKSettings,
73
74
  session_manager: SessionManager | None = None,
74
75
  ) -> None:
75
76
  """Initialize the Claude SDK client.
76
77
 
77
78
  Args:
78
- settings: Application settings for session pool configuration
79
+ config: Plugin-specific configuration for Claude SDK
79
80
  session_manager: Optional SessionManager instance for dependency injection
80
81
  """
81
82
  self._last_api_call_time_ms: float = 0.0
82
- self._settings = settings
83
+ self.config = config
83
84
  self._session_manager = session_manager
84
85
 
85
86
  @contextlib.asynccontextmanager
@@ -121,6 +122,7 @@ class ClaudeSDKClient:
121
122
  error_type=type(e).__name__,
122
123
  operation=operation,
123
124
  request_id=request_id,
125
+ exc_info=e,
124
126
  )
125
127
  raise ClaudeProxyError(
126
128
  message=f"Unexpected error: {str(e)}",
@@ -230,14 +232,10 @@ class ClaudeSDKClient:
230
232
  return False
231
233
 
232
234
  # Check settings using safe attribute chaining
233
- if not self._settings:
234
- return False
235
-
236
- claude_settings = getattr(self._settings, "claude", None)
237
- if not claude_settings:
235
+ if not self.config:
238
236
  return False
239
237
 
240
- pool_settings = getattr(claude_settings, "sdk_session_pool", None)
238
+ pool_settings = getattr(self.config, "sdk_session_pool", None)
241
239
  if not pool_settings:
242
240
  return False
243
241
 
@@ -246,7 +244,7 @@ class ClaudeSDKClient:
246
244
  async def query_completion(
247
245
  self,
248
246
  message: SDKMessage,
249
- options: ClaudeCodeOptions,
247
+ options: ClaudeAgentOptions,
250
248
  request_id: str | None = None,
251
249
  session_id: str | None = None,
252
250
  ) -> StreamHandle:
@@ -278,27 +276,46 @@ class ClaudeSDKClient:
278
276
  async def _create_direct_stream_handle(
279
277
  self,
280
278
  message: SDKMessage,
281
- options: ClaudeCodeOptions,
279
+ options: ClaudeAgentOptions,
282
280
  request_id: str | None = None,
283
281
  session_id: str | None = None,
284
282
  ) -> StreamHandle:
285
283
  """Create stream handle for direct query (no session pool)."""
286
284
  message_iterator = self._query(message, options, request_id, session_id)
287
285
 
286
+ # Convert core settings to plugin settings if available
287
+ plugin_session_config = None
288
+ if self.config and self.config.sdk_session_pool:
289
+ core_pool_settings = self.config.sdk_session_pool
290
+ plugin_session_config = SessionPoolSettings(
291
+ enabled=core_pool_settings.enabled,
292
+ session_ttl=core_pool_settings.session_ttl,
293
+ max_sessions=core_pool_settings.max_sessions,
294
+ cleanup_interval=getattr(core_pool_settings, "cleanup_interval", 300),
295
+ idle_threshold=getattr(core_pool_settings, "idle_threshold", 300),
296
+ connection_recovery=getattr(
297
+ core_pool_settings, "connection_recovery", True
298
+ ),
299
+ stream_first_chunk_timeout=getattr(
300
+ core_pool_settings, "stream_first_chunk_timeout", 8
301
+ ),
302
+ stream_ongoing_timeout=getattr(
303
+ core_pool_settings, "stream_ongoing_timeout", 60
304
+ ),
305
+ )
306
+
288
307
  return StreamHandle(
289
308
  message_iterator=message_iterator,
290
309
  session_id=session_id,
291
310
  request_id=request_id,
292
311
  session_client=None,
293
- session_config=self._settings.claude.sdk_session_pool
294
- if self._settings
295
- else None, # StreamHandle will use defaults
312
+ session_config=plugin_session_config, # StreamHandle will use defaults if None
296
313
  )
297
314
 
298
315
  async def _create_session_pool_stream_handle(
299
316
  self,
300
317
  message: SDKMessage,
301
- options: ClaudeCodeOptions,
318
+ options: ClaudeAgentOptions,
302
319
  request_id: str | None = None,
303
320
  session_id: str | None = None,
304
321
  ) -> StreamHandle:
@@ -343,7 +360,7 @@ class ClaudeSDKClient:
343
360
  async def _query(
344
361
  self,
345
362
  message: SDKMessage,
346
- options: ClaudeCodeOptions,
363
+ options: ClaudeAgentOptions,
347
364
  request_id: str | None = None,
348
365
  session_id: str | None = None,
349
366
  ) -> AsyncIterator[
@@ -380,12 +397,13 @@ class ClaudeSDKClient:
380
397
  "claude_sdk_disconnect_failed",
381
398
  error=str(e),
382
399
  request_id=request_id,
400
+ exc_info=e,
383
401
  )
384
402
 
385
403
  async def _query_with_session_pool(
386
404
  self,
387
405
  message: SDKMessage,
388
- options: ClaudeCodeOptions,
406
+ options: ClaudeAgentOptions,
389
407
  request_id: str | None = None,
390
408
  session_id: str | None = None,
391
409
  ) -> AsyncIterator[
@@ -487,10 +505,10 @@ class ClaudeSDKClient:
487
505
  error=str(e),
488
506
  error_type=type(e).__name__,
489
507
  session_id=session_id,
490
- exc_info=True,
508
+ exc_info=e,
491
509
  )
492
510
  # Fall back to direct query
493
- logger.info(
511
+ logger.debug(
494
512
  "claude_sdk_fallback_to_direct_query", session_id=session_id
495
513
  )
496
514
  async for msg in self._query(message, options, request_id, session_id):
@@ -520,7 +538,9 @@ class ClaudeSDKClient:
520
538
  """
521
539
  try:
522
540
  # Wait for the first chunk with timeout - don't care about message type
523
- logger.debug("waiting_for_first_chunk", timeout=timeout_seconds)
541
+ logger.debug(
542
+ "waiting_for_first_chunk", timeout=timeout_seconds, category="streaming"
543
+ )
524
544
  first_message = await asyncio.wait_for(
525
545
  anext(message_iterator), timeout=timeout_seconds
526
546
  )
@@ -556,6 +576,7 @@ class ClaudeSDKClient:
556
576
  "failed_to_interrupt_stuck_session",
557
577
  session_id=session_id,
558
578
  error=str(e),
579
+ exc_info=e,
559
580
  )
560
581
 
561
582
  # Raise a custom exception with error details
@@ -627,6 +648,7 @@ class ClaudeSDKClient:
627
648
  error=str(e),
628
649
  request_id=request_id,
629
650
  session_id=session_id,
651
+ exc_info=e,
630
652
  )
631
653
  break
632
654
  else:
@@ -659,7 +681,7 @@ class ClaudeSDKClient:
659
681
 
660
682
  async def drain_stream() -> None:
661
683
  try:
662
- logger.info(
684
+ logger.trace(
663
685
  "claude_sdk_starting_stream_drain",
664
686
  session_id=session_id,
665
687
  request_id=request_id,
@@ -675,7 +697,7 @@ class ClaudeSDKClient:
675
697
  ):
676
698
  message_count += 1
677
699
 
678
- logger.info(
700
+ logger.trace(
679
701
  "claude_sdk_stream_drained",
680
702
  session_id=session_id,
681
703
  request_id=request_id,
@@ -688,6 +710,7 @@ class ClaudeSDKClient:
688
710
  request_id=request_id,
689
711
  error=str(e),
690
712
  error_type=type(e).__name__,
713
+ exc_info=e,
691
714
  )
692
715
  finally:
693
716
  if session_client:
@@ -749,6 +772,7 @@ class ClaudeSDKClient:
749
772
  component="claude_sdk",
750
773
  error=str(e),
751
774
  error_type=type(e).__name__,
775
+ exc_info=e,
752
776
  )
753
777
  return False
754
778
 
@@ -763,7 +787,7 @@ class ClaudeSDKClient:
763
787
  """
764
788
  logger.debug("sdk_client_interrupt_session_started", session_id=session_id)
765
789
  if self._session_manager:
766
- logger.info(
790
+ logger.debug(
767
791
  "client_interrupt_session_requested",
768
792
  session_id=session_id,
769
793
  has_session_manager=True,
@@ -0,0 +1,210 @@
1
+ """Configuration for Claude SDK plugin."""
2
+
3
+ from enum import Enum
4
+ from typing import Any
5
+
6
+ from claude_agent_sdk import ClaudeAgentOptions
7
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
8
+
9
+ from ccproxy.models.provider import ModelCard, ModelMappingRule, ProviderConfig
10
+ from ccproxy.plugins.claude_shared.model_defaults import (
11
+ DEFAULT_CLAUDE_MODEL_CARDS,
12
+ DEFAULT_CLAUDE_MODEL_MAPPINGS,
13
+ )
14
+
15
+
16
+ def _create_default_claude_code_options(
17
+ builtin_permissions: bool = True,
18
+ continue_conversation: bool = False,
19
+ ) -> ClaudeAgentOptions:
20
+ """Create ClaudeAgentOptions with default values.
21
+
22
+ Args:
23
+ builtin_permissions: Whether to include built-in permission handling defaults
24
+ """
25
+ if builtin_permissions:
26
+ return ClaudeAgentOptions(
27
+ continue_conversation=continue_conversation,
28
+ mcp_servers={
29
+ "confirmation": {"type": "sse", "url": "http://127.0.0.1:8000/mcp"}
30
+ },
31
+ permission_prompt_tool_name="mcp__confirmation__check_permission",
32
+ )
33
+ else:
34
+ return ClaudeAgentOptions(
35
+ mcp_servers={},
36
+ permission_prompt_tool_name=None,
37
+ continue_conversation=continue_conversation,
38
+ )
39
+
40
+
41
+ class SDKMessageMode(str, Enum):
42
+ """Modes for handling SDK messages from Claude SDK.
43
+
44
+ - forward: Forward SDK content blocks directly with original types and metadata
45
+ - ignore: Skip SDK messages and blocks completely
46
+ - formatted: Format as XML tags with JSON data in text deltas
47
+ """
48
+
49
+ FORWARD = "forward"
50
+ IGNORE = "ignore"
51
+ FORMATTED = "formatted"
52
+
53
+
54
+ class SystemPromptInjectionMode(str, Enum):
55
+ """Modes for system prompt injection.
56
+
57
+ - minimal: Only inject Claude Code identification prompt
58
+ - full: Inject all detected system messages from Claude CLI
59
+ """
60
+
61
+ MINIMAL = "minimal"
62
+ FULL = "full"
63
+
64
+
65
+ class SessionPoolSettings(BaseModel):
66
+ """Session pool configuration settings."""
67
+
68
+ enabled: bool = Field(
69
+ default=True, description="Enable session-aware persistent pooling"
70
+ )
71
+
72
+ session_ttl: int = Field(
73
+ default=3600,
74
+ ge=60,
75
+ le=86400,
76
+ description="Session time-to-live in seconds (1 minute to 24 hours)",
77
+ )
78
+
79
+ max_sessions: int = Field(
80
+ default=1000,
81
+ ge=1,
82
+ le=10000,
83
+ description="Maximum number of concurrent sessions",
84
+ )
85
+
86
+ cleanup_interval: int = Field(
87
+ default=300,
88
+ ge=30,
89
+ le=3600,
90
+ description="Session cleanup interval in seconds (30 seconds to 1 hour)",
91
+ )
92
+
93
+ idle_threshold: int = Field(
94
+ default=600,
95
+ ge=60,
96
+ le=7200,
97
+ description="Session idle threshold in seconds (1 minute to 2 hours)",
98
+ )
99
+
100
+ connection_recovery: bool = Field(
101
+ default=True,
102
+ description="Enable automatic connection recovery for unhealthy sessions",
103
+ )
104
+
105
+ stream_first_chunk_timeout: int = Field(
106
+ default=3,
107
+ ge=1,
108
+ le=30,
109
+ description="Stream first chunk timeout in seconds (1-30 seconds)",
110
+ )
111
+
112
+ stream_ongoing_timeout: int = Field(
113
+ default=60,
114
+ ge=10,
115
+ le=600,
116
+ description="Stream ongoing timeout in seconds after first chunk (10 seconds to 10 minutes)",
117
+ )
118
+
119
+ stream_interrupt_timeout: int = Field(
120
+ default=10,
121
+ ge=2,
122
+ le=60,
123
+ description="Stream interrupt timeout in seconds for SDK and worker operations (2-60 seconds)",
124
+ )
125
+
126
+ @model_validator(mode="after")
127
+ def validate_timeout_hierarchy(self) -> "SessionPoolSettings":
128
+ """Ensure stream timeouts are less than session TTL."""
129
+ if self.stream_ongoing_timeout >= self.session_ttl:
130
+ raise ValueError(
131
+ f"stream_ongoing_timeout ({self.stream_ongoing_timeout}s) must be less than session_ttl ({self.session_ttl}s)"
132
+ )
133
+
134
+ if self.stream_first_chunk_timeout >= self.stream_ongoing_timeout:
135
+ raise ValueError(
136
+ f"stream_first_chunk_timeout ({self.stream_first_chunk_timeout}s) must be less than stream_ongoing_timeout ({self.stream_ongoing_timeout}s)"
137
+ )
138
+
139
+ return self
140
+
141
+
142
+ class ClaudeSDKSettings(ProviderConfig):
143
+ """Claude SDK specific configuration."""
144
+
145
+ # Base required fields for ProviderConfig
146
+ name: str = "claude_sdk"
147
+ base_url: str = "claude-sdk://local" # Special URL for SDK
148
+ supports_streaming: bool = True
149
+ requires_auth: bool = False # SDK handles auth internally
150
+ auth_type: str | None = None
151
+ model_mappings: list[ModelMappingRule] = Field(
152
+ default_factory=lambda: [
153
+ rule.model_copy(deep=True) for rule in DEFAULT_CLAUDE_MODEL_MAPPINGS
154
+ ]
155
+ )
156
+ models_endpoint: list[ModelCard] = Field(
157
+ default_factory=lambda: [
158
+ card.model_copy(deep=True) for card in DEFAULT_CLAUDE_MODEL_CARDS
159
+ ]
160
+ )
161
+
162
+ # Plugin lifecycle settings
163
+ enabled: bool = True
164
+ priority: int = 0
165
+
166
+ # Claude SDK specific settings
167
+ cli_path: str | None = None
168
+ builtin_permissions: bool = True
169
+ session_pool_enabled: bool = False
170
+ session_pool_size: int = 5
171
+ session_timeout_seconds: int = 300
172
+
173
+ # SDK behavior settings
174
+ include_system_messages_in_stream: bool = True
175
+ pretty_format: bool = True
176
+ sdk_message_mode: SDKMessageMode = SDKMessageMode.FORMATTED
177
+
178
+ # Performance settings
179
+ max_tokens_default: int = 4096
180
+ temperature_default: float = 0.7
181
+
182
+ # Additional fields from ClaudeSettings to prevent validation errors
183
+ # Use Any to avoid Pydantic schema generation on external TypedDicts (Py<3.12)
184
+ code_options: Any | None = None
185
+ system_prompt_injection_mode: SystemPromptInjectionMode = (
186
+ SystemPromptInjectionMode.MINIMAL
187
+ )
188
+ sdk_session_pool: SessionPoolSettings | None = None
189
+
190
+ # Default session configuration
191
+ default_session_id: str | None = Field(
192
+ default=None,
193
+ description="Default session ID to use when none is provided. "
194
+ "Useful for single-user setups or development environments.",
195
+ )
196
+ auto_generate_default_session: bool = Field(
197
+ default=False,
198
+ description="Automatically generate a random default session ID at startup. "
199
+ "Overrides default_session_id if enabled. Useful for single-user "
200
+ "setups where you want session persistence during runtime.",
201
+ )
202
+
203
+ @model_validator(mode="after")
204
+ def ensure_session_pool_settings(self) -> "ClaudeSDKSettings":
205
+ """Ensure sdk_session_pool is initialized."""
206
+ if self.sdk_session_pool is None:
207
+ self.sdk_session_pool = SessionPoolSettings()
208
+ return self
209
+
210
+ model_config = ConfigDict(extra="allow")
@@ -5,15 +5,15 @@ import json
5
5
  from collections.abc import Callable
6
6
  from typing import Any
7
7
 
8
- import structlog
9
-
10
- from ccproxy.config.claude import SDKMessageMode
11
8
  from ccproxy.core.async_utils import patched_typing
12
- from ccproxy.models import claude_sdk as sdk_models
13
- from ccproxy.models.messages import MessageResponse
9
+ from ccproxy.core.logging import get_plugin_logger
10
+
11
+ from . import models as sdk_models
12
+ from .config import SDKMessageMode
13
+ from .models import MessageResponse
14
14
 
15
15
 
16
- logger = structlog.get_logger(__name__)
16
+ logger = get_plugin_logger()
17
17
 
18
18
  with patched_typing():
19
19
  pass