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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1251
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,90 +1,96 @@
1
- """Claude SDK service orchestration for business logic."""
1
+ """Claude SDK handler for orchestrating SDK operations.
2
+
3
+ This module contains the core business logic migrated from claude_sdk_service.py,
4
+ handling SDK operations while maintaining clean separation of concerns.
5
+ """
2
6
 
3
7
  from collections.abc import AsyncIterator
4
8
  from typing import Any
9
+ from uuid import uuid4
5
10
 
6
- import structlog
7
- from claude_code_sdk import ClaudeCodeOptions
11
+ from claude_agent_sdk import ClaudeAgentOptions
8
12
 
9
13
  from ccproxy.auth.manager import AuthManager
10
- from ccproxy.claude_sdk.client import ClaudeSDKClient
11
- from ccproxy.claude_sdk.converter import MessageConverter
12
- from ccproxy.claude_sdk.exceptions import StreamTimeoutError
13
- from ccproxy.claude_sdk.manager import SessionManager
14
- from ccproxy.claude_sdk.options import OptionsHandler
15
- from ccproxy.claude_sdk.streaming import ClaudeStreamProcessor
16
- from ccproxy.config.claude import SDKMessageMode
17
- from ccproxy.config.settings import Settings
18
- from ccproxy.core.errors import (
19
- ClaudeProxyError,
20
- ServiceUnavailableError,
21
- )
22
- from ccproxy.models import claude_sdk as sdk_models
23
- from ccproxy.models.claude_sdk import SDKMessage, create_sdk_message
24
- from ccproxy.models.messages import MessageResponse
25
- from ccproxy.observability.context import RequestContext
26
- from ccproxy.observability.metrics import PrometheusMetrics
27
- from ccproxy.utils.model_mapping import map_model_to_claude
28
- from ccproxy.utils.simple_request_logger import write_request_log
29
-
30
-
31
- logger = structlog.get_logger(__name__)
32
-
33
-
34
- class ClaudeSDKService:
14
+ from ccproxy.core.errors import ClaudeProxyError, ServiceUnavailableError
15
+ from ccproxy.core.logging import get_plugin_logger
16
+ from ccproxy.core.request_context import RequestContext
17
+ from ccproxy.llms.models import anthropic as anthropic_models
18
+
19
+ # from ccproxy.observability.metrics import # Metrics moved to plugin PrometheusMetrics
20
+ from ccproxy.utils.model_mapper import ModelMapper, add_model_alias
21
+
22
+ from . import models as sdk_models
23
+ from .client import ClaudeSDKClient
24
+ from .config import ClaudeSDKSettings, SDKMessageMode
25
+ from .converter import MessageConverter
26
+ from .exceptions import StreamTimeoutError
27
+ from .hooks import ClaudeSDKStreamingHook
28
+ from .manager import SessionManager
29
+ from .models import MessageResponse, SDKMessage, create_sdk_message
30
+ from .options import OptionsHandler
31
+ from .streaming import ClaudeStreamProcessor
32
+
33
+
34
+ logger = get_plugin_logger()
35
+
36
+
37
+ def _convert_sdk_message_mode(core_mode: Any) -> SDKMessageMode:
38
+ """Convert core SDKMessageMode to plugin SDKMessageMode."""
39
+ if hasattr(core_mode, "value"):
40
+ # Convert enum value to plugin enum
41
+ if core_mode.value == "forward":
42
+ return SDKMessageMode.FORWARD
43
+ elif core_mode.value == "ignore":
44
+ return SDKMessageMode.IGNORE
45
+ elif core_mode.value == "formatted":
46
+ return SDKMessageMode.FORMATTED
47
+ return SDKMessageMode.FORWARD # Default fallback
48
+
49
+
50
+ class ClaudeSDKHandler:
35
51
  """
36
- Service layer for Claude SDK operations orchestration.
52
+ Handler for Claude SDK operations orchestration.
37
53
 
38
- This class handles business logic coordination between the pure SDK client,
39
- authentication, metrics, and format conversion while maintaining clean
40
- separation of concerns.
54
+ This class encapsulates the business logic for SDK operations,
55
+ migrated from the original claude_sdk_service.py.
41
56
  """
42
57
 
