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
@@ -0,0 +1,519 @@
1
+ """Plugin runtime system for managing plugin instances.
2
+
3
+ This module defines runtime classes that manage plugin instances and lifecycle.
4
+ Factory/loader utilities remain in their respective modules to avoid import
5
+ cycles during consolidation. Import runtime classes from here, and import
6
+ factories/loaders from their modules for now.
7
+ """
8
+
9
+ from typing import Any
10
+
11
+ from ccproxy.core.logging import TraceBoundLogger, get_logger
12
+
13
+ from .declaration import PluginContext, PluginManifest, PluginRuntimeProtocol
14
+
15
+
16
+ __all__ = [
17
+ "BasePluginRuntime",
18
+ "SystemPluginRuntime",
19
+ "AuthProviderPluginRuntime",
20
+ "ProviderPluginRuntime",
21
+ "PluginContext",
22
+ "PluginManifest",
23
+ "PluginRuntimeProtocol",
24
+ ]
25
+
26
+
27
+ logger: TraceBoundLogger = get_logger()
28
+
29
+
30
+ class BasePluginRuntime(PluginRuntimeProtocol):
31
+ """Base implementation of plugin runtime.
32
+
33
+ This class provides common functionality for all plugin runtimes.
34
+ Specific plugin types (system, provider) can extend this base class.
35
+ """
36
+
37
+ def __init__(self, manifest: PluginManifest):
38
+ """Initialize runtime with manifest.
39
+
40
+ Args:
41
+ manifest: Plugin manifest with static declarations
42
+ """
43
+ self.manifest = manifest
44
+ self.context: PluginContext | None = None
45
+ self.initialized = False
46
+
47
+ @property
48
+ def name(self) -> str:
49
+ """Plugin name from manifest."""
50
+ return self.manifest.name
51
+
52
+ @property
53
+ def version(self) -> str:
54
+ """Plugin version from manifest."""
55
+ return self.manifest.version
56
+
57
+ async def initialize(self, context: PluginContext) -> None:
58
+ """Initialize the plugin with runtime context.
59
+
60
+ Args:
61
+ context: Runtime context with services and configuration
62
+ """
63
+ if self.initialized:
64
+ logger.warning(
65
+ "plugin_already_initialized", plugin=self.name, category="plugin"
66
+ )
67
+ return
68
+
69
+ self.context = context
70
+
71
+ # Allow subclasses to perform custom initialization
72
+ await self._on_initialize()
73
+
74
+ self.initialized = True
75
+ logger.debug(
76
+ "plugin_initialized",
77
+ plugin=self.name,
78
+ version=self.version,
79
+ category="plugin",
80
+ )
81
+
82
+ async def _on_initialize(self) -> None:
83
+ """Hook for subclasses to perform custom initialization.
84
+
85
+ Override this method in subclasses to add custom initialization logic.
86
+ """
87
+ pass
88
+
89
+ async def shutdown(self) -> None:
90
+ """Cleanup on shutdown."""
91
+ if not self.initialized:
92
+ return
93
+
94
+ # Allow subclasses to perform custom cleanup
95
+ await self._on_shutdown()
96
+
97
+ self.initialized = False
98
+ logger.info("plugin_shutdown", plugin=self.name, category="plugin")
99
+
100
+ async def _on_shutdown(self) -> None:
101
+ """Hook for subclasses to perform custom cleanup.
102
+
103
+ Override this method in subclasses to add custom cleanup logic.
104
+ """
105
+ pass
106
+
107
+ async def validate(self) -> bool:
108
+ """Validate plugin is ready.
109
+
110
+ Returns:
111
+ True if plugin is ready, False otherwise
112
+ """
113
+ # Basic validation - plugin is initialized
114
+ if not self.initialized:
115
+ return False
116
+
117
+ # Allow subclasses to add custom validation
118
+ return await self._on_validate()
119
+
120
+ async def _on_validate(self) -> bool:
121
+ """Hook for subclasses to perform custom validation.
122
+
123
+ Override this method in subclasses to add custom validation logic.
124
+
125
+ Returns:
126
+ True if validation passes, False otherwise
127
+ """
128
+ return True
129
+
130
+ async def health_check(self) -> dict[str, Any]:
131
+ """Perform health check.
132
+
133
+ Returns:
134
+ Health check result following IETF format
135
+ """
136
+ try:
137
+ # Start with basic health check
138
+ is_healthy = await self.validate()
139
+
140
+ # Allow subclasses to provide detailed health info
141
+ details = await self._get_health_details()
142
+
143
+ return {
144
+ "status": "pass" if is_healthy else "fail",
145
+ "componentId": self.name,
146
+ "componentType": "provider_plugin"
147
+ if self.manifest.is_provider
148
+ else "system_plugin",
149
+ "version": self.version,
150
+ "details": details,
151
+ }
152
+ except Exception as e:
153
+ logger.error(
154
+ "plugin_health_check_failed",
155
+ plugin=self.name,
156
+ error=str(e),
157
+ exc_info=e,
158
+ category="plugin",
159
+ )
160
+ return {
161
+ "status": "fail",
162
+ "componentId": self.name,
163
+ "componentType": "provider_plugin"
164
+ if self.manifest.is_provider
165
+ else "system_plugin",
166
+ "version": self.version,
167
+ "output": str(e),
168
+ }
169
+
170
+ async def _get_health_details(self) -> dict[str, Any]:
171
+ """Hook for subclasses to provide health check details.
172
+
173
+ Override this method in subclasses to add custom health check details.
174
+
175
+ Returns:
176
+ Dictionary with health check details
177
+ """
178
+ return {}
179
+
180
+
181
+ class SystemPluginRuntime(BasePluginRuntime):
182
+ """Runtime for system plugins (non-provider plugins).
183
+
184
+ System plugins provide functionality like logging, monitoring,
185
+ permissions, etc., but don't proxy to external providers.
186
+ """
187
+
188
+ async def _on_initialize(self) -> None:
189
+ """System plugin initialization."""
190
+ logger.debug("system_plugin_initializing", plugin=self.name, category="plugin")
191
+ # System plugins typically don't need special initialization
192
+ # but can override this method if needed
193
+
194
+ async def _get_health_details(self) -> dict[str, Any]:
195
+ """System plugin health details."""
196
+ return {"type": "system", "initialized": self.initialized}
197
+
198
+
199
+ class AuthProviderPluginRuntime(BasePluginRuntime):
200
+ """Runtime for authentication provider plugins.
201
+
202
+ Auth provider plugins provide OAuth authentication flows and token management
203
+ for various API providers without directly proxying requests.
204
+ """
205
+
206
+ def __init__(self, manifest: PluginManifest):
207
+ """Initialize auth provider plugin runtime.
208
+
209
+ Args:
210
+ manifest: Plugin manifest with static declarations
211
+ """
212
+ super().__init__(manifest)
213
+ self.auth_provider: Any | None = None # OAuthProviderProtocol
214
+ self.token_manager: Any | None = None
215
+ self.storage: Any | None = None
216
+
217
+ async def _on_initialize(self) -> None:
218
+ """Auth provider plugin initialization."""
219
+ logger.debug(
220
+ "auth_provider_plugin_initializing", plugin=self.name, category="plugin"
221
+ )
222
+
223
+ if not self.context:
224
+ raise RuntimeError("Context not set")
225
+
226
+ # Extract auth-specific components from context
227
+ self.auth_provider = self.context.get("auth_provider")
228
+ self.token_manager = self.context.get("token_manager")
229
+ self.storage = self.context.get("storage")
230
+
231
+ # Register OAuth provider with app-scoped registry if present
232
+ if self.auth_provider:
233
+ await self._register_auth_provider()
234
+
235
+ async def _register_auth_provider(self) -> None:
236
+ """Register OAuth provider with the app-scoped registry."""
237
+ if not self.auth_provider:
238
+ return
239
+
240
+ try:
241
+ # Register with app-scoped registry from context
242
+ registry = None
243
+ if self.context and "oauth_registry" in self.context:
244
+ registry = self.context["oauth_registry"]
245
+ if registry is None:
246
+ logger.warning(
247
+ "oauth_registry_missing_in_context",
248
+ plugin=self.name,
249
+ category="plugin",
250
+ )
251
+ return
252
+ registry.register(self.auth_provider)
253
+
254
+ logger.debug(
255
+ "oauth_provider_registered",
256
+ plugin=self.name,
257
+ provider=self.auth_provider.provider_name,
258
+ category="plugin",
259
+ )
260
+ except Exception as e:
261
+ logger.error(
262
+ "oauth_provider_registration_failed",
263
+ plugin=self.name,
264
+ error=str(e),
265
+ exc_info=e,
266
+ category="plugin",
267
+ )
268
+
269
+ async def _on_shutdown(self) -> None:
270
+ """Auth provider plugin shutdown."""
271
+ # Cleanup provider resources if it has a cleanup method
272
+ if self.auth_provider and hasattr(self.auth_provider, "cleanup"):
273
+ try:
274
+ await self.auth_provider.cleanup()
275
+ logger.debug(
276
+ "oauth_provider_cleaned_up",
277
+ plugin=self.name,
278
+ provider=self.auth_provider.provider_name,
279
+ category="plugin",
280
+ )
281
+ except Exception as e:
282
+ logger.error(
283
+ "oauth_provider_cleanup_failed",
284
+ plugin=self.name,
285
+ error=str(e),
286
+ exc_info=e,
287
+ category="plugin",
288
+ )
289
+
290
+ # Unregister OAuth provider if present
291
+ if self.auth_provider:
292
+ await self._unregister_auth_provider()
293
+
294
+ async def _unregister_auth_provider(self) -> None:
295
+ """Unregister OAuth provider from the app-scoped registry."""
296
+ if not self.auth_provider:
297
+ return
298
+
299
+ try:
300
+ # Unregister from app-scoped registry available in context
301
+ registry = None
302
+ if self.context and "oauth_registry" in self.context:
303
+ registry = self.context["oauth_registry"]
304
+ if registry is None:
305
+ logger.warning(
306
+ "oauth_registry_missing_in_context_on_shutdown",
307
+ plugin=self.name,
308
+ category="plugin",
309
+ )
310
+ return
311
+ registry.unregister(self.auth_provider.provider_name)
312
+
313
+ logger.debug(
314
+ "oauth_provider_unregistered",
315
+ plugin=self.name,
316
+ provider=self.auth_provider.provider_name,
317
+ category="plugin",
318
+ )
319
+ except Exception as e:
320
+ logger.error(
321
+ "oauth_provider_unregistration_failed",
322
+ plugin=self.name,
323
+ error=str(e),
324
+ exc_info=e,
325
+ category="plugin",
326
+ )
327
+
328
+ async def _get_health_details(self) -> dict[str, Any]:
329
+ """Auth provider plugin health details."""
330
+ details = {
331
+ "type": "auth_provider",
332
+ "initialized": self.initialized,
333
+ }
334
+
335
+ if self.auth_provider:
336
+ # Check if provider is registered
337
+ try:
338
+ registry = None
339
+ if self.context and "oauth_registry" in self.context:
340
+ registry = self.context["oauth_registry"]
341
+ is_registered = (
342
+ registry.has(self.auth_provider.provider_name)
343
+ if registry is not None
344
+ else False
345
+ )
346
+ details.update(
347
+ {
348
+ "oauth_provider_registered": is_registered,
349
+ "oauth_provider_name": self.auth_provider.provider_name,
350
+ }
351
+ )
352
+ except Exception:
353
+ pass
354
+
355
+ return details
356
+
357
+
358
+ class ProviderPluginRuntime(BasePluginRuntime):
359
+ """Runtime for provider plugins.
360
+
361
+ Provider plugins proxy requests to external API providers and
362
+ require additional components like adapters and detection services.
363
+ """
364
+
365
+ def __init__(self, manifest: PluginManifest):
366
+ """Initialize provider plugin runtime.
367
+
368
+ Args:
369
+ manifest: Plugin manifest with static declarations
370
+ """
371
+ super().__init__(manifest)
372
+ self.adapter: Any | None = None # BaseAdapter
373
+ self.detection_service: Any | None = None
374
+ self.credentials_manager: Any | None = None
375
+
376
+ async def _on_initialize(self) -> None:
377
+ """Provider plugin initialization."""
378
+ logger.debug(
379
+ "provider_plugin_initializing", plugin=self.name, category="plugin"
380
+ )
381
+
382
+ if not self.context:
383
+ raise RuntimeError("Context not set")
384
+
385
+ # Extract provider-specific components from context
386
+ self.adapter = self.context.get("adapter")
387
+ self.detection_service = self.context.get("detection_service")
388
+ self.credentials_manager = self.context.get("credentials_manager")
389
+
390
+ # Initialize detection service if present
391
+ if self.detection_service and hasattr(
392
+ self.detection_service, "initialize_detection"
393
+ ):
394
+ await self.detection_service.initialize_detection()
395
+ logger.debug(
396
+ "detection_service_initialized", plugin=self.name, category="plugin"
397
+ )
398
+
399
+ # Register OAuth provider if factory is provided
400
+ if self.manifest.oauth_provider_factory:
401
+ await self._register_oauth_provider()
402
+
403
+ async def _register_oauth_provider(self) -> None:
404
+ """Register OAuth provider with the app-scoped registry."""
405
+ if not self.manifest.oauth_provider_factory:
406
+ return
407
+
408
+ try:
409
+ # Create OAuth provider instance
410
+ oauth_provider = self.manifest.oauth_provider_factory()
411
+
412
+ # Use oauth_registry from context (injected via core services)
413
+ registry = None
414
+ if self.context and "oauth_registry" in self.context:
415
+ registry = self.context["oauth_registry"]
416
+
417
+ if registry is None:
418
+ logger.warning(
419
+ "oauth_registry_missing_in_context",
420
+ plugin=self.name,
421
+ category="plugin",
422
+ )
423
+ return
424
+
425
+ registry.register(oauth_provider)
426
+
427
+ logger.trace(
428
+ "oauth_provider_registered",
429
+ plugin=self.name,
430
+ provider=oauth_provider.provider_name,
431
+ category="plugin",
432
+ )
433
+ except Exception as e:
434
+ logger.error(
435
+ "oauth_provider_registration_failed",
436
+ plugin=self.name,
437
+ error=str(e),
438
+ exc_info=e,
439
+ category="plugin",
440
+ )
441
+
442
+ async def _unregister_oauth_provider(self) -> None:
443
+ """Unregister OAuth provider from the app-scoped registry."""
444
+ if not self.manifest.oauth_provider_factory:
445
+ return
446
+
447
+ try:
448
+ # Determine provider name
449
+ oauth_provider = self.manifest.oauth_provider_factory()
450
+ provider_name = oauth_provider.provider_name
451
+
452
+ # Use oauth_registry from context (injected via core services)
453
+ registry = None
454
+ if self.context and "oauth_registry" in self.context:
455
+ registry = self.context["oauth_registry"]
456
+
457
+ if registry is None:
458
+ logger.warning(
459
+ "oauth_registry_missing_in_context_on_shutdown",
460
+ plugin=self.name,
461
+ category="plugin",
462
+ )
463
+ return
464
+
465
+ registry.unregister(provider_name)
466
+
467
+ logger.trace(
468
+ "oauth_provider_unregistered",
469
+ plugin=self.name,
470
+ provider=provider_name,
471
+ category="plugin",
472
+ )
473
+ except Exception as e:
474
+ logger.error(
475
+ "oauth_provider_unregistration_failed",
476
+ plugin=self.name,
477
+ error=str(e),
478
+ exc_info=e,
479
+ category="plugin",
480
+ )
481
+
482
+ async def _on_shutdown(self) -> None:
483
+ """Provider plugin cleanup."""
484
+ # Unregister OAuth provider if registered
485
+ await self._unregister_oauth_provider()
486
+
487
+ # Cleanup adapter if present
488
+ if self.adapter and hasattr(self.adapter, "cleanup"):
489
+ await self.adapter.cleanup()
490
+ logger.debug("adapter_cleaned_up", plugin=self.name, category="plugin")
491
+
492
+ async def _on_validate(self) -> bool:
493
+ """Provider plugin validation."""
494
+ # Check that required components are present
495
+ if self.manifest.is_provider and not self.adapter:
496
+ logger.warning(
497
+ "provider_plugin_missing_adapter", plugin=self.name, category="plugin"
498
+ )
499
+ return False
500
+ return True
501
+
502
+ async def _get_health_details(self) -> dict[str, Any]:
503
+ """Provider plugin health details."""
504
+ details: dict[str, Any] = {
505
+ "type": "provider",
506
+ "initialized": self.initialized,
507
+ "has_adapter": self.adapter is not None,
508
+ "has_detection": self.detection_service is not None,
509
+ "has_credentials": self.credentials_manager is not None,
510
+ }
511
+
512
+ # Add detection service info if available
513
+ if self.detection_service:
514
+ if hasattr(self.detection_service, "get_version"):
515
+ details["cli_version"] = self.detection_service.get_version()
516
+ if hasattr(self.detection_service, "get_cli_path"):
517
+ details["cli_path"] = self.detection_service.get_cli_path()
518
+
519
+ return details