flowent 0.0.6 → 0.0.10

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 (398) hide show
  1. package/README.md +1 -4
  2. package/backend/README.md +1 -4
  3. package/backend/pyproject.toml +2 -8
  4. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/cli.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 +217 -3094
  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 +364 -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 +449 -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__/config.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/mcp_service.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 -2508
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/mcp_service.py +0 -1918
  88. package/backend/src/flowent/model_metadata.py +0 -102
  89. package/backend/src/flowent/models/__init__.py +0 -125
  90. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  103. package/backend/src/flowent/models/agent.py +0 -34
  104. package/backend/src/flowent/models/base.py +0 -24
  105. package/backend/src/flowent/models/blueprint.py +0 -176
  106. package/backend/src/flowent/models/content.py +0 -164
  107. package/backend/src/flowent/models/delta.py +0 -44
  108. package/backend/src/flowent/models/event.py +0 -51
  109. package/backend/src/flowent/models/graph.py +0 -472
  110. package/backend/src/flowent/models/history.py +0 -272
  111. package/backend/src/flowent/models/llm.py +0 -62
  112. package/backend/src/flowent/models/message.py +0 -33
  113. package/backend/src/flowent/models/tab.py +0 -85
  114. package/backend/src/flowent/models/todo.py +0 -10
  115. package/backend/src/flowent/network.py +0 -146
  116. package/backend/src/flowent/observability_service.py +0 -218
  117. package/backend/src/flowent/prompts/__init__.py +0 -67
  118. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/prompts/common.py +0 -250
  122. package/backend/src/flowent/prompts/steward.py +0 -64
  123. package/backend/src/flowent/providers/__init__.py +0 -23
  124. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  138. package/backend/src/flowent/providers/anthropic.py +0 -468
  139. package/backend/src/flowent/providers/base_url.py +0 -60
  140. package/backend/src/flowent/providers/configuration.py +0 -189
  141. package/backend/src/flowent/providers/content.py +0 -122
  142. package/backend/src/flowent/providers/errors.py +0 -223
  143. package/backend/src/flowent/providers/gateway.py +0 -169
  144. package/backend/src/flowent/providers/gemini.py +0 -447
  145. package/backend/src/flowent/providers/headers.py +0 -20
  146. package/backend/src/flowent/providers/management.py +0 -96
  147. package/backend/src/flowent/providers/ollama.py +0 -293
  148. package/backend/src/flowent/providers/openai.py +0 -422
  149. package/backend/src/flowent/providers/openai_responses.py +0 -655
  150. package/backend/src/flowent/providers/registry.py +0 -144
  151. package/backend/src/flowent/providers/sse.py +0 -31
  152. package/backend/src/flowent/providers/thinking.py +0 -79
  153. package/backend/src/flowent/registry.py +0 -73
  154. package/backend/src/flowent/role_management.py +0 -267
  155. package/backend/src/flowent/routes/__init__.py +0 -28
  156. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  168. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  169. package/backend/src/flowent/routes/access.py +0 -48
  170. package/backend/src/flowent/routes/assistant.py +0 -155
  171. package/backend/src/flowent/routes/image_assets.py +0 -33
  172. package/backend/src/flowent/routes/mcp.py +0 -125
  173. package/backend/src/flowent/routes/meta.py +0 -28
  174. package/backend/src/flowent/routes/nodes.py +0 -413
  175. package/backend/src/flowent/routes/prompts.py +0 -46
  176. package/backend/src/flowent/routes/providers_route.py +0 -365
  177. package/backend/src/flowent/routes/roles.py +0 -207
  178. package/backend/src/flowent/routes/settings.py +0 -328
  179. package/backend/src/flowent/routes/tabs.py +0 -310
  180. package/backend/src/flowent/routes/ws.py +0 -33
  181. package/backend/src/flowent/runtime.py +0 -165
  182. package/backend/src/flowent/security.py +0 -57
  183. package/backend/src/flowent/settings.py +0 -2518
  184. package/backend/src/flowent/settings_management.py +0 -298
  185. package/backend/src/flowent/state_db.py +0 -120
  186. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +0 -1
  187. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +0 -1
  188. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +0 -7
  189. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +0 -1
  190. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +0 -1
  191. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +0 -3
  192. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +0 -1
  193. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +0 -3
  194. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +0 -1
  195. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  196. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +0 -3
  197. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  198. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +0 -1
  199. package/backend/src/flowent/static/assets/datetime-eJqd0V2S.js +0 -1
  200. package/backend/src/flowent/static/assets/dialog-BOvHIBrg.js +0 -1
  201. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  202. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  203. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  204. package/backend/src/flowent/static/assets/index-Biio-CoI.js +0 -10
  205. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +0 -1
  206. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  207. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  208. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +0 -1
  209. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  210. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +0 -1
  211. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  212. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +0 -1
  213. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +0 -1
  214. package/backend/src/flowent/static/assets/triState-DgLlKdRR.js +0 -1
  215. package/backend/src/flowent/static/assets/ui-vendor-UazN8rcv.js +0 -51
  216. package/backend/src/flowent/static/favicon.svg +0 -4
  217. package/backend/src/flowent/tools/__init__.py +0 -275
  218. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  236. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  237. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  238. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  239. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  240. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  241. package/backend/src/flowent/tools/connect.py +0 -100
  242. package/backend/src/flowent/tools/contacts.py +0 -22
  243. package/backend/src/flowent/tools/create_agent.py +0 -191
  244. package/backend/src/flowent/tools/create_tab.py +0 -61
  245. package/backend/src/flowent/tools/delete_tab.py +0 -39
  246. package/backend/src/flowent/tools/edit.py +0 -142
  247. package/backend/src/flowent/tools/exec.py +0 -118
  248. package/backend/src/flowent/tools/fetch.py +0 -85
  249. package/backend/src/flowent/tools/idle.py +0 -27
  250. package/backend/src/flowent/tools/list_roles.py +0 -75
  251. package/backend/src/flowent/tools/list_tabs.py +0 -100
  252. package/backend/src/flowent/tools/list_tools.py +0 -28
  253. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  254. package/backend/src/flowent/tools/manage_providers.py +0 -220
  255. package/backend/src/flowent/tools/manage_roles.py +0 -275
  256. package/backend/src/flowent/tools/manage_settings.py +0 -364
  257. package/backend/src/flowent/tools/mcp.py +0 -199
  258. package/backend/src/flowent/tools/read.py +0 -152
  259. package/backend/src/flowent/tools/send.py +0 -68
  260. package/backend/src/flowent/tools/set_permissions.py +0 -99
  261. package/backend/src/flowent/tools/sleep.py +0 -41
  262. package/backend/src/flowent/tools/todo.py +0 -51
  263. package/backend/src/flowent/workspace_store.py +0 -479
  264. package/backend/tests/__init__.py +0 -0
  265. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  266. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/conftest.py +0 -6
  268. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  271. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/integration/api/conftest.py +0 -29
  279. package/backend/tests/integration/api/test_access_api.py +0 -182
  280. package/backend/tests/integration/api/test_assistant_api.py +0 -354
  281. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  282. package/backend/tests/integration/api/test_mcp_api.py +0 -116
  283. package/backend/tests/integration/api/test_meta_api.py +0 -33
  284. package/backend/tests/integration/api/test_nodes_api.py +0 -722
  285. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  286. package/backend/tests/integration/api/test_roles_api.py +0 -228
  287. package/backend/tests/integration/api/test_tabs_api.py +0 -802
  288. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  291. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  293. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  295. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/agent/test_agent_public_api.py +0 -837
  297. package/backend/tests/unit/agent/test_agent_runtime.py +0 -2942
  298. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  300. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/logging/test_logging.py +0 -132
  302. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  303. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  304. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  305. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  306. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  307. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  308. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  312. package/backend/tests/unit/providers/test_errors.py +0 -68
  313. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  314. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  315. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  316. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  317. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  318. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/routes/test_prompts_routes.py +0 -104
  323. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  324. package/backend/tests/unit/routes/test_roles_routes.py +0 -535
  325. package/backend/tests/unit/routes/test_settings_routes.py +0 -1142
  326. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  327. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  328. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  329. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  330. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  331. package/backend/tests/unit/security/test_security.py +0 -124
  332. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/settings/test_settings_roles.py +0 -751
  334. package/backend/tests/unit/test_access.py +0 -45
  335. package/backend/tests/unit/test_cli.py +0 -124
  336. package/backend/tests/unit/test_graph_runtime.py +0 -72
  337. package/backend/tests/unit/test_network.py +0 -51
  338. package/backend/tests/unit/test_state_sqlite_storage.py +0 -159
  339. package/backend/tests/unit/test_workspace_store.py +0 -231
  340. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  346. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  347. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  348. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  349. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  350. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  351. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  352. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  353. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  354. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  355. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -436
  356. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  357. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  358. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  359. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  360. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -117
  361. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  362. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  363. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  364. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  365. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  366. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  367. package/backend/tests/unit/tools/test_tool_registry.py +0 -194
  368. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +0 -1
  369. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +0 -1
  370. package/dist/frontend/assets/McpPage-CHPm2TPY.js +0 -7
  371. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +0 -1
  372. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +0 -1
  373. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +0 -3
  374. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +0 -1
  375. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +0 -3
  376. package/dist/frontend/assets/ToolsPage-favNkj5C.js +0 -1
  377. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  378. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +0 -3
  379. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  380. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +0 -1
  381. package/dist/frontend/assets/datetime-eJqd0V2S.js +0 -1
  382. package/dist/frontend/assets/dialog-BOvHIBrg.js +0 -1
  383. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  384. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  385. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  386. package/dist/frontend/assets/index-Biio-CoI.js +0 -10
  387. package/dist/frontend/assets/index-CmQvO7sl.css +0 -1
  388. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  389. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  390. package/dist/frontend/assets/modelParams-DcEhGnu0.js +0 -1
  391. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  392. package/dist/frontend/assets/roles-BbIEIMeG.js +0 -1
  393. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  394. package/dist/frontend/assets/select-D9SwnlXF.js +0 -1
  395. package/dist/frontend/assets/surface-Bzr1FRG4.js +0 -1
  396. package/dist/frontend/assets/triState-DgLlKdRR.js +0 -1
  397. package/dist/frontend/assets/ui-vendor-UazN8rcv.js +0 -51
  398. package/dist/frontend/favicon.svg +0 -4