43
58
  def __init__(
44
59
  self,
60
+ config: ClaudeSDKSettings,
45
61
  sdk_client: ClaudeSDKClient | None = None,
46
62
  auth_manager: AuthManager | None = None,
47
- metrics: PrometheusMetrics | None = None,
48
- settings: Settings | None = None,
63
+ metrics: Any | None = None, # Metrics now handled by metrics plugin
49
64
  session_manager: SessionManager | None = None,
65
+ hook_manager: Any | None = None, # HookManager for emitting events
50
66
  ) -> None:
51
- """
52
- Initialize Claude SDK service.
53
-
54
- Args:
55
- sdk_client: Claude SDK client instance
56
- auth_manager: Authentication manager (optional)
57
- metrics: Prometheus metrics instance (optional)
58
- settings: Application settings (optional)
59
- session_manager: Session manager for dependency injection (optional)
60
- """
67
+ """Initialize Claude SDK handler."""
68
+ self.config = config
61
69
  self.sdk_client = sdk_client or ClaudeSDKClient(
62
- settings=settings, session_manager=session_manager
70
+ config=config, session_manager=session_manager
63
71
  )
64
72
  self.auth_manager = auth_manager
65
73
  self.metrics = metrics
66
- self.settings = settings
74
+ self.hook_manager = hook_manager
67
75
  self.message_converter = MessageConverter()
68
- self.options_handler = OptionsHandler(settings=settings)
76
+ self.options_handler = OptionsHandler(config=config)
77
+ self.model_mapper = ModelMapper(config.model_mappings)
78
+
79
+ # Create streaming hook if hook_manager is available
80
+ streaming_hook = None
81
+ if hook_manager:
82
+ streaming_hook = ClaudeSDKStreamingHook(hook_manager=hook_manager)
83
+
69
84
  self.stream_processor = ClaudeStreamProcessor(
70
85
  message_converter=self.message_converter,
71
86
  metrics=self.metrics,
87
+ streaming_hook=streaming_hook,
72
88
  )
73
89
 
74
90
  def _convert_messages_to_sdk_message(
75
91
  self, messages: list[dict[str, Any]], session_id: str | None = None
76
- ) -> "SDKMessage":
77
- """Convert list of Anthropic messages to single SDKMessage.
78
-
79
- Takes the last user message from the list and converts it to SDKMessage format.
80
-
81
- Args:
82
- messages: List of Anthropic API messages
83
- session_id: Optional session ID for conversation continuity
84
-
85
- Returns:
86
- SDKMessage ready to send to Claude SDK
87
- """
92
+ ) -> SDKMessage:
93
+ """Convert list of Anthropic messages to single SDKMessage."""
88
94
  # Find the last user message
89
95
  last_user_message = None
90
96
  for msg in reversed(messages):
@@ -117,15 +123,9 @@ class ClaudeSDKService:
117
123
  self,
118
124
  ctx: RequestContext,
119
125
  session_id: str | None,
120
- options: "ClaudeCodeOptions",
126
+ options: ClaudeAgentOptions,
121
127
  ) -> None:
122
- """Capture session metadata for access logging.
123
-
124
- Args:
125
- ctx: Request context to add metadata to
126
- session_id: Optional session ID
127
- options: Claude Code options
128
- """
128
+ """Capture session metadata for access logging."""
129
129
  if (
130
130
  session_id
131
131
  and hasattr(self.sdk_client, "_session_manager")
@@ -167,13 +167,14 @@ class ClaudeSDKService:
167
167
  "failed_to_capture_session_metadata",
168
168
  session_id=session_id,
169
169
  error=str(e),
170
+ exc_info=e,
170
171
  )
171
172
  else:
172
- # Add basic session metadata for direct connections (no session pool)
173
+ # Add basic session metadata for direct connections
173
174
  ctx.add_metadata(
174
175
  session_type="direct",
175
176
  session_pool_enabled=False,
176
- session_is_new=True, # Direct connections are always new
177
+ session_is_new=True,
177
178
  )
178
179
 
179
180
  async def create_completion(
@@ -187,32 +188,19 @@ class ClaudeSDKService:
187
188
  session_id: str | None = None,
188
189
  **kwargs: Any,
189
190
  ) -> MessageResponse | AsyncIterator[dict[str, Any]]:
