alloy-runtime-cli 0.1.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 (451) hide show
  1. alloy_runtime_cli-0.1.0.dist-info/METADATA +61 -0
  2. alloy_runtime_cli-0.1.0.dist-info/RECORD +451 -0
  3. alloy_runtime_cli-0.1.0.dist-info/WHEEL +5 -0
  4. alloy_runtime_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. alloy_runtime_cli-0.1.0.dist-info/top_level.txt +1 -0
  6. cli/__init__.py +0 -0
  7. cli/commands/__init__.py +0 -0
  8. cli/commands/admin/__init__.py +0 -0
  9. cli/commands/admin/bootstrap_command.py +118 -0
  10. cli/commands/admin/credentials/__init__.py +0 -0
  11. cli/commands/admin/credentials/create/__init__.py +0 -0
  12. cli/commands/admin/credentials/create/command.py +148 -0
  13. cli/commands/admin/credentials/create/presenter.py +16 -0
  14. cli/commands/admin/credentials/grant/__init__.py +0 -0
  15. cli/commands/admin/credentials/grant/command.py +119 -0
  16. cli/commands/admin/credentials/grant/fields.py +33 -0
  17. cli/commands/admin/credentials/grant/presenter.py +23 -0
  18. cli/commands/agents/__init__.py +0 -0
  19. cli/commands/agents/create/__init__.py +0 -0
  20. cli/commands/agents/create/command.py +475 -0
  21. cli/commands/agents/create/fields.py +64 -0
  22. cli/commands/agents/create/presenter.py +68 -0
  23. cli/commands/agents/delete/__init__.py +0 -0
  24. cli/commands/agents/delete/command.py +47 -0
  25. cli/commands/agents/delete/presenter.py +16 -0
  26. cli/commands/agents/get/command.py +37 -0
  27. cli/commands/agents/get/presenter.py +32 -0
  28. cli/commands/agents/list/__init__.py +1 -0
  29. cli/commands/agents/list/command.py +54 -0
  30. cli/commands/agents/list/presenter.py +82 -0
  31. cli/commands/agents/update/__init__.py +0 -0
  32. cli/commands/agents/update/command.py +435 -0
  33. cli/commands/agents/update/fields.py +40 -0
  34. cli/commands/agents/update/presenter.py +68 -0
  35. cli/commands/audio/__init__.py +0 -0
  36. cli/commands/audio/transcribe/__init__.py +0 -0
  37. cli/commands/audio/transcribe/command.py +144 -0
  38. cli/commands/audio/transcribe/presenter.py +15 -0
  39. cli/commands/auth/__init__.py +0 -0
  40. cli/commands/auth/login/__init__.py +0 -0
  41. cli/commands/auth/login/command.py +80 -0
  42. cli/commands/auth/signup/__init__.py +0 -0
  43. cli/commands/auth/signup/command.py +115 -0
  44. cli/commands/billing/__init__.py +1 -0
  45. cli/commands/billing/costs/__init__.py +1 -0
  46. cli/commands/billing/costs/by_agent/__init__.py +1 -0
  47. cli/commands/billing/costs/by_agent/command.py +57 -0
  48. cli/commands/billing/costs/by_agent/presenter.py +81 -0
  49. cli/commands/billing/costs/by_model/__init__.py +1 -0
  50. cli/commands/billing/costs/by_model/command.py +57 -0
  51. cli/commands/billing/costs/by_model/presenter.py +80 -0
  52. cli/commands/billing/costs/daily/__init__.py +1 -0
  53. cli/commands/billing/costs/daily/command.py +55 -0
  54. cli/commands/billing/costs/daily/presenter.py +75 -0
  55. cli/commands/billing/costs/summary/__init__.py +1 -0
  56. cli/commands/billing/costs/summary/command.py +57 -0
  57. cli/commands/billing/costs/summary/presenter.py +42 -0
  58. cli/commands/billing/projects/__init__.py +1 -0
  59. cli/commands/billing/projects/create/__init__.py +1 -0
  60. cli/commands/billing/projects/create/command.py +60 -0
  61. cli/commands/billing/projects/create/presenter.py +26 -0
  62. cli/commands/billing/projects/get/__init__.py +1 -0
  63. cli/commands/billing/projects/get/command.py +33 -0
  64. cli/commands/billing/projects/get/presenter.py +32 -0
  65. cli/commands/billing/projects/list/__init__.py +1 -0
  66. cli/commands/billing/projects/list/command.py +40 -0
  67. cli/commands/billing/projects/list/presenter.py +57 -0
  68. cli/commands/content/__init__.py +1 -0
  69. cli/commands/content/delete/__init__.py +0 -0
  70. cli/commands/content/delete/command.py +49 -0
  71. cli/commands/content/delete/presenter.py +18 -0
  72. cli/commands/content/edit/__init__.py +1 -0
  73. cli/commands/content/edit/command.py +155 -0
  74. cli/commands/content/edit/editor.py +150 -0
  75. cli/commands/content/edit/presenter.py +146 -0
  76. cli/commands/content/get/__init__.py +1 -0
  77. cli/commands/content/get/command.py +39 -0
  78. cli/commands/content/get/presenter.py +176 -0
  79. cli/commands/content/list/__init__.py +1 -0
  80. cli/commands/content/list/command.py +347 -0
  81. cli/commands/content/list/export_formatters.py +409 -0
  82. cli/commands/content/list/export_handler.py +165 -0
  83. cli/commands/content/list/presenter.py +190 -0
  84. cli/commands/credentials/__init__.py +0 -0
  85. cli/commands/credentials/create/__init__.py +0 -0
  86. cli/commands/credentials/create/command.py +165 -0
  87. cli/commands/credentials/create/fields.py +38 -0
  88. cli/commands/credentials/create/presenter.py +20 -0
  89. cli/commands/credentials/update/__init__.py +0 -0
  90. cli/commands/credentials/update/command.py +53 -0
  91. cli/commands/credentials/update/fields.py +71 -0
  92. cli/commands/credentials/update/presenter.py +16 -0
  93. cli/commands/flag_utils.py +366 -0
  94. cli/commands/generate/__init__.py +0 -0
  95. cli/commands/generate/cancel/__init__.py +1 -0
  96. cli/commands/generate/cancel/command.py +44 -0
  97. cli/commands/generate/cancel/presenter.py +26 -0
  98. cli/commands/generate/status/__init__.py +1 -0
  99. cli/commands/generate/status/command.py +58 -0
  100. cli/commands/generate/status/presenter.py +78 -0
  101. cli/commands/generate/text/__init__.py +0 -0
  102. cli/commands/generate/text/command.py +1325 -0
  103. cli/commands/generate/text/concurrent_renderer.py +355 -0
  104. cli/commands/generate/text/presenter.py +287 -0
  105. cli/commands/generate/text/stream_renderer.py +129 -0
  106. cli/commands/knowledge/__init__.py +0 -0
  107. cli/commands/knowledge/collections/__init__.py +0 -0
  108. cli/commands/knowledge/collections/cluster/__init__.py +0 -0
  109. cli/commands/knowledge/collections/cluster/command.py +64 -0
  110. cli/commands/knowledge/collections/cluster/presenter.py +74 -0
  111. cli/commands/knowledge/collections/cluster_status/__init__.py +0 -0
  112. cli/commands/knowledge/collections/cluster_status/command.py +46 -0
  113. cli/commands/knowledge/collections/cluster_status/presenter.py +10 -0
  114. cli/commands/knowledge/collections/create/__init__.py +0 -0
  115. cli/commands/knowledge/collections/create/command.py +137 -0
  116. cli/commands/knowledge/collections/create/presenter.py +38 -0
  117. cli/commands/knowledge/collections/delete/__init__.py +1 -0
  118. cli/commands/knowledge/collections/delete/command.py +47 -0
  119. cli/commands/knowledge/collections/delete/presenter.py +20 -0
  120. cli/commands/knowledge/collections/get/__init__.py +1 -0
  121. cli/commands/knowledge/collections/get/command.py +30 -0
  122. cli/commands/knowledge/collections/get/presenter.py +44 -0
  123. cli/commands/knowledge/collections/list/__init__.py +1 -0
  124. cli/commands/knowledge/collections/list/command.py +41 -0
  125. cli/commands/knowledge/collections/list/presenter.py +68 -0
  126. cli/commands/knowledge/collections/update/__init__.py +0 -0
  127. cli/commands/knowledge/collections/update/command.py +97 -0
  128. cli/commands/knowledge/collections/update/presenter.py +42 -0
  129. cli/commands/knowledge/documents/__init__.py +0 -0
  130. cli/commands/knowledge/documents/bulk_metadata/__init__.py +0 -0
  131. cli/commands/knowledge/documents/bulk_metadata/command.py +119 -0
  132. cli/commands/knowledge/documents/bulk_metadata/presenter.py +36 -0
  133. cli/commands/knowledge/documents/delete/__init__.py +0 -0
  134. cli/commands/knowledge/documents/delete/command.py +47 -0
  135. cli/commands/knowledge/documents/delete/presenter.py +20 -0
  136. cli/commands/knowledge/documents/get/__init__.py +0 -0
  137. cli/commands/knowledge/documents/get/command.py +39 -0
  138. cli/commands/knowledge/documents/get/presenter.py +78 -0
  139. cli/commands/knowledge/documents/ingest/__init__.py +0 -0
  140. cli/commands/knowledge/documents/ingest/command.py +222 -0
  141. cli/commands/knowledge/documents/ingest/presenter.py +41 -0
  142. cli/commands/knowledge/documents/list/__init__.py +0 -0
  143. cli/commands/knowledge/documents/list/command.py +69 -0
  144. cli/commands/knowledge/documents/list/presenter.py +86 -0
  145. cli/commands/knowledge/documents/reingest/__init__.py +0 -0
  146. cli/commands/knowledge/documents/reingest/command.py +102 -0
  147. cli/commands/knowledge/documents/reingest/presenter.py +70 -0
  148. cli/commands/knowledge/documents/update/__init__.py +0 -0
  149. cli/commands/knowledge/documents/update/command.py +85 -0
  150. cli/commands/knowledge/documents/update/presenter.py +37 -0
  151. cli/commands/knowledge/recover/__init__.py +0 -0
  152. cli/commands/knowledge/recover/command.py +46 -0
  153. cli/commands/knowledge/recover/presenter.py +79 -0
  154. cli/commands/knowledge/search/__init__.py +0 -0
  155. cli/commands/knowledge/search/command.py +218 -0
  156. cli/commands/knowledge/search/presenter.py +111 -0
  157. cli/commands/knowledge/synthesis/__init__.py +0 -0
  158. cli/commands/knowledge/synthesis/create/__init__.py +0 -0
  159. cli/commands/knowledge/synthesis/create/command.py +127 -0
  160. cli/commands/knowledge/synthesis/create/presenter.py +33 -0
  161. cli/commands/knowledge/synthesis/delete/__init__.py +0 -0
  162. cli/commands/knowledge/synthesis/delete/command.py +53 -0
  163. cli/commands/knowledge/synthesis/delete/presenter.py +31 -0
  164. cli/commands/knowledge/synthesis/get/__init__.py +0 -0
  165. cli/commands/knowledge/synthesis/get/command.py +55 -0
  166. cli/commands/knowledge/synthesis/get/presenter.py +114 -0
  167. cli/commands/knowledge/synthesis/list/__init__.py +0 -0
  168. cli/commands/knowledge/synthesis/list/command.py +132 -0
  169. cli/commands/knowledge/synthesis/list/presenter.py +84 -0
  170. cli/commands/knowledge/synthesis/refresh/__init__.py +0 -0
  171. cli/commands/knowledge/synthesis/refresh/command.py +42 -0
  172. cli/commands/knowledge/synthesis/refresh/presenter.py +33 -0
  173. cli/commands/knowledge/synthesis/update/__init__.py +0 -0
  174. cli/commands/knowledge/synthesis/update/command.py +76 -0
  175. cli/commands/knowledge/synthesis/update/presenter.py +41 -0
  176. cli/commands/models/__init__.py +0 -0
  177. cli/commands/models/list/__init__.py +0 -0
  178. cli/commands/models/list/command.py +84 -0
  179. cli/commands/models/list/presenter.py +114 -0
  180. cli/commands/organizations/__init__.py +0 -0
  181. cli/commands/organizations/create/command.py +32 -0
  182. cli/commands/organizations/create/presenter.py +9 -0
  183. cli/commands/pipelines/__init__.py +1 -0
  184. cli/commands/pipelines/approvals/__init__.py +1 -0
  185. cli/commands/pipelines/approvals/decide_command.py +77 -0
  186. cli/commands/pipelines/approvals/get_command.py +44 -0
  187. cli/commands/pipelines/approvals/presenter.py +56 -0
  188. cli/commands/pipelines/costs/__init__.py +1 -0
  189. cli/commands/pipelines/costs/command.py +57 -0
  190. cli/commands/pipelines/costs/daily_command.py +54 -0
  191. cli/commands/pipelines/costs/daily_presenter.py +59 -0
  192. cli/commands/pipelines/costs/presenter.py +37 -0
  193. cli/commands/pipelines/create/__init__.py +1 -0
  194. cli/commands/pipelines/create/command.py +103 -0
  195. cli/commands/pipelines/create/presenter.py +22 -0
  196. cli/commands/pipelines/env_vars/__init__.py +1 -0
  197. cli/commands/pipelines/env_vars/command.py +51 -0
  198. cli/commands/pipelines/env_vars/presenter.py +16 -0
  199. cli/commands/pipelines/execute/__init__.py +1 -0
  200. cli/commands/pipelines/execute/command.py +142 -0
  201. cli/commands/pipelines/execute/presenter.py +47 -0
  202. cli/commands/pipelines/executions/__init__.py +1 -0
  203. cli/commands/pipelines/executions/costs/__init__.py +1 -0
  204. cli/commands/pipelines/executions/costs/command.py +48 -0
  205. cli/commands/pipelines/executions/costs/presenter.py +29 -0
  206. cli/commands/pipelines/executions/costs_by_model/__init__.py +1 -0
  207. cli/commands/pipelines/executions/costs_by_model/command.py +50 -0
  208. cli/commands/pipelines/executions/costs_by_model/presenter.py +78 -0
  209. cli/commands/pipelines/executions/costs_by_step/__init__.py +1 -0
  210. cli/commands/pipelines/executions/costs_by_step/command.py +50 -0
  211. cli/commands/pipelines/executions/costs_by_step/presenter.py +72 -0
  212. cli/commands/pipelines/executions/get_command.py +38 -0
  213. cli/commands/pipelines/executions/list_command.py +123 -0
  214. cli/commands/pipelines/executions/presenter.py +131 -0
  215. cli/commands/pipelines/executions/rerun_command.py +41 -0
  216. cli/commands/pipelines/executions/update/__init__.py +1 -0
  217. cli/commands/pipelines/executions/update/command.py +110 -0
  218. cli/commands/pipelines/executions/update/presenter.py +28 -0
  219. cli/commands/pipelines/get/__init__.py +1 -0
  220. cli/commands/pipelines/get/command.py +33 -0
  221. cli/commands/pipelines/get/presenter.py +48 -0
  222. cli/commands/pipelines/list/__init__.py +1 -0
  223. cli/commands/pipelines/list/command.py +53 -0
  224. cli/commands/pipelines/list/presenter.py +66 -0
  225. cli/commands/pipelines/schedules/__init__.py +1 -0
  226. cli/commands/pipelines/schedules/create_command.py +119 -0
  227. cli/commands/pipelines/schedules/create_presenter.py +35 -0
  228. cli/commands/pipelines/schedules/delete_command.py +52 -0
  229. cli/commands/pipelines/schedules/env_vars_command.py +59 -0
  230. cli/commands/pipelines/schedules/env_vars_presenter.py +16 -0
  231. cli/commands/pipelines/schedules/get_command.py +38 -0
  232. cli/commands/pipelines/schedules/list_command.py +33 -0
  233. cli/commands/pipelines/schedules/once_command.py +90 -0
  234. cli/commands/pipelines/schedules/once_presenter.py +30 -0
  235. cli/commands/pipelines/schedules/presenter.py +104 -0
  236. cli/commands/pipelines/schedules/update_command.py +139 -0
  237. cli/commands/pipelines/schedules/update_presenter.py +29 -0
  238. cli/commands/render/__init__.py +0 -0
  239. cli/commands/render/html_to_image/__init__.py +0 -0
  240. cli/commands/render/html_to_image/command.py +170 -0
  241. cli/commands/schemas/__init__.py +0 -0
  242. cli/commands/schemas/create/__init__.py +0 -0
  243. cli/commands/schemas/create/command.py +122 -0
  244. cli/commands/schemas/create/presenter.py +53 -0
  245. cli/commands/schemas/delete/command.py +45 -0
  246. cli/commands/schemas/delete/presenter.py +9 -0
  247. cli/commands/schemas/get/__init__.py +0 -0
  248. cli/commands/schemas/get/command.py +56 -0
  249. cli/commands/schemas/get/presenter.py +129 -0
  250. cli/commands/schemas/list/__init__.py +0 -0
  251. cli/commands/schemas/list/command.py +64 -0
  252. cli/commands/schemas/list/presenter.py +133 -0
  253. cli/commands/schemas/update/__init__.py +0 -0
  254. cli/commands/schemas/update/command.py +369 -0
  255. cli/commands/schemas/update/presenter.py +53 -0
  256. cli/commands/sessions/__init__.py +1 -0
  257. cli/commands/sessions/delete/__init__.py +1 -0
  258. cli/commands/sessions/delete/command.py +47 -0
  259. cli/commands/sessions/delete/presenter.py +10 -0
  260. cli/commands/sessions/get/__init__.py +1 -0
  261. cli/commands/sessions/get/command.py +42 -0
  262. cli/commands/sessions/get/presenter.py +59 -0
  263. cli/commands/sessions/list/__init__.py +1 -0
  264. cli/commands/sessions/list/command.py +61 -0
  265. cli/commands/sessions/list/presenter.py +68 -0
  266. cli/commands/sessions/messages/__init__.py +1 -0
  267. cli/commands/sessions/messages/command.py +78 -0
  268. cli/commands/sessions/messages/presenter.py +79 -0
  269. cli/commands/shared_flags.py +500 -0
  270. cli/commands/sync/__init__.py +0 -0
  271. cli/commands/sync/command.py +45 -0
  272. cli/commands/sync/presenter.py +49 -0
  273. cli/commands/tags/__init__.py +1 -0
  274. cli/commands/tags/create/__init__.py +1 -0
  275. cli/commands/tags/create/command.py +60 -0
  276. cli/commands/tags/delete/__init__.py +1 -0
  277. cli/commands/tags/delete/command.py +47 -0
  278. cli/commands/tags/delete/presenter.py +10 -0
  279. cli/commands/tags/get/command.py +31 -0
  280. cli/commands/tags/get/presenter.py +23 -0
  281. cli/commands/tags/list/__init__.py +1 -0
  282. cli/commands/tags/list/command.py +52 -0
  283. cli/commands/tags/list/presenter.py +49 -0
  284. cli/commands/tags/update/command.py +64 -0
  285. cli/commands/tags/update/presenter.py +9 -0
  286. cli/commands/templates/__init__.py +0 -0
  287. cli/commands/templates/create/__init__.py +0 -0
  288. cli/commands/templates/create/command.py +152 -0
  289. cli/commands/templates/create/presenter.py +86 -0
  290. cli/commands/templates/delete/__init__.py +0 -0
  291. cli/commands/templates/delete/command.py +47 -0
  292. cli/commands/templates/delete/presenter.py +16 -0
  293. cli/commands/templates/get/__init__.py +0 -0
  294. cli/commands/templates/get/command.py +52 -0
  295. cli/commands/templates/get/presenter.py +233 -0
  296. cli/commands/templates/get_by_version/command.py +32 -0
  297. cli/commands/templates/get_by_version/presenter.py +30 -0
  298. cli/commands/templates/list/__init__.py +1 -0
  299. cli/commands/templates/list/command.py +102 -0
  300. cli/commands/templates/list/presenter.py +93 -0
  301. cli/commands/templates/render/__init__.py +0 -0
  302. cli/commands/templates/render/command.py +115 -0
  303. cli/commands/templates/render/presenter.py +276 -0
  304. cli/commands/templates/update/__init__.py +0 -0
  305. cli/commands/templates/update/command.py +199 -0
  306. cli/commands/templates/update/presenter.py +94 -0
  307. cli/commands/templates/version/__init__.py +1 -0
  308. cli/commands/templates/version/command.py +116 -0
  309. cli/commands/templates/version/presenter.py +100 -0
  310. cli/commands/tool_configs/__init__.py +0 -0
  311. cli/commands/tool_configs/create/__init__.py +0 -0
  312. cli/commands/tool_configs/create/command.py +118 -0
  313. cli/commands/tool_configs/create/presenter.py +53 -0
  314. cli/commands/tool_configs/delete/__init__.py +0 -0
  315. cli/commands/tool_configs/delete/command.py +47 -0
  316. cli/commands/tool_configs/delete/presenter.py +18 -0
  317. cli/commands/tool_configs/get/__init__.py +0 -0
  318. cli/commands/tool_configs/get/command.py +31 -0
  319. cli/commands/tool_configs/get/presenter.py +62 -0
  320. cli/commands/tool_configs/list/__init__.py +0 -0
  321. cli/commands/tool_configs/list/command.py +59 -0
  322. cli/commands/tool_configs/list/presenter.py +60 -0
  323. cli/commands/tool_configs/update/__init__.py +0 -0
  324. cli/commands/tool_configs/update/command.py +128 -0
  325. cli/commands/tool_configs/update/presenter.py +53 -0
  326. cli/commands/tools/__init__.py +1 -0
  327. cli/commands/tools/get/__init__.py +1 -0
  328. cli/commands/tools/get/command.py +42 -0
  329. cli/commands/tools/get/presenter.py +45 -0
  330. cli/commands/tools/list/__init__.py +1 -0
  331. cli/commands/tools/list/command.py +56 -0
  332. cli/commands/tools/list/presenter.py +44 -0
  333. cli/commands/users/__init__.py +0 -0
  334. cli/commands/users/create/command.py +53 -0
  335. cli/commands/users/create/presenter.py +9 -0
  336. cli/commands/whoami/__init__.py +0 -0
  337. cli/commands/whoami/command.py +42 -0
  338. cli/infrastructure/__init__.py +0 -0
  339. cli/infrastructure/auth_storage.py +71 -0
  340. cli/infrastructure/client_factory.py +36 -0
  341. cli/infrastructure/command.py +75 -0
  342. cli/infrastructure/config.py +188 -0
  343. cli/infrastructure/console.py +27 -0
  344. cli/infrastructure/editor.py +138 -0
  345. cli/infrastructure/error_display.py +178 -0
  346. cli/infrastructure/field_extractor.py +360 -0
  347. cli/infrastructure/file_content.py +210 -0
  348. cli/infrastructure/filter_parser.py +256 -0
  349. cli/infrastructure/formatters/__init__.py +0 -0
  350. cli/infrastructure/formatters/base.py +99 -0
  351. cli/infrastructure/formatters/compact_formatter.py +245 -0
  352. cli/infrastructure/formatters/json_formatter.py +84 -0
  353. cli/infrastructure/formatters/lines_formatter.py +102 -0
  354. cli/infrastructure/formatting/__init__.py +0 -0
  355. cli/infrastructure/formatting/fields.py +193 -0
  356. cli/infrastructure/forms/__init__.py +0 -0
  357. cli/infrastructure/forms/agent_picker.py +123 -0
  358. cli/infrastructure/forms/agent_tool_editor.py +384 -0
  359. cli/infrastructure/forms/agent_tools_manager.py +212 -0
  360. cli/infrastructure/forms/base_picker.py +469 -0
  361. cli/infrastructure/forms/components.py +126 -0
  362. cli/infrastructure/forms/json_schema_builder.py +149 -0
  363. cli/infrastructure/forms/model_picker.py +134 -0
  364. cli/infrastructure/forms/parsers.py +173 -0
  365. cli/infrastructure/forms/resolution_modal.py +302 -0
  366. cli/infrastructure/forms/schema_picker.py +137 -0
  367. cli/infrastructure/forms/tag_management_modal.py +103 -0
  368. cli/infrastructure/forms/tag_picker.py +207 -0
  369. cli/infrastructure/forms/template_picker.py +131 -0
  370. cli/infrastructure/forms/tool_config_picker.py +130 -0
  371. cli/infrastructure/forms/tool_picker.py +103 -0
  372. cli/infrastructure/injection/__init__.py +0 -0
  373. cli/infrastructure/injection/parser.py +302 -0
  374. cli/infrastructure/injection/resolver.py +399 -0
  375. cli/infrastructure/kv_parser.py +130 -0
  376. cli/infrastructure/local_storage.py +227 -0
  377. cli/infrastructure/macro_parser.py +215 -0
  378. cli/infrastructure/output.py +192 -0
  379. cli/infrastructure/provider_setup.py +81 -0
  380. cli/infrastructure/renderers/__init__.py +0 -0
  381. cli/infrastructure/renderers/entity_renderer.py +77 -0
  382. cli/infrastructure/renderers/list_renderer.py +114 -0
  383. cli/infrastructure/scope_utils.py +47 -0
  384. cli/infrastructure/spinner.py +101 -0
  385. cli/infrastructure/tui/__init__.py +0 -0
  386. cli/infrastructure/tui/clipboard.py +41 -0
  387. cli/infrastructure/tui/formatters.py +105 -0
  388. cli/infrastructure/tui/preview.py +14 -0
  389. cli/infrastructure/tui/selectable.py +198 -0
  390. cli/infrastructure/validation/__init__.py +0 -0
  391. cli/infrastructure/validation/tag_validation.py +74 -0
  392. cli/main.py +759 -0
  393. cli/tui/__init__.py +0 -0
  394. cli/tui/app.py +199 -0
  395. cli/tui/app_store.py +73 -0
  396. cli/tui/chat/__init__.py +0 -0
  397. cli/tui/chat/commands/__init__.py +0 -0
  398. cli/tui/chat/commands/base.py +65 -0
  399. cli/tui/chat/commands/create_session.py +135 -0
  400. cli/tui/chat/commands/load_session.py +119 -0
  401. cli/tui/chat/commands/regenerate.py +120 -0
  402. cli/tui/chat/commands/reload_session.py +63 -0
  403. cli/tui/chat/commands/send_message.py +190 -0
  404. cli/tui/chat/commands/undo.py +66 -0
  405. cli/tui/chat/editor.py +71 -0
  406. cli/tui/chat/messages.py +223 -0
  407. cli/tui/chat/pane.py +141 -0
  408. cli/tui/chat/renderers/__init__.py +0 -0
  409. cli/tui/chat/renderers/base.py +72 -0
  410. cli/tui/chat/renderers/markdown.py +250 -0
  411. cli/tui/chat/renderers/plain.py +83 -0
  412. cli/tui/chat/screen.py +1155 -0
  413. cli/tui/chat/services/__init__.py +0 -0
  414. cli/tui/chat/services/injection.py +386 -0
  415. cli/tui/chat/services/name_generator.py +256 -0
  416. cli/tui/chat/slash_commands.py +424 -0
  417. cli/tui/chat/store.py +280 -0
  418. cli/tui/chat/types.py +220 -0
  419. cli/tui/chat/widgets/__init__.py +0 -0
  420. cli/tui/chat/widgets/chat_header.py +75 -0
  421. cli/tui/chat/widgets/chat_input.py +362 -0
  422. cli/tui/chat/widgets/injection_popup.py +161 -0
  423. cli/tui/chat/widgets/message_display.py +287 -0
  424. cli/tui/chat/widgets/session_sidebar.py +214 -0
  425. cli/tui/chat/widgets/welcome_screen.py +290 -0
  426. cli/tui/screens/__init__.py +0 -0
  427. cli/tui/screens/agents.py +344 -0
  428. cli/tui/screens/base.py +301 -0
  429. cli/tui/screens/content.py +508 -0
  430. cli/tui/screens/dashboard.py +89 -0
  431. cli/tui/screens/models.py +96 -0
  432. cli/tui/screens/nav_screen.py +186 -0
  433. cli/tui/screens/schemas.py +522 -0
  434. cli/tui/screens/templates.py +734 -0
  435. cli/tui/screens/tool_configs.py +335 -0
  436. cli/tui/styles/__init__.py +0 -0
  437. cli/tui/widgets/__init__.py +0 -0
  438. cli/tui/widgets/agent_create_modal.py +139 -0
  439. cli/tui/widgets/agent_form_modal.py +659 -0
  440. cli/tui/widgets/agent_update_modal.py +299 -0
  441. cli/tui/widgets/base_form_modal.py +77 -0
  442. cli/tui/widgets/confirm_modal.py +75 -0
  443. cli/tui/widgets/help_modal.py +145 -0
  444. cli/tui/widgets/new_session_modal.py +328 -0
  445. cli/tui/widgets/schema_create_modal.py +271 -0
  446. cli/tui/widgets/schema_update_modal.py +188 -0
  447. cli/tui/widgets/status_footer.py +147 -0
  448. cli/tui/widgets/template_create_modal.py +502 -0
  449. cli/tui/widgets/template_update_modal.py +308 -0
  450. cli/tui/widgets/tool_config_create_modal.py +216 -0
  451. cli/tui/widgets/tool_config_update_modal.py +208 -0
