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
File without changes
@@ -0,0 +1,386 @@
1
+ """Injection service for TUI chat.
2
+
3
+ Wraps the shared InjectionResolver and provides caching for autocomplete.
4
+ This service is designed for use with Textual's async workers.
5
+
6
+ Supports the same macro syntax as templates:
7
+ - @fragment(name) - Include a fragment template
8
+ - @text(name) - Insert text from a content part
9
+ - @json(name) - Insert JSON from a content part
10
+ - @schema(name) - Reference a schema definition
11
+ """
12
+
13
+ import logging
14
+ import time
15
+ from dataclasses import dataclass
16
+
17
+ from alloy_runtime_sdk.api_client.client import ApiClient
18
+
19
+ from cli.infrastructure.injection.resolver import (
20
+ InjectionResolver,
21
+ ResolvedMessage,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Cache TTL in seconds (5 minutes)
27
+ CACHE_TTL = 300
28
+
29
+
30
+ @dataclass
31
+ class FragmentInfo:
32
+ """Fragment template information for autocomplete."""
33
+
34
+ id: str
35
+ name: str
36
+ description: str | None
37
+ content_type_id: str
38
+
39
+
40
+ @dataclass
41
+ class ContentPartInfo:
42
+ """Content part information for autocomplete."""
43
+
44
+ id: str
45
+ content_type_id: str
46
+ content_text: str | None
47
+ has_structured: bool
48
+
49
+
50
+ @dataclass
51
+ class SchemaInfo:
52
+ """Schema information for autocomplete."""
53
+
54
+ id: str
55
+ schema_name: str
56
+ version: str
57
+ schema_format_id: str
58
+ description: str | None
59
+
60
+
61
+ @dataclass
62
+ class InjectionCompletion:
63
+ """A single injection autocomplete suggestion."""
64
+
65
+ text: str # Full insertion text: @fragment(my-fragment)
66
+ display_name: str # Display name: "my-fragment"
67
+ description: str # Description/type info
68
+ injection_type: str # "fragment", "text", "json", or "schema"
69
+
70
+
71
+ class InjectionService:
72
+ """Manages injection resolution and caching for TUI.
73
+
74
+ Features:
75
+ - Wraps InjectionResolver for API calls
76
+ - Caches fragments/content/schemas for autocomplete
77
+ - Provides async methods suitable for Textual workers
78
+ - Shares ApiClient with the rest of the TUI
79
+
80
+ Usage:
81
+ service = InjectionService(client)
82
+ await service.refresh_cache()
83
+
84
+ # Check for completions
85
+ completions = service.get_completions("fragment", "my-fra")
86
+
87
+ # Resolve message before sending
88
+ if has_injections(message):
89
+ result = await service.resolve_message(message)
90
+ if result.has_errors:
91
+ # Handle errors
92
+ ...
93
+ message = result.text
94
+ """
95
+
96
+ def __init__(self, client: ApiClient):
97
+ """Initialize the injection service.
98
+
99
+ Args:
100
+ client: Shared ApiClient instance from the TUI app.
101
+ """
102
+ self._client = client
103
+ self._resolver = InjectionResolver(client=client)
104
+
105
+ # Autocomplete caches
106
+ self._fragment_cache: list[FragmentInfo] = []
107
+ self._content_cache: list[ContentPartInfo] = []
108
+ self._schema_cache: list[SchemaInfo] = []
109
+ self._cache_expiry: float = 0
110
+
111
+ @property
112
+ def is_cache_valid(self) -> bool:
113
+ """Check if the autocomplete cache is still valid."""
114
+ return time.time() < self._cache_expiry
115
+
116
+ async def resolve_message(self, message: str) -> ResolvedMessage:
117
+ """Resolve all injection patterns in a message.
118
+
119
+ Args:
120
+ message: User message with potential injection patterns.
121
+
122
+ Returns:
123
+ ResolvedMessage with resolved text and metadata.
124
+ """
125
+ return await self._resolver.resolve(message)
126
+
127
+ async def refresh_cache(self) -> None:
128
+ """Refresh all autocomplete caches from the API.
129
+
130
+ This should be called:
131
+ - On session load/create
132
+ - Periodically in the background
133
+ - When cache expires and user triggers autocomplete
134
+ """
135
+ try:
136
+ # Fetch all data in parallel
137
+ import asyncio
138
+
139
+ results = await asyncio.gather(
140
+ self._fetch_fragments(),
141
+ self._fetch_content(),
142
+ self._fetch_schemas(),
143
+ return_exceptions=True,
144
+ )
145
+
146
+ # Log any errors but don't fail
147
+ for i, result in enumerate(results):
148
+ if isinstance(result, Exception):
149
+ logger.debug(
150
+ "injection_cache_refresh_partial_failure",
151
+ extra={"index": i, "error": str(result)},
152
+ )
153
+
154
+ # Update expiry
155
+ self._cache_expiry = time.time() + CACHE_TTL
156
+
157
+ logger.debug(
158
+ "injection_cache_refreshed",
159
+ extra={
160
+ "fragments": len(self._fragment_cache),
161
+ "content": len(self._content_cache),
162
+ "schemas": len(self._schema_cache),
163
+ },
164
+ )
165
+
166
+ except Exception as e:
167
+ logger.debug(
168
+ "injection_cache_refresh_failed",
169
+ extra={"error": str(e)},
170
+ )
171
+
172
+ async def _fetch_fragments(self) -> None:
173
+ """Fetch fragment templates for autocomplete cache.
174
+
175
+ Only fetches templates with content_type='fragment'.
176
+ """
177
+ from alloy_runtime_types.enums.template_enums import TemplateContentType
178
+
179
+ try:
180
+ response = await self._client.list_templates(
181
+ limit=100, content_type=TemplateContentType.FRAGMENT
182
+ )
183
+ self._fragment_cache = [
184
+ FragmentInfo(
185
+ id=str(t.id),
186
+ name=t.name,
187
+ description=t.description,
188
+ content_type_id=t.content_type_id,
189
+ )
190
+ for t in response.templates
191
+ ]
192
+ except Exception as e:
193
+ logger.debug("Failed to fetch fragments: %s", e)
194
+ raise
195
+
196
+ async def _fetch_content(self) -> None:
197
+ """Fetch content parts for autocomplete cache."""
198
+ try:
199
+ response = await self._client.list_content_parts(limit=100)
200
+ self._content_cache = [
201
+ ContentPartInfo(
202
+ id=str(p.id),
203
+ content_type_id=p.content_type_id,
204
+ content_text=p.content_text,
205
+ has_structured=p.content_structured is not None,
206
+ )
207
+ for p in response.content_parts
208
+ ]
209
+ except Exception as e:
210
+ logger.debug("Failed to fetch content parts: %s", e)
211
+ raise
212
+
213
+ async def _fetch_schemas(self) -> None:
214
+ """Fetch schemas for autocomplete cache."""
215
+ try:
216
+ response = await self._client.list_schemas(limit=100)
217
+ self._schema_cache = [
218
+ SchemaInfo(
219
+ id=str(s.id),
220
+ schema_name=s.schema_name,
221
+ version=str(s.version),
222
+ schema_format_id=s.schema_format_id,
223
+ description=s.description,
224
+ )
225
+ for s in response.schemas
226
+ ]
227
+ except Exception as e:
228
+ logger.debug("Failed to fetch schemas: %s", e)
229
+ raise
230
+
231
+ def get_completions(
232
+ self,
233
+ injection_type: str,
234
+ partial_identifier: str = "",
235
+ limit: int = 10,
236
+ ) -> list[InjectionCompletion]:
237
+ """Get autocomplete suggestions for a partial injection.
238
+
239
+ Args:
240
+ injection_type: Type of injection ("fragment", "text", "json", "schema").
241
+ partial_identifier: Partial identifier typed by user.
242
+ limit: Maximum number of suggestions to return.
243
+
244
+ Returns:
245
+ List of InjectionCompletion objects.
246
+ """
247
+ partial_lower = partial_identifier.lower()
248
+
249
+ if injection_type == "fragment":
250
+ return self._get_fragment_completions(partial_lower, limit)
251
+ elif injection_type in ("text", "json"):
252
+ return self._get_content_completions(
253
+ partial_lower, injection_type == "json", limit
254
+ )
255
+ elif injection_type == "schema":
256
+ return self._get_schema_completions(partial_lower, limit)
257
+ else:
258
+ return []
259
+
260
+ def _get_fragment_completions(
261
+ self, partial: str, limit: int
262
+ ) -> list[InjectionCompletion]:
263
+ """Get fragment completions matching the partial identifier."""
264
+ completions: list[InjectionCompletion] = []
265
+
266
+ for fragment in self._fragment_cache:
267
+ name_lower = fragment.name.lower()
268
+ desc_lower = (fragment.description or "").lower()
269
+
270
+ if partial in name_lower or partial in desc_lower:
271
+ # Build the insertion text (no quotes, no variables)
272
+ text = f"@fragment({fragment.name})"
273
+
274
+ # Format description
275
+ description = fragment.description or "no description"
276
+ if len(description) > 30:
277
+ description = description[:27] + "..."
278
+
279
+ completions.append(
280
+ InjectionCompletion(
281
+ text=text,
282
+ display_name=fragment.name,
283
+ description=description,
284
+ injection_type="fragment",
285
+ )
286
+ )
287
+
288
+ if len(completions) >= limit:
289
+ break
290
+
291
+ return completions
292
+
293
+ def _get_content_completions(
294
+ self, partial: str, is_json: bool, limit: int
295
+ ) -> list[InjectionCompletion]:
296
+ """Get content completions matching the partial identifier."""
297
+ completions: list[InjectionCompletion] = []
298
+
299
+ for content in self._content_cache:
300
+ # Match on UUID or content text preview
301
+ id_lower = content.id.lower()
302
+ text_lower = (content.content_text or "").lower()
303
+
304
+ if partial in id_lower or partial in text_lower:
305
+ # Build the insertion text (no quotes)
306
+ if is_json:
307
+ text = f"@json({content.id})"
308
+ format_label = "JSON"
309
+ else:
310
+ text = f"@text({content.id})"
311
+ format_label = "text"
312
+
313
+ # Create preview from content text
314
+ if content.content_text:
315
+ preview = content.content_text.replace("\n", " ").strip()[:30]
316
+ if len(content.content_text) > 30:
317
+ preview += "..."
318
+ else:
319
+ preview = "{structured}" if content.has_structured else "{empty}"
320
+
321
+ description = f"{content.content_type_id} | {format_label}"
322
+
323
+ completions.append(
324
+ InjectionCompletion(
325
+ text=text,
326
+ display_name=preview,
327
+ description=description,
328
+ injection_type="json" if is_json else "text",
329
+ )
330
+ )
331
+
332
+ if len(completions) >= limit:
333
+ break
334
+
335
+ return completions
336
+
337
+ def _get_schema_completions(
338
+ self, partial: str, limit: int
339
+ ) -> list[InjectionCompletion]:
340
+ """Get schema completions matching the partial identifier."""
341
+ completions: list[InjectionCompletion] = []
342
+
343
+ for schema in self._schema_cache:
344
+ name_lower = schema.schema_name.lower()
345
+ desc_lower = (schema.description or "").lower()
346
+
347
+ if partial in name_lower or partial in desc_lower:
348
+ # Build insertion text (no quotes)
349
+ text = f"@schema({schema.schema_name})"
350
+
351
+ # Format description
352
+ desc_preview = (
353
+ schema.description[:20] + "..."
354
+ if schema.description and len(schema.description) > 20
355
+ else schema.description or "no description"
356
+ )
357
+ description = f"{schema.schema_format_id} | {desc_preview}"
358
+
359
+ completions.append(
360
+ InjectionCompletion(
361
+ text=text,
362
+ display_name=schema.schema_name,
363
+ description=description,
364
+ injection_type="schema",
365
+ )
366
+ )
367
+
368
+ if len(completions) >= limit:
369
+ break
370
+
371
+ return completions
372
+
373
+ @property
374
+ def fragments(self) -> list[FragmentInfo]:
375
+ """Get cached fragments."""
376
+ return self._fragment_cache
377
+
378
+ @property
379
+ def content_parts(self) -> list[ContentPartInfo]:
380
+ """Get cached content parts."""
381
+ return self._content_cache
382
+
383
+ @property
384
+ def schemas(self) -> list[SchemaInfo]:
385
+ """Get cached schemas."""
386
+ return self._schema_cache
@@ -0,0 +1,256 @@
1
+ """Session name generation service.
2
+
3
+ Automatically generates meaningful session names using a fast, cheap model
4
+ based on the first user message in a chat session.
5
+ """
6
+
7
+ from alloy_runtime_sdk.api_client.client import ApiClient
8
+ from alloy_runtime_types.dtos.generation import DirectModelSource, GenerateTextRequest
9
+ from alloy_runtime_types.enums.provider import Provider
10
+ from alloy_runtime_sdk.logging.config import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ # Fallback models to try for name generation (fast, cheap models)
15
+ # These are ordered by preference - will try each in sequence until one works
16
+ # Note: Model names must match what's configured in the system
17
+ FALLBACK_MODELS: list[tuple[str, str]] = [
18
+ ("google", "gemini-2.0-flash-lite"),
19
+ ("google", "gemini-1.5-flash"),
20
+ ("google", "gemini-2.0-flash"),
21
+ ("openai", "gpt-4o-mini"),
22
+ ("openai", "gpt-3.5-turbo"),
23
+ ("anthropic", "claude-3-5-haiku-latest"),
24
+ ("anthropic", "claude-3-haiku-20240307"),
25
+ ("groq", "llama-3.1-8b-instant"),
26
+ ("groq", "llama3-8b-8192"),
27
+ ("deepseek", "deepseek-chat"),
28
+ ]
29
+
30
+ # Prompt for generating session names
31
+ NAME_GENERATION_PROMPT = """Generate a short, descriptive title (3-6 words) for a chat conversation based on the user's first message. The title should capture the main topic or intent.
32
+
33
+ Rules:
34
+ - Return ONLY the title, nothing else
35
+ - No quotes, punctuation at the end, or explanations
36
+ - Use title case
37
+ - Be specific but concise
38
+ - If the message is a greeting or very generic, create a generic but friendly title
39
+
40
+ Examples:
41
+ - "Help me write a Python script" -> "Python Script Assistance"
42
+ - "What's the weather like?" -> "Weather Information Request"
43
+ - "Hello!" -> "New Conversation"
44
+ - "Debug this React component" -> "React Component Debugging"
45
+
46
+ User's first message:
47
+ {message}
48
+
49
+ Title:"""
50
+
51
+
52
+ class SessionNameGenerator:
53
+ """Generates session names using a fast, cheap model.
54
+
55
+ Tries multiple fallback models to ensure name generation works even if
56
+ some providers are not configured.
57
+
58
+ Usage:
59
+ generator = SessionNameGenerator()
60
+ name = await generator.generate_name(client, "Help me write a function")
61
+ # Returns something like "Function Writing Help"
62
+ """
63
+
64
+ # Cheap models that are OK to use for naming (won't cost much)
65
+ _CHEAP_MODEL_PATTERNS = [
66
+ "flash",
67
+ "mini",
68
+ "nano",
69
+ "haiku",
70
+ "instant",
71
+ "3.5-turbo",
72
+ "8b",
73
+ "7b",
74
+ ]
75
+
76
+ def __init__(
77
+ self,
78
+ fallback_models: list[tuple[str, str]] | None = None,
79
+ max_message_length: int = 500,
80
+ ) -> None:
81
+ """Initialize the session name generator.
82
+
83
+ Args:
84
+ fallback_models: List of (provider_key, model_name) tuples to try.
85
+ Defaults to FALLBACK_MODELS.
86
+ max_message_length: Maximum length of user message to include in prompt.
87
+ """
88
+ self._fallback_models = fallback_models or FALLBACK_MODELS
89
+ self._max_message_length = max_message_length
90
+
91
+ def _is_cheap_model(self, model_name: str) -> bool:
92
+ """Check if a model is considered cheap enough for naming tasks."""
93
+ model_lower = model_name.lower()
94
+ return any(pattern in model_lower for pattern in self._CHEAP_MODEL_PATTERNS)
95
+
96
+ async def generate_name(
97
+ self,
98
+ client: ApiClient,
99
+ first_message: str,
100
+ session_provider_key: str | None = None,
101
+ session_model_name: str | None = None,
102
+ ) -> str | None:
103
+ """Generate a session name based on the first message.
104
+
105
+ Tries each fallback model in sequence until one succeeds.
106
+ If a session model is provided and it's cheap, tries that first.
107
+
108
+ Args:
109
+ client: ApiClient for making API calls.
110
+ first_message: The user's first message in the session.
111
+ session_provider_key: Optional provider key from the session (tried first if cheap).
112
+ session_model_name: Optional model name from the session (tried first if cheap).
113
+
114
+ Returns:
115
+ Generated session name (3-6 words), or None if all models fail.
116
+ """
117
+ logger.info(
118
+ "session_name_generator_starting",
119
+ first_message_length=len(first_message),
120
+ session_provider_key=session_provider_key,
121
+ session_model_name=session_model_name,
122
+ )
123
+
124
+ # Truncate message if too long
125
+ truncated_message = first_message[: self._max_message_length]
126
+ if len(first_message) > self._max_message_length:
127
+ truncated_message += "..."
128
+
129
+ prompt = NAME_GENERATION_PROMPT.format(message=truncated_message)
130
+
131
+ # Build list of models to try
132
+ models_to_try: list[tuple[str, str]] = []
133
+
134
+ # If session has a cheap model, try it first (it's likely configured)
135
+ if (
136
+ session_provider_key
137
+ and session_model_name
138
+ and self._is_cheap_model(session_model_name)
139
+ ):
140
+ models_to_try.append((session_provider_key, session_model_name))
141
+ logger.info(
142
+ "session_name_using_session_model_first",
143
+ provider=session_provider_key,
144
+ model=session_model_name,
145
+ )
146
+ else:
147
+ logger.info(
148
+ "session_name_not_using_session_model",
149
+ session_provider_key=session_provider_key,
150
+ session_model_name=session_model_name,
151
+ is_cheap=self._is_cheap_model(session_model_name)
152
+ if session_model_name
153
+ else None,
154
+ )
155
+
156
+ # Add fallback models
157
+ models_to_try.extend(self._fallback_models)
158
+ logger.info(
159
+ "session_name_models_to_try",
160
+ total_models=len(models_to_try),
161
+ fallback_models=len(self._fallback_models),
162
+ )
163
+
164
+ for idx, (provider_key, model_name) in enumerate(models_to_try):
165
+ try:
166
+ logger.info(
167
+ "session_name_generation_attempt",
168
+ attempt_number=idx + 1,
169
+ provider=provider_key,
170
+ model=model_name,
171
+ )
172
+
173
+ model_source = DirectModelSource(
174
+ provider_key=Provider(provider_key),
175
+ provider_model_name=model_name,
176
+ )
177
+
178
+ request = GenerateTextRequest(
179
+ model_source=model_source,
180
+ user_message=prompt,
181
+ stream=False,
182
+ tags=["system.session_naming"],
183
+ )
184
+
185
+ response = await client.generate_text(request)
186
+ logger.info(
187
+ "session_name_generation_response",
188
+ provider=provider_key,
189
+ model=model_name,
190
+ raw_output=response.output_text[:100]
191
+ if response.output_text
192
+ else None,
193
+ )
194
+
195
+ generated_name = self._clean_name(response.output_text)
196
+
197
+ if generated_name:
198
+ logger.info(
199
+ "session_name_generated_success",
200
+ provider=provider_key,
201
+ model=model_name,
202
+ generated_name=generated_name,
203
+ )
204
+ return generated_name
205
+ else:
206
+ logger.warning(
207
+ "session_name_cleaned_to_empty",
208
+ provider=provider_key,
209
+ model=model_name,
210
+ raw_output=response.output_text,
211
+ )
212
+
213
+ except Exception as e:
214
+ logger.info(
215
+ "session_name_generation_model_failed",
216
+ attempt_number=idx + 1,
217
+ provider=provider_key,
218
+ model=model_name,
219
+ error=str(e),
220
+ error_type=type(e).__name__,
221
+ )
222
+ continue
223
+
224
+ logger.warning(
225
+ "session_name_generation_all_models_failed",
226
+ models_tried=len(models_to_try),
227
+ )
228
+ return None
229
+
230
+ def _clean_name(self, raw_name: str) -> str | None:
231
+ """Clean and validate the generated name.
232
+
233
+ Args:
234
+ raw_name: Raw output from the model.
235
+
236
+ Returns:
237
+ Cleaned name (max 100 chars), or None if invalid.
238
+ """
239
+ # Strip whitespace and quotes
240
+ name = raw_name.strip().strip("\"'")
241
+
242
+ # Remove common prefixes the model might add
243
+ prefixes_to_remove = ["Title:", "title:", "Name:", "name:"]
244
+ for prefix in prefixes_to_remove:
245
+ if name.startswith(prefix):
246
+ name = name[len(prefix) :].strip()
247
+
248
+ # Validate length
249
+ if not name or len(name) < 2:
250
+ return None
251
+
252
+ # Truncate if too long
253
+ if len(name) > 100:
254
+ name = name[:97] + "..."
255
+
256
+ return name