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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0a4.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0a4.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1251
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -8,15 +8,16 @@ import uuid
8
8
  from collections.abc import AsyncIterator
9
9
  from typing import Any
10
10
 
11
- import structlog
11
+ from ccproxy.core.async_task_manager import create_managed_task
12
+ from ccproxy.core.logging import get_plugin_logger
12
13
 
13
- from ccproxy.claude_sdk.message_queue import QueueListener
14
- from ccproxy.claude_sdk.session_client import SessionClient
15
- from ccproxy.claude_sdk.stream_worker import StreamWorker, WorkerStatus
16
- from ccproxy.config.claude import SessionPoolSettings
14
+ from .config import SessionPoolSettings
15
+ from .message_queue import QueueListener
16
+ from .session_client import SessionClient
17
+ from .stream_worker import StreamWorker, WorkerStatus
17
18
 
18
19
 
19
- logger = structlog.get_logger(__name__)
20
+ logger = get_plugin_logger()
20
21
 
21
22
 
22
23
  class StreamHandle:
@@ -73,19 +74,39 @@ class StreamHandle:
73
74
  async def create_listener(self) -> AsyncIterator[Any]:
74
75
  """Create a new listener for this stream.
75
76
 
76
- This method starts the worker on first listener and returns
77
- an async iterator for consuming messages.
77
+ This method creates the worker if needed, pre-registers the listener,
78
+ then starts the worker. This ordering prevents race conditions where
79
+ fast STDIO tools could return results before the listener was ready.
78
80
 
79
81
  Yields:
80
82
  Messages from the stream
81
83
  """
82
- # Start worker if needed
83
- await self._ensure_worker_started()
84
+ # Create worker if needed (but don't start yet)
85
+ async with self._worker_lock:
86
+ if self._worker is None:
87
+ worker_id = f"{self.handle_id}-worker"
88
+ self._worker = StreamWorker(
89
+ worker_id=worker_id,
90
+ message_iterator=self._message_iterator,
91
+ session_id=self.session_id,
92
+ request_id=self.request_id,
93
+ session_client=self._session_client,
94
+ stream_handle=self,
95
+ )
96
+ logger.debug(
97
+ "stream_handle_worker_created",
98
+ handle_id=self.handle_id,
99
+ worker_id=worker_id,
100
+ session_id=self.session_id,
101
+ category="streaming",
102
+ )
84
103
 
85
104
  if not self._worker:
86
- raise RuntimeError("Failed to start stream worker")
105
+ raise RuntimeError("Failed to create stream worker")
87
106
 
88
- # Create listener
107
+ # Pre-register listener BEFORE starting worker
108
+ # This fixes the race condition where fast STDIO tools could
109
+ # return results before the listener was ready
89
110
  queue = self._worker.get_message_queue()
90
111
  listener = await queue.create_listener()
91
112
  self._listeners[listener.listener_id] = listener
@@ -93,12 +114,16 @@ class StreamHandle:
93
114
  if self._first_listener_at is None:
94
115
  self._first_listener_at = time.time()
95
116
 
117
+ # NOW start the worker (after listener is registered)
118
+ await self._worker.start()
119
+
96
120
  logger.debug(
97
121
  "stream_handle_listener_created",
98
122
  handle_id=self.handle_id,
99
123
  listener_id=listener.listener_id,
100
124
  total_listeners=len(self._listeners),
101
125
  worker_status=self._worker.status.value,
126
+ category="streaming",
102
127
  )
103
128
 
104
129
  try:
@@ -133,31 +158,6 @@ class StreamHandle:
133
158
  # Check if we should trigger cleanup
134
159
  await self._check_cleanup()
135
160
 
136
- async def _ensure_worker_started(self) -> None:
137
- """Ensure the worker is started, creating it if needed."""
138
- async with self._worker_lock:
139
- if self._worker is None:
140
- # Create worker
141
- worker_id = f"{self.handle_id}-worker"
142
- self._worker = StreamWorker(
143
- worker_id=worker_id,
144
- message_iterator=self._message_iterator,
145
- session_id=self.session_id,
146
- request_id=self.request_id,
147
- session_client=self._session_client,
148
- stream_handle=self, # Pass self for message tracking
149
- )
150
-
151
- # Start worker
152
- await self._worker.start()
153
-
154
- logger.debug(
155
- "stream_handle_worker_created",
156
- handle_id=self.handle_id,
157
- worker_id=worker_id,
158
- session_id=self.session_id,
159
- )
160
-
161
161
  async def _remove_listener(self, listener_id: str) -> None:
