flowent 0.0.7 → 0.0.11

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 (387) hide show
  1. package/README.md +0 -3
  2. package/backend/README.md +0 -3
  3. package/backend/pyproject.toml +2 -8
  4. package/backend/src/flowent/__init__.py +6 -2
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/agent.py +213 -3173
  17. package/backend/src/flowent/cli.py +19 -24
  18. package/backend/src/flowent/context.py +127 -0
  19. package/backend/src/flowent/llm.py +256 -0
  20. package/backend/src/flowent/logging.py +170 -129
  21. package/backend/src/flowent/main.py +321 -70
  22. package/backend/src/flowent/patch.py +182 -0
  23. package/backend/src/flowent/paths.py +11 -0
  24. package/backend/src/flowent/sandbox.py +214 -40
  25. package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  26. package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  27. package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  28. package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
  29. package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
  30. package/backend/src/flowent/static/flowent.png +0 -0
  31. package/backend/src/flowent/static/index.html +5 -25
  32. package/backend/src/flowent/storage.py +302 -0
  33. package/backend/src/flowent/tools.py +376 -0
  34. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  35. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  36. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  37. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  38. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  39. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/test_agent_tools.py +477 -0
  41. package/backend/tests/test_health.py +12 -0
  42. package/backend/tests/test_llm_providers.py +113 -0
  43. package/backend/tests/test_logging.py +182 -0
  44. package/backend/tests/test_persistence.py +125 -0
  45. package/backend/tests/test_workspace_chat.py +578 -0
  46. package/backend/uv.lock +803 -99
  47. package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  48. package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  49. package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  50. package/dist/frontend/assets/index-C76K95ty.js +81 -0
  51. package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
  52. package/dist/frontend/flowent.png +0 -0
  53. package/dist/frontend/index.html +5 -25
  54. package/package.json +1 -2
  55. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  75. package/backend/src/flowent/access.py +0 -247
  76. package/backend/src/flowent/assistant_commands.py +0 -115
  77. package/backend/src/flowent/channels/__init__.py +0 -3
  78. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/channels/telegram.py +0 -615
  81. package/backend/src/flowent/config.py +0 -14
  82. package/backend/src/flowent/dev.py +0 -3
  83. package/backend/src/flowent/events.py +0 -157
  84. package/backend/src/flowent/graph_runtime.py +0 -60
  85. package/backend/src/flowent/graph_service.py +0 -2401
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/model_metadata.py +0 -102
  88. package/backend/src/flowent/models/__init__.py +0 -125
  89. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/agent.py +0 -34
  103. package/backend/src/flowent/models/base.py +0 -24
  104. package/backend/src/flowent/models/blueprint.py +0 -176
  105. package/backend/src/flowent/models/content.py +0 -164
  106. package/backend/src/flowent/models/delta.py +0 -44
  107. package/backend/src/flowent/models/event.py +0 -51
  108. package/backend/src/flowent/models/graph.py +0 -472
  109. package/backend/src/flowent/models/history.py +0 -272
  110. package/backend/src/flowent/models/llm.py +0 -62
  111. package/backend/src/flowent/models/message.py +0 -33
  112. package/backend/src/flowent/models/tab.py +0 -85
  113. package/backend/src/flowent/models/todo.py +0 -10
  114. package/backend/src/flowent/network.py +0 -146
  115. package/backend/src/flowent/observability_service.py +0 -218
  116. package/backend/src/flowent/prompts/__init__.py +0 -67
  117. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/common.py +0 -250
  121. package/backend/src/flowent/prompts/steward.py +0 -64
  122. package/backend/src/flowent/providers/__init__.py +0 -23
  123. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/anthropic.py +0 -468
  138. package/backend/src/flowent/providers/base_url.py +0 -60
  139. package/backend/src/flowent/providers/configuration.py +0 -189
  140. package/backend/src/flowent/providers/content.py +0 -122
  141. package/backend/src/flowent/providers/errors.py +0 -223
  142. package/backend/src/flowent/providers/gateway.py +0 -169
  143. package/backend/src/flowent/providers/gemini.py +0 -447
  144. package/backend/src/flowent/providers/headers.py +0 -20
  145. package/backend/src/flowent/providers/management.py +0 -96
  146. package/backend/src/flowent/providers/ollama.py +0 -293
  147. package/backend/src/flowent/providers/openai.py +0 -422
  148. package/backend/src/flowent/providers/openai_responses.py +0 -655
  149. package/backend/src/flowent/providers/registry.py +0 -144
  150. package/backend/src/flowent/providers/sse.py +0 -31
  151. package/backend/src/flowent/providers/thinking.py +0 -79
  152. package/backend/src/flowent/registry.py +0 -73
  153. package/backend/src/flowent/role_management.py +0 -270
  154. package/backend/src/flowent/routes/__init__.py +0 -26
  155. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  156. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/access.py +0 -48
  168. package/backend/src/flowent/routes/assistant.py +0 -158
  169. package/backend/src/flowent/routes/image_assets.py +0 -33
  170. package/backend/src/flowent/routes/meta.py +0 -28
  171. package/backend/src/flowent/routes/nodes.py +0 -423
  172. package/backend/src/flowent/routes/prompts.py +0 -46
  173. package/backend/src/flowent/routes/providers_route.py +0 -365
  174. package/backend/src/flowent/routes/roles.py +0 -207
  175. package/backend/src/flowent/routes/settings.py +0 -379
  176. package/backend/src/flowent/routes/tabs.py +0 -298
  177. package/backend/src/flowent/routes/ws.py +0 -33
  178. package/backend/src/flowent/runtime.py +0 -160
  179. package/backend/src/flowent/security.py +0 -37
  180. package/backend/src/flowent/settings.py +0 -2112
  181. package/backend/src/flowent/settings_management.py +0 -394
  182. package/backend/src/flowent/state_db.py +0 -108
  183. package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
  184. package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
  185. package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
  186. package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
  187. package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
  188. package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
  189. package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
  190. package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
  191. package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  192. package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
  193. package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
  194. package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
  195. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  196. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  197. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  198. package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
  199. package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
  200. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  201. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  202. package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
  203. package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
  204. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  205. package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
  206. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  207. package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
  208. package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
  209. package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
  210. package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
  211. package/backend/src/flowent/static/favicon.svg +0 -4
  212. package/backend/src/flowent/tools/__init__.py +0 -176
  213. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  214. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  215. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  216. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  217. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  218. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/connect.py +0 -100
  236. package/backend/src/flowent/tools/contacts.py +0 -22
  237. package/backend/src/flowent/tools/create_agent.py +0 -191
  238. package/backend/src/flowent/tools/create_tab.py +0 -61
  239. package/backend/src/flowent/tools/delete_tab.py +0 -39
  240. package/backend/src/flowent/tools/edit.py +0 -142
  241. package/backend/src/flowent/tools/exec.py +0 -118
  242. package/backend/src/flowent/tools/fetch.py +0 -85
  243. package/backend/src/flowent/tools/idle.py +0 -27
  244. package/backend/src/flowent/tools/list_roles.py +0 -68
  245. package/backend/src/flowent/tools/list_tabs.py +0 -100
  246. package/backend/src/flowent/tools/list_tools.py +0 -28
  247. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  248. package/backend/src/flowent/tools/manage_providers.py +0 -220
  249. package/backend/src/flowent/tools/manage_roles.py +0 -275
  250. package/backend/src/flowent/tools/manage_settings.py +0 -326
  251. package/backend/src/flowent/tools/read.py +0 -152
  252. package/backend/src/flowent/tools/send.py +0 -68
  253. package/backend/src/flowent/tools/set_permissions.py +0 -99
  254. package/backend/src/flowent/tools/sleep.py +0 -41
  255. package/backend/src/flowent/tools/todo.py +0 -51
  256. package/backend/src/flowent/workspace_store.py +0 -479
  257. package/backend/tests/__init__.py +0 -0
  258. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  259. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/conftest.py +0 -6
  261. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  265. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/conftest.py +0 -29
  271. package/backend/tests/integration/api/test_access_api.py +0 -182
  272. package/backend/tests/integration/api/test_assistant_api.py +0 -422
  273. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  274. package/backend/tests/integration/api/test_meta_api.py +0 -32
  275. package/backend/tests/integration/api/test_nodes_api.py +0 -787
  276. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  277. package/backend/tests/integration/api/test_roles_api.py +0 -228
  278. package/backend/tests/integration/api/test_tabs_api.py +0 -688
  279. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  280. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  281. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
  288. package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
  289. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  291. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/logging/test_logging.py +0 -132
  293. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  295. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  297. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  303. package/backend/tests/unit/providers/test_errors.py +0 -68
  304. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  305. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  306. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  307. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  308. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  309. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
  314. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  315. package/backend/tests/unit/routes/test_roles_routes.py +0 -539
  316. package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
  317. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  319. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  321. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/security/test_security.py +0 -124
  323. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  324. package/backend/tests/unit/settings/test_settings_roles.py +0 -703
  325. package/backend/tests/unit/test_access.py +0 -45
  326. package/backend/tests/unit/test_cli.py +0 -102
  327. package/backend/tests/unit/test_graph_runtime.py +0 -72
  328. package/backend/tests/unit/test_network.py +0 -51
  329. package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
  330. package/backend/tests/unit/test_workspace_store.py +0 -228
  331. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  332. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  334. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  335. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  336. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  337. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  338. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  339. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  340. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  346. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
  347. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  348. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  349. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  350. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  351. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
  352. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  353. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  354. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  355. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  356. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  357. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  358. package/backend/tests/unit/tools/test_tool_registry.py +0 -199
  359. package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
  360. package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
  361. package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
  362. package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
  363. package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
  364. package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
  365. package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
  366. package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
  367. package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  368. package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
  369. package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
  370. package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
  371. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  372. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  373. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  374. package/dist/frontend/assets/index--o_0fv0N.css +0 -1
  375. package/dist/frontend/assets/index-C9HuekJm.js +0 -10
  376. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  377. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  378. package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
  379. package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
  380. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  381. package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
  382. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  383. package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
  384. package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
  385. package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
  386. package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
  387. package/dist/frontend/favicon.svg +0 -4