190
- """
191
- Create a completion using Claude SDK with business logic orchestration.
192
-
193
- Args:
194
- messages: List of messages in Anthropic format
195
- model: The model to use
196
- temperature: Temperature for response generation
197
- max_tokens: Maximum tokens in response
198
- stream: Whether to stream responses
199
- session_id: Optional session ID for Claude SDK integration
200
- request_context: Existing request context to use instead of creating new one
201
- **kwargs: Additional arguments
202
-
203
- Returns:
204
- Response dict or async iterator of response chunks if streaming
205
-
206
- Raises:
207
- ClaudeProxyError: If request fails
208
- ServiceUnavailableError: If service is unavailable
209
- """
210
-
191
+ """Create a completion using Claude SDK with business logic orchestration."""
211
192
  # Extract system message and create options
212
193
  system_message = self.options_handler.extract_system_message(messages)
213
194
 
214
- # Map model to Claude model
215
- model = map_model_to_claude(model)
195
+ if isinstance(request_context, RequestContext):
196
+ metadata = request_context.metadata
197
+ else:
198
+ metadata = None
199
+
200
+ match = self.model_mapper.map(model)
201
+ if match.mapped != match.original and isinstance(metadata, dict):
202
+ add_model_alias(metadata, match.original, match.mapped)
203
+ model = match.mapped
216
204
 
217
205
  options = self.options_handler.create_options(
218
206
  model=model,
@@ -223,9 +211,7 @@ class ClaudeSDKService:
223
211
  **kwargs,
224
212
  )
225
213
 
226
- # Messages will be converted to SDK format in the client layer
227
-
228
- # Use existing context, but update metadata for this service (preserve original service_type)
214
+ # Use existing context
229
215
  ctx = request_context
230
216
  metadata = {
231
217
  "endpoint": "messages",
@@ -235,19 +221,13 @@ class ClaudeSDKService:
235
221
  if session_id:
236
222
  metadata["session_id"] = session_id
237
223
  ctx.add_metadata(**metadata)
238
- # Use existing request ID from context
239
224
  request_id = ctx.request_id
240
225
 
241
226
  try:
242
- # Log SDK request parameters
227
+ # Removed SDK request logging (simple_request_logger removed)
243
228
  timestamp = ctx.get_log_timestamp_prefix() if ctx else None
244
- await self._log_sdk_request(
245
- request_id, messages, options, model, stream, session_id, timestamp
246
- )
247
229
 
248
230
  if stream:
249
- # For streaming, return the async iterator directly
250
- # Access logging will be handled by the stream processor when ResultMessage is received
251
231
  return self._stream_completion(
252
232
  ctx, messages, options, model, session_id, timestamp
253
233
  )
@@ -257,7 +237,6 @@ class ClaudeSDKService:
257
237
  )
258
238
  return result
259
239
  except (ClaudeProxyError, ServiceUnavailableError) as e:
260
- # Add error info to context for automatic access logging
261
240
  ctx.add_metadata(error_message=str(e), error_type=type(e).__name__)
262
241
  raise
263
242
 
@@ -265,27 +244,14 @@ class ClaudeSDKService:
265
244
  self,
266
245
  ctx: RequestContext,
267
246
  messages: list[dict[str, Any]],
268
- options: "ClaudeCodeOptions",
247
+ options: ClaudeAgentOptions,
269
248
  model: str,
270
249
  session_id: str | None = None,
271
250
  timestamp: str | None = None,
272
251
  ) -> MessageResponse:
273
- """
274
- Complete a non-streaming request with business logic.
275
-
276
- Args:
277
- prompt: The formatted prompt
278
- options: Claude SDK options
279
- model: The model being used
280
-
281
- Returns:
282
- Response in Anthropic format
283
-
284
- Raises:
285
- ClaudeProxyError: If completion fails
286
- """
252
+ """Complete a non-streaming request with business logic."""
287
253
  request_id = ctx.request_id
288
- logger.debug("claude_sdk_completion_start", request_id=request_id)
254
+ logger.debug("completion_start", request_id=request_id)
289
255
 
290
256
  # Convert messages to single SDKMessage
291
257
  sdk_message = self._convert_messages_to_sdk_message(messages, session_id)
@@ -295,7 +261,7 @@ class ClaudeSDKService:
295
261
  sdk_message, options, request_id, session_id
296
262
  )
297
263
 
298
- # Capture session metadata for access logging
264
+ # Capture session metadata
299
265
  await self._capture_session_metadata(ctx, session_id, options)
300
266
 
301
267
  # Create a listener and collect all messages