162
162
  """Remove a listener and clean it up.
163
163
 
@@ -177,6 +177,7 @@ class StreamHandle:
177
177
  handle_id=self.handle_id,
178
178
  listener_id=listener_id,
179
179
  remaining_listeners=len(self._listeners),
180
+ category="streaming",
180
181
  )
181
182
 
182
183
  async def _check_cleanup(self) -> None:
@@ -220,7 +221,7 @@ class StreamHandle:
220
221
  )
221
222
  # Still stop the worker to ensure cleanup
222
223
  if self._worker:
223
- logger.info(
224
+ logger.trace(
224
225
  "stream_handle_stopping_worker_direct",
225
226
  handle_id=self.handle_id,
226
227
  message="Stopping worker directly since SDK interrupt not needed",
@@ -245,7 +246,11 @@ class StreamHandle:
245
246
  # Schedule interrupt using a background task with timeout control
246
247
  try:
247
248
  # Create a background task with proper timeout and error handling
248
- asyncio.create_task(self._safe_interrupt_with_timeout())
249
+ await create_managed_task(
250
+ self._safe_interrupt_with_timeout(),
251
+ name=f"stream_interrupt_{self.handle_id}",
252
+ creator="StreamHandle",
253
+ )
249
254
  logger.debug(
250
255
  "stream_handle_interrupt_scheduled",
251
256
  handle_id=self.handle_id,
@@ -295,7 +300,7 @@ class StreamHandle:
295
300
 
296
301
  # Stop our worker after SDK interrupt to ensure it's not blocking the session
297
302
  if self._worker:
298
- logger.info(
303
+ logger.trace(
299
304
  "stream_handle_stopping_worker_after_interrupt",
300
305
  handle_id=self.handle_id,
301
306
  message="Stopping worker to free up session for reuse",
@@ -319,7 +324,7 @@ class StreamHandle:
319
324
 
320
325
  # Fallback: Stop our worker manually if SDK interrupt timed out
321
326
  if self._worker:
322
- logger.info(
327
+ logger.trace(
323
328
  "stream_handle_fallback_worker_stop",
324
329
  handle_id=self.handle_id,
325
330
  message="SDK interrupt timed out, stopping worker as fallback",
@@ -345,7 +350,7 @@ class StreamHandle:
345
350
 
346
351
  # Fallback: Stop our worker manually if SDK interrupt failed
347
352
  if self._worker:
348
- logger.info(
353
+ logger.trace(
349
354
  "stream_handle_fallback_worker_stop_after_error",
350
355
  handle_id=self.handle_id,
351
356
  message="SDK interrupt failed, stopping worker as fallback",
@@ -389,7 +394,7 @@ class StreamHandle:
389
394
  listener.close()
390
395
  self._listeners.clear()
391
396
 
392
- logger.info(
397
+ logger.trace(
393
398
  "stream_handle_interrupted",
394
399
  handle_id=self.handle_id,
395
400
  )
@@ -8,18 +8,19 @@ from collections.abc import AsyncIterator
8
8
  from enum import Enum
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
- import structlog
11
+ from ccproxy.core.async_task_manager import create_managed_task
12
+ from ccproxy.core.logging import get_plugin_logger
12
13
 
13
- from ccproxy.claude_sdk.exceptions import StreamTimeoutError
14
- from ccproxy.claude_sdk.message_queue import MessageQueue
15
- from ccproxy.models import claude_sdk as sdk_models
14
+ from . import models as sdk_models
15
+ from .exceptions import StreamTimeoutError
16
+ from .message_queue import MessageQueue
16
17
 
17
18
 
18
19
  if TYPE_CHECKING:
19
- from ccproxy.claude_sdk.session_client import SessionClient
20
- from ccproxy.claude_sdk.stream_handle import StreamHandle
20
+ from .session_client import SessionClient
21
+ from .stream_handle import StreamHandle
21
22
 
22
- logger = structlog.get_logger(__name__)
23
+ logger = get_plugin_logger()
23
24
 
24
25
 
25
26
  class WorkerStatus(str, Enum):
@@ -35,7 +36,7 @@ class WorkerStatus(str, Enum):
35
36
 
36
37
 
37
38
  class StreamWorker:
38
- """Worker that consumes messages from Claude SDK and distributes via queue."""
39
+ """Worker that consumes messa`es from Claude SDK and distributes via queue."""
39
40
 
40
41
  def __init__(
41
42
  self,
@@ -90,7 +91,11 @@ class StreamWorker:
90
91
  self._started_at = time.time()
91
92
 
92
93
  # Create worker task
93
- self._worker_task = asyncio.create_task(self._run_worker())
94
+ self._worker_task = await create_managed_task(
95
+ self._run_worker(),
96
+ name=f"stream_worker_{self.worker_id}",
97
+ creator="StreamWorker",
98
+ )
94
99
 
95
100
  logger.debug(
96
101
  "stream_worker_started",
@@ -189,13 +194,13 @@ class StreamWorker:
189
194
  self._total_messages += 1
190
195
  self._last_message_time = time.time()
191
196
 
192
- # Check if we have listeners
193
- if await self._message_queue.has_listeners():
194
- # Broadcast to all listeners
195
- delivered_count = await self._message_queue.broadcast(message)
196
- self._messages_delivered += delivered_count
197
+ # Always broadcast - the queue handles no-listeners case atomically
198
+ # Previous bug: Separate has_listeners() check was racy with fast STDIO tools
199
+ delivered_count = await self._message_queue.broadcast(message)
197
200
 
198
- logger.debug(
201
+ if delivered_count > 0:
202
+ self._messages_delivered += delivered_count
203
+ logger.trace(
199
204
  "stream_worker_message_delivered",
200
205
  worker_id=self.worker_id,
201
206
  message_type=type(message).__name__,
@@ -203,10 +208,9 @@ class StreamWorker:
203
208
  total_messages=self._total_messages,
204
209
  )
205
210
  else:
206
- # No listeners - discard message
211
+ # No listeners at broadcast time - message discarded
207
212
  self._messages_discarded += 1
208
-
209
- logger.debug(
213
+ logger.trace(
210
214
  "stream_worker_message_discarded",
211
215
  worker_id=self.worker_id,
212
216
  message_type=type(message).__name__,
@@ -4,16 +4,17 @@ from collections.abc import AsyncIterator
4
4
  from typing import Any
5
5
  from uuid import uuid4
6
6
 
7
- import structlog
7
+ from ccproxy.core.logging import get_plugin_logger
8
+ from ccproxy.core.request_context import RequestContext
8
9
 
9
- from ccproxy.claude_sdk.converter import MessageConverter
10
- from ccproxy.config.claude import SDKMessageMode
11
- from ccproxy.models import claude_sdk as sdk_models
12
- from ccproxy.observability.context import RequestContext
13
- from ccproxy.observability.metrics import PrometheusMetrics
10
+ # from ccproxy.observability.metrics import # Metrics moved to plugin PrometheusMetrics
11
+ from . import models as sdk_models
12
+ from .config import SDKMessageMode
13
+ from .converter import MessageConverter
14
+ from .hooks import ClaudeSDKStreamingHook
14
15
 
15
16
 
16
- logger = structlog.get_logger(__name__)
17
+ logger = get_plugin_logger()
17
18
 
18
19
 
19
20
  class ClaudeStreamProcessor:
@@ -22,16 +23,19 @@ class ClaudeStreamProcessor:
22
23
  def __init__(
23
24
  self,
24
25
  message_converter: MessageConverter,
25
- metrics: PrometheusMetrics | None = None,
26
+ metrics: Any | None = None, # Metrics now handled by metrics plugin
27
+ streaming_hook: ClaudeSDKStreamingHook | None = None,
26
28
  ) -> None:
27
29
  """Initialize the stream processor.
28
30
 
29
31
  Args:
30
32
  message_converter: Converter for message formats.
31
- metrics: Prometheus metrics instance.
33
+ metrics: Optional metrics handler.
34
+ streaming_hook: Hook for emitting streaming events.
32
35
  """
33
36
  self.message_converter = message_converter
34
37
  self.metrics = metrics
38
+ self.streaming_hook = streaming_hook
35
39
 
36
40
  async def process_stream(
37
41
  self,
@@ -72,7 +76,7 @@ class ClaudeStreamProcessor:
72
76
  yield chunk
73
77
 
74
78
  async for message in sdk_stream:
75
- logger.debug(
79
+ logger.trace(
76
80
  "sdk_message_received",
77
81
  message_type=type(message).__name__,
78
82
  request_id=request_id,
@@ -82,7 +86,7 @@ class ClaudeStreamProcessor:
82
86
  )
83
87
 
84
88
  if isinstance(message, sdk_models.SystemMessage):
85
- logger.debug(
89
+ logger.trace(
86
90
  "sdk_system_message_processing",
87
91
  mode=sdk_message_mode.value,
88
92
  subtype=message.subtype,
@@ -109,7 +113,7 @@ class ClaudeStreamProcessor:
109
113
  )
110
114
  for block in message.content:
111
115
  if isinstance(block, sdk_models.TextBlock):
112
- logger.debug(
116
+ logger.trace(
113
117
  "sdk_text_block_processing",
114
118
  text_length=len(block.text),
115
119
  text_preview=block.text[:50],
@@ -139,7 +143,7 @@ class ClaudeStreamProcessor:
139
143
  mode=sdk_message_mode.value,
140
144
  request_id=request_id,
141
145
  )
142
- logger.info(
146
+ logger.debug(
143
147
  "sdk_tool_use_block",
144
148
  tool_id=block.id,
145
149
  tool_name=block.name,
@@ -176,7 +180,7 @@ class ClaudeStreamProcessor:
176
180
  mode=sdk_message_mode.value,
177
181
  request_id=request_id,
178
182
  )
179
- logger.info(
183
+ logger.debug(
180
184
  "sdk_tool_result_block",
181
185
  tool_use_id=block.tool_use_id,
182
186
  is_error=block.is_error,
@@ -287,6 +291,32 @@ class ClaudeStreamProcessor:
287
291
  num_turns=message.num_turns,
288
292
  )
289
293
 
294
+ # Emit PROVIDER_STREAM_END hook with usage metrics
295
+ if self.streaming_hook and message.usage:
296
+ usage_metrics = {
297
+ "tokens_input": message.usage_model.input_tokens,
298
+ "tokens_output": message.usage_model.output_tokens,
299
+ "cache_read_tokens": message.usage_model.cache_read_input_tokens,
300
+ "cache_write_tokens": message.usage_model.cache_creation_input_tokens,
301
+ "cost_usd": message.total_cost_usd,
302
+ "model": getattr(
303
+ message, "model", "claude-3-5-sonnet-20241022"
304
+ ),
305
+ }
306
+
307
+ # Emit the hook asynchronously
308
+ import asyncio
309
+
310
+ asyncio.create_task(
311
+ self.streaming_hook.emit_stream_end(
312
+ request_id=str(request_id or ""),
313
+ usage_metrics=usage_metrics,
314
+ provider="claude_sdk",
315
+ url="claude-sdk://direct",
316
+ method="POST",
317
+ )
318
+ )
319
+
290
320
  end_chunks = self.message_converter.create_streaming_end_chunks(
291
321
  stop_reason=message.stop_reason
292
322
  )
@@ -300,7 +330,7 @@ class ClaudeStreamProcessor:
300
330
  yield end_chunks[1][1] # message_stop
301
331
  break # End of stream
302
332
  else:
303
- logger.warning( # type: ignore[unreachable]
333
+ logger.warning(
304
334
  "sdk_unknown_message_type",
305
335
  message_type=type(message).__name__,
306
336
  message_content=str(message)[:200],
@@ -325,4 +355,8 @@ class ClaudeStreamProcessor:
325
355
  # NOTE: Access logging is now handled by StreamingResponseWithLogging
326
356
  # No need for manual access logging here anymore
327
357
 
328
- logger.debug("claude_sdk_stream_processing_completed", request_id=request_id)
358
+ logger.info(
359
+ "streaming_complete",
360
+ plugin="claude_sdk",
361
+ request_id=request_id,
362
+ )
@@ -0,0 +1,97 @@
1
+ """Scheduled tasks for Claude SDK plugin."""
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from ccproxy.core.logging import get_plugin_logger
6
+ from ccproxy.scheduler.tasks import BaseScheduledTask
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from .detection_service import ClaudeSDKDetectionService
11
+
12
+
13
+ logger = get_plugin_logger()
14
+
15
+
16
+ class ClaudeSDKDetectionRefreshTask(BaseScheduledTask):
17
+ """Task to periodically refresh Claude CLI detection."""
18
+
19
+ def __init__(
20
+ self,
21
+ name: str,
22
+ interval_seconds: float,
23
+ detection_service: "ClaudeSDKDetectionService",
24
+ enabled: bool = True,
25
+ skip_initial_run: bool = True,
26
+ **kwargs: Any,
27
+ ):
28
+ """Initialize the detection refresh task.
29
+
30
+ Args:
31
+ name: Task name
32
+ interval_seconds: How often to run the task
33
+ detection_service: Claude CLI detection service
34
+ enabled: Whether the task is enabled
35
+ skip_initial_run: Whether to skip the first run
36
+ **kwargs: Additional task arguments
37
+ """
38
+ super().__init__(
39
+ name=name,
40
+ interval_seconds=interval_seconds,
41
+ enabled=enabled,
42
+ **kwargs,
43
+ )
44
+ self.detection_service = detection_service
45
+ self.skip_initial_run = skip_initial_run
46
+ self._first_run = True
47
+
48
+ async def run(self) -> bool:
49
+ """Execute the Claude CLI detection refresh.
50
+
51
+ Returns:
52
+ True if successful, False otherwise
53
+ """
54
+ if self._first_run and self.skip_initial_run:
55
+ self._first_run = False
56
+ logger.debug(
57
+ "claude_sdk_detection_refresh_skipped_initial",
58
+ task_name=self.name,
59
+ )
60
+ return True
61
+
62
+ self._first_run = False
63
+
64
+ try:
65
+ logger.debug(
66
+ "claude_sdk_detection_refresh_starting",
67
+ task_name=self.name,
68
+ )
69
+
70
+ # Refresh Claude CLI detection
71
+ detection_data = await self.detection_service.initialize_detection()
72
+
73
+ logger.debug(
74
+ "claude_sdk_detection_refresh_completed",
75
+ task_name=self.name,
76
+ version=detection_data.claude_version or "unknown",
77
+ cli_command=detection_data.cli_command,
78
+ is_available=detection_data.is_available,
79
+ )
80
+ return True
81
+
82
+ except Exception as e:
83
+ logger.error(
84
+ "claude_sdk_detection_refresh_failed",
85
+ task_name=self.name,
86
+ error=str(e),
87
+ exc_info=e,
88
+ )
89
+ return False
90
+
91
+ async def setup(self) -> None:
92
+ """Setup before task execution starts."""
93
+ pass
94
+
95
+ async def cleanup(self) -> None:
96
+ """Cleanup after task execution stops."""
97
+ pass
@@ -0,0 +1,18 @@
1
+ # Claude Shared Utilities
2
+
3
+ Shared metadata consumed by Claude provider plugins.
4
+
5
+ ## Highlights
6
+ - Defines default `ModelCard` entries for common Claude model releases
7
+ - Declares mapping rules that translate OpenAI-style aliases to Claude IDs
8
+
9
+ ## Configuration
10
+ - No standalone plugin configuration; these helpers are imported by
11
+ `claude_api` and `claude_sdk` settings modules.
12
+
13
+ ## Usage
14
+ - Imported during Claude plugin configuration to seed model catalogs
15
+ - Extend by editing `DEFAULT_CLAUDE_MODEL_CARDS` or mapping rules
16
+
17
+ ## Related Components
18
+ - `model_defaults.py`: source of cards and alias mapping helpers
@@ -0,0 +1,12 @@
1
+ """Shared Claude plugin defaults."""
2
+
3
+ from .model_defaults import (
4
+ DEFAULT_CLAUDE_MODEL_CARDS,
5
+ DEFAULT_CLAUDE_MODEL_MAPPINGS,
6
+ )
7
+
8
+
9
+ __all__ = [
10
+ "DEFAULT_CLAUDE_MODEL_CARDS",
11
+ "DEFAULT_CLAUDE_MODEL_MAPPINGS",
12
+ ]