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,1325 @@
1
+ """Generate text command implementation."""
2
+
3
+ import asyncio
4
+ import json
5
+
6
+ from pathlib import Path
7
+ from typing import Annotated, Any, Optional, cast
8
+ from uuid import UUID
9
+
10
+ import typer
11
+ from rich.console import Console
12
+
13
+ from cli.commands.generate.text.concurrent_renderer import ConcurrentRenderer
14
+ from cli.commands.generate.text.presenter import GenerateTextPresenter
15
+ from cli.commands.generate.text.stream_renderer import StreamRenderer
16
+ from cli.commands.shared_flags import (
17
+ AGENT,
18
+ MAX_TOKENS,
19
+ MESSAGE,
20
+ OUTPUT_FILE,
21
+ OUTPUT_MODE,
22
+ REASONING_EFFORT,
23
+ SCHEMA_DEF,
24
+ SCHEMA_FORMAT,
25
+ SCHEMA_REF,
26
+ SHOW_THINKING,
27
+ STREAM,
28
+ TAGS,
29
+ TEMPERATURE,
30
+ )
31
+ from cli.infrastructure.command import async_command, authenticated_client
32
+ from cli.infrastructure.console import get_console
33
+ from cli.infrastructure.editor import EditorError, open_editor
34
+ from cli.infrastructure.file_content import FileContentError, resolve_content
35
+ from cli.infrastructure.output import OutputService
36
+ from cli.infrastructure.spinner import spinner
37
+ from alloy_runtime_types.dtos.generation import (
38
+ AgentSource,
39
+ DirectModelSource,
40
+ GenerateTextRequest,
41
+ GenerateTextResponse,
42
+ GenerationParameters,
43
+ InlineOutputSchema,
44
+ SchemaReference,
45
+ )
46
+ from alloy_runtime_types.enums.provider import Provider
47
+ from alloy_runtime_types.enums.schema_enums import SchemaFormat as SchemaFormatEnum
48
+
49
+ # Type alias for repeatable --header option
50
+ REQUEST_HEADERS = Annotated[
51
+ Optional[list[str]],
52
+ typer.Option(
53
+ "-H",
54
+ "--header",
55
+ help="Request header (key:value). Repeatable. E.g., -H 'anthropic-beta:context-1m-2025-08-07'",
56
+ ),
57
+ ]
58
+
59
+
60
+ def generate_text_command(
61
+ # Message (required - one of message or message-template)
62
+ message: Optional[str] = MESSAGE,
63
+ message_template: Optional[str] = typer.Option(
64
+ None,
65
+ "--message-template",
66
+ help="Template name/UUID for user message",
67
+ ),
68
+ message_vars: Annotated[
69
+ Optional[list[str]],
70
+ typer.Option(
71
+ "-v",
72
+ "--var",
73
+ help="Variable (key=value or key:=json). Repeatable.",
74
+ ),
75
+ ] = None,
76
+ edit: bool = typer.Option(
77
+ False,
78
+ "-e",
79
+ "--edit",
80
+ help="Open $EDITOR to compose message",
81
+ ),
82
+ # Model Selection (mutually exclusive with agent)
83
+ agent: Optional[str] = AGENT,
84
+ # Multi-agent comparison (mutually exclusive with agent and provider/model)
85
+ agents: Annotated[
86
+ Optional[list[str]],
87
+ typer.Option(
88
+ "-A",
89
+ "--agents",
90
+ help="Compare multiple agents. Repeatable.",
91
+ ),
92
+ ] = None,
93
+ provider: Optional[str] = typer.Option(
94
+ None,
95
+ "-p",
96
+ "--provider",
97
+ help="Provider (requires -M/--model)",
98
+ ),
99
+ model: Optional[str] = typer.Option(
100
+ None,
101
+ "-M",
102
+ "--model",
103
+ help="Model name (requires -p/--provider)",
104
+ ),
105
+ # System Instruction (optional)
106
+ system_instruction: Optional[str] = typer.Option(
107
+ None,
108
+ "--system",
109
+ help="System instruction (or @file)",
110
+ ),
111
+ system_template: Optional[str] = typer.Option(
112
+ None,
113
+ "--system-template",
114
+ help="Template name/UUID for system instruction",
115
+ ),
116
+ system_vars: Annotated[
117
+ Optional[list[str]],
118
+ typer.Option(
119
+ "--system-var",
120
+ help="System template var (key=value). Repeatable.",
121
+ ),
122
+ ] = None,
123
+ # Generation Parameters
124
+ temperature: Optional[float] = TEMPERATURE,
125
+ max_tokens: Optional[int] = MAX_TOKENS,
126
+ reasoning_effort: Optional[str] = REASONING_EFFORT,
127
+ # Additional Generation Parameters
128
+ top_p: Optional[float] = typer.Option(
129
+ None,
130
+ "--top-p",
131
+ min=0.0,
132
+ max=1.0,
133
+ help="Nucleus sampling parameter (0.0-1.0)",
134
+ ),
135
+ top_k: Optional[int] = typer.Option(
136
+ None,
137
+ "--top-k",
138
+ min=1,
139
+ help="Top-k sampling parameter",
140
+ ),
141
+ frequency_penalty: Optional[float] = typer.Option(
142
+ None,
143
+ "--frequency-penalty",
144
+ min=-2.0,
145
+ max=2.0,
146
+ help="Frequency penalty (-2.0 to 2.0)",
147
+ ),
148
+ presence_penalty: Optional[float] = typer.Option(
149
+ None,
150
+ "--presence-penalty",
151
+ min=-2.0,
152
+ max=2.0,
153
+ help="Presence penalty (-2.0 to 2.0)",
154
+ ),
155
+ seed: Optional[int] = typer.Option(
156
+ None,
157
+ "--seed",
158
+ help="Random seed for reproducible outputs",
159
+ ),
160
+ stop: Optional[str] = typer.Option(
161
+ None,
162
+ "--stop",
163
+ help="Stop sequence(s) - comma-separated for multiple",
164
+ ),
165
+ logprobs: Optional[bool] = typer.Option(
166
+ None,
167
+ "--logprobs/--no-logprobs",
168
+ help="Include log probabilities in response",
169
+ ),
170
+ top_logprobs: Optional[int] = typer.Option(
171
+ None,
172
+ "--top-logprobs",
173
+ min=1,
174
+ max=20,
175
+ help="Number of top log probabilities to return (1-20)",
176
+ ),
177
+ user: Optional[str] = typer.Option(
178
+ None,
179
+ "--user",
180
+ help="End-user ID for tracking and abuse monitoring",
181
+ ),
182
+ # Session and Context
183
+ session_id: Optional[UUID] = typer.Option(
184
+ None,
185
+ "--session",
186
+ help="Session ID for multi-turn",
187
+ ),
188
+ tags: Optional[str] = TAGS,
189
+ # Display Options
190
+ stream: bool = STREAM,
191
+ show_thinking: bool = SHOW_THINKING,
192
+ # Concurrent Execution
193
+ count: int = typer.Option(
194
+ 1,
195
+ "--count",
196
+ min=1,
197
+ max=10,
198
+ help="Concurrent executions (1-10)",
199
+ ),
200
+ # Output Schema Options
201
+ schema: Optional[str] = SCHEMA_REF,
202
+ schema_format: Optional[str] = SCHEMA_FORMAT,
203
+ schema_def: Optional[str] = SCHEMA_DEF,
204
+ # Output Options
205
+ output: str = OUTPUT_MODE,
206
+ output_file: Optional[str] = OUTPUT_FILE,
207
+ # Request Headers (for direct model mode)
208
+ headers: REQUEST_HEADERS = None,
209
+ # Async Execution Options
210
+ run_async: bool = typer.Option(
211
+ False,
212
+ "--async",
213
+ help="Submit for background processing. Returns execution_id for polling.",
214
+ ),
215
+ external_id: Optional[str] = typer.Option(
216
+ None,
217
+ "--external-id",
218
+ help="External identifier for idempotency and tracking",
219
+ ),
220
+ billing_project_id: Optional[UUID] = typer.Option(
221
+ None,
222
+ "--billing-project",
223
+ help="Billing project ID for cost tracking",
224
+ ),
225
+ parent_execution_id: Optional[UUID] = typer.Option(
226
+ None,
227
+ "--parent-execution",
228
+ help="Parent execution ID for lineage tracking",
229
+ ),
230
+ execution_purpose: Optional[str] = typer.Option(
231
+ None,
232
+ "--execution-purpose",
233
+ help="Label for why this execution exists (e.g., 'batch_export', 'testing')",
234
+ ),
235
+ max_history_messages: Optional[int] = typer.Option(
236
+ None,
237
+ "--max-history",
238
+ min=1,
239
+ max=1000,
240
+ help="Limit chat history to N most recent messages",
241
+ ),
242
+ regenerate: bool = typer.Option(
243
+ False,
244
+ "--regenerate",
245
+ help="Regenerate response to the last user message in the session",
246
+ ),
247
+ chat_history: Optional[str] = typer.Option(
248
+ None,
249
+ "--chat-history",
250
+ help="Chat history as JSON or @file.json",
251
+ ),
252
+ user_metadata: Optional[str] = typer.Option(
253
+ None,
254
+ "--user-metadata",
255
+ help="User metadata as JSON or @file.json",
256
+ ),
257
+ webhook_url: Optional[str] = typer.Option(
258
+ None,
259
+ "--webhook-url",
260
+ help="Webhook URL for completion notification",
261
+ ),
262
+ tools: Optional[str] = typer.Option(
263
+ None,
264
+ "--tools",
265
+ help="Tool definitions as JSON or @file.json (direct model mode)",
266
+ ),
267
+ ) -> None:
268
+ """Generate text using AI models.
269
+
270
+ Use a pre-configured agent or specify provider/model directly.
271
+ Supports streaming, concurrent execution, and multi-agent comparison.
272
+
273
+ Examples:
274
+
275
+ # With agent
276
+ ai generate text -a my-agent -m "Write a haiku"
277
+
278
+ # With provider/model
279
+ ai generate text -p anthropic -M claude-3-sonnet -m "Explain decorators"
280
+
281
+ # Concurrent executions
282
+ ai generate text -a my-agent -m "Random fact" --count 3
283
+
284
+ # Multi-agent comparison
285
+ ai generate text -A agent1 -A agent2 -m "Explain AI"
286
+
287
+ # With template and variables
288
+ ai generate text -a my-agent --message-template tpl -v topic=AI
289
+
290
+ # Compose in editor
291
+ ai generate text -a my-agent -e
292
+
293
+ # Output to clipboard or file
294
+ ai generate text -a my-agent -m "Story" -o clipboard
295
+ ai generate text -a my-agent -m "Story" -o file -O out.txt
296
+
297
+ # Structured output with schema
298
+ ai generate text -p openai -M gpt-4o -m "List colors" --schema my-schema
299
+
300
+ # With custom headers (e.g., Anthropic 1M context beta)
301
+ ai generate text -p anthropic -M claude-sonnet-4-5 -m @big-file.txt -H 'anthropic-beta:context-1m-2025-08-07'
302
+ """
303
+ # Resolve @file references for message, system_instruction, and schema_def
304
+ try:
305
+ message = resolve_content(message)
306
+ system_instruction = resolve_content(system_instruction)
307
+ schema_def = resolve_content(schema_def)
308
+ except FileContentError as e:
309
+ raise typer.BadParameter(str(e))
310
+
311
+ # Handle --edit flag: open editor to compose message
312
+ if edit:
313
+ if message_template:
314
+ raise typer.BadParameter(
315
+ "Cannot use --edit with --message-template. "
316
+ "Use --edit with --message or alone."
317
+ )
318
+ try:
319
+ edited_message = open_editor(initial_content=message or "")
320
+ if edited_message is None:
321
+ raise typer.Exit(code=1)
322
+ # Strip whitespace and check for empty content
323
+ edited_message = edited_message.strip()
324
+ if not edited_message:
325
+ console = get_console()
326
+ console.print("[yellow]No message entered. Aborting.[/yellow]")
327
+ raise typer.Exit(code=1)
328
+ message = edited_message
329
+ except EditorError as e:
330
+ console = get_console()
331
+ console.print(f"[red]Editor error:[/red] {e}")
332
+ raise typer.Exit(code=1)
333
+
334
+ # Validate output options
335
+ if output not in ("console", "clipboard", "file"):
336
+ raise typer.BadParameter(
337
+ f"Invalid output '{output}'. Valid options: console, clipboard, file"
338
+ )
339
+ if output == "file" and not output_file:
340
+ raise typer.BadParameter("--output-file is required when using --output=file")
341
+
342
+ # Validate output schema options
343
+ if schema and (schema_format or schema_def):
344
+ raise typer.BadParameter(
345
+ "--schema cannot be combined with --schema-format/--schema-def. "
346
+ "Use --schema for existing schemas, or --schema-format + --schema-def for inline."
347
+ )
348
+ if (schema_format and not schema_def) or (schema_def and not schema_format):
349
+ raise typer.BadParameter(
350
+ "--schema-format and --schema-def must be used together"
351
+ )
352
+ if schema_format and schema_format not in ("json_schema", "regex"):
353
+ valid = ", ".join(f.value for f in SchemaFormatEnum)
354
+ raise typer.BadParameter(
355
+ f"Invalid schema format '{schema_format}'. Valid: {valid}"
356
+ )
357
+ # Output schema cannot be used with agents (agents have their own)
358
+ if (schema or schema_format) and (agent or agents):
359
+ raise typer.BadParameter(
360
+ "--schema/--schema-format cannot be used with --agent or --agents. "
361
+ "Agents have their own output schema configuration."
362
+ )
363
+
364
+ # Headers can only be used with direct model mode (not agents)
365
+ if headers and (agent or agents):
366
+ raise typer.BadParameter(
367
+ "--header cannot be used with --agent or --agents. "
368
+ "For agents, configure request_headers in the agent's settings."
369
+ )
370
+
371
+ # Validate --async and --stream are mutually exclusive
372
+ if run_async and stream:
373
+ raise typer.BadParameter(
374
+ "--async cannot be used with --stream. "
375
+ "Use --async for background processing or --stream for real-time responses."
376
+ )
377
+
378
+ # Validate --async incompatible with concurrent execution
379
+ if run_async and count > 1:
380
+ raise typer.BadParameter(
381
+ "--async cannot be used with --count. "
382
+ "Async execution does not support concurrent runs."
383
+ )
384
+
385
+ # Validate --async incompatible with multi-agent mode
386
+ if run_async and agents:
387
+ raise typer.BadParameter(
388
+ "--async cannot be used with --agents. "
389
+ "Async execution does not support multi-agent comparison."
390
+ )
391
+
392
+ # Validate --tools can only be used with direct model mode (not agents)
393
+ if tools and (agent or agents):
394
+ raise typer.BadParameter(
395
+ "--tools cannot be used with --agent or --agents. "
396
+ "For agents, configure tools in the agent's settings."
397
+ )
398
+
399
+ # Validate mutually exclusive options
400
+ _validate_arguments(
401
+ message=message,
402
+ message_template=message_template,
403
+ agent=agent,
404
+ agents=agents,
405
+ provider=provider,
406
+ model=model,
407
+ count=count,
408
+ )
409
+
410
+ # Execute the command
411
+ # Parse variables from key=value format to dict
412
+ from cli.infrastructure.kv_parser import parse_kv_pairs
413
+
414
+ parsed_message_vars = parse_kv_pairs(message_vars)
415
+ parsed_system_vars = parse_kv_pairs(system_vars)
416
+
417
+ # Parse headers from key:value format to dict
418
+ parsed_headers: dict[str, str] | None = None
419
+ if headers:
420
+ parsed_headers = {}
421
+ for h in headers:
422
+ if ":" not in h:
423
+ raise typer.BadParameter(
424
+ f"Invalid header format '{h}'. Use 'key:value' format."
425
+ )
426
+ key, value = h.split(":", 1)
427
+ parsed_headers[key.strip()] = value.strip()
428
+
429
+ # Helper function to parse JSON or @file content
430
+ def parse_json_or_file(value: str | None, param_name: str) -> Any | None:
431
+ if value is None:
432
+ return None
433
+ try:
434
+ resolved = resolve_content(value)
435
+ except FileContentError as e:
436
+ raise typer.BadParameter(str(e))
437
+ if resolved is None:
438
+ return None
439
+ try:
440
+ return json.loads(resolved)
441
+ except json.JSONDecodeError as e:
442
+ raise typer.BadParameter(f"Invalid JSON for {param_name}: {e}")
443
+
444
+ # Parse JSON options
445
+ parsed_chat_history = parse_json_or_file(chat_history, "--chat-history")
446
+ parsed_user_metadata = parse_json_or_file(user_metadata, "--user-metadata")
447
+ parsed_tools = parse_json_or_file(tools, "--tools")
448
+
449
+ # Parse stop sequences (can be single string or comma-separated)
450
+ parsed_stop: str | list[str] | None = None
451
+ if stop:
452
+ if "," in stop:
453
+ parsed_stop = [s.strip() for s in stop.split(",") if s.strip()]
454
+ else:
455
+ parsed_stop = stop
456
+
457
+ _execute_generate_text(
458
+ message=message,
459
+ message_template=message_template,
460
+ message_vars=parsed_message_vars,
461
+ agent=agent,
462
+ agents=agents,
463
+ provider=provider,
464
+ model=model,
465
+ system_instruction=system_instruction,
466
+ system_template=system_template,
467
+ system_vars=parsed_system_vars,
468
+ temperature=temperature,
469
+ max_tokens=max_tokens,
470
+ reasoning_effort=reasoning_effort,
471
+ top_p=top_p,
472
+ top_k=top_k,
473
+ frequency_penalty=frequency_penalty,
474
+ presence_penalty=presence_penalty,
475
+ seed=seed,
476
+ stop=parsed_stop,
477
+ logprobs=logprobs,
478
+ top_logprobs=top_logprobs,
479
+ user=user,
480
+ session_id=session_id,
481
+ tags=tags,
482
+ stream=stream,
483
+ show_thinking=show_thinking,
484
+ count=count,
485
+ output_mode=output,
486
+ output_file=output_file,
487
+ schema=schema,
488
+ schema_format=schema_format,
489
+ schema_def=schema_def,
490
+ request_headers=parsed_headers,
491
+ run_async=run_async,
492
+ external_id=external_id,
493
+ billing_project_id=billing_project_id,
494
+ max_history_messages=max_history_messages,
495
+ regenerate=regenerate,
496
+ chat_history=parsed_chat_history,
497
+ user_metadata=parsed_user_metadata,
498
+ webhook_url=webhook_url,
499
+ tools=parsed_tools,
500
+ parent_execution_id=parent_execution_id,
501
+ execution_purpose=execution_purpose,
502
+ )
503
+
504
+
505
+ def _validate_arguments(
506
+ message: str | None,
507
+ message_template: str | None,
508
+ agent: str | None,
509
+ agents: list[str] | None,
510
+ provider: str | None,
511
+ model: str | None,
512
+ count: int,
513
+ ) -> None:
514
+ """Validate command arguments.
515
+
516
+ Args:
517
+ message: User message text
518
+ message_template: User message template name or UUID
519
+ agent: Agent name or UUID
520
+ agents: List of agent names/UUIDs for multi-agent comparison
521
+ provider: Provider key
522
+ model: Model name
523
+ count: Concurrent execution count
524
+
525
+ Raises:
526
+ typer.BadParameter: If validation fails
527
+ """
528
+ # Require message OR message_template
529
+ if not message and not message_template:
530
+ raise typer.BadParameter(
531
+ "Either --message (-m) or --message-template is required"
532
+ )
533
+
534
+ if message and message_template:
535
+ raise typer.BadParameter("Cannot specify both --message and --message-template")
536
+
537
+ # Multi-agent mode validation
538
+ if agents:
539
+ if agent:
540
+ raise typer.BadParameter(
541
+ "Cannot specify both --agent (-a) and --agents (-A)"
542
+ )
543
+ if provider or model:
544
+ raise typer.BadParameter(
545
+ "Cannot specify both --agents (-A) and --provider/--model"
546
+ )
547
+ if len(agents) < 2:
548
+ raise typer.BadParameter(
549
+ "--agents (-A) requires at least 2 agents for comparison"
550
+ )
551
+ if len(agents) > 10:
552
+ raise typer.BadParameter("--agents (-A) supports a maximum of 10 agents")
553
+ if count > 1:
554
+ raise typer.BadParameter(
555
+ "Cannot use --count with --agents. Each agent runs once."
556
+ )
557
+ return # Skip other validations in multi-agent mode
558
+
559
+ # Require agent OR (provider AND model)
560
+ if not agent and not (provider and model):
561
+ raise typer.BadParameter(
562
+ "Either --agent (-a) or both --provider (-p) and --model are required"
563
+ )
564
+
565
+ if agent and (provider or model):
566
+ raise typer.BadParameter("Cannot specify both --agent and --provider/--model")
567
+
568
+ # Validate count range
569
+ if count < 1 or count > 10:
570
+ raise typer.BadParameter("--count must be between 1 and 10")
571
+
572
+
573
+ def _copy_to_clipboard(content: str, console: Console) -> bool:
574
+ """Copy generated content to clipboard.
575
+
576
+ Args:
577
+ content: Generated text content
578
+ console: Rich console for status messages
579
+
580
+ Returns:
581
+ True if copy succeeded, False otherwise
582
+ """
583
+ try:
584
+ import pyperclip
585
+
586
+ pyperclip.copy(content)
587
+ console.print(
588
+ f"[green]Copied to clipboard[/green] ({len(content):,} characters)"
589
+ )
590
+ return True
591
+ except ImportError:
592
+ console.print(
593
+ "[yellow]pyperclip not installed. Install with: pip install pyperclip[/yellow]"
594
+ )
595
+ console.print("[dim]Falling back to console output...[/dim]")
596
+ console.print(content)
597
+ return False
598
+ except Exception as e:
599
+ console.print(f"[red]Failed to copy to clipboard:[/red] {e}")
600
+ console.print("[dim]Falling back to console output...[/dim]")
601
+ console.print(content)
602
+ return False
603
+
604
+
605
+ def _write_output_file(output_file: str, content: str, console: Console) -> None:
606
+ """Write generated content to a file.
607
+
608
+ Args:
609
+ output_file: File path to write to
610
+ content: Generated text content
611
+ console: Rich console for status messages
612
+ """
613
+ try:
614
+ path = Path(output_file)
615
+ # Create parent directories if they don't exist
616
+ path.parent.mkdir(parents=True, exist_ok=True)
617
+ path.write_text(content, encoding="utf-8")
618
+ console.print(f"[green]Output written to:[/green] {output_file}")
619
+ except OSError as e:
620
+ console.print(f"[red]Failed to write output file:[/red] {e}")
621
+ raise typer.Exit(code=1)
622
+
623
+
624
+ def _get_numbered_filepath(base_path: str, index: int) -> str:
625
+ """Generate a numbered filepath for concurrent outputs.
626
+
627
+ Args:
628
+ base_path: Base file path (e.g., "output.txt")
629
+ index: Execution index (1-based)
630
+
631
+ Returns:
632
+ Numbered filepath (e.g., "output_1.txt")
633
+ """
634
+ path = Path(base_path)
635
+ stem = path.stem
636
+ suffix = path.suffix
637
+ return str(path.parent / f"{stem}_{index}{suffix}")
638
+
639
+
640
+ def _write_concurrent_outputs(
641
+ output_file: str,
642
+ results: list[tuple[str, str, str | None, str | None]],
643
+ console: Console,
644
+ ) -> None:
645
+ """Write concurrent streaming results to numbered files.
646
+
647
+ Args:
648
+ output_file: Base file path
649
+ results: List of (content, thinking, error, session_id) tuples
650
+ console: Rich console for status messages
651
+ """
652
+ for i, (content, _, error, _) in enumerate(results, 1):
653
+ if error is None and content:
654
+ filepath = _get_numbered_filepath(output_file, i)
655
+ _write_output_file(filepath, content, console)
656
+
657
+
658
+ def _write_concurrent_responses(
659
+ output_file: str,
660
+ responses: list[GenerateTextResponse],
661
+ console: Console,
662
+ ) -> None:
663
+ """Write concurrent non-streaming responses to numbered files.
664
+
665
+ Args:
666
+ output_file: Base file path
667
+ responses: List of GenerateTextResponse objects
668
+ console: Rich console for status messages
669
+ """
670
+ for i, response in enumerate(responses, 1):
671
+ filepath = _get_numbered_filepath(output_file, i)
672
+ _write_output_file(filepath, response.output_text, console)
673
+
674
+
675
+ def _get_agent_filepath(base_path: str, agent_id: str) -> str:
676
+ """Generate a filepath with agent identifier for multi-agent outputs.
677
+
678
+ Args:
679
+ base_path: Base file path (e.g., "output.txt")
680
+ agent_id: Agent name or UUID
681
+
682
+ Returns:
683
+ Filepath with agent identifier (e.g., "output_my-agent.txt")
684
+ """
685
+ path = Path(base_path)
686
+ stem = path.stem
687
+ suffix = path.suffix
688
+ # Sanitize agent_id for use in filename (replace problematic chars)
689
+ safe_agent_id = agent_id.replace("/", "_").replace("\\", "_").replace(":", "_")
690
+ return str(path.parent / f"{stem}_{safe_agent_id}{suffix}")
691
+
692
+
693
+ def _write_multi_agent_outputs(
694
+ output_file: str,
695
+ agents: list[str],
696
+ results: list[tuple[str, str, str | None, str | None]],
697
+ console: Console,
698
+ ) -> None:
699
+ """Write multi-agent streaming results to agent-named files.
700
+
701
+ Args:
702
+ output_file: Base file path
703
+ agents: List of agent identifiers
704
+ results: List of (content, thinking, error, session_id) tuples
705
+ console: Rich console for status messages
706
+ """
707
+ for i, (content, _, error, _) in enumerate(results):
708
+ if error is None and content:
709
+ filepath = _get_agent_filepath(output_file, agents[i])
710
+ _write_output_file(filepath, content, console)
711
+
712
+
713
+ def _write_multi_agent_responses(
714
+ output_file: str,
715
+ successful_responses: list[tuple[str, GenerateTextResponse]],
716
+ console: Console,
717
+ ) -> None:
718
+ """Write multi-agent non-streaming responses to agent-named files.
719
+
720
+ Args:
721
+ output_file: Base file path
722
+ successful_responses: List of (agent_id, response) tuples
723
+ console: Rich console for status messages
724
+ """
725
+ for agent_id, response in successful_responses:
726
+ filepath = _get_agent_filepath(output_file, agent_id)
727
+ _write_output_file(filepath, response.output_text, console)
728
+
729
+
730
+ @async_command
731
+ async def _execute_generate_text(
732
+ message: str | None,
733
+ message_template: str | None,
734
+ message_vars: dict[str, Any],
735
+ agent: str | None,
736
+ agents: list[str] | None,
737
+ provider: str | None,
738
+ model: str | None,
739
+ system_instruction: str | None,
740
+ system_template: str | None,
741
+ system_vars: dict[str, Any],
742
+ temperature: float | None,
743
+ max_tokens: int | None,
744
+ reasoning_effort: str | None,
745
+ top_p: float | None,
746
+ top_k: int | None,
747
+ frequency_penalty: float | None,
748
+ presence_penalty: float | None,
749
+ seed: int | None,
750
+ stop: str | list[str] | None,
751
+ logprobs: bool | None,
752
+ top_logprobs: int | None,
753
+ user: str | None,
754
+ session_id: UUID | None,
755
+ tags: str | None,
756
+ stream: bool,
757
+ show_thinking: bool,
758
+ count: int,
759
+ output_mode: str,
760
+ output_file: str | None,
761
+ schema: str | None,
762
+ schema_format: str | None,
763
+ schema_def: str | None,
764
+ request_headers: dict[str, str] | None = None,
765
+ run_async: bool = False,
766
+ external_id: str | None = None,
767
+ billing_project_id: UUID | None = None,
768
+ max_history_messages: int | None = None,
769
+ regenerate: bool = False,
770
+ chat_history: list[dict[str, str]] | None = None,
771
+ user_metadata: dict[str, Any] | None = None,
772
+ webhook_url: str | None = None,
773
+ tools: list[Any] | None = None,
774
+ parent_execution_id: UUID | None = None,
775
+ execution_purpose: str | None = None,
776
+ ) -> None:
777
+ """Execute the generate text command.
778
+
779
+ Args:
780
+ message: User message text
781
+ message_template: User message template name or UUID
782
+ message_vars: User message template variables (parsed from key=value)
783
+ agent: Agent name or UUID
784
+ agents: List of agent names/UUIDs for multi-agent comparison
785
+ provider: Provider key
786
+ model: Model name
787
+ system_instruction: System instruction text
788
+ system_template: System instruction template name or UUID
789
+ system_vars: System instruction template variables (parsed from key=value)
790
+ temperature: Temperature parameter
791
+ max_tokens: Max tokens parameter
792
+ reasoning_effort: Reasoning effort level
793
+ session_id: Chat session ID
794
+ tags: Comma-separated tags
795
+ stream: Whether to stream
796
+ show_thinking: Whether to show thinking blocks
797
+ count: Number of concurrent executions
798
+ output_mode: Output destination - 'console', 'clipboard', or 'file'
799
+ output_file: File path for 'file' output mode
800
+ schema: Schema reference (name or UUID) for structured output
801
+ schema_format: Inline schema format ('json_schema' or 'regex')
802
+ schema_def: Inline schema definition string
803
+ request_headers: Custom HTTP headers for direct model mode
804
+ """
805
+ console = get_console()
806
+ output = OutputService.get()
807
+
808
+ # Parse provider enum if specified
809
+ provider_enum = None
810
+ if provider:
811
+ try:
812
+ provider_enum = Provider(provider)
813
+ except ValueError:
814
+ valid = ", ".join(
815
+ p.value for p in Provider if not p.name.startswith("TESTING")
816
+ )
817
+ raise typer.BadParameter(
818
+ f"Invalid provider '{provider}'. Valid providers: {valid[:200]}..."
819
+ )
820
+
821
+ # Get template variables (already parsed by callback, convert empty dict to None)
822
+ message_variables = message_vars if message_vars else None
823
+ system_variables = system_vars if system_vars else None
824
+
825
+ # Parse tags
826
+ tag_list = [t.strip() for t in tags.split(",")] if tags else []
827
+
828
+ # Build generation parameters
829
+ gen_params = None
830
+ if any(
831
+ [
832
+ temperature is not None,
833
+ max_tokens is not None,
834
+ reasoning_effort is not None,
835
+ top_p is not None,
836
+ top_k is not None,
837
+ frequency_penalty is not None,
838
+ presence_penalty is not None,
839
+ seed is not None,
840
+ stop is not None,
841
+ logprobs is not None,
842
+ top_logprobs is not None,
843
+ user is not None,
844
+ ]
845
+ ):
846
+ gen_params = GenerationParameters(
847
+ temperature=temperature,
848
+ max_tokens=max_tokens,
849
+ reasoning_effort=reasoning_effort, # type: ignore[arg-type]
850
+ top_p=top_p,
851
+ top_k=top_k,
852
+ frequency_penalty=frequency_penalty,
853
+ presence_penalty=presence_penalty,
854
+ seed=seed,
855
+ stop=stop,
856
+ logprobs=logprobs,
857
+ top_logprobs=top_logprobs,
858
+ user=user,
859
+ )
860
+
861
+ # Build output schema (already validated in command function)
862
+ output_schema: InlineOutputSchema | SchemaReference | None = None
863
+ if schema:
864
+ output_schema = SchemaReference(schema_id=schema)
865
+ elif schema_format and schema_def:
866
+ output_schema = InlineOutputSchema(
867
+ schema_format=SchemaFormatEnum(schema_format),
868
+ schema_definition=schema_def,
869
+ )
870
+
871
+ # Multi-agent comparison mode
872
+ if agents:
873
+ await _execute_multi_agent(
874
+ agents=agents,
875
+ message=message,
876
+ message_template=message_template,
877
+ message_variables=message_variables,
878
+ system_instruction=system_instruction,
879
+ system_template=system_template,
880
+ system_variables=system_variables,
881
+ gen_params=gen_params,
882
+ tag_list=tag_list,
883
+ stream=stream,
884
+ show_thinking=show_thinking,
885
+ console=console,
886
+ _output=output,
887
+ output_mode=output_mode,
888
+ output_file=output_file,
889
+ )
890
+ return
891
+
892
+ # Build model_source based on user input
893
+ if agent:
894
+ model_source = AgentSource(agent=agent, generation_params=gen_params)
895
+ else:
896
+ # Must be provider + model (validated earlier)
897
+ assert provider_enum is not None and model is not None
898
+ model_source = DirectModelSource(
899
+ provider_key=provider_enum,
900
+ provider_model_name=model,
901
+ generation_params=gen_params,
902
+ request_headers=request_headers,
903
+ )
904
+
905
+ # Build request
906
+ request = GenerateTextRequest(
907
+ model_source=model_source,
908
+ user_message=message,
909
+ user_message_template=message_template,
910
+ user_message_variables=message_variables,
911
+ system_instruction=system_instruction,
912
+ system_instruction_template=system_template,
913
+ system_instruction_variables=system_variables,
914
+ chat_session_id=session_id,
915
+ tags=tag_list,
916
+ stream=stream,
917
+ output_schema=output_schema,
918
+ external_id=external_id,
919
+ billing_project_id=billing_project_id,
920
+ max_history_messages=max_history_messages,
921
+ regenerate=regenerate,
922
+ chat_history=chat_history,
923
+ webhook_url=webhook_url,
924
+ tools=tools,
925
+ parent_execution_id=parent_execution_id,
926
+ execution_purpose=execution_purpose,
927
+ )
928
+
929
+ # Handle async execution mode
930
+ if run_async:
931
+ await _execute_async(
932
+ request=request,
933
+ user_metadata=user_metadata,
934
+ webhook_url=webhook_url,
935
+ console=console,
936
+ )
937
+ return
938
+
939
+ # Execute based on count and streaming mode
940
+ request = GenerateTextRequest(
941
+ model_source=model_source,
942
+ user_message=message,
943
+ user_message_template=message_template,
944
+ user_message_variables=message_variables,
945
+ system_instruction=system_instruction,
946
+ system_instruction_template=system_template,
947
+ system_instruction_variables=system_variables,
948
+ chat_session_id=session_id,
949
+ tags=tag_list,
950
+ stream=stream,
951
+ output_schema=output_schema,
952
+ parent_execution_id=parent_execution_id,
953
+ execution_purpose=execution_purpose,
954
+ )
955
+
956
+ # Execute based on count and streaming mode
957
+ if count == 1:
958
+ await _execute_single(
959
+ request, stream, show_thinking, console, output_mode, output_file
960
+ )
961
+ else:
962
+ await _execute_concurrent(
963
+ request,
964
+ stream,
965
+ show_thinking,
966
+ count,
967
+ console,
968
+ output,
969
+ output_mode,
970
+ output_file,
971
+ )
972
+
973
+
974
+ async def _execute_async(
975
+ request: GenerateTextRequest,
976
+ user_metadata: dict[str, Any] | None,
977
+ webhook_url: str | None,
978
+ console: Console,
979
+ ) -> None:
980
+ """Execute a text generation asynchronously (background processing).
981
+
982
+ Submits the generation request for background processing and returns immediately
983
+ with an execution ID for polling.
984
+
985
+ Args:
986
+ request: Generation request (stream must be False)
987
+ user_metadata: Optional metadata attached to the execution
988
+ webhook_url: Optional webhook URL for completion notification
989
+ console: Rich console for output
990
+ """
991
+ async with authenticated_client() as (_config, client):
992
+ async with spinner("Submitting async generation..."):
993
+ response = await client.generate_text_async(
994
+ request,
995
+ webhook_url=webhook_url,
996
+ user_metadata=user_metadata,
997
+ )
998
+
999
+ # Display submission result
1000
+ console.print()
1001
+ console.print("[green]Generation queued for background processing[/green]")
1002
+ console.print()
1003
+ console.print(f"[dim]Execution ID:[/dim] {response.execution_id}")
1004
+ if response.external_id:
1005
+ console.print(f"[dim]External ID:[/dim] {response.external_id}")
1006
+ console.print()
1007
+ console.print("[dim]Check status with:[/dim] ai generate status ")
1008
+ console.print()
1009
+
1010
+
1011
+ async def _execute_single(
1012
+ request: GenerateTextRequest,
1013
+ stream: bool,
1014
+ show_thinking: bool,
1015
+ console: Console,
1016
+ output_mode: str = "console",
1017
+ output_file: str | None = None,
1018
+ ) -> None:
1019
+ """Execute a single text generation.
1020
+
1021
+ Args:
1022
+ request: Generation request
1023
+ stream: Whether to stream
1024
+ show_thinking: Whether to show thinking blocks
1025
+ console: Rich console
1026
+ output_mode: Output destination - 'console', 'clipboard', or 'file'
1027
+ output_file: File path for 'file' output mode
1028
+ """
1029
+ async with authenticated_client() as (_config, client):
1030
+ if stream:
1031
+ # Streaming mode
1032
+ renderer = StreamRenderer(console)
1033
+ stream_gen = client.generate_text_stream(request)
1034
+ content, thinking, session_id = await renderer.render_stream(
1035
+ stream_gen, show_thinking
1036
+ )
1037
+
1038
+ # Handle output based on mode
1039
+ if output_mode == "clipboard":
1040
+ _copy_to_clipboard(content, console)
1041
+ elif output_mode == "file" and output_file:
1042
+ _write_output_file(output_file, content, console)
1043
+
1044
+ # Display completion message with session_id
1045
+ console.print()
1046
+ if thinking and show_thinking:
1047
+ console.print(
1048
+ f"[dim]Completed with {len(thinking)} chars of thinking[/dim]"
1049
+ )
1050
+ if session_id:
1051
+ console.print(f"[dim]Session ID:[/dim] {session_id}")
1052
+ console.print()
1053
+
1054
+ else:
1055
+ # Non-streaming mode
1056
+ async with spinner("Generating text..."):
1057
+ response = await client.generate_text(request)
1058
+
1059
+ # Handle output based on mode
1060
+ if output_mode == "clipboard":
1061
+ _copy_to_clipboard(response.output_text, console)
1062
+ elif output_mode == "file" and output_file:
1063
+ _write_output_file(output_file, response.output_text, console)
1064
+ else:
1065
+ presenter = GenerateTextPresenter(console)
1066
+ presenter.present_result(response, show_metadata=True)
1067
+
1068
+
1069
+ async def _execute_concurrent(
1070
+ request: GenerateTextRequest,
1071
+ stream: bool,
1072
+ show_thinking: bool,
1073
+ count: int,
1074
+ console: Console,
1075
+ _output: OutputService,
1076
+ output_mode: str = "console",
1077
+ output_file: str | None = None,
1078
+ ) -> None:
1079
+ """Execute multiple concurrent text generations.
1080
+
1081
+ Args:
1082
+ request: Base generation request (reused for each execution)
1083
+ stream: Whether to stream
1084
+ show_thinking: Whether to show thinking blocks
1085
+ count: Number of concurrent executions
1086
+ console: Rich console
1087
+ output: Output service (used for status messages)
1088
+ output_mode: Output destination - 'console', 'clipboard', or 'file'
1089
+ output_file: File path for 'file' output mode
1090
+ """
1091
+ async with authenticated_client() as (_config, client):
1092
+ if stream:
1093
+ # Concurrent streaming mode
1094
+ renderer = ConcurrentRenderer(console, count)
1095
+
1096
+ # Create stream generators
1097
+ streams = [client.generate_text_stream(request) for _ in range(count)]
1098
+
1099
+ # Render concurrently
1100
+ results = await renderer.render_streams(streams, show_thinking)
1101
+
1102
+ # Handle output based on mode
1103
+ if output_mode == "clipboard":
1104
+ # Combine all successful outputs for clipboard
1105
+ combined = "\n\n---\n\n".join(
1106
+ content
1107
+ for content, _, error, _ in results
1108
+ if error is None and content
1109
+ )
1110
+ if combined:
1111
+ _copy_to_clipboard(combined, console)
1112
+ elif output_mode == "file" and output_file:
1113
+ _write_concurrent_outputs(output_file, results, console)
1114
+
1115
+ # Display completion summary with session IDs
1116
+ console.print()
1117
+ successful = sum(1 for _, _, error, _ in results if error is None)
1118
+ failed = count - successful
1119
+
1120
+ if failed > 0:
1121
+ console.print(
1122
+ f"[yellow]Completed: {successful} successful, {failed} failed[/yellow]"
1123
+ )
1124
+ else:
1125
+ console.print(
1126
+ f"[green]All {count} executions completed successfully[/green]"
1127
+ )
1128
+
1129
+ # Display session IDs for each execution
1130
+ console.print()
1131
+ for i, (_, _, error, session_id) in enumerate(results, 1):
1132
+ if error is None and session_id:
1133
+ console.print(f"[dim]Execution {i} Session ID:[/dim] {session_id}")
1134
+ console.print()
1135
+
1136
+ else:
1137
+ # Concurrent non-streaming mode
1138
+ async with spinner(f"Generating {count} concurrent executions..."):
1139
+ # Create tasks
1140
+ tasks = [client.generate_text(request) for _ in range(count)]
1141
+
1142
+ # Execute concurrently
1143
+ responses = await asyncio.gather(*tasks, return_exceptions=True)
1144
+
1145
+ # Separate successful responses from errors
1146
+ successful_responses: list[GenerateTextResponse] = []
1147
+ errors: list[tuple[int, str]] = []
1148
+ for i, response in enumerate(responses):
1149
+ if isinstance(response, Exception):
1150
+ errors.append((i + 1, str(response)))
1151
+ else:
1152
+ successful_responses.append(cast(GenerateTextResponse, response))
1153
+
1154
+ # Handle output based on mode
1155
+ if output_mode == "clipboard" and successful_responses:
1156
+ # Combine all outputs for clipboard
1157
+ combined = "\n\n---\n\n".join(
1158
+ r.output_text for r in successful_responses
1159
+ )
1160
+ _copy_to_clipboard(combined, console)
1161
+ elif output_mode == "file" and output_file and successful_responses:
1162
+ _write_concurrent_responses(output_file, successful_responses, console)
1163
+ elif successful_responses:
1164
+ # Display results only if console mode
1165
+ presenter = GenerateTextPresenter(console)
1166
+ presenter.present_multiple_results(
1167
+ successful_responses, show_metadata=True
1168
+ )
1169
+
1170
+ # Display errors if any
1171
+ if errors:
1172
+ console.print()
1173
+ console.print("[red]Failed executions:[/red]")
1174
+ for idx, error in errors:
1175
+ console.print(f" [red]Execution {idx}:[/red] {error}")
1176
+ console.print()
1177
+
1178
+
1179
+ async def _execute_multi_agent(
1180
+ agents: list[str],
1181
+ message: str | None,
1182
+ message_template: str | None,
1183
+ message_variables: dict[str, Any] | None,
1184
+ system_instruction: str | None,
1185
+ system_template: str | None,
1186
+ system_variables: dict[str, Any] | None,
1187
+ gen_params: GenerationParameters | None,
1188
+ tag_list: list[str],
1189
+ stream: bool,
1190
+ show_thinking: bool,
1191
+ console: Console,
1192
+ _output: OutputService,
1193
+ output_mode: str = "console",
1194
+ output_file: str | None = None,
1195
+ ) -> None:
1196
+ """Execute text generation across multiple agents for comparison.
1197
+
1198
+ Sends the same message to each specified agent concurrently and displays
1199
+ the results side-by-side for easy comparison.
1200
+
1201
+ Args:
1202
+ agents: List of agent names or UUIDs to compare
1203
+ message: User message text
1204
+ message_template: User message template name or UUID
1205
+ message_variables: Template variables for user message
1206
+ system_instruction: System instruction text
1207
+ system_template: System instruction template name or UUID
1208
+ system_variables: Template variables for system instruction
1209
+ gen_params: Generation parameters
1210
+ tag_list: Tags for generated content
1211
+ stream: Whether to stream responses
1212
+ show_thinking: Whether to show thinking blocks
1213
+ console: Rich console for output
1214
+ output: Output service for status messages
1215
+ output_mode: Output destination - 'console', 'clipboard', or 'file'
1216
+ output_file: File path for 'file' output mode
1217
+ """
1218
+ async with authenticated_client() as (_config, client):
1219
+ # Build a request for each agent
1220
+ requests = [
1221
+ GenerateTextRequest(
1222
+ model_source=AgentSource(agent=agent_id, generation_params=gen_params),
1223
+ user_message=message,
1224
+ user_message_template=message_template,
1225
+ user_message_variables=message_variables,
1226
+ system_instruction=system_instruction,
1227
+ system_instruction_template=system_template,
1228
+ system_instruction_variables=system_variables,
1229
+ tags=tag_list,
1230
+ stream=stream,
1231
+ )
1232
+ for agent_id in agents
1233
+ ]
1234
+
1235
+ if stream:
1236
+ # Concurrent streaming mode with agent identifiers
1237
+ renderer = ConcurrentRenderer(
1238
+ console, len(agents), agent_identifiers=agents
1239
+ )
1240
+
1241
+ # Create stream generators for each agent's request
1242
+ streams = [client.generate_text_stream(req) for req in requests]
1243
+
1244
+ # Render concurrently
1245
+ results = await renderer.render_streams(streams, show_thinking)
1246
+
1247
+ # Handle output based on mode
1248
+ if output_mode == "clipboard":
1249
+ # Combine all successful outputs with agent labels for clipboard
1250
+ parts: list[str] = []
1251
+ for i, (content, _, error, _) in enumerate(results):
1252
+ if error is None and content:
1253
+ parts.append(f"## {agents[i]}\n\n{content}")
1254
+ if parts:
1255
+ combined = "\n\n---\n\n".join(parts)
1256
+ _copy_to_clipboard(combined, console)
1257
+ elif output_mode == "file" and output_file:
1258
+ _write_multi_agent_outputs(output_file, agents, results, console)
1259
+
1260
+ # Display completion summary
1261
+ console.print()
1262
+ successful = sum(1 for _, _, error, _ in results if error is None)
1263
+ failed = len(agents) - successful
1264
+
1265
+ if failed > 0:
1266
+ console.print(
1267
+ f"[yellow]Completed: {successful} successful, {failed} failed[/yellow]"
1268
+ )
1269
+ else:
1270
+ console.print(
1271
+ f"[green]All {len(agents)} agents completed successfully[/green]"
1272
+ )
1273
+
1274
+ # Display session IDs for each agent
1275
+ console.print()
1276
+ for i, (_, _, error, session_id) in enumerate(results):
1277
+ if error is None and session_id:
1278
+ console.print(f"[dim]{agents[i]} Session ID:[/dim] {session_id}")
1279
+ console.print()
1280
+
1281
+ else:
1282
+ # Concurrent non-streaming mode
1283
+ async with spinner(f"Generating responses from {len(agents)} agents..."):
1284
+ # Create tasks for each agent
1285
+ tasks = [client.generate_text(req) for req in requests]
1286
+
1287
+ # Execute concurrently
1288
+ responses = await asyncio.gather(*tasks, return_exceptions=True)
1289
+
1290
+ # Separate successful responses from errors
1291
+ successful_responses: list[tuple[str, GenerateTextResponse]] = []
1292
+ errors: list[tuple[str, str]] = []
1293
+ for i, response in enumerate(responses):
1294
+ agent_id = agents[i]
1295
+ if isinstance(response, Exception):
1296
+ errors.append((agent_id, str(response)))
1297
+ else:
1298
+ successful_responses.append(
1299
+ (agent_id, cast(GenerateTextResponse, response))
1300
+ )
1301
+
1302
+ # Handle output based on mode
1303
+ if output_mode == "clipboard" and successful_responses:
1304
+ # Combine all outputs with agent labels for clipboard
1305
+ parts = [
1306
+ f"## {agent_id}\n\n{response.output_text}"
1307
+ for agent_id, response in successful_responses
1308
+ ]
1309
+ combined = "\n\n---\n\n".join(parts)
1310
+ _copy_to_clipboard(combined, console)
1311
+ elif output_mode == "file" and output_file and successful_responses:
1312
+ _write_multi_agent_responses(output_file, successful_responses, console)
1313
+ elif successful_responses:
1314
+ presenter = GenerateTextPresenter(console)
1315
+ for agent_id, response in successful_responses:
1316
+ console.print(f"\n[bold cyan]Agent: {agent_id}[/bold cyan]")
1317
+ presenter.present_result(response, show_metadata=True)
1318
+
1319
+ # Display errors if any
1320
+ if errors:
1321
+ console.print()
1322
+ console.print("[red]Failed agents:[/red]")
1323
+ for agent_id, error in errors:
1324
+ console.print(f" [red]{agent_id}:[/red] {error}")
1325
+ console.print()