keboola-cli 0.63.4__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 (306) hide show
  1. keboola_agent_cli/__init__.py +34 -0
  2. keboola_agent_cli/__main__.py +5 -0
  3. keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js +2 -0
  4. keboola_agent_cli/_ui_dist/assets/arc-DhFYIddx.js.map +1 -0
  5. keboola_agent_cli/_ui_dist/assets/architecture-7EHR7CIX-hNCijx_H.js +1 -0
  6. keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js +37 -0
  7. keboola_agent_cli/_ui_dist/assets/architectureDiagram-3BPJPVTR-C6hUlprM.js.map +1 -0
  8. keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js +2 -0
  9. keboola_agent_cli/_ui_dist/assets/array-BifhSqXX.js.map +1 -0
  10. keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js +133 -0
  11. keboola_agent_cli/_ui_dist/assets/blockDiagram-GPEHLZMM-DC7qY9i4.js.map +1 -0
  12. keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js +11 -0
  13. keboola_agent_cli/_ui_dist/assets/c4Diagram-AAUBKEIU-5Lh44evt.js.map +1 -0
  14. keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js +2 -0
  15. keboola_agent_cli/_ui_dist/assets/channel-DBMrXlxx.js.map +1 -0
  16. keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js +2 -0
  17. keboola_agent_cli/_ui_dist/assets/chunk-2J33WTMH-Coy82EBh.js.map +1 -0
  18. keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js +63 -0
  19. keboola_agent_cli/_ui_dist/assets/chunk-3OPIFGDE-BQC5CRHI.js.map +1 -0
  20. keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js +2 -0
  21. keboola_agent_cli/_ui_dist/assets/chunk-4BX2VUAB-DUuEt70o.js.map +1 -0
  22. keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js +2 -0
  23. keboola_agent_cli/_ui_dist/assets/chunk-55IACEB6-BvR-6chF.js.map +1 -0
  24. keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js +3 -0
  25. keboola_agent_cli/_ui_dist/assets/chunk-5ZQYHXKU-BjcTN7ul.js.map +1 -0
  26. keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js +207 -0
  27. keboola_agent_cli/_ui_dist/assets/chunk-727SXJPM-C0zxqqRN.js.map +1 -0
  28. keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js +232 -0
  29. keboola_agent_cli/_ui_dist/assets/chunk-AQP2D5EJ-CXf7rIlZ.js.map +1 -0
  30. keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js +2 -0
  31. keboola_agent_cli/_ui_dist/assets/chunk-BSJP7CBP-Oj_FO9Q7.js.map +1 -0
  32. keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js +124 -0
  33. keboola_agent_cli/_ui_dist/assets/chunk-CSCIHK7Q-CcTsLrFc.js.map +1 -0
  34. keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js +16 -0
  35. keboola_agent_cli/_ui_dist/assets/chunk-FMBD7UC4-FH-zLkkW.js.map +1 -0
  36. keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js +2 -0
  37. keboola_agent_cli/_ui_dist/assets/chunk-L5ZTLDWV-B1Ky_e7O.js.map +1 -0
  38. keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js +2 -0
  39. keboola_agent_cli/_ui_dist/assets/chunk-ND2GUHAM-BHz1rpbm.js.map +1 -0
  40. keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js +160 -0
  41. keboola_agent_cli/_ui_dist/assets/chunk-NNHCCRGN-DlpIbxXb.js.map +1 -0
  42. keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js +2 -0
  43. keboola_agent_cli/_ui_dist/assets/chunk-NZK2D7GU-tnrSoegS.js.map +1 -0
  44. keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js +71 -0
  45. keboola_agent_cli/_ui_dist/assets/chunk-O5CBEL6O-DxxqDH0l.js.map +1 -0
  46. keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js +2 -0
  47. keboola_agent_cli/_ui_dist/assets/chunk-QZHKN3VN-CSjc2gjj.js.map +1 -0
  48. keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js +2 -0
  49. keboola_agent_cli/_ui_dist/assets/classDiagram-4FO5ZUOK-BuZcZu85.js.map +1 -0
  50. keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js +2 -0
  51. keboola_agent_cli/_ui_dist/assets/classDiagram-v2-Q7XG4LA2-BuZcZu85.js.map +1 -0
  52. keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js +2 -0
  53. keboola_agent_cli/_ui_dist/assets/cose-bilkent-S5V4N54A-Y0L8LDMa.js.map +1 -0
  54. keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js +322 -0
  55. keboola_agent_cli/_ui_dist/assets/cytoscape.esm-C8YCVR3_.js.map +1 -0
  56. keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js +5 -0
  57. keboola_agent_cli/_ui_dist/assets/dagre-BM42HDAG-UZ-9BTqF.js.map +1 -0
  58. keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js +2 -0
  59. keboola_agent_cli/_ui_dist/assets/dagre-Bx709z4p.js.map +1 -0
  60. keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js +2 -0
  61. keboola_agent_cli/_ui_dist/assets/defaultLocale-C8Fc0cco.js.map +1 -0
  62. keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js +44 -0
  63. keboola_agent_cli/_ui_dist/assets/diagram-2AECGRRQ-DoDQ60wi.js.map +1 -0
  64. keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js +11 -0
  65. keboola_agent_cli/_ui_dist/assets/diagram-5GNKFQAL-CMGFxpUs.js.map +1 -0
  66. keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js +4 -0
  67. keboola_agent_cli/_ui_dist/assets/diagram-KO2AKTUF-1uGDa-Iu.js.map +1 -0
  68. keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js +25 -0
  69. keboola_agent_cli/_ui_dist/assets/diagram-LMA3HP47-XtFH7B51.js.map +1 -0
  70. keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js +25 -0
  71. keboola_agent_cli/_ui_dist/assets/diagram-OG6HWLK6-B4_Te1T5.js.map +1 -0
  72. keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js +2 -0
  73. keboola_agent_cli/_ui_dist/assets/dist-Di6zmlv0.js.map +1 -0
  74. keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js +86 -0
  75. keboola_agent_cli/_ui_dist/assets/erDiagram-TEJ5UH35-NjQkrdFt.js.map +1 -0
  76. keboola_agent_cli/_ui_dist/assets/eventmodeling-FCH6USID-BrJMIks8.js +1 -0
  77. keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js +163 -0
  78. keboola_agent_cli/_ui_dist/assets/flowDiagram-I6XJVG4X-CIr8DWl7.js.map +1 -0
  79. keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js +293 -0
  80. keboola_agent_cli/_ui_dist/assets/ganttDiagram-6RSMTGT7-C1VY_xbQ.js.map +1 -0
  81. keboola_agent_cli/_ui_dist/assets/gitGraph-WXDBUCRP-COacYjo-.js +1 -0
  82. keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js +107 -0
  83. keboola_agent_cli/_ui_dist/assets/gitGraphDiagram-PVQCEYII-DQT8-kg2.js.map +1 -0
  84. keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js +2 -0
  85. keboola_agent_cli/_ui_dist/assets/graphlib-B8gBHxth.js.map +1 -0
  86. keboola_agent_cli/_ui_dist/assets/index-CMq50kkV.css +1 -0
  87. keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js +118 -0
  88. keboola_agent_cli/_ui_dist/assets/index-D8W97DAz.js.map +1 -0
  89. keboola_agent_cli/_ui_dist/assets/info-J43DQDTF-DdCTRIzU.js +1 -0
  90. keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js +3 -0
  91. keboola_agent_cli/_ui_dist/assets/infoDiagram-5YYISTIA-C77rsoTp.js.map +1 -0
  92. keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js +2 -0
  93. keboola_agent_cli/_ui_dist/assets/init-D6jRqBbL.js.map +1 -0
  94. keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js +71 -0
  95. keboola_agent_cli/_ui_dist/assets/ishikawaDiagram-YF4QCWOH-BcTbXaLy.js.map +1 -0
  96. keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js +140 -0
  97. keboola_agent_cli/_ui_dist/assets/journeyDiagram-JHISSGLW-BejeAJQ_.js.map +1 -0
  98. keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js +90 -0
  99. keboola_agent_cli/_ui_dist/assets/kanban-definition-UN3LZRKU-BRNz_UrH.js.map +1 -0
  100. keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js +258 -0
  101. keboola_agent_cli/_ui_dist/assets/katex-C4eR7coU.js.map +1 -0
  102. keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js +2 -0
  103. keboola_agent_cli/_ui_dist/assets/line-CzAQKFbJ.js.map +1 -0
  104. keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js +2 -0
  105. keboola_agent_cli/_ui_dist/assets/linear-DUNFFdck.js.map +1 -0
  106. keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js +5 -0
  107. keboola_agent_cli/_ui_dist/assets/mermaid-parser.core-CpuBOkFa.js.map +1 -0
  108. keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js +97 -0
  109. keboola_agent_cli/_ui_dist/assets/mindmap-definition-RKZ34NQL-9EJQNjH0.js.map +1 -0
  110. keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js +2 -0
  111. keboola_agent_cli/_ui_dist/assets/ordinal-hYBb2elL.js.map +1 -0
  112. keboola_agent_cli/_ui_dist/assets/packet-YPE3B663-DLiiw_B2.js +1 -0
  113. keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js +2 -0
  114. keboola_agent_cli/_ui_dist/assets/path-BWPyau1x.js.map +1 -0
  115. keboola_agent_cli/_ui_dist/assets/pie-LRSECV5Y-CRoO8G1g.js +1 -0
  116. keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js +31 -0
  117. keboola_agent_cli/_ui_dist/assets/pieDiagram-4H26LBE5-XH4cy6Cb.js.map +1 -0
  118. keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js +8 -0
  119. keboola_agent_cli/_ui_dist/assets/quadrantDiagram-W4KKPZXB-fdhc93U8.js.map +1 -0
  120. keboola_agent_cli/_ui_dist/assets/radar-GUYGQ44K-DAlLVJHm.js +1 -0
  121. keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js +85 -0
  122. keboola_agent_cli/_ui_dist/assets/requirementDiagram-4Y6WPE33-a94eP3R9.js.map +1 -0
  123. keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js +2 -0
  124. keboola_agent_cli/_ui_dist/assets/rough.esm-CSKSodPl.js.map +1 -0
  125. keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js +41 -0
  126. keboola_agent_cli/_ui_dist/assets/sankeyDiagram-5OEKKPKP-jcBa02sp.js.map +1 -0
  127. keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js +163 -0
  128. keboola_agent_cli/_ui_dist/assets/sequenceDiagram-3UESZ5HK-A5-GGM-e.js.map +1 -0
  129. keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js +2 -0
  130. keboola_agent_cli/_ui_dist/assets/src-ZI-V_AF0.js.map +1 -0
  131. keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js +2 -0
  132. keboola_agent_cli/_ui_dist/assets/stateDiagram-AJRCARHV-BKAA5rqE.js.map +1 -0
  133. keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js +2 -0
  134. keboola_agent_cli/_ui_dist/assets/stateDiagram-v2-BHNVJYJU-DnJwJBsE.js.map +1 -0
  135. keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js +121 -0
  136. keboola_agent_cli/_ui_dist/assets/timeline-definition-PNZ67QCA-Cy39jp8b.js.map +1 -0
  137. keboola_agent_cli/_ui_dist/assets/treeView-BLDUP644-DbLYl23-.js +1 -0
  138. keboola_agent_cli/_ui_dist/assets/treemap-LRROVOQU-Bp0eGlOt.js +1 -0
  139. keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js +35 -0
  140. keboola_agent_cli/_ui_dist/assets/vennDiagram-CIIHVFJN-BGECKubd.js.map +1 -0
  141. keboola_agent_cli/_ui_dist/assets/wardley-L42UT6IY-D4yH4jqS.js +1 -0
  142. keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js +79 -0
  143. keboola_agent_cli/_ui_dist/assets/wardleyDiagram-YWT4CUSO-D6XRG3cZ.js.map +1 -0
  144. keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js +8 -0
  145. keboola_agent_cli/_ui_dist/assets/xychartDiagram-2RQKCTM6-DRre-pfZ.js.map +1 -0
  146. keboola_agent_cli/_ui_dist/index.html +50 -0
  147. keboola_agent_cli/ai_client.py +83 -0
  148. keboola_agent_cli/auto_update.py +550 -0
  149. keboola_agent_cli/changelog.py +1198 -0
  150. keboola_agent_cli/cli.py +448 -0
  151. keboola_agent_cli/client.py +3422 -0
  152. keboola_agent_cli/commands/__init__.py +0 -0
  153. keboola_agent_cli/commands/_data_app_git.py +343 -0
  154. keboola_agent_cli/commands/_helpers.py +377 -0
  155. keboola_agent_cli/commands/_metadata_input.py +49 -0
  156. keboola_agent_cli/commands/_semantic_layer_crud.py +632 -0
  157. keboola_agent_cli/commands/_semantic_layer_helpers.py +44 -0
  158. keboola_agent_cli/commands/_semantic_layer_reference_data.py +247 -0
  159. keboola_agent_cli/commands/agent.py +968 -0
  160. keboola_agent_cli/commands/branch.py +423 -0
  161. keboola_agent_cli/commands/changelog.py +168 -0
  162. keboola_agent_cli/commands/component.py +216 -0
  163. keboola_agent_cli/commands/config.py +2442 -0
  164. keboola_agent_cli/commands/context.py +1481 -0
  165. keboola_agent_cli/commands/data_app.py +1279 -0
  166. keboola_agent_cli/commands/dev_portal.py +584 -0
  167. keboola_agent_cli/commands/doctor.py +37 -0
  168. keboola_agent_cli/commands/encrypt.py +145 -0
  169. keboola_agent_cli/commands/feature.py +311 -0
  170. keboola_agent_cli/commands/flow.py +948 -0
  171. keboola_agent_cli/commands/http_client.py +157 -0
  172. keboola_agent_cli/commands/init.py +279 -0
  173. keboola_agent_cli/commands/job.py +661 -0
  174. keboola_agent_cli/commands/kai.py +301 -0
  175. keboola_agent_cli/commands/lineage.py +1464 -0
  176. keboola_agent_cli/commands/org.py +292 -0
  177. keboola_agent_cli/commands/permissions.py +360 -0
  178. keboola_agent_cli/commands/project.py +1192 -0
  179. keboola_agent_cli/commands/repl.py +243 -0
  180. keboola_agent_cli/commands/schedule.py +340 -0
  181. keboola_agent_cli/commands/search.py +178 -0
  182. keboola_agent_cli/commands/semantic_layer.py +939 -0
  183. keboola_agent_cli/commands/serve.py +272 -0
  184. keboola_agent_cli/commands/sharing.py +340 -0
  185. keboola_agent_cli/commands/storage.py +2630 -0
  186. keboola_agent_cli/commands/stream.py +266 -0
  187. keboola_agent_cli/commands/sync.py +1277 -0
  188. keboola_agent_cli/commands/tool.py +206 -0
  189. keboola_agent_cli/commands/version.py +186 -0
  190. keboola_agent_cli/commands/workspace.py +635 -0
  191. keboola_agent_cli/config_store.py +582 -0
  192. keboola_agent_cli/constants.py +528 -0
  193. keboola_agent_cli/data_science_client.py +342 -0
  194. keboola_agent_cli/dev_portal_client.py +323 -0
  195. keboola_agent_cli/errors.py +248 -0
  196. keboola_agent_cli/http_base.py +315 -0
  197. keboola_agent_cli/json_utils.py +126 -0
  198. keboola_agent_cli/lib.py +536 -0
  199. keboola_agent_cli/manage_client.py +324 -0
  200. keboola_agent_cli/metastore_client.py +214 -0
  201. keboola_agent_cli/models.py +427 -0
  202. keboola_agent_cli/output.py +1084 -0
  203. keboola_agent_cli/permissions.py +469 -0
  204. keboola_agent_cli/py.typed +3 -0
  205. keboola_agent_cli/result_models.py +271 -0
  206. keboola_agent_cli/server/__init__.py +34 -0
  207. keboola_agent_cli/server/agent_runner.py +1289 -0
  208. keboola_agent_cli/server/agents_store.py +325 -0
  209. keboola_agent_cli/server/app.py +764 -0
  210. keboola_agent_cli/server/auth.py +117 -0
  211. keboola_agent_cli/server/dependencies.py +149 -0
  212. keboola_agent_cli/server/pricing.py +303 -0
  213. keboola_agent_cli/server/routers/__init__.py +1 -0
  214. keboola_agent_cli/server/routers/agents.py +616 -0
  215. keboola_agent_cli/server/routers/ai_chat.py +129 -0
  216. keboola_agent_cli/server/routers/branches.py +133 -0
  217. keboola_agent_cli/server/routers/components.py +48 -0
  218. keboola_agent_cli/server/routers/configs.py +507 -0
  219. keboola_agent_cli/server/routers/data_apps.py +384 -0
  220. keboola_agent_cli/server/routers/dev_portal.py +67 -0
  221. keboola_agent_cli/server/routers/encrypt.py +35 -0
  222. keboola_agent_cli/server/routers/feature.py +179 -0
  223. keboola_agent_cli/server/routers/flows.py +204 -0
  224. keboola_agent_cli/server/routers/health.py +53 -0
  225. keboola_agent_cli/server/routers/jobs.py +175 -0
  226. keboola_agent_cli/server/routers/kai.py +80 -0
  227. keboola_agent_cli/server/routers/lineage.py +226 -0
  228. keboola_agent_cli/server/routers/mcp.py +70 -0
  229. keboola_agent_cli/server/routers/members.py +170 -0
  230. keboola_agent_cli/server/routers/org.py +96 -0
  231. keboola_agent_cli/server/routers/projects.py +106 -0
  232. keboola_agent_cli/server/routers/schedules.py +54 -0
  233. keboola_agent_cli/server/routers/search.py +30 -0
  234. keboola_agent_cli/server/routers/semantic_layer.py +650 -0
  235. keboola_agent_cli/server/routers/sharing.py +86 -0
  236. keboola_agent_cli/server/routers/storage.py +574 -0
  237. keboola_agent_cli/server/routers/stream.py +100 -0
  238. keboola_agent_cli/server/routers/workspaces.py +302 -0
  239. keboola_agent_cli/server/run_broadcaster.py +329 -0
  240. keboola_agent_cli/server/sse.py +25 -0
  241. keboola_agent_cli/services/__init__.py +0 -0
  242. keboola_agent_cli/services/_encryption.py +217 -0
  243. keboola_agent_cli/services/_semantic_layer_cascade.py +147 -0
  244. keboola_agent_cli/services/_semantic_layer_crud.py +382 -0
  245. keboola_agent_cli/services/_semantic_layer_internals.py +1078 -0
  246. keboola_agent_cli/services/_semantic_layer_lookup.py +181 -0
  247. keboola_agent_cli/services/_semantic_layer_reference_data.py +217 -0
  248. keboola_agent_cli/services/_sync_bindings.py +456 -0
  249. keboola_agent_cli/services/_sync_branch.py +191 -0
  250. keboola_agent_cli/services/_sync_bulk.py +228 -0
  251. keboola_agent_cli/services/_sync_clone.py +163 -0
  252. keboola_agent_cli/services/_sync_models.py +97 -0
  253. keboola_agent_cli/services/_sync_push_ops.py +369 -0
  254. keboola_agent_cli/services/_sync_storage.py +376 -0
  255. keboola_agent_cli/services/_sync_writeback.py +167 -0
  256. keboola_agent_cli/services/agent_service.py +458 -0
  257. keboola_agent_cli/services/base.py +175 -0
  258. keboola_agent_cli/services/branch_service.py +588 -0
  259. keboola_agent_cli/services/component_service.py +694 -0
  260. keboola_agent_cli/services/config_service.py +2099 -0
  261. keboola_agent_cli/services/data_app_git_service.py +224 -0
  262. keboola_agent_cli/services/data_app_service.py +2082 -0
  263. keboola_agent_cli/services/deep_lineage_service.py +1322 -0
  264. keboola_agent_cli/services/dev_portal_service.py +345 -0
  265. keboola_agent_cli/services/doctor_service.py +445 -0
  266. keboola_agent_cli/services/encrypt_service.py +87 -0
  267. keboola_agent_cli/services/feature_service.py +268 -0
  268. keboola_agent_cli/services/flow_service.py +769 -0
  269. keboola_agent_cli/services/flow_validation.py +188 -0
  270. keboola_agent_cli/services/http_forwarder_service.py +236 -0
  271. keboola_agent_cli/services/job_idempotency_store.py +285 -0
  272. keboola_agent_cli/services/job_service.py +797 -0
  273. keboola_agent_cli/services/kai_service.py +367 -0
  274. keboola_agent_cli/services/lineage_service.py +274 -0
  275. keboola_agent_cli/services/mcp_service.py +1498 -0
  276. keboola_agent_cli/services/mcp_transport.py +259 -0
  277. keboola_agent_cli/services/member_service.py +593 -0
  278. keboola_agent_cli/services/org_service.py +619 -0
  279. keboola_agent_cli/services/project_service.py +947 -0
  280. keboola_agent_cli/services/repo_validate_service.py +767 -0
  281. keboola_agent_cli/services/schedule_service.py +731 -0
  282. keboola_agent_cli/services/search_service.py +331 -0
  283. keboola_agent_cli/services/semantic_layer_service.py +1497 -0
  284. keboola_agent_cli/services/sharing_service.py +307 -0
  285. keboola_agent_cli/services/storage_service.py +2524 -0
  286. keboola_agent_cli/services/stream_service.py +395 -0
  287. keboola_agent_cli/services/sync_service.py +2244 -0
  288. keboola_agent_cli/services/variables_service.py +447 -0
  289. keboola_agent_cli/services/version_service.py +1038 -0
  290. keboola_agent_cli/services/workspace_service.py +1103 -0
  291. keboola_agent_cli/stream_client.py +217 -0
  292. keboola_agent_cli/sync/__init__.py +1 -0
  293. keboola_agent_cli/sync/branch_mapping.py +174 -0
  294. keboola_agent_cli/sync/clone.py +211 -0
  295. keboola_agent_cli/sync/code_extraction.py +655 -0
  296. keboola_agent_cli/sync/config_format.py +290 -0
  297. keboola_agent_cli/sync/diff_engine.py +566 -0
  298. keboola_agent_cli/sync/git_utils.py +93 -0
  299. keboola_agent_cli/sync/manifest.py +162 -0
  300. keboola_agent_cli/sync/naming.py +90 -0
  301. keboola_agent_cli/sync/secrets.py +62 -0
  302. keboola_agent_cli/sync/sql_split.py +134 -0
  303. keboola_cli-0.63.4.dist-info/METADATA +308 -0
  304. keboola_cli-0.63.4.dist-info/RECORD +306 -0
  305. keboola_cli-0.63.4.dist-info/WHEEL +4 -0
  306. keboola_cli-0.63.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,616 @@