@@ -325,13 +291,13 @@ class ClaudeSDKService:
325
291
  status_code=500,
326
292
  )
327
293
 
328
- logger.debug("claude_sdk_completion_received")
294
+ logger.debug("completion_received")
329
295
  mode = (
330
- self.settings.claude.sdk_message_mode
331
- if self.settings
296
+ _convert_sdk_message_mode(self.config.sdk_message_mode)
297
+ if self.config
332
298
  else SDKMessageMode.FORWARD
333
299
  )
334
- pretty_format = self.settings.claude.pretty_format if self.settings else True
300
+ pretty_format = self.config.pretty_format if self.config else True
335
301
 
336
302
  response = self.message_converter.convert_to_anthropic_response(
337
303
  assistant_message, result_message, model, mode, pretty_format
@@ -358,19 +324,19 @@ class ClaudeSDKService:
358
324
  },
359
325
  )
360
326
  if content_block:
361
- # Only validate as SDKMessageMode if it's a system_message type
362
327
  if content_block.get("type") == "system_message":
363
328
  response.content.append(
364
329
  sdk_models.SDKMessageMode.model_validate(content_block)
365
330
  )
366
331
  else:
367
- # For other types (like text blocks in FORMATTED mode), create appropriate content block
368
332
  if content_block.get("type") == "text":
333
+ # Convert SDK TextBlock to core TextContentBlock
369
334
  response.content.append(
370
- sdk_models.TextBlock.model_validate(content_block)
335
+ anthropic_models.TextBlock(
336
+ type="text", text=content_block["text"]
337
+ )
371
338
  )
372
339
  else:
373
- # Fallback for other content block types
374
340
  logger.warning(
375
341
  "unknown_content_block_type",
376
342
  content_block_type=content_block.get("type"),
@@ -378,14 +344,20 @@ class ClaudeSDKService:
378
344
  elif isinstance(message, sdk_models.UserMessage):
379
345
  for block in message.content:
380
346
  if isinstance(block, sdk_models.ToolResultBlock):
381
- response.content.append(block)
347
+ # Convert SDK ToolResultBlock to ToolResultSDKBlock
348
+ response.content.append(
349
+ sdk_models.ToolResultSDKBlock(
350
+ type="tool_result_sdk",
351
+ tool_use_id=block.tool_use_id,
352
+ content=block.content,
353
+ is_error=block.is_error,
354
+ source="claude_agent_sdk",
355
+ )
356
+ )
382
357
 
383
358
  cost_usd = result_message.total_cost_usd
384
359
  usage = result_message.usage_model
385
360
 
386
- # if cost_usd is not None and response.usage:
387
- # response.usage.cost_usd = cost_usd
388
-
389
361
  logger.debug(
390
362
  "claude_sdk_completion_completed",
391
363
  model=model,
@@ -407,12 +379,6 @@ class ClaudeSDKService:
407
379
  session_id=result_message.session_id,
408
380
  num_turns=result_message.num_turns,
409
381
  )
410
- # Add success status to context for automatic access logging
411
- ctx.add_metadata(status_code=200)
412
-
413
- # Log SDK response
414
- if request_id:
415
- await self._log_sdk_response(request_id, response, timestamp)
416
382
 
417
383
  return response
418
384
 
@@ -420,40 +386,29 @@ class ClaudeSDKService:
420
386
  self,
421
387
  ctx: RequestContext,
422
388
  messages: list[dict[str, Any]],
423
- options: "ClaudeCodeOptions",
389
+ options: ClaudeAgentOptions,
424
390
  model: str,
425
391
  session_id: str | None = None,
426
392
  timestamp: str | None = None,
427
393
  ) -> AsyncIterator[dict[str, Any]]:
428
- """
429
- Stream completion responses with business logic.
430
-
431
- Args:
432
- prompt: The formatted prompt
433
- options: Claude SDK options
434
- model: The model being used
435
- ctx: Optional request context for metrics
436
-
437
- Yields:
438
- Response chunks in Anthropic format
439
- """
394
+ """Stream completion responses with business logic."""
440
395
  request_id = ctx.request_id
441
396
  sdk_message_mode = (
442
- self.settings.claude.sdk_message_mode
443
- if self.settings
397
+ _convert_sdk_message_mode(self.config.sdk_message_mode)
398
+ if self.config
444
399
  else SDKMessageMode.FORWARD
445
400
  )
