flowent 0.0.1 → 0.0.4

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 (493) hide show
  1. package/README.md +19 -8
  2. package/backend/.python-version +1 -0
  3. package/backend/pyproject.toml +57 -0
  4. package/backend/src/flowent/__init__.py +3 -0
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  23. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  24. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  25. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  26. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  27. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  28. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  29. package/backend/src/flowent/__pycache__/stats_service.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  31. package/backend/src/flowent/_version.py +7 -0
  32. package/backend/src/flowent/access.py +247 -0
  33. package/backend/src/flowent/agent.py +2808 -0
  34. package/backend/src/flowent/assistant_commands.py +106 -0
  35. package/backend/src/flowent/channels/__init__.py +3 -0
  36. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  37. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  38. package/backend/src/flowent/channels/telegram.py +615 -0
  39. package/backend/src/flowent/cli.py +85 -0
  40. package/backend/src/flowent/config.py +14 -0
  41. package/backend/src/flowent/dev.py +3 -0
  42. package/backend/src/flowent/events.py +157 -0
  43. package/backend/src/flowent/graph_runtime.py +60 -0
  44. package/backend/src/flowent/graph_service.py +1346 -0
  45. package/backend/src/flowent/image_assets.py +356 -0
  46. package/backend/src/flowent/logging.py +155 -0
  47. package/backend/src/flowent/main.py +124 -0
  48. package/backend/src/flowent/mcp_service.py +1904 -0
  49. package/backend/src/flowent/model_metadata.py +98 -0
  50. package/backend/src/flowent/models/__init__.py +121 -0
  51. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  52. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  53. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  54. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  55. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/models/agent.py +33 -0
  65. package/backend/src/flowent/models/base.py +24 -0
  66. package/backend/src/flowent/models/blueprint.py +176 -0
  67. package/backend/src/flowent/models/content.py +164 -0
  68. package/backend/src/flowent/models/delta.py +44 -0
  69. package/backend/src/flowent/models/event.py +51 -0
  70. package/backend/src/flowent/models/graph.py +437 -0
  71. package/backend/src/flowent/models/history.py +214 -0
  72. package/backend/src/flowent/models/llm.py +61 -0
  73. package/backend/src/flowent/models/message.py +27 -0
  74. package/backend/src/flowent/models/tab.py +48 -0
  75. package/backend/src/flowent/models/todo.py +10 -0
  76. package/backend/src/flowent/network.py +146 -0
  77. package/backend/src/flowent/prompts/__init__.py +67 -0
  78. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  81. package/backend/src/flowent/prompts/common.py +250 -0
  82. package/backend/src/flowent/prompts/steward.py +64 -0
  83. package/backend/src/flowent/providers/__init__.py +23 -0
  84. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  85. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  86. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  87. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  88. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/providers/anthropic.py +468 -0
  99. package/backend/src/flowent/providers/base_url.py +60 -0
  100. package/backend/src/flowent/providers/configuration.py +182 -0
  101. package/backend/src/flowent/providers/content.py +122 -0
  102. package/backend/src/flowent/providers/errors.py +223 -0
  103. package/backend/src/flowent/providers/gateway.py +169 -0
  104. package/backend/src/flowent/providers/gemini.py +447 -0
  105. package/backend/src/flowent/providers/headers.py +20 -0
  106. package/backend/src/flowent/providers/management.py +96 -0
  107. package/backend/src/flowent/providers/ollama.py +293 -0
  108. package/backend/src/flowent/providers/openai.py +422 -0
  109. package/backend/src/flowent/providers/openai_responses.py +655 -0
  110. package/backend/src/flowent/providers/registry.py +144 -0
  111. package/backend/src/flowent/providers/sse.py +31 -0
  112. package/backend/src/flowent/providers/thinking.py +79 -0
  113. package/backend/src/flowent/registry.py +73 -0
  114. package/backend/src/flowent/role_management.py +255 -0
  115. package/backend/src/flowent/routes/__init__.py +30 -0
  116. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  122. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/routes/access.py +48 -0
  131. package/backend/src/flowent/routes/assistant.py +155 -0
  132. package/backend/src/flowent/routes/image_assets.py +33 -0
  133. package/backend/src/flowent/routes/mcp.py +125 -0
  134. package/backend/src/flowent/routes/meta.py +28 -0
  135. package/backend/src/flowent/routes/nodes.py +365 -0
  136. package/backend/src/flowent/routes/prompts.py +46 -0
  137. package/backend/src/flowent/routes/providers_route.py +364 -0
  138. package/backend/src/flowent/routes/roles.py +207 -0
  139. package/backend/src/flowent/routes/settings.py +324 -0
  140. package/backend/src/flowent/routes/stats.py +229 -0
  141. package/backend/src/flowent/routes/tabs.py +292 -0
  142. package/backend/src/flowent/routes/ws.py +33 -0
  143. package/backend/src/flowent/runtime.py +188 -0
  144. package/backend/src/flowent/sandbox.py +45 -0
  145. package/backend/src/flowent/security.py +42 -0
  146. package/backend/src/flowent/settings.py +2467 -0
  147. package/backend/src/flowent/settings_management.py +286 -0
  148. package/backend/src/flowent/state_db.py +120 -0
  149. package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
  150. package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
  151. package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
  152. package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
  153. package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
  154. package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
  155. package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
  156. package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
  157. package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
  158. package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
  159. package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
  160. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  161. package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  162. package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
  163. package/backend/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
  164. package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
  165. package/backend/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
  166. package/backend/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
  167. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  168. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
  169. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
  170. package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
  171. package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
  172. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
  173. package/backend/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
  174. package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
  175. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
  176. package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
  177. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  178. package/backend/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
  179. package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
  180. package/backend/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
  181. package/backend/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
  182. package/backend/src/flowent/static/index.html +36 -0
  183. package/backend/src/flowent/stats_service.py +218 -0
  184. package/backend/src/flowent/tools/__init__.py +201 -0
  185. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  186. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  187. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  188. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  189. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  190. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  191. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  192. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  193. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  194. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  195. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  196. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  197. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  198. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  199. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  200. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  201. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  202. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  203. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  204. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  205. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  206. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  207. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  208. package/backend/src/flowent/tools/connect.py +156 -0
  209. package/backend/src/flowent/tools/contacts.py +22 -0
  210. package/backend/src/flowent/tools/create_agent.py +270 -0
  211. package/backend/src/flowent/tools/create_tab.py +59 -0
  212. package/backend/src/flowent/tools/delete_tab.py +39 -0
  213. package/backend/src/flowent/tools/edit.py +142 -0
  214. package/backend/src/flowent/tools/exec.py +117 -0
  215. package/backend/src/flowent/tools/fetch.py +85 -0
  216. package/backend/src/flowent/tools/idle.py +27 -0
  217. package/backend/src/flowent/tools/list_roles.py +50 -0
  218. package/backend/src/flowent/tools/list_tabs.py +96 -0
  219. package/backend/src/flowent/tools/list_tools.py +24 -0
  220. package/backend/src/flowent/tools/manage_prompts.py +102 -0
  221. package/backend/src/flowent/tools/manage_providers.py +220 -0
  222. package/backend/src/flowent/tools/manage_roles.py +275 -0
  223. package/backend/src/flowent/tools/manage_settings.py +346 -0
  224. package/backend/src/flowent/tools/mcp.py +199 -0
  225. package/backend/src/flowent/tools/read.py +152 -0
  226. package/backend/src/flowent/tools/send.py +50 -0
  227. package/backend/src/flowent/tools/set_permissions.py +84 -0
  228. package/backend/src/flowent/tools/sleep.py +41 -0
  229. package/backend/src/flowent/tools/todo.py +51 -0
  230. package/backend/src/flowent/workspace_store.py +479 -0
  231. package/backend/tests/__init__.py +0 -0
  232. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  233. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  234. package/backend/tests/conftest.py +6 -0
  235. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  236. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  237. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  238. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  239. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  240. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  241. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  242. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  243. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  244. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  245. package/backend/tests/integration/api/conftest.py +29 -0
  246. package/backend/tests/integration/api/test_access_api.py +182 -0
  247. package/backend/tests/integration/api/test_assistant_api.py +354 -0
  248. package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
  249. package/backend/tests/integration/api/test_mcp_api.py +116 -0
  250. package/backend/tests/integration/api/test_meta_api.py +33 -0
  251. package/backend/tests/integration/api/test_nodes_api.py +486 -0
  252. package/backend/tests/integration/api/test_prompts_api.py +47 -0
  253. package/backend/tests/integration/api/test_roles_api.py +227 -0
  254. package/backend/tests/integration/api/test_tabs_api.py +501 -0
  255. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  256. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  257. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  258. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  261. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/unit/agent/test_agent_public_api.py +746 -0
  264. package/backend/tests/unit/agent/test_agent_runtime.py +2726 -0
  265. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
  267. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/unit/logging/test_logging.py +132 -0
  269. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/unit/prompts/test_prompts.py +569 -0
  271. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
  279. package/backend/tests/unit/providers/test_errors.py +68 -0
  280. package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
  281. package/backend/tests/unit/providers/test_openai_provider.py +139 -0
  282. package/backend/tests/unit/providers/test_openai_responses.py +402 -0
  283. package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
  284. package/backend/tests/unit/providers/test_think_tag_parser.py +36 -0
  285. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  288. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
  291. package/backend/tests/unit/routes/test_providers_route.py +368 -0
  292. package/backend/tests/unit/routes/test_roles_routes.py +426 -0
  293. package/backend/tests/unit/routes/test_settings_routes.py +1138 -0
  294. package/backend/tests/unit/routes/test_stats_routes.py +149 -0
  295. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +1012 -0
  297. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -0
  299. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/security/test_security.py +110 -0
  301. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/settings/test_settings_roles.py +711 -0
  303. package/backend/tests/unit/test_access.py +45 -0
  304. package/backend/tests/unit/test_cli.py +124 -0
  305. package/backend/tests/unit/test_graph_runtime.py +72 -0
  306. package/backend/tests/unit/test_network.py +51 -0
  307. package/backend/tests/unit/test_state_sqlite_storage.py +93 -0
  308. package/backend/tests/unit/test_workspace_store.py +231 -0
  309. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  314. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  315. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  316. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  323. package/backend/tests/unit/tools/test_connect_tool.py +229 -0
  324. package/backend/tests/unit/tools/test_create_agent_tool.py +524 -0
  325. package/backend/tests/unit/tools/test_delete_tab_tool.py +83 -0
  326. package/backend/tests/unit/tools/test_edit_tool.py +115 -0
  327. package/backend/tests/unit/tools/test_exec_tool.py +81 -0
  328. package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
  329. package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
  330. package/backend/tests/unit/tools/test_manage_providers_tool.py +458 -0
  331. package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
  332. package/backend/tests/unit/tools/test_manage_settings_tool.py +608 -0
  333. package/backend/tests/unit/tools/test_read_tool.py +33 -0
  334. package/backend/tests/unit/tools/test_set_permissions_tool.py +391 -0
  335. package/backend/tests/unit/tools/test_todo_tool.py +37 -0
  336. package/backend/tests/unit/tools/test_tool_registry.py +91 -0
  337. package/backend/uv.lock +1144 -0
  338. package/bin/flowent.mjs +62 -36
  339. package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +1 -0
  340. package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +1 -0
  341. package/dist/frontend/assets/HomePage-C0hAx9_l.js +3 -0
  342. package/dist/frontend/assets/McpPage-DkrYLvBv.js +7 -0
  343. package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +1 -0
  344. package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +1 -0
  345. package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +3 -0
  346. package/dist/frontend/assets/RolesPage-CqcclGRw.js +1 -0
  347. package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +3 -0
  348. package/dist/frontend/assets/StatsPage-BX9khYzu.js +1 -0
  349. package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +1 -0
  350. package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  351. package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  352. package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +1 -0
  353. package/dist/frontend/assets/badge-74-3jsCg.js +1 -0
  354. package/dist/frontend/assets/constants-XUzFf6i1.js +1 -0
  355. package/dist/frontend/assets/datetime-m6_O_Ci9.js +1 -0
  356. package/dist/frontend/assets/dialog-BeGSweF6.js +1 -0
  357. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  358. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
  359. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
  360. package/dist/frontend/assets/index-BHC1Vhy8.css +1 -0
  361. package/dist/frontend/assets/index-CL1ALZ3r.js +10 -0
  362. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
  363. package/dist/frontend/assets/markdown-vendor-DVdy_w12.js +29 -0
  364. package/dist/frontend/assets/modelParams-CaHd0903.js +1 -0
  365. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
  366. package/dist/frontend/assets/roles-2OLDeTc5.js +1 -0
  367. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  368. package/dist/frontend/assets/select-DL_LPeDj.js +1 -0
  369. package/dist/frontend/assets/shared-CMxbpLeQ.js +1 -0
  370. package/dist/frontend/assets/triState-DEr3NkXV.js +1 -0
  371. package/dist/frontend/assets/ui-vendor-Dg9NNnWX.js +51 -0
  372. package/dist/frontend/index.html +36 -0
  373. package/package.json +27 -41
  374. package/dist/.next/BUILD_ID +0 -1
  375. package/dist/.next/app-path-routes-manifest.json +0 -6
  376. package/dist/.next/build-manifest.json +0 -20
  377. package/dist/.next/package.json +0 -1
  378. package/dist/.next/prerender-manifest.json +0 -114
  379. package/dist/.next/required-server-files.json +0 -333
  380. package/dist/.next/routes-manifest.json +0 -69
  381. package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  382. package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
  383. package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  384. package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  385. package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  386. package/dist/.next/server/app/_global-error/page.js +0 -9
  387. package/dist/.next/server/app/_global-error/page.js.map +0 -5
  388. package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
  389. package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
  390. package/dist/.next/server/app/_global-error.html +0 -1
  391. package/dist/.next/server/app/_global-error.meta +0 -15
  392. package/dist/.next/server/app/_global-error.rsc +0 -14
  393. package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  394. package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
  395. package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  396. package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
  397. package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  398. package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  399. package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
  400. package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
  401. package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  402. package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  403. package/dist/.next/server/app/_not-found/page.js +0 -13
  404. package/dist/.next/server/app/_not-found/page.js.map +0 -5
  405. package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
  406. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
  407. package/dist/.next/server/app/_not-found.html +0 -1
  408. package/dist/.next/server/app/_not-found.meta +0 -16
  409. package/dist/.next/server/app/_not-found.rsc +0 -16
  410. package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
  411. package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  412. package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
  413. package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  414. package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
  415. package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  416. package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
  417. package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
  418. package/dist/.next/server/app/icon.svg/route.js +0 -6
  419. package/dist/.next/server/app/icon.svg/route.js.map +0 -5
  420. package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
  421. package/dist/.next/server/app/icon.svg.meta +0 -1
  422. package/dist/.next/server/app/index.html +0 -1
  423. package/dist/.next/server/app/index.meta +0 -14
  424. package/dist/.next/server/app/index.rsc +0 -15
  425. package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
  426. package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
  427. package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
  428. package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
  429. package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
  430. package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
  431. package/dist/.next/server/app/page/build-manifest.json +0 -16
  432. package/dist/.next/server/app/page/next-font-manifest.json +0 -10
  433. package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
  434. package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
  435. package/dist/.next/server/app/page.js +0 -14
  436. package/dist/.next/server/app/page.js.map +0 -5
  437. package/dist/.next/server/app/page.js.nft.json +0 -1
  438. package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
  439. package/dist/.next/server/app-paths-manifest.json +0 -6
  440. package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
  441. package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
  442. package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
  443. package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
  444. package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
  445. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
  446. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
  447. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
  448. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
  449. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
  450. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
  451. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
  452. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
  453. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
  454. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
  455. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
  456. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
  457. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
  458. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
  459. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
  460. package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
  461. package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  462. package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  463. package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  464. package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
  465. package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
  466. package/dist/.next/server/functions-config-manifest.json +0 -4
  467. package/dist/.next/server/middleware-build-manifest.js +0 -20
  468. package/dist/.next/server/middleware-manifest.json +0 -6
  469. package/dist/.next/server/next-font-manifest.js +0 -1
  470. package/dist/.next/server/next-font-manifest.json +0 -13
  471. package/dist/.next/server/pages/404.html +0 -1
  472. package/dist/.next/server/pages/500.html +0 -1
  473. package/dist/.next/server/pages-manifest.json +0 -4
  474. package/dist/.next/server/prefetch-hints.json +0 -1
  475. package/dist/.next/server/server-reference-manifest.js +0 -1
  476. package/dist/.next/server/server-reference-manifest.json +0 -5
  477. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
  478. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
  479. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
  480. package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
  481. package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
  482. package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
  483. package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
  484. package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
  485. package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
  486. package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
  487. package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
  488. package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
  489. package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
  490. package/dist/package.json +0 -88
  491. package/dist/server.js +0 -38
  492. /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
  493. /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+ from typing import Protocol