@@ -0,0 +1,449 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from fastapi.testclient import TestClient
5
+
6
+ from flowent.agent import FLOWENT_AGENT_SYSTEM_PROMPT
7
+ from flowent.main import create_app
8
+ from flowent.sandbox import SandboxRunner
9
+ from flowent.tools import ToolContext, run_tool
10
+
11
+
12
+ def stream_events(content: str) -> list[dict[str, object]]:
13
+ events: list[dict[str, object]] = []
14
+ for raw_event in content.strip().split("\n\n"):
15
+ event_type = ""
16
+ data = ""
17
+ for line in raw_event.splitlines():
18
+ if line.startswith("event: "):
19
+ event_type = line.removeprefix("event: ")
20
+ if line.startswith("data: "):
21
+ data = line.removeprefix("data: ")
22
+ events.append({"event": event_type, "data": json.loads(data)})
23
+ return events
24
+
25
+
26
+ def configure_provider(client: TestClient) -> None:
27
+ client.post(
28
+ "/api/providers",
29
+ json={
30
+ "api_key": "sk-local",
31
+ "base_url": "",
32
+ "id": "provider-openai",
33
+ "models": ["gpt-5.1"],
34
+ "name": "OpenAI",
35
+ "type": "openai",
36
+ },
37
+ )
38
+ client.put(
39
+ "/api/settings",
40
+ json={
41
+ "selected_model": "gpt-5.1",
42
+ "selected_provider_id": "provider-openai",
43
+ },
44
+ )
45
+
46
+
47
+ def tool_call_chunk(
48
+ name: str, arguments: dict[str, object], call_id: str = "call-1"
49
+ ) -> dict[str, object]:
50
+ return {
51
+ "choices": [
52
+ {
53
+ "delta": {
54
+ "tool_calls": [
55
+ {
56
+ "index": 0,
57
+ "id": call_id,
58
+ "type": "function",
59
+ "function": {
60
+ "name": name,
61
+ "arguments": json.dumps(arguments),
62
+ },
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ ]
68
+ }
69
+
70
+
71
+ def text_chunk(content: str) -> dict[str, object]:
72
+ return {"choices": [{"delta": {"content": content}}]}
73
+
74
+
75
+ def test_workspace_response_streams_tool_process_and_final_text(
76
+ tmp_path, monkeypatch
77
+ ) -> None:
78
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
79
+ workdir = tmp_path / "workdir"
80
+ workdir.mkdir()
81
+ (workdir / "notes.txt").write_text("Launch notes")
82
+ monkeypatch.chdir(workdir)
83
+ captured_requests: list[dict[str, object]] = []
84
+
85
+ async def fake_completion(**request: object) -> object:
86
+ captured_requests.append(request)
87
+
88
+ async def chunks() -> object:
89
+ if len(captured_requests) == 1:
90
+ yield tool_call_chunk("read_file", {"path": "notes.txt"})
91
+ else:
92
+ yield text_chunk("Read the notes.")
93
+
94
+ return chunks()
95
+
96
+ client = TestClient(
97
+ create_app(serve_frontend=False, chat_completion=fake_completion)
98
+ )
99
+ configure_provider(client)
100
+
101
+ response = client.post(
102
+ "/api/workspace/respond",
103
+ json={"content": "Use the notes."},
104
+ )
105
+
106
+ assert response.status_code == 200
107
+ events = stream_events(response.text)
108
+ assert [event["event"] for event in events] == [
109
+ "start",
110
+ "output_start",
111
+ "tool_start",
112
+ "tool_done",
113
+ "output_start",
114
+ "delta",
115
+ "done",
116
+ ]
117
+ assert events[1]["data"] == {"index": 1}
118
+ assert events[2]["data"]["tool"]["status"] == "running"
119
+ assert events[3]["data"]["status"] == "success"
120
+ assert events[4]["data"] == {"index": 2}
121
+ assert events[5]["data"] == {"content": "Read the notes."}
122
+ assert events[6]["data"]["message"]["content"] == "Read the notes."
123
+ assert len(captured_requests) == 2
124
+ assert captured_requests[0]["messages"][0] == {
125
+ "role": "system",
126
+ "content": FLOWENT_AGENT_SYSTEM_PROMPT,
127
+ }
128
+ second_messages = captured_requests[1]["messages"]
129
+ assert second_messages[0] == {
130
+ "role": "system",
131
+ "content": FLOWENT_AGENT_SYSTEM_PROMPT,
132
+ }
133
+ assert second_messages[-2]["tool_calls"][0]["function"]["name"] == "read_file"
134
+ assert second_messages[-1] == {
135
+ "role": "tool",
136
+ "tool_call_id": "call-1",
137
+ "content": "Launch notes",
138
+ }
139
+
140
+
141
+ def test_tools_can_read_paths_outside_workdir(tmp_path) -> None:
142
+ outside = tmp_path / "outside.txt"
143
+ outside.write_text("outside content")
144
+
145
+ result = run_tool(
146
+ "read_file", {"path": str(outside)}, ToolContext(cwd=tmp_path / "work")
147
+ )
148
+
149
+ assert result.ok
150
+ assert result.content == "outside content"
151
+
152
+
153
+ def test_list_dir_can_list_paths_outside_workdir(tmp_path) -> None:
154
+ outside = tmp_path / "outside"
155
+ outside.mkdir()
156
+ (outside / "file.txt").write_text("content")
157
+
158
+ result = run_tool(
159
+ "list_dir", {"path": str(outside)}, ToolContext(cwd=tmp_path / "work")
160
+ )
161
+
162
+ assert result.ok
163
+ assert "file.txt" in result.content
164
+
165
+
166
+ def test_grep_files_can_search_paths_outside_workdir(tmp_path) -> None:
167
+ outside = tmp_path / "outside"
168
+ outside.mkdir()
169
+ (outside / "file.txt").write_text("alpha beta")
170
+
171
+ result = run_tool(
172
+ "grep_files",
173
+ {"pattern": "alpha", "path": str(outside)},
174
+ ToolContext(cwd=tmp_path / "work"),
175
+ )
176
+
177
+ assert result.ok
178
+ assert "file.txt" in result.content
179
+
180
+
181
+ def test_shell_command_can_write_workdir_and_tmp(tmp_path) -> None:
182
+ result = run_tool(
183
+ "shell_command",
184
+ {"command": "echo ok > work.txt && echo tmp > /tmp/flowent-tool-test.txt"},
185
+ ToolContext(cwd=tmp_path),
186
+ )
187
+
188
+ assert result.ok
189
+ assert (tmp_path / "work.txt").read_text().strip() == "ok"
190
+
191
+
192
+ def test_shell_command_cannot_write_outside_workdir_and_tmp(tmp_path) -> None:
193
+ outside = Path("/project/flowent/backend/tests/flowent-outside-denied.txt")
194
+ if outside.exists():
195
+ outside.unlink()
196
+
197
+ result = run_tool(
198
+ "shell_command",
199
+ {"command": f"echo denied > {outside}"},
200
+ ToolContext(cwd=tmp_path),
201
+ )
202
+
203
+ assert not result.ok
204
+ assert not outside.exists()
205
+
206
+
207
+ def test_shell_command_has_network_by_default(tmp_path) -> None:
208
+ result = run_tool(
209
+ "shell_command",
210
+ {
211
+ "command": "python - <<'PY'\nimport socket\ns=socket.socket()\nprint('network-ready')\nPY"
212
+ },
213
+ ToolContext(cwd=tmp_path),
214
+ )
215
+
216
+ assert result.ok
217
+ assert "network-ready" in result.content
218
+
219
+
220
+ def test_shell_command_denies_ptrace_when_seccomp_is_available(tmp_path) -> None:
221
+ command = SandboxRunner(cwd=tmp_path).build_command(["/bin/true"])
222
+ if not command.seccomp_available:
223
+ assert command.args[0].endswith("bwrap")
224
+ return
225
+
226
+ result = run_tool(
227
+ "shell_command",
228
+ {
229
+ "command": "python - <<'PY'\nimport ctypes, os\nprint(ctypes.CDLL(None).ptrace(0, 0, None, None))\nPY"
230
+ },
231
+ ToolContext(cwd=tmp_path),
232
+ )
233
+
234
+ assert not result.ok or "-1" in result.content
235
+
236
+
237
+ def test_apply_patch_modifies_workdir_file(tmp_path) -> None:
238
+ target = tmp_path / "notes.txt"
239
+ target.write_text("alpha\nbeta\n")
240
+ patch = """*** Begin Patch
241
+ *** Update File: notes.txt
242
+ @@
243
+ -beta
244
+ +ready
245
+ *** End Patch
246
+ """
247
+
248
+ result = run_tool("apply_patch", {"patch": patch}, ToolContext(cwd=tmp_path))
249
+
250
+ assert result.ok
251
+ assert target.read_text() == "alpha\nready\n"
252
+
253
+
254
+ def test_apply_patch_rejects_outside_workdir_file(tmp_path) -> None:
255
+ outside = Path(__file__).resolve().parent / "outside-patch.txt"
256
+ outside.write_text("alpha\n")
257
+ try:
258
+ patch = f"""*** Begin Patch
259
+ *** Update File: {outside}
260
+ @@
261
+ -alpha
262
+ +beta
263
+ *** End Patch
264
+ """
265
+
266
+ result = run_tool("apply_patch", {"patch": patch}, ToolContext(cwd=tmp_path))
267
+
268
+ assert not result.ok
269
+ assert outside.read_text() == "alpha\n"
270
+ finally:
271
+ outside.unlink(missing_ok=True)
272
+
273
+
274
+ def test_apply_patch_uses_internal_subcommand(tmp_path, monkeypatch) -> None:
275
+ calls: list[list[str]] = []
276
+
277
+ def fake_run(self, command, **kwargs):
278
+ calls.append(command)
279
+ from flowent.sandbox import CommandResult
280
+
281
+ return CommandResult(
282
+ command=" ".join(command), exit_code=0, stderr="", stdout="{}"
283
+ )
284
+
285
+ monkeypatch.setattr(SandboxRunner, "run", fake_run)
286
+ patch = """*** Begin Patch
287
+ *** Add File: created.txt
288
+ +hello
289
+ *** End Patch
290
+ """
291
+
292
+ result = run_tool("apply_patch", {"patch": patch}, ToolContext(cwd=tmp_path))
293
+
294
+ assert result.ok
295
+ assert calls
296
+ assert calls[0][1:4] == ["-m", "flowent.cli", "apply-patch"]
297
+
298
+
299
+ def test_web_search_result_enters_tool_output(tmp_path) -> None:
300
+ def fake_search(query: str):
301
+ return [{"title": "Result", "url": "https://example.test", "snippet": query}]
302
+
303
+ result = run_tool(
304
+ "web_search",
305
+ {"query": "release checklist"},
306
+ ToolContext(cwd=tmp_path, web_searcher=fake_search),
307
+ )
308
+
309
+ assert result.ok
310
+ assert "https://example.test" in result.content
311
+
312
+
313
+ def test_agent_continues_until_final_text_after_multiple_tool_rounds(
314
+ tmp_path, monkeypatch
315
+ ) -> None:
316
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
317
+ workdir = tmp_path / "workdir"
318
+ workdir.mkdir()
319
+ (workdir / "notes.txt").write_text("Launch notes")
320
+ monkeypatch.chdir(workdir)
321
+ captured_requests: list[dict[str, object]] = []
322
+
323
+ async def fake_completion(**request: object) -> object:
324
+ captured_requests.append(request)
325
+
326
+ async def chunks() -> object:
327
+ if len(captured_requests) == 1:
328
+ yield tool_call_chunk("list_dir", {"path": "."}, call_id="call-list")
329
+ elif len(captured_requests) == 2:
330
+ yield tool_call_chunk(
331
+ "read_file", {"path": "notes.txt"}, call_id="call-read"
332
+ )
333
+ else:
334
+ yield text_chunk("The notes are ready.")
335
+
336
+ return chunks()
337
+
338
+ client = TestClient(
339
+ create_app(serve_frontend=False, chat_completion=fake_completion)
340
+ )
341
+ configure_provider(client)
342
+
343
+ response = client.post(
344
+ "/api/workspace/respond",
345
+ json={"content": "Inspect the workspace."},
346
+ )
347
+
348
+ assert response.status_code == 200
349
+ events = stream_events(response.text)
350
+ assert [event["event"] for event in events] == [
351
+ "start",
352
+ "output_start",
353
+ "tool_start",
354
+ "tool_done",
355
+ "output_start",
356
+ "tool_start",
357
+ "tool_done",
358
+ "output_start",
359
+ "delta",
360
+ "done",
361
+ ]
362
+ assert len(captured_requests) == 3
363
+ assert captured_requests[2]["messages"][-1] == {
364
+ "role": "tool",
365
+ "tool_call_id": "call-read",
366
+ "content": "Launch notes",
367
+ }
368
+ assert events[-1]["data"]["message"]["content"] == "The notes are ready."
369
+
370
+
371
+ def test_agent_finishes_without_tools(tmp_path, monkeypatch) -> None:
372
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
373
+ monkeypatch.chdir(tmp_path)
374
+ captured_requests: list[dict[str, object]] = []
375
+
376
+ async def fake_completion(**request: object) -> object:
377
+ captured_requests.append(request)
378
+
379
+ async def chunks() -> object:
380
+ yield text_chunk("Direct answer.")
381
+
382
+ return chunks()
383
+
384
+ client = TestClient(
385
+ create_app(serve_frontend=False, chat_completion=fake_completion)
386
+ )
387
+ configure_provider(client)
388
+
389
+ response = client.post(
390
+ "/api/workspace/respond",
391
+ json={"content": "Answer directly."},
392
+ )
393
+
394
+ assert response.status_code == 200
395
+ events = stream_events(response.text)
396
+ assert [event["event"] for event in events] == [
397
+ "start",
398
+ "output_start",
399
+ "delta",
400
+ "done",
401
+ ]
402
+ assert len(captured_requests) == 1
403
+ assert events[-1]["data"]["message"]["content"] == "Direct answer."
404
+
405
+
406
+ def test_tool_failure_is_reported_and_agent_continues(tmp_path, monkeypatch) -> None:
407
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
408
+ monkeypatch.chdir(tmp_path)
409
+ captured_requests: list[dict[str, object]] = []
410
+
411
+ async def fake_completion(**request: object) -> object:
412
+ captured_requests.append(request)
413
+
414
+ async def chunks() -> object:
415
+ if len(captured_requests) == 1:
416
+ yield tool_call_chunk("read_file", {"path": "missing.txt"})
417
+ else:
418
+ yield text_chunk("I could not read it.")
419
+
420
+ return chunks()
421
+
422
+ client = TestClient(
423
+ create_app(serve_frontend=False, chat_completion=fake_completion)
424
+ )
425
+ configure_provider(client)
426
+
427
+ response = client.post(
428
+ "/api/workspace/respond",
429
+ json={"content": "Read it."},
430
+ )
431
+
432
+ events = stream_events(response.text)
433
+ assert "tool_error" in [event["event"] for event in events]
434
+ assert len(captured_requests) == 2
435
+ assert captured_requests[1]["messages"][-1]["role"] == "tool"
436
+ assert captured_requests[1]["messages"][-1]["tool_call_id"] == "call-1"
437
+ assert "missing.txt" in captured_requests[1]["messages"][-1]["content"]
438
+ assert events[-1]["data"]["message"]["content"] == "I could not read it."
439
+
440
+
441
+ def test_update_plan_outputs_plan_state(tmp_path) -> None:
442
+ result = run_tool(
443
+ "update_plan",
444
+ {"items": [{"step": "Read files", "status": "completed"}]},
445
+ ToolContext(cwd=tmp_path),
446
+ )
447
+
448
+ assert result.ok
449
+ assert "Read files" in result.content
@@ -0,0 +1,12 @@
1
+ from fastapi.testclient import TestClient
2
+
3
+ from flowent.main import create_app
4
+
5
+
6
+ def test_health_endpoint() -> None:
7
+ client = TestClient(create_app(serve_frontend=False))
8
+
9
+ response = client.get("/api/health")
10
+
11
+ assert response.status_code == 200
12
+ assert response.json() == {"status": "ok"}
@@ -0,0 +1,113 @@
1
+ import pytest
2
+
3
+ from flowent.llm import (
4
+ ChatMessage,
5
+ ProviderConnection,
6
+ ProviderFormat,
7
+ build_litellm_request,
8
+ complete_chat,
9
+ stream_chat,
10
+ )
11
+
12
+
13
+ def test_supported_provider_formats_match_product_choices() -> None:
14
+ assert [provider.value for provider in ProviderFormat] == [
15
+ "openai",
16
+ "openai_responses",
17
+ "anthropic",
18
+ "gemini",
19
+ ]
20
+
21
+
22
+ def test_build_litellm_request_maps_provider_connection_to_completion_args() -> None:
23
+ connection = ProviderConnection(
24
+ name="Primary",
25
+ provider=ProviderFormat.ANTHROPIC,
26
+ model="claude-sonnet-4-5",
27
+ secret_reference="connection-primary",
28
+ base_url="https://example.test/v1",
29
+ )
30
+ messages = [
31
+ ChatMessage(role="system", content="Keep answers direct."),
32
+ ChatMessage(role="user", content="Draft a launch checklist."),
33
+ ]
34
+
35
+ request = build_litellm_request(connection, messages)
36
+
37
+ assert request == {
38
+ "api_base": "https://example.test/v1",
39
+ "api_key": "connection-primary",
40
+ "messages": [
41
+ {"role": "system", "content": "Keep answers direct."},
42
+ {"role": "user", "content": "Draft a launch checklist."},
43
+ ],
44
+ "model": "anthropic/claude-sonnet-4-5",
45
+ }
46
+
47
+
48
+ @pytest.mark.anyio
49
+ async def test_complete_chat_uses_injected_litellm_completion() -> None:
50
+ captured_request: dict[str, object] = {}
51
+
52
+ async def fake_completion(**request: object) -> dict[str, object]:
53
+ captured_request.update(request)
54
+ return {
55
+ "choices": [
56
+ {
57
+ "message": {
58
+ "content": "Here is the checklist.",
59
+ "role": "assistant",
60
+ },
61
+ }
62
+ ]
63
+ }
64
+
65
+ connection = ProviderConnection(
66
+ name="Responses",
67
+ provider=ProviderFormat.OPENAI_RESPONSES,
68
+ model="gpt-5.1",
69
+ secret_reference="connection-responses",
70
+ )
71
+
72
+ answer = await complete_chat(
73
+ connection,
74
+ [ChatMessage(role="user", content="Create a checklist.")],
75
+ completion=fake_completion,
76
+ )
77
+
78
+ assert captured_request["model"] == "openai/gpt-5.1"
79
+ assert answer == ChatMessage(role="assistant", content="Here is the checklist.")
80
+
81
+
82
+ @pytest.mark.anyio
83
+ async def test_stream_chat_uses_litellm_streaming() -> None:
84
+ captured_request: dict[str, object] = {}
85
+
86
+ async def fake_completion(**request: object) -> object:
87
+ captured_request.update(request)
88
+
89
+ async def chunks() -> object:
90
+ yield {"choices": [{"delta": {"content": "Here is "}}]}
91
+ yield {"choices": [{"delta": {"content": "the checklist."}}]}
92
+
93
+ return chunks()
94
+
95
+ connection = ProviderConnection(
96
+ name="Responses",
97
+ provider=ProviderFormat.OPENAI_RESPONSES,
98
+ model="gpt-5.1",
99
+ secret_reference="connection-responses",
100
+ )
101
+
102
+ chunks = [
103
+ chunk
104
+ async for chunk in stream_chat(
105
+ connection,
106
+ [ChatMessage(role="user", content="Create a checklist.")],
107
+ completion=fake_completion,
108
+ )
109
+ ]
110
+
111
+ assert captured_request["stream"] is True
112
+ assert captured_request["model"] == "openai/gpt-5.1"
113
+ assert chunks == ["Here is ", "the checklist."]