446
- pretty_format = self.settings.claude.pretty_format if self.settings else True
401
+ pretty_format = self.config.pretty_format if self.config else True
447
402
 
448
403
  # Convert messages to single SDKMessage
449
404
  sdk_message = self._convert_messages_to_sdk_message(messages, session_id)
450
405
 
451
- # Get stream handle instead of direct iterator
406
+ # Get stream handle
452
407
  stream_handle = await self.sdk_client.query_completion(
453
408
  sdk_message, options, request_id, session_id
454
409
  )
455
410
 
456
- # Store handle in session client if available for cleanup
411
+ # Store handle in session client if available
457
412
  if (
458
413
  session_id
459
414
  and hasattr(self.sdk_client, "_session_manager")
@@ -472,9 +427,10 @@ class ClaudeSDKService:
472
427
  "failed_to_store_stream_handle",
473
428
  session_id=session_id,
474
429
  error=str(e),
430
+ exc_info=e,
475
431
  )
476
432
 
477
- # Capture session metadata for access logging
433
+ # Capture session metadata
478
434
  await self._capture_session_metadata(ctx, session_id, options)
479
435
 
480
436
  # Create a listener for this stream
@@ -489,19 +445,13 @@ class ClaudeSDKService:
489
445
  sdk_message_mode=sdk_message_mode,
490
446
  pretty_format=pretty_format,
491
447
  ):
492
- # Log streaming chunk
493
- if request_id:
494
- await self._log_sdk_streaming_chunk(request_id, chunk, timestamp)
495
448
  yield chunk
496
449
  except GeneratorExit:
497
- # Client disconnected - log and re-raise to propagate to create_listener()
498
- logger.info(
499
- "claude_sdk_service_client_disconnected",
450
+ logger.debug(
451
+ "claude_sdk_handler_client_disconnected",
500
452
  request_id=request_id,
501
453
  session_id=session_id,
502
- message="Client disconnected from SDK service stream, propagating to stream handle",
503
454
  )
504
- # CRITICAL: Re-raise GeneratorExit to trigger interrupt in create_listener()
505
455
  raise
506
456
  except StreamTimeoutError as e:
507
457
  # Send error events to the client
@@ -513,12 +463,9 @@ class ClaudeSDKService:
513
463
  request_id=request_id,
514
464
  )
515
465
 
516
- # Create a unique message ID for the error response
517
- from uuid import uuid4
518
-
519
466
  error_message_id = f"msg_error_{uuid4()}"
520
467
 
521
- # Yield message_start event
468
+ # Yield error events
522
469
  yield {
523
470
  "type": "message_start",
524
471
  "message": {
@@ -533,14 +480,12 @@ class ClaudeSDKService:
533
480
  },
534
481
  }
535
482
 
536
- # Yield content_block_start for error message
537
483
  yield {
538
484
  "type": "content_block_start",
539
485
  "index": 0,
540
486
  "content_block": {"type": "text", "text": ""},
541
487
  }
542
488
 
543
- # Yield error text delta
544
489
  error_text = f"Error: {e}"
545
490
  yield {
546
491
  "type": "content_block_delta",
@@ -548,136 +493,25 @@ class ClaudeSDKService:
548
493
  "delta": {"type": "text_delta", "text": error_text},
549
494
  }
550
495
 
551
- # Yield content_block_stop
552
- yield {
553
- "type": "content_block_stop",
554
- "index": 0,
555
- }
496
+ yield {"type": "content_block_stop", "index": 0}
556
497
 
557
- # Yield message_delta with stop reason
558
498
  yield {
559
499
  "type": "message_delta",
560
500
  "delta": {"stop_reason": "error", "stop_sequence": None},
561
501
  "usage": {"output_tokens": len(error_text.split())},
562
502
  }
563
503
 
564
- # Yield message_stop
565
- yield {
566
- "type": "message_stop",
567
- }
504
+ yield {"type": "message_stop"}
568
505
 
569
- # Update context with error status
570
506
  ctx.add_metadata(
571
- status_code=504, # Gateway Timeout
507
+ status_code=504,
572
508
  error_message=str(e),
573
509
  error_type="stream_timeout",
574
510
  session_id=e.session_id,
575
511
  )
576
512
 