1
+ """Agent-task scheduling endpoints (CRUD + manual run + history + SSE stream)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import AsyncIterator
7
+ from datetime import UTC
8
+ from typing import Any
9
+
10
+ from fastapi import APIRouter, Depends, HTTPException, Request
11
+ from fastapi.responses import StreamingResponse
12
+ from pydantic import BaseModel
13
+
14
+ from ...constants import AI_PROMPT_HELPER_TIMEOUT
15
+ from ..agent_runner import (
16
+ compute_next_run,
17
+ run_task_once,
18
+ stream_ai_agent_events,
19
+ )
20
+ from ..agents_store import (
21
+ AgentAction,
22
+ AgentRun,
23
+ AgentStore,
24
+ AgentTask,
25
+ Trigger,
26
+ merge_runtime_input,
27
+ validate_trigger,
28
+ )
29
+ from ..dependencies import ServiceRegistry, get_registry
30
+
31
+ router = APIRouter(prefix="/agents", tags=["agents"])
32
+
33
+
34
+ class _NullStore(AgentStore):
35
+ """Drop-in AgentStore for one-off /test runs: all mutations are no-ops.
36
+
37
+ Extends AgentStore so run_task_once's type annotation is satisfied.
38
+ We pass a non-existent sentinel path; the no-op overrides never call
39
+ _ensure_dirs so no filesystem access happens. The path is never touched,
40
+ so the Unix-only ``/dev/null`` form is harmless on Windows too.
41
+ """
42
+
43
+ def __init__(self) -> None:
44
+ from pathlib import Path
45
+
46
+ super().__init__(config_dir=Path("/dev/null/_kbagent_test"))
47
+
48
+ def append_run(self, run: AgentRun) -> None:
49
+ return None
50
+
51
+ def upsert_task(self, task: AgentTask) -> AgentTask:
52
+ return task
53
+
54
+ def get_task(self, task_id: str) -> AgentTask | None:
55
+ return None
56
+
57
+ def append_events(self, task_id: str, run_id: str, events: list[dict[str, Any]]) -> str:
58
+ return f"{task_id}/{run_id}.jsonl"
59
+
60
+
61
+ def _validate_trigger(
62
+ store: Any, trigger: Trigger | None, *, owner_task_id: str | None = None
63
+ ) -> None:
64
+ """HTTP wrapper around :func:`validate_trigger`.
65
+
66
+ The core check (existence + no self-loop) lives in ``agents_store`` so
67
+ the CLI service can reuse it. The router translates ``ValueError`` into
68
+ the 422 response shape the existing tests assert on.
69
+ """
70
+ try:
71
+ validate_trigger(store, trigger, owner_task_id=owner_task_id)
72
+ except ValueError as exc:
73
+ raise HTTPException(status_code=422, detail=str(exc)) from exc
74
+
75
+
76
+ class AgentTaskCreate(BaseModel):
77
+ name: str
78
+ description: str = ""
79
+ cron: str = "0 * * * *"
80
+ manual: bool = False
81
+ enabled: bool = True
82
+ action: AgentAction
83
+ trigger: Trigger | None = None
84
+
85
+
86
+ class AgentTaskUpdate(BaseModel):
87
+ name: str | None = None
88
+ description: str | None = None
89
+ cron: str | None = None
90
+ manual: bool | None = None
91
+ enabled: bool | None = None
92
+ action: AgentAction | None = None
93
+ # Sentinel: pass an explicit JSON ``null`` to clear the trigger; omitting
94
+ # the key (Pydantic default-unset) means "leave as is". FastAPI maps both
95
+ # to ``None`` here, so we rely on ``model_fields_set`` below.
96
+ trigger: Trigger | None = None
97
+
98
+
99
+ def _store(request: Request):
100
+ store = getattr(request.app.state, "agent_store", None)
101
+ if store is None:
102
+ raise HTTPException(
103
+ status_code=503,
104
+ detail="Agent scheduler is not enabled on this server.",
105
+ )
106
+ return store
107
+
108
+
109
+ @router.get("", summary="List scheduled tasks")
110
+ def list_tasks(request: Request) -> dict[str, Any]:
111
+ """All persisted agent tasks (cron-scheduled + manual)."""
112
+ store = _store(request)
113
+ tasks = store.load_tasks()
114
+ return {"tasks": [t.model_dump(mode="json") for t in tasks]}
115
+
116
+
117
+ @router.post("", summary="Create a scheduled task")
118
+ def create_task(body: AgentTaskCreate, request: Request) -> dict[str, Any]:
119
+ """Persist a new task. `manual=true` disables cron firing -- the task
120
+ only runs via `POST /agents/{id}/run` or downstream `trigger` chain.
121
+ """
122
+ store = _store(request)
123
+ _validate_trigger(store, body.trigger)
124
+ task = AgentTask(
125
+ name=body.name,
126
+ description=body.description,
127
+ cron=body.cron,
128
+ manual=body.manual,
129
+ enabled=body.enabled,
130
+ action=body.action,
131
+ trigger=body.trigger,
132
+ # Manual tasks have no future cron firing; skip the croniter call so
133
+ # we don't store a bogus next_run_at the UI would then display.
134
+ next_run_at=None if body.manual else compute_next_run(body.cron),
135
+ )
136
+ saved = store.upsert_task(task)
137
+ return saved.model_dump(mode="json")
138
+
139
+
140
+ @router.get("/{task_id}", summary="Fetch one task")
141
+ def get_task(task_id: str, request: Request) -> dict[str, Any]:
142
+ """Single task by ID (404 if no such task)."""
143
+ store = _store(request)
144
+ task = store.get_task(task_id)
145
+ if task is None:
146
+ raise HTTPException(status_code=404, detail=f"Task '{task_id}' not found")
147
+ return task.model_dump(mode="json")
148
+
149
+
150
+ @router.patch("/{task_id}", summary="Update a task")
151
+ def update_task(task_id: str, body: AgentTaskUpdate, request: Request) -> dict[str, Any]:
152
+ """Partial update. Omitted fields stay unchanged; `trigger: null`
153
+ explicitly clears the chain trigger (Pydantic `model_fields_set` is
154
+ used to distinguish absent vs. explicit-null).
155
+ """
156
+ store = _store(request)
157
+ task = store.get_task(task_id)
158
+ if task is None:
159
+ raise HTTPException(status_code=404, detail=f"Task '{task_id}' not found")
160
+ if body.name is not None:
161
+ task.name = body.name
162
+ if body.description is not None:
163
+ task.description = body.description
164
+ if body.cron is not None:
165
+ task.cron = body.cron
166
+ task.next_run_at = compute_next_run(body.cron)
167
+ if body.manual is not None:
168
+ task.manual = body.manual
169
+ # Switching to manual nulls out next_run_at; switching back recomputes.
170
+ task.next_run_at = None if body.manual else compute_next_run(task.cron)
171
+ if body.enabled is not None:
172
+ task.enabled = body.enabled
173
+ if body.action is not None:
174
+ task.action = body.action
175
+ # ``trigger`` uses model_fields_set so we can distinguish "field absent"
176
+ # (leave alone) from "explicit null" (clear chain). Pydantic v2 exposes
177
+ # this via the model_fields_set attribute on the BaseModel instance.
178
+ if "trigger" in body.model_fields_set:
179
+ _validate_trigger(store, body.trigger, owner_task_id=task.id)
180
+ task.trigger = body.trigger
181
+ store.upsert_task(task)
182
+ return task.model_dump(mode="json")
183
+
184
+
185
+ @router.delete("/{task_id}", summary="Delete a task")
186
+ def delete_task(task_id: str, request: Request) -> dict[str, Any]:
187
+ """Remove a task. Existing run history on disk is left intact (the
188
+ run files live under `runs/{task_id}/` and are out of band for the
189
+ task record itself).
190
+ """
191
+ store = _store(request)
192
+ if not store.delete_task(task_id):
193
+ raise HTTPException(status_code=404, detail=f"Task '{task_id}' not found")
194
+ return {"status": "deleted", "id": task_id}
195
+
196
+
197
+ class RunNowBody(BaseModel):
198
+ """Optional per-run input merged into the task's persisted action params.
199
+
200
+ Used so manual tasks (``manual=True``) can receive ad-hoc runtime input
201
+ — e.g. the AI Data Lab pattern where the persisted prompt says "read the
202
+ user's question from runtime input", and each invocation passes a
203
+ different question. The merge is one-shot (does not mutate the saved
204
+ task); the next cron / chain firing sees only the persisted params.
205
+
206
+ Per-action-type semantics:
207
+ - ``ai_agent``: ``runtime_input.prompt`` (string) is appended to the
208
+ persisted prompt as a labeled section so the AI sees both the
209
+ operator's static instructions and the runtime ask.
210
+ - ``cli_command``: ``runtime_input.argv`` (list of strings) is appended
211
+ to the persisted argv list.
212
+ - ``mcp_tool``: ``runtime_input`` (dict) is shallow-merged into the
213
+ persisted MCP tool input, with runtime keys winning on conflict.
214
+ """
215
+
216
+ runtime_input: dict[str, Any] | None = None
217
+
218
+
219
+ @router.post("/{task_id}/run", summary="Run a task now (blocking)")
220
+ async def run_now(
221
+ task_id: str,
222
+ request: Request,
223
+ registry: ServiceRegistry = Depends(get_registry),
224
+ body: RunNowBody | None = None,
225
+ ) -> dict[str, Any]:
226
+ """Trigger a task immediately (does not wait for the next cron tick).
227
+
228
+ Blocking variant kept for compatibility / scripted callers.
229
+ UI uses ``POST /agents/{task_id}/run/stream`` for live progress + attach.
230
+
231
+ Optional ``runtime_input`` body (typically used by manual tasks) is
232
+ merged into the task's persisted action params for this run only —
233
+ e.g. the operator types an ad-hoc question into the UI, the persisted
234
+ prompt is preserved, and the cron-driven next firing is unaffected.
235
+ """
236
+ store = _store(request)
237
+ task = store.get_task(task_id)
238
+ if task is None:
239
+ raise HTTPException(status_code=404, detail=f"Task '{task_id}' not found")
240
+ runtime_input = body.runtime_input if body is not None else None
241
+ task_for_run = merge_runtime_input(task, runtime_input)
242
+ run = await run_task_once(task_for_run, registry, store)
243
+ return run.model_dump(mode="json")
244
+
245
+
246
+ @router.post("/{task_id}/run/stream", summary="Run a task now (SSE stream)")
247
+ async def run_now_stream(
248
+ task_id: str,
249
+ request: Request,
250
+ registry: ServiceRegistry = Depends(get_registry),
251
+ ) -> StreamingResponse:
252
+ """Run the task with SSE event streaming, supporting late attach.
253
+
254
+ If a run is already in flight for this task (someone else started it),
255
+ we attach: replay the buffered events from the start, then tail live.
256
+ If no run is active, we start one. Kill-on-empty: when every consumer
257
+ disconnects, the runner is cancelled so we don't leak claude subprocesses.
258
+
259
+ The final ``done`` event mirrors the AgentRun record persisted to disk;
260
+ callers don't need to also GET ``/agents/{id}/runs`` to learn the
261
+ outcome (though the persistent record is available there once the run
262
+ finishes).
263
+ """
264
+ store = _store(request)
265
+ task = store.get_task(task_id)
266
+ if task is None:
267
+ raise HTTPException(status_code=404, detail=f"Task '{task_id}' not found")
268
+ broadcaster = getattr(request.app.state, "run_broadcaster", None)
269
+ if broadcaster is None:
270
+ raise HTTPException(status_code=503, detail="Run broadcaster not installed.")
271
+
272
+ async def gen() -> AsyncIterator[bytes]:
273
+ # Lead with `init` so the client always gets a packet within the
274
+ # SSE handshake window (no 30s+ silence before the agent emits).
275
+ yield _sse(
276
+ "init",
277
+ {"task_id": task.id, "name": task.name, "action_type": task.action.type},
278
+ )
279
+ try:
280
+ async for evt in broadcaster.start_or_attach(task, registry, store):
281
+ yield _sse(evt["event"], evt["data"])
282
+ except Exception as exc:
283
+ yield _sse("done", {"status": "error", "error": str(exc)})
284
+
285
+ return StreamingResponse(
286
+ gen(),
287
+ media_type="text/event-stream",
288
+ headers={"Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no"},
289
+ )
290
+
291
+
292
+ @router.get("/{task_id}/runs", summary="List recent runs")
293
+ def list_runs(task_id: str, request: Request, limit: int = 50) -> dict[str, Any]:
294
+ """Last `limit` runs (default 50) for one task, newest first."""
295
+ store = _store(request)
296
+ runs = store.list_runs(task_id, limit=limit)
297
+ return {"runs": [r.model_dump(mode="json") for r in runs]}
298
+
299
+
300
+ @router.get("/{task_id}/runs/{run_id}", summary="Fetch one run")
301
+ def get_run(task_id: str, run_id: str, request: Request) -> dict[str, Any]:
302
+ """Fetch a single persisted run record by its run_id.
303
+
304
+ Lighter than ``/runs?limit=...`` when the UI already has the run_id
305
+ (e.g. clicking on a row from the runs list). Includes the ``summary``
306
+ (model, tokens, cost, tool counts) and ``events_path`` so the caller
307
+ knows whether a timeline can be replayed via ``/runs/{run_id}/events``.
308
+ """
309
+ store = _store(request)
310
+ run = store.get_run(task_id, run_id)
311
+ if run is None:
312
+ raise HTTPException(status_code=404, detail=f"Run '{run_id}' not found")
313
+ return run.model_dump(mode="json")
314
+
315
+
316
+ @router.get("/{task_id}/runs/{run_id}/events", summary="Replay run event timeline")
317
+ def get_run_events(task_id: str, run_id: str, request: Request) -> dict[str, Any]:
318
+ """Return the full event timeline for one finished run.
319
+
320
+ Used by the detail drawer to "replay" a run with the same per-step
321
+ UI shown during a live run. ``events`` mirrors the SSE stream shape
322
+ one-for-one (each item has ``event`` + ``data`` + ``seq`` keys); the
323
+ frontend renderer can treat live and replay sources interchangeably.
324
+
325
+ Returns 404 if the run exists but no timeline was persisted (e.g. an
326
+ older run from before v0.10.x), so the caller can fall back to the
327
+ legacy ``output.response`` rendering.
328
+ """
329
+ store = _store(request)
330
+ events = store.load_events(task_id, run_id)
331
+ if events is None:
332
+ raise HTTPException(
333
+ status_code=404,
334
+ detail=f"No event timeline persisted for run '{run_id}' (likely a pre-0.10 run)",
335
+ )
336
+ return {"events": events, "count": len(events)}
337
+
338
+
339
+ @router.post("/test", summary="Dry-run an action (no persistence)")
340
+ async def test_action(
341
+ body: AgentTaskCreate,
342
+ registry: ServiceRegistry = Depends(get_registry),
343
+ ) -> dict[str, Any]:
344
+ """Execute an action ad-hoc -- no persistence, no scheduling.
345
+
346
+ Used by the React "Run preview" button so users can validate an action
347
+ before saving the task. The result mirrors what a real run would
348
+ produce, but nothing is written to ``agents.json`` or run history.
349
+ """
350
+ # Build a transient task. Reuse run_task_once so the dispatch logic
351
+ # (mcp_tool / cli_command / ai_agent) is the same code path that
352
+ # the scheduler uses -- prevents test-time and live-time divergence.
353
+ transient = AgentTask(
354
+ name=body.name or "[preview]",
355
+ description=body.description,
356
+ cron=body.cron,
357
+ enabled=False,
358
+ action=body.action,
359
+ )
360
+ # Use a throwaway in-memory store so run_task_once's persistence side
361
+ # effects (append_run, upsert_task) write to /dev/null.
362
+ store = _NullStore()
363
+ run: AgentRun = await run_task_once(transient, registry, store)
364
+ return run.model_dump(mode="json")
365
+
366
+
367
+ def _sse(event: str, data: Any) -> bytes:
368
+ """Format one SSE message: `event: <name>\\ndata: <json>\\n\\n`."""
369
+ payload = json.dumps(data, ensure_ascii=False)
370
+ return f"event: {event}\ndata: {payload}\n\n".encode()
371
+
372
+
373
+ @router.post("/test/stream", summary="Dry-run an action (SSE stream)")
374
+ async def test_action_stream(
375
+ body: AgentTaskCreate,
376
+ registry: ServiceRegistry = Depends(get_registry),
377
+ ) -> StreamingResponse:
378
+ """Stream a test-run as SSE.
379
+
380
+ AI-agent runs (action.type == "ai_agent") emit one SSE event per line of
381
+ claude/codex/gemini stdout (claude is JSONL via ``--output-format=stream-json``;
382
+ codex / gemini emit raw text). Stderr is interleaved as ``stderr`` events.
383
+ A final ``done`` event carries exit_code, elapsed_seconds, response_text,
384
+ and full stderr -- so even if the client missed earlier events, the
385
+ last one is self-contained.
386
+
387
+ Non-streaming action types (``cli_command``, ``mcp_tool``) are wrapped:
388
+ the full result is emitted as a single ``done`` event. This keeps the
389
+ frontend code path uniform (always `/agents/test/stream`).
390
+ """
391
+ action_type = body.action.type
392
+
393
+ async def gen() -> AsyncIterator[bytes]:
394
+ # Always lead with an init event so the client knows the request
395
+ # made it through (no 30-second silence if the AI takes a while
396
+ # to start producing output).
397
+ yield _sse(
398
+ "init",
399
+ {
400
+ "name": body.name or "[preview]",
401
+ "action_type": action_type,
402
+ "cron": body.cron,
403
+ },
404
+ )
405
+ if action_type == "ai_agent":
406
+ try:
407
+ async for evt in stream_ai_agent_events(registry, body.action.params):
408
+ yield _sse(evt["event"], evt["data"])
409
+ except Exception as exc:
410
+ yield _sse(
411
+ "done",
412
+ {"status": "error", "error": str(exc)},
413
+ )
414
+ else:
415
+ # Reuse the existing non-stream dispatch for the other two
416
+ # action types -- they emit no incremental events anyway.
417
+ transient = AgentTask(
418
+ name=body.name or "[preview]",
419
+ description=body.description,
420
+ cron=body.cron,
421
+ enabled=False,
422
+ action=body.action,
423
+ )
424
+ try:
425
+ run = await run_task_once(transient, registry, _NullStore())
426
+ yield _sse("done", run.model_dump(mode="json"))
427
+ except Exception as exc:
428
+ yield _sse("done", {"status": "error", "error": str(exc)})
429
+
430
+ return StreamingResponse(
431
+ gen(),
432
+ media_type="text/event-stream",
433
+ headers={
434
+ # Disable proxy buffering so events flush to the client
435
+ # immediately (matches how the BFF's streamSSE helper
436
+ # already passes through these headers).
437
+ "Cache-Control": "no-cache, no-transform",
438
+ "X-Accel-Buffering": "no",
439
+ },
440
+ )
441
+
442
+
443
+ @router.get("/cron/preview", summary="Preview cron firings")
444
+ def cron_preview(cron: str, count: int = 5) -> dict[str, Any]:
445
+ """Preview the next ``count`` firings of a cron expression. Validates syntax."""
446
+ from datetime import datetime
447
+
448
+ from croniter import croniter
449
+
450
+ try:
451
+ it = croniter(cron, datetime.now(UTC))
452
+ firings = []
453
+ for _ in range(max(1, min(count, 20))):
454
+ dt = it.get_next(datetime)
455
+ if dt.tzinfo is None:
456
+ dt = dt.replace(tzinfo=UTC)
457
+ firings.append(dt.isoformat())
458
+ return {"cron": cron, "firings": firings}
459
+ except Exception as exc:
460
+ raise HTTPException(status_code=400, detail=f"Invalid cron expression: {exc}") from exc
461
+
462
+
463
+ class PromptHelperRequest(BaseModel):
464
+ """Input for the /agents/prompt/improve/stream endpoint.
465
+
466
+ The helper rewrites the user's plain-English goal (and any half-baked
467
+ draft) into a polished prompt suitable for an AI-agent scheduled task.
468
+ The output is streamed back as SSE so the UI can show progress, and the
469
+ final ``done`` event carries the cleaned prompt body ready to drop into
470
+ the task's prompt textarea.
471
+ """
472
+
473
+ cli: str # claude | codex | gemini -- same recipe as ai_agent runs
474
+ goal: str
475
+ draft: str = ""
476
+ project: str | None = None
477
+ extra_args: list[str] = []
478
+
479
+
480
+ @router.post("/prompt/improve", summary="AI-rewrite a prompt (blocking)")
481
+ async def improve_prompt(
482
+ body: PromptHelperRequest,
483
+ registry: ServiceRegistry = Depends(get_registry),
484
+ ) -> dict[str, Any]:
485
+ """Blocking variant of ``/prompt/improve/stream`` -- consumes the SSE
486
+ generator server-side and returns the final ``done`` payload.
487
+
488
+ Drives the same ``stream_ai_agent_events`` machinery the streaming
489
+ variant uses, so the resulting ``data.prompt`` is byte-for-byte
490
+ identical to what the SSE consumer would see in its last event.
491
+ Useful for scripted callers (``kbagent http`` from inside a scheduled
492
+ task, batch wrappers) where opening an SSE connection just to read
493
+ the final frame is needless ceremony.
494
+
495
+ Returns the enriched ``done.data`` payload directly: ``{status,
496
+ exit_code, elapsed_seconds, response, prompt, raw_response, ...}``.
497
+ The CLI's ``kbagent agent prompt-improve --no-stream`` consumes this
498
+ endpoint's shape verbatim.
499
+ """
500
+ from ..agent_runner import (
501
+ build_prompt_helper_meta_prompt,
502
+ clean_prompt_helper_response,
503
+ )
504
+
505
+ goal = body.goal.strip()
506
+ if not goal:
507
+ raise HTTPException(status_code=400, detail="goal must not be empty")
508
+ meta_prompt = build_prompt_helper_meta_prompt(
509
+ goal=goal,
510
+ draft=body.draft,
511
+ project=body.project,
512
+ )
513
+ params: dict[str, Any] = {
514
+ "cli": body.cli,
515
+ "prompt": meta_prompt,
516
+ "extra_args": body.extra_args,
517
+ "timeout": AI_PROMPT_HELPER_TIMEOUT,
518
+ }
519
+ final_done: dict[str, Any] | None = None
520
+ try:
521
+ async for evt in stream_ai_agent_events(registry, params):
522
+ if evt["event"] == "done":
523
+ raw = str(evt["data"].get("response") or "")
524
+ final_done = {
525
+ **evt["data"],
526
+ "prompt": clean_prompt_helper_response(raw),
527
+ "raw_response": raw,
528
+ }
529
+ except ValueError as exc:
530
+ # Same translation the streaming variant uses (bad CLI, empty
531
+ # prompt, malformed extra_args). 400 surfaces it cleanly to the
532
+ # caller without leaking a stack trace.
533
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
534
+ if final_done is None:
535
+ raise HTTPException(
536
+ status_code=502,
537
+ detail="ai_agent stream ended without a done event",
538
+ )
539
+ return final_done
540
+
541
+
542
+ @router.post("/prompt/improve/stream", summary="AI-rewrite a prompt (SSE stream)")
543
+ async def improve_prompt_stream(
544
+ body: PromptHelperRequest,
545
+ registry: ServiceRegistry = Depends(get_registry),
546
+ ) -> StreamingResponse:
547
+ """Stream an AI-generated, polished prompt back to the UI as SSE events.
548
+
549
+ The chosen CLI (claude / codex / gemini) is invoked exactly the same way
550
+ a scheduled ai_agent run would invoke it -- via
551
+ :func:`stream_ai_agent_events` -- but the *prompt* it receives is a
552
+ meta-prompt asking it to rewrite the user's draft into a polished
553
+ single-shot prompt. The UI consumes the same SSE event shapes
554
+ (``init`` / ``stdout`` / ``stderr`` / ``done``) the test-stream
555
+ endpoint emits, so it can reuse the live-progress renderer.
556
+
557
+ The ``done`` event is enriched with a ``prompt`` field carrying the
558
+ cleaned AI response (code fences and "Here is the prompt:" preambles
559
+ stripped) so the frontend can drop it straight into the textarea.
560
+ """
561
+ from ..agent_runner import (
562
+ build_prompt_helper_meta_prompt,
563
+ clean_prompt_helper_response,
564
+ )
565
+
566
+ goal = body.goal.strip()
567
+ if not goal:
568
+ raise HTTPException(status_code=400, detail="goal must not be empty")
569
+ meta_prompt = build_prompt_helper_meta_prompt(
570
+ goal=goal,
571
+ draft=body.draft,
572
+ project=body.project,
573
+ )
574
+ params: dict[str, Any] = {
575
+ "cli": body.cli,
576
+ "prompt": meta_prompt,
577
+ "extra_args": body.extra_args,
578
+ "timeout": AI_PROMPT_HELPER_TIMEOUT,
579
+ }
580
+
581
+ async def gen() -> AsyncIterator[bytes]:
582
+ # Mirror the /test/stream init shape so the React side can reuse
583
+ # AgentRunView verbatim.
584
+ yield _sse(
585
+ "init",
586
+ {
587
+ "kind": "prompt_helper",
588
+ "cli": body.cli,
589
+ "goal_preview": goal[:200],
590
+ },
591
+ )
592
+ try:
593
+ async for evt in stream_ai_agent_events(registry, params):
594
+ if evt["event"] == "done":
595
+ raw = str(evt["data"].get("response") or "")
596
+ cleaned = clean_prompt_helper_response(raw)
597
+ enriched = {**evt["data"], "prompt": cleaned, "raw_response": raw}
598
+ yield _sse("done", enriched)
599
+ else:
600
+ yield _sse(evt["event"], evt["data"])
601
+ except ValueError as exc:
602
+ # build_*_meta_prompt and stream_ai_agent_events raise ValueError
603
+ # for bad CLI / empty prompt / malformed extra_args. Send a final
604
+ # done event so the client doesn't hang waiting.
605
+ yield _sse("done", {"status": "error", "error": str(exc)})
606
+ except Exception as exc:
607
+ yield _sse("done", {"status": "error", "error": str(exc)})
608
+
609
+ return StreamingResponse(
610
+ gen(),
611
+ media_type="text/event-stream",
612
+ headers={
613
+ "Cache-Control": "no-cache, no-transform",
614
+ "X-Accel-Buffering": "no",
615
+ },
616
+ )