@@ -1,48 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from fastapi import APIRouter, HTTPException, Request
4
- from pydantic import BaseModel
5
-
6
- from flowent.access import (
7
- ACCESS_SESSION_KEY,
8
- build_access_state_payload,
9
- is_access_configured,
10
- verify_access_code,
11
- )
12
-
13
- router = APIRouter()
14
-
15
-
16
- class AccessLoginRequest(BaseModel):
17
- code: str = ""
18
-
19
-
20
- @router.get("/api/access/state")
21
- async def get_access_state(request: Request) -> dict[str, object]:
22
- return build_access_state_payload(request.session)
23
-
24
-
25
- @router.post("/api/access/login")
26
- async def login_access(
27
- payload: AccessLoginRequest,
28
- request: Request,
29
- ) -> dict[str, object]:
30
- from flowent.access import _read_live_access_settings
31
-
32
- access = _read_live_access_settings()
33
- if not is_access_configured(access):
34
- raise HTTPException(
35
- status_code=503,
36
- detail="Access code is not initialized. Restart Flowent to generate a new access code.",
37
- )
38
- if not verify_access_code(access, payload.code):
39
- raise HTTPException(status_code=401, detail="Invalid access code")
40
- request.session.clear()
41
- request.session[ACCESS_SESSION_KEY] = access.session_generation
42
- return build_access_state_payload(request.session)
43
-
44
-
45
- @router.post("/api/access/logout")
46
- async def logout_access(request: Request) -> dict[str, object]:
47
- request.session.clear()
48
- return build_access_state_payload(request.session)
@@ -1,158 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import uuid
4
- from typing import Literal
5
-
6
- from fastapi import APIRouter, HTTPException
7
- from pydantic import BaseModel
8
-
9
- from flowent.assistant_commands import (
10
- ConversationCommandError,
11
- execute_conversation_command_input,
12
- )
13
- from flowent.image_assets import require_image_asset
14
- from flowent.models import (
15
- AgentState,
16
- Message,
17
- content_parts_to_text,
18
- has_image_parts,
19
- parse_content_parts_payload,
20
- )
21
- from flowent.models import TextPart as ModelTextPart
22
- from flowent.providers.errors import LLMProviderError
23
- from flowent.registry import registry
24
-
25
- router = APIRouter()
26
-
27
-
28
- def _get_assistant():
29
- return registry.get_assistant()
30
-
31
-
32
- @router.get("/api/assistant")
33
- async def get_assistant() -> dict:
34
- assistant = _get_assistant()
35
- if assistant is None:
36
- raise HTTPException(status_code=404, detail="Assistant not found")
37
- return {
38
- "id": assistant.uuid,
39
- "name": assistant.config.name,
40
- "role_name": assistant.config.role_name,
41
- "state": assistant.state.value,
42
- "connections": assistant.get_connections_snapshot(),
43
- }
44
-
45
-
46
- class AssistantMessageRequest(BaseModel):
47
- content: str | None = None
48
- parts: list[AssistantMessagePart] | None = None
49
-
50
-
51
- class AssistantMessagePart(BaseModel):
52
- type: Literal["text", "image"]
53
- text: str | None = None
54
- asset_id: str | None = None
55
- mime_type: str | None = None
56
- width: int | None = None
57
- height: int | None = None
58
- alt: str | None = None
59
-
60
-
61
- AssistantMessageRequest.model_rebuild()
62
-
63
-
64
- class AssistantRetryResponse(BaseModel):
65
- status: Literal["retried"]
66
- message_id: str
67
-
68
-
69
- def _parse_request_parts(req: AssistantMessageRequest):
70
- if req.parts:
71
- parts = parse_content_parts_payload(
72
- [part.model_dump(exclude_none=True) for part in req.parts]
73
- )
74
- elif isinstance(req.content, str):
75
- parts = [ModelTextPart(text=req.content)]
76
- else:
77
- parts = []
78
- if not parts:
79
- raise HTTPException(status_code=400, detail="Assistant message cannot be empty")
80
- if not has_image_parts(parts) and not content_parts_to_text(parts).strip():
81
- raise HTTPException(status_code=400, detail="Assistant message cannot be empty")
82
- for part in parts:
83
- asset_id = getattr(part, "asset_id", None)
84
- if isinstance(asset_id, str):
85
- require_image_asset(asset_id)
86
- return parts
87
-
88
-
89
- @router.post("/api/assistant/message")
90
- async def send_assistant_message(req: AssistantMessageRequest) -> dict:
91
- assistant = _get_assistant()
92
- if assistant is None:
93
- raise HTTPException(status_code=404, detail="Assistant not found")
94
- if assistant.state == AgentState.TERMINATED:
95
- raise HTTPException(status_code=409, detail="Assistant is no longer available")
96
-
97
- try:
98
- parts = _parse_request_parts(req)
99
- except ValueError as exc:
100
- raise HTTPException(status_code=400, detail=str(exc)) from exc
101
-
102
- if has_image_parts(parts) and not assistant.supports_input_image():
103
- raise HTTPException(
104
- status_code=409,
105
- detail="Assistant current model does not support `input_image`.",
106
- )
107
-
108
- command_input = (
109
- parts[0].text
110
- if len(parts) == 1 and isinstance(parts[0], ModelTextPart)
111
- else None
112
- )
113
-
114
- try:
115
- executed_command = (
116
- execute_conversation_command_input(assistant, command_input)
117
- if isinstance(command_input, str)
118
- else None
119
- )
120
- except ConversationCommandError as exc:
121
- raise HTTPException(status_code=400, detail=str(exc)) from exc
122
- except (RuntimeError, TimeoutError, LLMProviderError) as exc:
123
- raise HTTPException(status_code=409, detail=str(exc)) from exc
124
-
125
- if executed_command is not None:
126
- return {
127
- "status": "command_executed",
128
- "command_name": executed_command.command_name,
129
- }
130
-
131
- message_id = str(uuid.uuid4())
132
- msg = Message(
133
- from_id="human",
134
- to_id=assistant.uuid,
135
- parts=parts,
136
- message_id=message_id,
137
- )
138
- assistant.enqueue_message(msg)
139
- return {"status": "sent", "message_id": message_id}
140
-
141
-
142
- @router.post(
143
- "/api/assistant/messages/{message_id}/retry",
144
- response_model=AssistantRetryResponse,
145
- )
146
- async def retry_assistant_message(message_id: str) -> AssistantRetryResponse:
147
- assistant = _get_assistant()
148
- if assistant is None:
149
- raise HTTPException(status_code=404, detail="Assistant not found")
150
-
151
- try:
152
- retried_message_id = assistant.retry_human_message(message_id=message_id)
153
- except LookupError as exc:
154
- raise HTTPException(status_code=404, detail=str(exc)) from exc
155
- except (RuntimeError, TimeoutError, ValueError) as exc:
156
- raise HTTPException(status_code=409, detail=str(exc)) from exc
157
-
158
- return AssistantRetryResponse(status="retried", message_id=retried_message_id)
@@ -1,33 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from fastapi import APIRouter, File, HTTPException, UploadFile
4
- from fastapi.responses import FileResponse
5
-
6
- from flowent.image_assets import create_image_asset, get_image_asset
7
-
8
- router = APIRouter()
9
- image_upload_file = File(...)
10
-
11
-
12
- @router.post("/api/image-assets")
13
- async def upload_image_asset(
14
- file: UploadFile = image_upload_file,
15
- ) -> dict[str, object]:
16
- try:
17
- data = await file.read()
18
- asset = create_image_asset(
19
- data,
20
- mime_type=file.content_type,
21
- original_name=file.filename,
22
- )
23
- except ValueError as exc:
24
- raise HTTPException(status_code=400, detail=str(exc)) from exc
25
- return asset.serialize()
26
-
27
-
28
- @router.get("/api/image-assets/{asset_id}")
29
- async def get_uploaded_image_asset(asset_id: str) -> FileResponse:
30
- asset = get_image_asset(asset_id)
31
- if asset is None:
32
- raise HTTPException(status_code=404, detail="Image asset not found")
33
- return FileResponse(asset.file_path, media_type=asset.mime_type)
@@ -1,28 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from fastapi import APIRouter
4
-
5
- router = APIRouter()
6
-
7
-
8
- @router.get("/health")
9
- async def health_check() -> dict:
10
- return {"status": "healthy"}
11
-
12
-
13
- @router.get("/api/meta")
14
- async def get_meta() -> dict:
15
- from flowent._version import __version__
16
- from flowent.providers.registry import ProviderType
17
-
18
- return {
19
- "provider_types": [pt.value for pt in ProviderType],
20
- "version": __version__,
21
- }
22
-
23
-
24
- @router.get("/api/tools")
25
- async def list_tools() -> dict:
26
- from flowent.tools import list_agent_visible_tool_descriptors
27
-
28
- return {"tools": list_agent_visible_tool_descriptors()}
@@ -1,423 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Literal
4
-
5
- from fastapi import APIRouter, HTTPException
6
- from pydantic import BaseModel
7
-
8
- from flowent.assistant_commands import (
9
- ConversationCommandError,
10
- execute_conversation_command_input,
11
- )
12
- from flowent.graph_service import (
13
- is_tab_leader,
14
- list_node_connection_ids,
15
- resolve_effective_permissions_for_agent,
16
- resolve_effective_permissions_for_node_record,
17
- )
18
- from flowent.providers.errors import LLMProviderError
19
- from flowent.registry import registry
20
- from flowent.settings import (
21
- find_provider,
22
- find_role,
23
- get_settings,
24
- resolve_model_info,
25
- )
26
- from flowent.tools import MINIMUM_TOOLS
27
- from flowent.workspace_store import workspace_store
28
-
29
- router = APIRouter()
30
-
31
-
32
- class DispatchNodeMessagePart(BaseModel):
33
- type: Literal["text", "image"]
34
- text: str | None = None
35
- asset_id: str | None = None
36
- mime_type: str | None = None
37
- width: int | None = None
38
- height: int | None = None
39
- alt: str | None = None
40
-
41
-
42
- class DispatchNodeMessageRequest(BaseModel):
43
- content: str | None = None
44
- parts: list[DispatchNodeMessagePart] | None = None
45
- from_id: str = "human"
46
-
47
-
48
- DispatchNodeMessageRequest.model_rebuild()
49
-
50
-
51
- def _parse_dispatch_parts(req: DispatchNodeMessageRequest):
52
- from flowent.image_assets import require_image_asset
53
- from flowent.models import TextPart as ModelTextPart
54
- from flowent.models import (
55
- content_parts_to_text,
56
- has_image_parts,
57
- parse_content_parts_payload,
58
- )
59
-
60
- if req.parts:
61
- parts = parse_content_parts_payload(
62
- [part.model_dump(exclude_none=True) for part in req.parts]
63
- )
64
- elif isinstance(req.content, str):
65
- parts = [ModelTextPart(text=req.content)]
66
- else:
67
- parts = []
68
-
69
- if not parts:
70
- raise HTTPException(status_code=400, detail="Node message cannot be empty")
71
- if not has_image_parts(parts) and not content_parts_to_text(parts).strip():
72
- raise HTTPException(status_code=400, detail="Node message cannot be empty")
73
-
74
- for part in parts:
75
- asset_id = getattr(part, "asset_id", None)
76
- if isinstance(asset_id, str):
77
- require_image_asset(asset_id)
78
- return parts
79
-
80
-
81
- def _serialize_model_capabilities(role_name: str | None) -> dict[str, bool] | None:
82
- settings = get_settings()
83
- provider_id = settings.model.active_provider_id
84
- model_id = settings.model.active_model
85
- use_system_model_overrides = True
86
- role_cfg = find_role(settings, role_name) if role_name else None
87
- if (
88
- role_cfg is not None
89
- and role_cfg.model is not None
90
- and role_cfg.model.provider_id
91
- and role_cfg.model.model
92
- ):
93
- provider_id = role_cfg.model.provider_id
94
- model_id = role_cfg.model.model
95
- use_system_model_overrides = False
96
- if not provider_id or not model_id:
97
- return None
98
- provider = find_provider(settings, provider_id)
99
- if provider is None:
100
- return None
101
- model_info = resolve_model_info(
102
- provider=provider,
103
- model_id=model_id,
104
- input_image=settings.model.input_image if use_system_model_overrides else None,
105
- output_image=(
106
- settings.model.output_image if use_system_model_overrides else None
107
- ),
108
- context_window_tokens=(
109
- settings.model.context_window_tokens if use_system_model_overrides else None
110
- ),
111
- )
112
- return {
113
- "input_image": model_info.capabilities.input_image,
114
- "output_image": model_info.capabilities.output_image,
115
- }
116
-
117
-
118
- @router.get("/api/nodes")
119
- async def list_nodes() -> dict:
120
- nodes_by_id: dict[str, dict[str, object]] = {}
121
-
122
- assistant = registry.get_assistant()
123
- if assistant is not None:
124
- nodes_by_id[assistant.uuid] = {
125
- "id": assistant.uuid,
126
- "node_type": assistant.config.node_type.value,
127
- "workflow_id": assistant.config.tab_id,
128
- "role_name": assistant.config.role_name,
129
- "state": assistant.state.value,
130
- "connections": assistant.get_connections_snapshot(),
131
- "name": assistant.config.name,
132
- "is_leader": False,
133
- "todos": [t.serialize() for t in assistant.todos],
134
- "capabilities": _serialize_model_capabilities(assistant.config.role_name),
135
- "position": None,
136
- }
137
-
138
- for record in workspace_store.list_node_records():
139
- live = registry.get(record.id)
140
- nodes_by_id[record.id] = {
141
- "id": record.id,
142
- "node_type": record.config.node_type.value,
143
- "workflow_id": record.config.tab_id,
144
- "role_name": record.config.role_name,
145
- "is_leader": is_tab_leader(node_id=record.id, tab_id=record.config.tab_id),
146
- "state": (live.state if live is not None else record.state).value,
147
- "connections": (
148
- list_node_connection_ids(
149
- tab_id=record.config.tab_id,
150
- node_id=record.id,
151
- )
152
- if record.config.tab_id
153
- else []
154
- ),
155
- "name": record.config.name,
156
- "todos": [
157
- todo.serialize()
158
- for todo in (
159
- live.get_todos_snapshot() if live is not None else record.todos
160
- )
161
- ],
162
- "capabilities": _serialize_model_capabilities(record.config.role_name),
163
- "position": record.position.serialize()
164
- if record.position is not None
165
- else None,
166
- }
167
-
168
- for node in registry.get_all():
169
- if node.uuid in nodes_by_id:
170
- continue
171
- nodes_by_id[node.uuid] = {
172
- "id": node.uuid,
173
- "node_type": node.config.node_type.value,
174
- "workflow_id": node.config.tab_id,
175
- "role_name": node.config.role_name,
176
- "is_leader": is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id),
177
- "state": node.state.value,
178
- "connections": (
179
- list_node_connection_ids(
180
- tab_id=node.config.tab_id,
181
- node_id=node.uuid,
182
- )
183
- if node.config.tab_id
184
- else node.get_connections_snapshot()
185
- ),
186
- "name": node.config.name,
187
- "todos": [t.serialize() for t in node.todos],
188
- "capabilities": _serialize_model_capabilities(node.config.role_name),
189
- "position": None,
190
- }
191
-
192
- return {
193
- "nodes": list(nodes_by_id.values()),
194
- }
195
-
196
-
197
- @router.get("/api/nodes/{node_id}")
198
- async def get_node(node_id: str) -> dict:
199
- node = registry.get(node_id)
200
- record = workspace_store.get_node_record(node_id)
201
-
202
- if node is None and record is None:
203
- raise HTTPException(status_code=404, detail="Node not found")
204
-
205
- if node is not None:
206
- record_id = node.uuid
207
- record_state = node.state
208
- target_config = node.config
209
- allow_network, write_dirs = resolve_effective_permissions_for_agent(node)
210
- else:
211
- assert record is not None
212
- record_id = record.id
213
- record_state = record.state
214
- target_config = record.config
215
- allow_network, write_dirs = resolve_effective_permissions_for_node_record(
216
- record
217
- )
218
- history = (
219
- node.get_history_snapshot()
220
- if node is not None
221
- else (record.history if record is not None else [])
222
- )
223
- todos = (
224
- node.get_todos_snapshot()
225
- if node is not None
226
- else (record.todos if record is not None else [])
227
- )
228
-
229
- return {
230
- "id": record_id,
231
- "node_type": target_config.node_type.value,
232
- "workflow_id": target_config.tab_id,
233
- "role_name": target_config.role_name,
234
- "is_leader": is_tab_leader(node_id=record_id, tab_id=target_config.tab_id),
235
- "state": record_state.value,
236
- "contacts": node.get_contacts_info() if node is not None else [],
237
- "connections": (
238
- list_node_connection_ids(
239
- tab_id=target_config.tab_id,
240
- node_id=record_id,
241
- )
242
- if target_config.tab_id
243
- else (node.get_connections_snapshot() if node is not None else [])
244
- ),
245
- "name": target_config.name,
246
- "todos": [t.serialize() for t in todos],
247
- "capabilities": _serialize_model_capabilities(target_config.role_name),
248
- "tools": sorted(set(target_config.tools) | set(MINIMUM_TOOLS)),
249
- "write_dirs": list(write_dirs),
250
- "allow_network": allow_network,
251
- "workflow_permissions": {
252
- "allow_network": allow_network,
253
- "write_dirs": list(write_dirs),
254
- },
255
- "position": record.position.serialize()
256
- if record is not None and record.position is not None
257
- else None,
258
- "history": [entry.serialize() for entry in history],
259
- }
260
-
261
-
262
- @router.post("/api/nodes/{node_id}/terminate")
263
- async def terminate_node(node_id: str) -> dict:
264
- node = registry.get(node_id)
265
- if node is None:
266
- raise HTTPException(status_code=404, detail="Node not found")
267
-
268
- from flowent.models import NodeType
269
-
270
- if node.config.node_type == NodeType.ASSISTANT:
271
- raise HTTPException(status_code=400, detail="Cannot terminate assistant")
272
- if is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id):
273
- raise HTTPException(
274
- status_code=400,
275
- detail="Cannot terminate a workflow Leader directly",
276
- )
277
-
278
- node.request_termination("user_requested")
279
- return {"status": "terminating"}
280
-
281
-
282
- @router.post("/api/nodes/{node_id}/interrupt")
283
- async def interrupt_node(node_id: str) -> dict:
284
- node = registry.get(node_id)
285
- if node is None:
286
- raise HTTPException(status_code=404, detail="Node not found")
287
- if not node.request_interrupt():
288
- return {"status": "ignored"}
289
- return {"status": "interrupting"}
290
-
291
-
292
- @router.post("/api/nodes/{node_id}/messages/{message_id}/retry")
293
- async def retry_node_message(node_id: str, message_id: str) -> dict:
294
- node = registry.get(node_id)
295
- if node is None:
296
- raise HTTPException(status_code=404, detail="Node not found")
297
- from flowent.models import NodeType
298
-
299
- if node.config.node_type == NodeType.ASSISTANT:
300
- raise HTTPException(
301
- status_code=400,
302
- detail="Use /api/assistant/messages/{message_id}/retry for Assistant retry",
303
- )
304
- if not is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id):
305
- raise HTTPException(
306
- status_code=400,
307
- detail="Only a Workflow Leader can retry chat history",
308
- )
309
- try:
310
- retried_message_id = node.retry_received_message(message_id=message_id)
311
- except LookupError as exc:
312
- raise HTTPException(status_code=404, detail=str(exc)) from exc
313
- except (RuntimeError, TimeoutError, ValueError) as exc:
314
- raise HTTPException(status_code=409, detail=str(exc)) from exc
315
- return {"status": "retried", "message_id": retried_message_id}
316
-
317
-
318
- @router.post("/api/nodes/{node_id}/clear-chat")
319
- async def clear_node_chat(node_id: str) -> dict:
320
- from flowent.models import NodeType
321
-
322
- node = registry.get(node_id)
323
- if node is None:
324
- raise HTTPException(status_code=404, detail="Node not found")
325
- if node.config.node_type != NodeType.ASSISTANT and not is_tab_leader(
326
- node_id=node.uuid,
327
- tab_id=node.config.tab_id,
328
- ):
329
- raise HTTPException(
330
- status_code=400,
331
- detail="Can only clear Assistant or workflow chats",
332
- )
333
-
334
- try:
335
- node.clear_chat_history()
336
- except RuntimeError as exc:
337
- raise HTTPException(status_code=409, detail=str(exc)) from exc
338
- except TimeoutError as exc:
339
- raise HTTPException(status_code=409, detail=str(exc)) from exc
340
-
341
- return {"status": "cleared"}
342
-
343
-
344
- @router.post("/api/nodes/{node_id}/messages")
345
- async def dispatch_node_message(node_id: str, req: DispatchNodeMessageRequest) -> dict:
346
- from flowent.graph_service import dispatch_node_message
347
- from flowent.models import (
348
- AgentState,
349
- NodeType,
350
- content_parts_to_text,
351
- has_image_parts,
352
- )
353
- from flowent.models import TextPart as ModelTextPart
354
-
355
- node = registry.get(node_id)
356
- if node is None:
357
- raise HTTPException(status_code=404, detail="Node not found")
358
-
359
- if req.from_id != "human":
360
- raise HTTPException(
361
- status_code=400,
362
- detail="Web UI node messages must originate from `human`",
363
- )
364
-
365
- if node.config.node_type == NodeType.ASSISTANT:
366
- raise HTTPException(
367
- status_code=400,
368
- detail="Use /api/assistant/message for Assistant input",
369
- )
370
- if not is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id):
371
- raise HTTPException(
372
- status_code=400,
373
- detail="Human input can only target Assistant or a Workflow Leader",
374
- )
375
- if node.state == AgentState.TERMINATED:
376
- raise HTTPException(
377
- status_code=409,
378
- detail="This workflow chat is no longer available",
379
- )
380
-
381
- try:
382
- parts = _parse_dispatch_parts(req)
383
- except ValueError as exc:
384
- raise HTTPException(status_code=400, detail=str(exc)) from exc
385
-
386
- if has_image_parts(parts) and not node.supports_input_image():
387
- raise HTTPException(
388
- status_code=409,
389
- detail="Current model does not support `input_image`.",
390
- )
391
-
392
- command_input = (
393
- parts[0].text
394
- if len(parts) == 1 and isinstance(parts[0], ModelTextPart)
395
- else None
396
- )
397
-
398
- try:
399
- executed_command = (
400
- execute_conversation_command_input(node, command_input)
401
- if isinstance(command_input, str)
402
- else None
403
- )
404
- except ConversationCommandError as exc:
405
- raise HTTPException(status_code=400, detail=str(exc)) from exc
406
- except (RuntimeError, TimeoutError, LLMProviderError) as exc:
407
- raise HTTPException(status_code=409, detail=str(exc)) from exc
408
-
409
- if executed_command is not None:
410
- return {
411
- "status": "command_executed",
412
- "command_name": executed_command.command_name,
413
- }
414
-
415
- error, message_id = dispatch_node_message(
416
- node_id=node_id,
417
- content=content_parts_to_text(parts),
418
- parts=parts,
419
- from_id="human",
420
- )
421
- if error is not None:
422
- raise HTTPException(status_code=400, detail=error)
423
- return {"status": "sent", "message_id": message_id}