@@ -0,0 +1,355 @@
1
+ """Concurrent execution renderer with side-by-side display.
2
+
3
+ Manages multiple simultaneous text generations with a clean
4
+ layout that adapts based on the number of concurrent executions.
5
+ """
6
+
7
+ import asyncio
8
+ from collections.abc import AsyncGenerator
9
+
10
+ from rich.console import Console, Group, RenderableType
11
+ from rich.layout import Layout
12
+ from rich.live import Live
13
+ from rich.panel import Panel
14
+ from rich.text import Text
15
+
16
+ from alloy_runtime_types.dtos.generation import GenerateTextStreamChunk
17
+
18
+ # Display configuration
19
+ CONCURRENT_REFRESH_RATE = 8 # Frames per second
20
+ CHUNK_UPDATE_INTERVAL = 5 # Update display every N chunks
21
+ THINKING_HEIGHT_RATIO = 0.25 # Thinking panel gets 25% of space
22
+
23
+
24
+ class ExecutionState:
25
+ """Tracks state for a single concurrent execution."""
26
+
27
+ def __init__(self, index: int, agent_identifier: str | None = None):
28
+ """Initialize execution state.
29
+
30
+ Args:
31
+ index: Execution index (0-based)
32
+ agent_identifier: Agent name or UUID for display (optional)
33
+ """
34
+ self.index = index
35
+ self.agent_identifier = agent_identifier
36
+ self.thinking_text = ""
37
+ self.content_text = ""
38
+ self.execution_id: str | None = None
39
+ self.session_id: str | None = None
40
+ self.is_complete = False
41
+ self.has_error = False
42
+ self.error_message: str | None = None
43
+
44
+ def process_chunk(self, chunk: GenerateTextStreamChunk) -> None:
45
+ """Process a stream chunk and update state.
46
+
47
+ Args:
48
+ chunk: Stream chunk to process
49
+ """
50
+ if self.execution_id is None:
51
+ self.execution_id = str(chunk.execution_id)
52
+
53
+ if chunk.chunk_type == "thinking":
54
+ if chunk.reasoning_content:
55
+ self.thinking_text += chunk.reasoning_content
56
+ elif chunk.chunk_type == "metadata":
57
+ # Metadata chunks - capture session_id from final metadata
58
+ if chunk.metadata and "chat_session_id" in chunk.metadata:
59
+ self.session_id = chunk.metadata["chat_session_id"]
60
+ else:
61
+ if chunk.content:
62
+ self.content_text += chunk.content
63
+
64
+
65
+ class ConcurrentRenderer:
66
+ """Renders multiple concurrent text generations.
67
+
68
+ Features:
69
+ - Side-by-side layout for 2-3 concurrent executions
70
+ - Stacked layout for 4+ executions
71
+ - Individual thinking and content panels per execution
72
+ - Graceful error handling per execution
73
+ - Agent name display for multi-agent comparison mode
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ console: Console,
79
+ execution_count: int,
80
+ agent_identifiers: list[str] | None = None,
81
+ ):
82
+ """Initialize concurrent renderer.
83
+
84
+ Args:
85
+ console: Rich console for output
86
+ execution_count: Number of concurrent executions (1-10)
87
+ agent_identifiers: Optional list of agent names/UUIDs for display
88
+ """
89
+ self.console = console
90
+ self.execution_count = min(max(execution_count, 1), 10) # Clamp 1-10
91
+ self._agent_identifiers = agent_identifiers
92
+
93
+ # Create execution states with optional agent identifiers
94
+ self._states: list[ExecutionState] = []
95
+ for i in range(self.execution_count):
96
+ agent_id = (
97
+ agent_identifiers[i]
98
+ if agent_identifiers and i < len(agent_identifiers)
99
+ else None
100
+ )
101
+ self._states.append(ExecutionState(i, agent_identifier=agent_id))
102
+
103
+ self._use_side_by_side = execution_count <= 3
104
+
105
+ # Calculate dimensions based on terminal size
106
+ terminal_height = console.size.height or 24
107
+ terminal_width = console.size.width or 80
108
+
109
+ # Calculate panel width (account for side-by-side splitting and borders)
110
+ if self._use_side_by_side:
111
+ # Each panel gets equal share, minus borders (2 chars each side)
112
+ self._panel_width = max((terminal_width // execution_count) - 4, 20)
113
+ else:
114
+ self._panel_width = max(terminal_width - 4, 20)
115
+
116
+ # Calculate max lines per panel
117
+ reserved_lines = 6 # Status bar, panel borders, spacing
118
+ available_height = max(terminal_height - reserved_lines, 10)
119
+ self._max_panel_lines = available_height
120
+
121
+ async def render_streams(
122
+ self,
123
+ streams: list[AsyncGenerator[GenerateTextStreamChunk, None]],
124
+ show_thinking: bool = True,
125
+ ) -> list[tuple[str, str, str | None, str | None]]:
126
+ """Render multiple concurrent streams.
127
+
128
+ Args:
129
+ streams: List of async generators for each execution
130
+ show_thinking: Whether to display thinking blocks
131
+
132
+ Returns:
133
+ List of (content_text, thinking_text, error, session_id) tuples for each execution
134
+ """
135
+ if len(streams) != self.execution_count:
136
+ raise ValueError(
137
+ f"Expected {self.execution_count} streams, got {len(streams)}"
138
+ )
139
+
140
+ with Live(
141
+ self._build_display(),
142
+ console=self.console,
143
+ refresh_per_second=CONCURRENT_REFRESH_RATE,
144
+ transient=False,
145
+ ) as live:
146
+ # Create tasks for each stream, passing live for updates
147
+ tasks = [
148
+ self._consume_stream(i, stream, show_thinking, live)
149
+ for i, stream in enumerate(streams)
150
+ ]
151
+
152
+ # Run all streams concurrently
153
+ await asyncio.gather(*tasks, return_exceptions=True)
154
+
155
+ # Final update
156
+ live.update(self._build_display())
157
+
158
+ # Return results
159
+ return [
160
+ (
161
+ state.content_text,
162
+ state.thinking_text,
163
+ state.error_message,
164
+ state.session_id,
165
+ )
166
+ for state in self._states
167
+ ]
168
+
169
+ async def _consume_stream(
170
+ self,
171
+ index: int,
172
+ stream: AsyncGenerator[GenerateTextStreamChunk, None],
173
+ show_thinking: bool,
174
+ live: Live,
175
+ ) -> None:
176
+ """Consume a single stream and update its state.
177
+
178
+ Args:
179
+ index: Execution index
180
+ stream: Async generator of chunks
181
+ show_thinking: Whether to process thinking chunks
182
+ live: Rich Live display to update
183
+ """
184
+ state = self._states[index]
185
+ chunk_count = 0
186
+ try:
187
+ async for chunk in stream:
188
+ if show_thinking or chunk.chunk_type != "thinking":
189
+ state.process_chunk(chunk)
190
+ chunk_count += 1
191
+ # Update display every N chunks to reduce flicker
192
+ if chunk_count % CHUNK_UPDATE_INTERVAL == 0:
193
+ live.update(self._build_display())
194
+ state.is_complete = True
195
+ # Final update when complete
196
+ live.update(self._build_display())
197
+ except Exception as e:
198
+ state.has_error = True
199
+ state.error_message = str(e)
200
+ state.is_complete = True
201
+ # Update display to show error
202
+ live.update(self._build_display())
203
+
204
+ def _build_display(self) -> Layout | Group:
205
+ """Build the display layout based on execution count.
206
+
207
+ Returns:
208
+ Layout for side-by-side or Group for stacked
209
+ """
210
+ if self._use_side_by_side:
211
+ return self._build_side_by_side_layout()
212
+ else:
213
+ return self._build_stacked_layout()
214
+
215
+ def _build_side_by_side_layout(self) -> Layout:
216
+ """Build side-by-side layout for 2-3 executions.
217
+
218
+ Returns:
219
+ Layout with horizontal split
220
+ """
221
+ layout = Layout()
222
+
223
+ # Create columns
224
+ columns = [
225
+ Layout(self._render_execution_panel(state), name=f"exec_{state.index}")
226
+ for state in self._states
227
+ ]
228
+
229
+ layout.split_row(*columns)
230
+ return layout
231
+
232
+ def _build_stacked_layout(self) -> Group:
233
+ """Build stacked layout for 4+ executions.
234
+
235
+ Returns:
236
+ Group of panels
237
+ """
238
+ panels = [self._render_execution_panel(state) for state in self._states]
239
+ return Group(*panels)
240
+
241
+ def _tail_text(self, text: str, max_visual_lines: int, width: int) -> str:
242
+ """Get the last N visual lines of text (tail behavior with wrapping).
243
+
244
+ Accounts for line wrapping based on panel width to ensure content
245
+ fits within the visible area.
246
+
247
+ Args:
248
+ text: Full text content
249
+ max_visual_lines: Maximum number of visual lines to return
250
+ width: Panel content width for wrap calculation
251
+
252
+ Returns:
253
+ Last max_visual_lines worth of text
254
+ """
255
+ if not text or max_visual_lines <= 0 or width <= 0:
256
+ return text
257
+
258
+ lines = text.split("\n")
259
+ result_lines: list[str] = []
260
+ visual_line_count = 0
261
+
262
+ # Work backwards through lines
263
+ for line in reversed(lines):
264
+ # Calculate how many visual lines this text line takes
265
+ line_visual_lines = max(1, (len(line) + width - 1) // width) if line else 1
266
+
267
+ if visual_line_count + line_visual_lines > max_visual_lines:
268
+ # This line would exceed our budget
269
+ # If we have nothing yet, take a truncated version of this line
270
+ if not result_lines:
271
+ chars_available = max_visual_lines * width
272
+ result_lines.append(
273
+ line[-chars_available:] if len(line) > chars_available else line
274
+ )
275
+ break
276
+
277
+ result_lines.append(line)
278
+ visual_line_count += line_visual_lines
279
+
280
+ result_lines.reverse()
281
+ return "\n".join(result_lines)
282
+
283
+ def _render_execution_panel(self, state: ExecutionState) -> Panel:
284
+ """Render a single execution as a panel.
285
+
286
+ Args:
287
+ state: Execution state to render
288
+
289
+ Returns:
290
+ Panel with execution content
291
+ """
292
+ content_parts: list[RenderableType] = []
293
+
294
+ # Calculate line budget - split between thinking and content
295
+ thinking_max_lines = int(self._max_panel_lines * THINKING_HEIGHT_RATIO)
296
+ content_max_lines = (
297
+ self._max_panel_lines - thinking_max_lines - 4
298
+ ) # 4 for borders
299
+
300
+ # Content width accounts for panel padding (2 chars each side)
301
+ content_width = max(self._panel_width - 4, 10)
302
+
303
+ # Add thinking section if present (show tail/latest)
304
+ if state.thinking_text:
305
+ thinking_display = self._tail_text(
306
+ state.thinking_text, thinking_max_lines, content_width
307
+ )
308
+ thinking_text = Text(thinking_display, style="dim italic")
309
+ thinking_panel = Panel(
310
+ thinking_text,
311
+ title="[red]Thinking[/red]",
312
+ title_align="left",
313
+ border_style="red",
314
+ padding=(0, 1),
315
+ )
316
+ content_parts.append(thinking_panel)
317
+
318
+ # Add content (show tail/latest)
319
+ if state.content_text:
320
+ content_display = self._tail_text(
321
+ state.content_text, content_max_lines, content_width
322
+ )
323
+ content_parts.append(Text(content_display))
324
+ elif state.has_error:
325
+ content_parts.append(Text(f"Error: {state.error_message}", style="red"))
326
+ elif not state.is_complete:
327
+ content_parts.append(Text("Generating...", style="dim"))
328
+ else:
329
+ content_parts.append(Text("(empty response)", style="dim"))
330
+
331
+ # Determine panel styling
332
+ # Use agent identifier if available, otherwise fall back to execution number
333
+ if state.agent_identifier:
334
+ title = f"Agent: {state.agent_identifier}"
335
+ else:
336
+ title = f"Execution {state.index + 1}"
337
+
338
+ if state.is_complete:
339
+ if state.has_error:
340
+ border_style = "red"
341
+ title = f"{title} [red](error)[/red]"
342
+ else:
343
+ border_style = "green"
344
+ title = f"{title} [green](complete)[/green]"
345
+ else:
346
+ border_style = "blue"
347
+ title = f"{title} [blue](running)[/blue]"
348
+
349
+ return Panel(
350
+ Group(*content_parts) if content_parts else Text(""),
351
+ title=title,
352
+ title_align="left",
353
+ border_style=border_style,
354
+ padding=(0, 1),
355
+ )
@@ -0,0 +1,287 @@
1
+ """Non-streaming result presenter for text generation.
2
+
3
+ Displays final generation results with usage summary and costs.
4
+ """
5
+
6
+ from decimal import Decimal
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from alloy_runtime_types.dtos.generation import GenerateTextResponse
14
+
15
+
16
+ class GenerateTextPresenter:
17
+ """Presents completed text generation results.
18
+
19
+ Features:
20
+ - Final text output with optional thinking block
21
+ - Usage statistics (tokens) table
22
+ - Cost breakdown table
23
+ - Execution metadata
24
+ """
25
+
26
+ def __init__(self, console: Console):
27
+ """Initialize presenter.
28
+
29
+ Args:
30
+ console: Rich console for output
31
+ """
32
+ self.console = console
33
+
34
+ def present_result(
35
+ self, response: GenerateTextResponse, show_metadata: bool = True
36
+ ) -> None:
37
+ """Present a non-streaming generation result.
38
+
39
+ Args:
40
+ response: Complete generation response
41
+ show_metadata: Whether to show usage/cost tables (default True)
42
+ """
43
+ # Display main output text
44
+ self.console.print()
45
+ self.console.print(
46
+ Panel(
47
+ Text(response.output_text),
48
+ title="Generated Text",
49
+ title_align="left",
50
+ border_style="green",
51
+ padding=(1, 2),
52
+ )
53
+ )
54
+ self.console.print()
55
+
56
+ # Display metadata if requested
57
+ if show_metadata:
58
+ self._display_usage_table(response)
59
+ self._display_cost_table(response)
60
+ self._display_execution_info(response)
61
+
62
+ def present_multiple_results(
63
+ self, responses: list[GenerateTextResponse], show_metadata: bool = True
64
+ ) -> None:
65
+ """Present multiple concurrent generation results.
66
+
67
+ Args:
68
+ responses: List of generation responses
69
+ show_metadata: Whether to show usage/cost summaries
70
+ """
71
+ self.console.print()
72
+
73
+ for i, response in enumerate(responses, 1):
74
+ # Display each result with index
75
+ self.console.print(
76
+ Panel(
77
+ Text(response.output_text),
78
+ title=f"Generated Text {i}/{len(responses)}",
79
+ title_align="left",
80
+ border_style="green",
81
+ padding=(1, 2),
82
+ )
83
+ )
84
+ self.console.print()
85
+
86
+ # Display aggregated metadata if requested
87
+ if show_metadata and responses:
88
+ self._display_aggregated_usage(responses)
89
+ self._display_aggregated_costs(responses)
90
+
91
+ # Display session IDs for each execution
92
+ self.console.print()
93
+ for i, response in enumerate(responses, 1):
94
+ self.console.print(
95
+ f"[dim]Execution {i} Session ID:[/dim] {response.chat_session_id}"
96
+ )
97
+ self.console.print()
98
+
99
+ def _display_usage_table(self, response: GenerateTextResponse) -> None:
100
+ """Display token usage table.
101
+
102
+ Args:
103
+ response: Generation response
104
+ """
105
+ table = Table(title="Token Usage", show_header=True, header_style="bold cyan")
106
+ table.add_column("Metric", style="dim")
107
+ table.add_column("Count", justify="right")
108
+
109
+ usage = response.usage
110
+ table.add_row("Input Tokens", str(usage.input_tokens))
111
+ table.add_row("Output Tokens", str(usage.output_tokens))
112
+
113
+ if usage.cache_creation_tokens:
114
+ table.add_row(
115
+ "Cache Creation Tokens", str(usage.cache_creation_tokens), style="dim"
116
+ )
117
+ if usage.cache_read_tokens:
118
+ table.add_row(
119
+ "Cache Read Tokens", str(usage.cache_read_tokens), style="dim"
120
+ )
121
+
122
+ total = usage.input_tokens + usage.output_tokens
123
+ table.add_row("Total Tokens", str(total), style="bold")
124
+
125
+ self.console.print(table)
126
+ self.console.print()
127
+
128
+ def _display_cost_table(self, response: GenerateTextResponse) -> None:
129
+ """Display cost breakdown table.
130
+
131
+ Args:
132
+ response: Generation response
133
+ """
134
+ table = Table(
135
+ title="Cost Breakdown", show_header=True, header_style="bold cyan"
136
+ )
137
+ table.add_column("Component", style="dim")
138
+ table.add_column("USD", justify="right")
139
+
140
+ cost = response.cost
141
+ table.add_row("Input Cost", self._format_cost(cost.input_cost_usd))
142
+ table.add_row("Output Cost", self._format_cost(cost.output_cost_usd))
143
+
144
+ if cost.cache_cost_usd:
145
+ table.add_row(
146
+ "Cache Cost", self._format_cost(cost.cache_cost_usd), style="dim"
147
+ )
148
+
149
+ table.add_row(
150
+ "Total Cost", self._format_cost(cost.total_cost_usd), style="bold green"
151
+ )
152
+
153
+ self.console.print(table)
154
+ self.console.print()
155
+
156
+ def _display_execution_info(self, response: GenerateTextResponse) -> None:
157
+ """Display execution metadata.
158
+
159
+ Args:
160
+ response: Generation response
161
+ """
162
+ info_parts = [
163
+ f"[dim]Session ID:[/dim] {response.chat_session_id}",
164
+ f"[dim]Execution ID:[/dim] {response.execution_id}",
165
+ f"[dim]Model:[/dim] {response.provider_key}/{response.provider_model_name}",
166
+ f"[dim]Finish Reason:[/dim] {response.finish_reason}",
167
+ ]
168
+
169
+ if response.tags:
170
+ info_parts.append(f"[dim]Tags:[/dim] {', '.join(response.tags)}")
171
+
172
+ for part in info_parts:
173
+ self.console.print(part)
174
+
175
+ self.console.print()
176
+
177
+ def _display_aggregated_usage(self, responses: list[GenerateTextResponse]) -> None:
178
+ """Display aggregated token usage across multiple executions.
179
+
180
+ Args:
181
+ responses: List of generation responses
182
+ """
183
+ table = Table(
184
+ title=f"Aggregated Token Usage ({len(responses)} executions)",
185
+ show_header=True,
186
+ header_style="bold cyan",
187
+ )
188
+ table.add_column("Metric", style="dim")
189
+ table.add_column("Total", justify="right")
190
+ table.add_column("Average", justify="right")
191
+
192
+ total_input = sum(r.usage.input_tokens for r in responses)
193
+ total_output = sum(r.usage.output_tokens for r in responses)
194
+ total_cache_creation = sum(
195
+ r.usage.cache_creation_tokens or 0 for r in responses
196
+ )
197
+ total_cache_read = sum(r.usage.cache_read_tokens or 0 for r in responses)
198
+
199
+ count = len(responses)
200
+ table.add_row("Input Tokens", str(total_input), str(total_input // count))
201
+ table.add_row("Output Tokens", str(total_output), str(total_output // count))
202
+
203
+ if total_cache_creation > 0:
204
+ table.add_row(
205
+ "Cache Creation",
206
+ str(total_cache_creation),
207
+ str(total_cache_creation // count),
208
+ style="dim",
209
+ )
210
+ if total_cache_read > 0:
211
+ table.add_row(
212
+ "Cache Read",
213
+ str(total_cache_read),
214
+ str(total_cache_read // count),
215
+ style="dim",
216
+ )
217
+
218
+ total_tokens = total_input + total_output
219
+ table.add_row(
220
+ "Total Tokens", str(total_tokens), str(total_tokens // count), style="bold"
221
+ )
222
+
223
+ self.console.print(table)
224
+ self.console.print()
225
+
226
+ def _display_aggregated_costs(self, responses: list[GenerateTextResponse]) -> None:
227
+ """Display aggregated costs across multiple executions.
228
+
229
+ Args:
230
+ responses: List of generation responses
231
+ """
232
+ table = Table(
233
+ title=f"Aggregated Costs ({len(responses)} executions)",
234
+ show_header=True,
235
+ header_style="bold cyan",
236
+ )
237
+ table.add_column("Component", style="dim")
238
+ table.add_column("Total USD", justify="right")
239
+ table.add_column("Average USD", justify="right")
240
+
241
+ total_input_cost = Decimal(sum(r.cost.input_cost_usd for r in responses))
242
+ total_output_cost = Decimal(sum(r.cost.output_cost_usd for r in responses))
243
+ total_cache_cost = Decimal(
244
+ sum(r.cost.cache_cost_usd or Decimal(0) for r in responses)
245
+ )
246
+ total_cost = Decimal(sum(r.cost.total_cost_usd for r in responses))
247
+
248
+ count = Decimal(len(responses))
249
+ table.add_row(
250
+ "Input Cost",
251
+ self._format_cost(total_input_cost),
252
+ self._format_cost(total_input_cost / count),
253
+ )
254
+ table.add_row(
255
+ "Output Cost",
256
+ self._format_cost(total_output_cost),
257
+ self._format_cost(total_output_cost / count),
258
+ )
259
+
260
+ if total_cache_cost > 0:
261
+ table.add_row(
262
+ "Cache Cost",
263
+ self._format_cost(total_cache_cost),
264
+ self._format_cost(total_cache_cost / count),
265
+ style="dim",
266
+ )
267
+
268
+ table.add_row(
269
+ "Total Cost",
270
+ self._format_cost(total_cost),
271
+ self._format_cost(total_cost / count),
272
+ style="bold green",
273
+ )
274
+
275
+ self.console.print(table)
276
+ self.console.print()
277
+
278
+ def _format_cost(self, cost: Decimal) -> str:
279
+ """Format cost value as currency string.
280
+
281
+ Args:
282
+ cost: Cost in USD
283
+
284
+ Returns:
285
+ Formatted cost string with dollar sign
286
+ """
287
+ return f"${cost:.6f}"