5
+
6
+ from flowent.providers import LLMProvider
7
+ from flowent.providers.base_url import resolve_provider_base_url
8
+
9
+
10
+ class ProviderType(StrEnum):
11
+ OPENAI_COMPATIBLE = "openai_compatible"
12
+ OPENAI_RESPONSES = "openai_responses"
13
+ ANTHROPIC = "anthropic"
14
+ GEMINI = "gemini"
15
+
16
+
17
+ class ProviderFactory(Protocol):
18
+ def __call__(
19
+ self,
20
+ *,
21
+ provider_name: str,
22
+ api_base_url: str,
23
+ api_key: str,
24
+ headers: dict[str, str],
25
+ model: str,
26
+ request_timeout_seconds: float,
27
+ ) -> LLMProvider: ...
28
+
29
+
30
+ def _build_openai_compatible_provider(
31
+ *,
32
+ provider_name: str,
33
+ api_base_url: str,
34
+ api_key: str,
35
+ headers: dict[str, str],
36
+ model: str,
37
+ request_timeout_seconds: float,
38
+ ) -> LLMProvider:
39
+ from flowent.providers.openai import OpenAIProvider
40
+
41
+ return OpenAIProvider(
42
+ provider_name=provider_name,
43
+ api_base_url=api_base_url,
44
+ api_key=api_key,
45
+ headers=headers,
46
+ model=model,
47
+ request_timeout_seconds=request_timeout_seconds,
48
+ )
49
+
50
+
51
+ def _build_openai_responses_provider(
52
+ *,
53
+ provider_name: str,
54
+ api_base_url: str,
55
+ api_key: str,
56
+ headers: dict[str, str],
57
+ model: str,
58
+ request_timeout_seconds: float,
59
+ ) -> LLMProvider:
60
+ from flowent.providers.openai_responses import OpenAIResponsesProvider
61
+
62
+ return OpenAIResponsesProvider(
63
+ provider_name=provider_name,
64
+ api_base_url=api_base_url,
65
+ api_key=api_key,
66
+ headers=headers,
67
+ model=model,
68
+ request_timeout_seconds=request_timeout_seconds,
69
+ )
70
+
71
+
72
+ def _build_anthropic_provider(
73
+ *,
74
+ provider_name: str,
75
+ api_base_url: str,
76
+ api_key: str,
77
+ headers: dict[str, str],
78
+ model: str,
79
+ request_timeout_seconds: float,
80
+ ) -> LLMProvider:
81
+ from flowent.providers.anthropic import AnthropicProvider
82
+
83
+ return AnthropicProvider(
84
+ provider_name=provider_name,
85
+ api_base_url=api_base_url,
86
+ api_key=api_key,
87
+ headers=headers,
88
+ model=model,
89
+ request_timeout_seconds=request_timeout_seconds,
90
+ )
91
+
92
+
93
+ def _build_gemini_provider(
94
+ *,
95
+ provider_name: str,
96
+ api_base_url: str,
97
+ api_key: str,
98
+ headers: dict[str, str],
99
+ model: str,
100
+ request_timeout_seconds: float,
101
+ ) -> LLMProvider:
102
+ from flowent.providers.gemini import GeminiProvider
103
+
104
+ return GeminiProvider(
105
+ provider_name=provider_name,
106
+ api_base_url=api_base_url,
107
+ api_key=api_key,
108
+ headers=headers,
109
+ model=model,
110
+ request_timeout_seconds=request_timeout_seconds,
111
+ )
112
+
113
+
114
+ PROVIDER_FACTORIES: dict[ProviderType, ProviderFactory] = {
115
+ ProviderType.OPENAI_COMPATIBLE: _build_openai_compatible_provider,
116
+ ProviderType.OPENAI_RESPONSES: _build_openai_responses_provider,
117
+ ProviderType.ANTHROPIC: _build_anthropic_provider,
118
+ ProviderType.GEMINI: _build_gemini_provider,
119
+ }
120
+
121
+
122
+ def create_provider(
123
+ provider_type: str,
124
+ base_url: str,
125
+ api_key: str = "",
126
+ headers: dict[str, str] | None = None,
127
+ model: str = "",
128
+ provider_name: str = "",
129
+ request_timeout_seconds: float = 120.0,
130
+ ) -> LLMProvider:
131
+ try:
132
+ normalized_type = ProviderType(provider_type.lower())
133
+ except ValueError as exc:
134
+ raise ValueError(f"Unknown provider type: {provider_type}") from exc
135
+
136
+ resolved_base_url = resolve_provider_base_url(normalized_type, base_url)
137
+ return PROVIDER_FACTORIES[normalized_type](
138
+ provider_name=provider_name,
139
+ api_base_url=resolved_base_url,
140
+ api_key=api_key,
141
+ headers=headers or {},
142
+ model=model,
143
+ request_timeout_seconds=request_timeout_seconds,
144
+ )
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Iterator
5
+ from typing import Any
6
+
7
+ from flowent.network import iter_response_lines
8
+
9
+
10
+ def iter_sse_data(response: Any) -> Iterator[str]:
11
+ for line in iter_response_lines(response):
12
+ if not line or line.startswith(":"):
13
+ continue
14
+ if not line.startswith("data: "):
15
+ continue
16
+ yield line[6:]
17
+
18
+
19
+ def iter_sse_json(
20
+ response: Any,
21
+ done_token: str | None = None,
22
+ ) -> Iterator[dict[str, Any]]:
23
+ for payload in iter_sse_data(response):
24
+ if done_token is not None and payload.strip() == done_token:
25
+ break
26
+ try:
27
+ parsed = json.loads(payload)
28
+ except json.JSONDecodeError:
29
+ continue
30
+ if isinstance(parsed, dict):
31
+ yield parsed
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class ThinkTagParser:
5
+ OPEN_TAG = "<think>"
6
+ CLOSE_TAG = "</think>"
7
+
8
+ def __init__(self) -> None:
9
+ self._inside = False
10
+ self._pending = ""
11
+ self._skip_newlines = False
12
+
13
+ def feed(self, text: str) -> list[tuple[str, str]]:
14
+ if self._skip_newlines:
15
+ text = text.lstrip("\n")
16
+ if not text:
17
+ return []
18
+ self._skip_newlines = False
19
+ self._pending += text
20
+ return self._drain()
21
+
22
+ def flush(self) -> list[tuple[str, str]]:
23
+ if not self._pending:
24
+ return []
25
+ chunk_type = "thinking" if self._inside else "content"
26
+ result = [(chunk_type, self._pending)]
27
+ self._pending = ""
28
+ return result
29
+
30
+ def _drain(self) -> list[tuple[str, str]]:
31
+ results: list[tuple[str, str]] = []
32
+ while self._pending:
33
+ tag = self.CLOSE_TAG if self._inside else self.OPEN_TAG
34
+ idx = self._pending.find(tag)
35
+ if idx >= 0:
36
+ before = self._pending[:idx]
37
+ if before:
38
+ chunk_type = "thinking" if self._inside else "content"
39
+ results.append((chunk_type, before))
40
+ self._pending = self._pending[idx + len(tag) :]
41
+ self._inside = not self._inside
42
+ self._pending = self._pending.lstrip("\n")
43
+ if not self._pending:
44
+ self._skip_newlines = True
45
+ continue
46
+
47
+ for n in range(min(len(tag) - 1, len(self._pending)), 0, -1):
48
+ if self._pending.endswith(tag[:n]):
49
+ safe = self._pending[:-n]
50
+ if safe:
51
+ chunk_type = "thinking" if self._inside else "content"
52
+ results.append((chunk_type, safe))
53
+ self._pending = self._pending[-n:]
54
+ return results
55
+
56
+ chunk_type = "thinking" if self._inside else "content"
57
+ results.append((chunk_type, self._pending))
58
+ self._pending = ""
59
+
60
+ return results
61
+
62
+
63
+ def split_thinking_content(text: str) -> tuple[str, str]:
64
+ parser = ThinkTagParser()
65
+ chunks = parser.feed(text)
66
+ chunks.extend(parser.flush())
67
+
68
+ content_parts: list[str] = []
69
+ thinking_parts: list[str] = []
70
+
71
+ for chunk_type, chunk_text in chunks:
72
+ if not chunk_text:
73
+ continue
74
+ if chunk_type == "thinking":
75
+ thinking_parts.append(chunk_text)
76
+ else:
77
+ content_parts.append(chunk_text)
78
+
79
+ return "".join(content_parts), "".join(thinking_parts)
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import TYPE_CHECKING
5
+
6
+ from loguru import logger
7
+
8
+ from flowent.models import NodeType
9
+
10
+ if TYPE_CHECKING:
11
+ from flowent.agent import Agent
12
+
13
+
14
+ class AgentRegistry:
15
+ def __init__(self) -> None:
16
+ self._agents: dict[str, Agent] = {}
17
+ self._lock = threading.Lock()
18
+
19
+ def register(self, agent: Agent) -> None:
20
+ with self._lock:
21
+ self._agents[agent.uuid] = agent
22
+ logger.info(
23
+ "Node registered: {} (type={})",
24
+ agent.uuid[:8],
25
+ agent.config.node_type.value,
26
+ )
27
+
28
+ def unregister(self, agent_id: str) -> None:
29
+ with self._lock:
30
+ removed = self._agents.pop(agent_id, None)
31
+ if removed:
32
+ logger.info("Node unregistered: {}", agent_id[:8])
33
+
34
+ def get(self, agent_id: str) -> Agent | None:
35
+ with self._lock:
36
+ return self._agents.get(agent_id)
37
+
38
+ def get_assistant(self) -> Agent | None:
39
+ with self._lock:
40
+ for agent in self._agents.values():
41
+ if agent.node_type == NodeType.ASSISTANT:
42
+ return agent
43
+ return None
44
+
45
+ def find_by_name(self, name: str) -> Agent | None:
46
+ with self._lock:
47
+ matches = [a for a in self._agents.values() if a.config.name == name]
48
+ if len(matches) == 1:
49
+ return matches[0]
50
+ return None
51
+
52
+ def find_by_uuid_prefix(self, prefix: str) -> Agent | None:
53
+ with self._lock:
54
+ matches = [
55
+ agent
56
+ for agent_id, agent in self._agents.items()
57
+ if agent_id.startswith(prefix)
58
+ ]
59
+ if len(matches) == 1:
60
+ return matches[0]
61
+ return None
62
+
63
+ def get_all(self) -> list[Agent]:
64
+ with self._lock:
65
+ return list(self._agents.values())
66
+
67
+ def reset(self) -> None:
68
+ with self._lock:
69
+ self._agents.clear()
70
+ logger.debug("Registry reset")
71
+
72
+
73
+ registry = AgentRegistry()
@@ -0,0 +1,255 @@
1
+ from __future__ import annotations
2
+
3
+ from flowent.settings import (
4
+ ModelParams,
5
+ RoleConfig,
6
+ RoleModelConfig,
7
+ Settings,
8
+ build_model_params_from_mapping,
9
+ clear_role_references,
10
+ find_provider,
11
+ is_builtin_role_name,
12
+ normalize_tool_names,
13
+ rename_role_references,
14
+ validate_role_tool_config,
15
+ )
16
+
17
+
18
+ class RoleConflictError(ValueError):
19
+ pass
20
+
21
+
22
+ def find_role_by_name(roles: list[RoleConfig], role_name: str) -> RoleConfig | None:
23
+ for role in roles:
24
+ if role.name == role_name:
25
+ return role
26
+ return None
27
+
28
+
29
+ def require_role_name(value: str) -> str:
30
+ name = value.strip()
31
+ if not name:
32
+ raise ValueError("Role name is required")
33
+ return name
34
+
35
+
36
+ def require_role_description(value: str) -> str:
37
+ description = value.strip()
38
+ if not description:
39
+ raise ValueError("Role description is required")
40
+ return description
41
+
42
+
43
+ def normalize_optional_role_name(value: str | None) -> str | None:
44
+ if value is None:
45
+ return None
46
+ return require_role_name(value)
47
+
48
+
49
+ def normalize_optional_role_description(value: str | None) -> str | None:
50
+ if value is None:
51
+ return None
52
+ return require_role_description(value)
53
+
54
+
55
+ def ensure_role_name_available(
56
+ roles: list[RoleConfig],
57
+ role_name: str,
58
+ *,
59
+ current_name: str | None = None,
60
+ ) -> None:
61
+ if any(role.name == role_name and role.name != current_name for role in roles):
62
+ raise RoleConflictError(f"Role '{role_name}' already exists")
63
+
64
+
65
+ def resolve_role_tool_config(
66
+ current: RoleConfig | None,
67
+ included_tools: list[str] | None,
68
+ excluded_tools: list[str] | None,
69
+ ) -> tuple[list[str], list[str]]:
70
+ next_included = normalize_tool_names(
71
+ included_tools
72
+ if included_tools is not None
73
+ else current.included_tools
74
+ if current
75
+ else []
76
+ )
77
+ next_excluded = normalize_tool_names(
78
+ excluded_tools
79
+ if excluded_tools is not None
80
+ else current.excluded_tools
81
+ if current
82
+ else []
83
+ )
84
+ validate_role_tool_config(next_included, next_excluded)
85
+ return next_included, next_excluded
86
+
87
+
88
+ def resolve_role_model(
89
+ requested: object,
90
+ *,
91
+ settings: Settings,
92
+ current: RoleModelConfig | None,
93
+ provided: bool,
94
+ invalid_type_error: str,
95
+ missing_provider_id_error: str,
96
+ missing_model_error: str,
97
+ ) -> RoleModelConfig | None:
98
+ if not provided:
99
+ return current
100
+ if requested is None:
101
+ return None
102
+
103
+ extracted = _extract_role_model_fields(requested)
104
+ if extracted is None:
105
+ raise ValueError(invalid_type_error)
106
+
107
+ provider_id_value, model_value = extracted
108
+ if not isinstance(provider_id_value, str) or not provider_id_value.strip():
109
+ raise ValueError(missing_provider_id_error)
110
+ if not isinstance(model_value, str) or not model_value.strip():
111
+ raise ValueError(missing_model_error)
112
+
113
+ provider_id = provider_id_value.strip()
114
+ model = model_value.strip()
115
+ if find_provider(settings, provider_id) is None:
116
+ raise ValueError(f"Provider '{provider_id}' not found")
117
+
118
+ return RoleModelConfig(provider_id=provider_id, model=model)
119
+
120
+
121
+ def resolve_role_model_params(
122
+ requested: object,
123
+ *,
124
+ current: ModelParams | None,
125
+ provided: bool,
126
+ ) -> ModelParams | None:
127
+ if not provided:
128
+ return current
129
+ return build_model_params_from_mapping(requested)
130
+
131
+
132
+ def validate_builtin_role_update(
133
+ role: RoleConfig,
134
+ *,
135
+ next_name: str | None,
136
+ next_description: str | None,
137
+ next_system_prompt: str | None,
138
+ next_included_tools: list[str],
139
+ next_excluded_tools: list[str],
140
+ ) -> None:
141
+ if not is_builtin_role_name(role.name):
142
+ return
143
+ if next_name is not None and next_name != role.name:
144
+ raise ValueError(f"Cannot rename built-in role '{role.name}'")
145
+ if next_description is not None and next_description != role.description:
146
+ raise ValueError(
147
+ f"Cannot modify built-in role '{role.name}' fields other than "
148
+ "model or model_params"
149
+ )
150
+ if next_system_prompt is not None and next_system_prompt != role.system_prompt:
151
+ raise ValueError(
152
+ f"Cannot modify built-in role '{role.name}' fields other than "
153
+ "model or model_params"
154
+ )
155
+ if (
156
+ next_included_tools != role.included_tools
157
+ or next_excluded_tools != role.excluded_tools
158
+ ):
159
+ raise ValueError(
160
+ f"Cannot modify built-in role '{role.name}' fields other than "
161
+ "model or model_params"
162
+ )
163
+
164
+
165
+ def build_role_config(
166
+ *,
167
+ name: str,
168
+ description: str,
169
+ system_prompt: str,
170
+ model: RoleModelConfig | None,
171
+ model_params: ModelParams | None,
172
+ included_tools: list[str],
173
+ excluded_tools: list[str],
174
+ ) -> RoleConfig:
175
+ return RoleConfig(
176
+ name=name,
177
+ description=description,
178
+ system_prompt=system_prompt,
179
+ model=model,
180
+ model_params=model_params,
181
+ included_tools=included_tools,
182
+ excluded_tools=excluded_tools,
183
+ )
184
+
185
+
186
+ def apply_role_update(
187
+ *,
188
+ settings: Settings,
189
+ role: RoleConfig,
190
+ next_name: str | None,
191
+ next_description: str | None,
192
+ next_system_prompt: str | None,
193
+ next_model: RoleModelConfig | None,
194
+ update_model: bool,
195
+ next_model_params: ModelParams | None,
196
+ update_model_params: bool,
197
+ next_included_tools: list[str],
198
+ next_excluded_tools: list[str],
199
+ ) -> RoleConfig:
200
+ previous_name = role.name
201
+ if next_name is not None:
202
+ role.name = next_name
203
+ rename_role_references(settings, previous_name, role.name)
204
+ if next_description is not None:
205
+ role.description = next_description
206
+ if next_system_prompt is not None:
207
+ role.system_prompt = next_system_prompt
208
+ if update_model:
209
+ role.model = next_model
210
+ if update_model_params:
211
+ role.model_params = next_model_params
212
+ role.included_tools = next_included_tools
213
+ role.excluded_tools = next_excluded_tools
214
+ return role
215
+
216
+
217
+ def remove_role(settings: Settings, role: RoleConfig) -> None:
218
+ settings.roles = [existing for existing in settings.roles if existing is not role]
219
+ clear_role_references(settings, role.name)
220
+
221
+
222
+ def sync_running_system_roles() -> None:
223
+ from flowent.graph_service import sync_assistant_role, sync_tab_leaders
224
+
225
+ sync_assistant_role(reason="assistant role updated")
226
+ sync_tab_leaders(reason="leader role updated")
227
+
228
+
229
+ def _extract_role_model_fields(requested: object) -> tuple[object, object] | None:
230
+ if isinstance(requested, dict):
231
+ return requested.get("provider_id"), requested.get("model")
232
+ provider_id = getattr(requested, "provider_id", None)
233
+ model = getattr(requested, "model", None)
234
+ if provider_id is None and model is None:
235
+ return None
236
+ return provider_id, model
237
+
238
+
239
+ __all__ = [
240
+ "RoleConflictError",
241
+ "apply_role_update",
242
+ "build_role_config",
243
+ "ensure_role_name_available",
244
+ "find_role_by_name",
245
+ "normalize_optional_role_description",
246
+ "normalize_optional_role_name",
247
+ "remove_role",
248
+ "require_role_description",
249
+ "require_role_name",
250
+ "resolve_role_model",
251
+ "resolve_role_model_params",
252
+ "resolve_role_tool_config",
253
+ "sync_running_system_roles",
254
+ "validate_builtin_role_update",
255
+ ]
@@ -0,0 +1,30 @@
1
+ from fastapi import APIRouter
2
+
3
+ from flowent.routes.access import router as access_router
4
+ from flowent.routes.assistant import router as assistant_router
5
+ from flowent.routes.image_assets import router as image_assets_router
6
+ from flowent.routes.mcp import router as mcp_router
7
+ from flowent.routes.meta import router as meta_router
8
+ from flowent.routes.nodes import router as nodes_router
9
+ from flowent.routes.prompts import router as prompts_router
10
+ from flowent.routes.providers_route import router as providers_router
11
+ from flowent.routes.roles import router as roles_router
12
+ from flowent.routes.settings import router as settings_router
13
+ from flowent.routes.stats import router as stats_router
14
+ from flowent.routes.tabs import router as tabs_router
15
+ from flowent.routes.ws import router as ws_router
16
+
17
+ router = APIRouter()
18
+ router.include_router(access_router)
19
+ router.include_router(nodes_router)
20
+ router.include_router(assistant_router)
21
+ router.include_router(image_assets_router)
22
+ router.include_router(mcp_router)
23
+ router.include_router(roles_router)
24
+ router.include_router(providers_router)
25
+ router.include_router(prompts_router)
26
+ router.include_router(settings_router)
27
+ router.include_router(stats_router)
28
+ router.include_router(tabs_router)
29
+ router.include_router(meta_router)
30
+ router.include_router(ws_router)
@@ -0,0 +1,48 @@
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)