577
- async def _log_sdk_request(
578
- self,
579
- request_id: str,
580
- messages: list[dict[str, Any]],
581
- options: "ClaudeCodeOptions",
582
- model: str,
583
- stream: bool,
584
- session_id: str | None = None,
585
- timestamp: str | None = None,
586
- ) -> None:
587
- """Log SDK input parameters as JSON dump.
588
-
589
- Args:
590
- request_id: Request identifier
591
- messages: List of Anthropic API messages
592
- options: Claude SDK options
593
- model: The model being used
594
- stream: Whether streaming is enabled
595
- session_id: Optional session ID for Claude SDK integration
596
- timestamp: Optional timestamp prefix
597
- """
598
- # timestamp is already provided from context, no need for fallback
599
-
600
- # JSON dump of the parameters passed to SDK completion
601
- sdk_request_data = {
602
- "messages": messages,
603
- "options": options,
604
- "stream": stream,
605
- "request_id": request_id,
606
- }
607
- if session_id:
608
- sdk_request_data["session_id"] = session_id
609
-
610
- await write_request_log(
611
- request_id=request_id,
612
- log_type="sdk_request",
613
- data=sdk_request_data,
614
- timestamp=timestamp,
615
- )
616
-
617
- async def _log_sdk_response(
618
- self,
619
- request_id: str,
620
- result: Any,
621
- timestamp: str | None = None,
622
- ) -> None:
623
- """Log SDK response result as JSON dump.
624
-
625
- Args:
626
- request_id: Request identifier
627
- result: The result from _complete_non_streaming
628
- timestamp: Optional timestamp prefix
629
- """
630
- # timestamp is already provided from context, no need for fallback
631
-
632
- # JSON dump of the result from _complete_non_streaming
633
- sdk_response_data = {
634
- "result": result.model_dump()
635
- if hasattr(result, "model_dump")
636
- else str(result),
637
- }
638
-
639
- await write_request_log(
640
- request_id=request_id,
641
- log_type="sdk_response",
642
- data=sdk_response_data,
643
- timestamp=timestamp,
644
- )
645
-
646
- async def _log_sdk_streaming_chunk(
647
- self,
648
- request_id: str,
649
- chunk: dict[str, Any],
650
- timestamp: str | None = None,
651
- ) -> None:
652
- """Log streaming chunk as JSON dump.
653
-
654
- Args:
655
- request_id: Request identifier
656
- chunk: The streaming chunk from process_stream
657
- timestamp: Optional timestamp prefix
658
- """
659
- # timestamp is already provided from context, no need for fallback
660
-
661
- # Append streaming chunk as JSON to raw file
662
- import json
663
-
664
- from ccproxy.utils.simple_request_logger import append_streaming_log
665
-
666
- chunk_data = json.dumps(chunk, default=str) + "\n"
667
- await append_streaming_log(
668
- request_id=request_id,
669
- log_type="sdk_streaming",
670
- data=chunk_data.encode("utf-8"),
671
- timestamp=timestamp,
672
- )
673
-
674
513
  async def validate_health(self) -> bool:
675
- """
676
- Validate that the service is healthy.
677
-
678
- Returns:
679
- True if healthy, False otherwise
680
- """
514
+ """Validate that the handler is healthy."""
681
515
  try:
682
516
  return await self.sdk_client.validate_health()
683
517
  except Exception as e:
@@ -685,29 +519,14 @@ class ClaudeSDKService:
685
519
  "health_check_failed",
686
520
  error=str(e),
687
521
  error_type=type(e).__name__,
688
- exc_info=True,
522
+ exc_info=e,
689
523
  )
690
524
  return False
691
525
 
692
526
  async def interrupt_session(self, session_id: str) -> bool:
693
- """Interrupt a Claude session due to client disconnection.
694
-
695
- Args:
696
- session_id: The session ID to interrupt
697
-
698
- Returns:
699
- True if session was found and interrupted, False otherwise
700
- """
527
+ """Interrupt a Claude session due to client disconnection."""
701
528
  return await self.sdk_client.interrupt_session(session_id)
702
529
 
703
530
  async def close(self) -> None:
704
- """Close the service and cleanup resources."""
531
+ """Close the handler and cleanup resources."""
705
532
  await self.sdk_client.close()
706
-
707
- async def __aenter__(self) -> "ClaudeSDKService":
708
- """Async context manager entry."""
709
- return self
710
-
711
- async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
712
- """Async context manager exit."""
713
- await self.close()