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,64 @@
1
+ STEWARD_ROLE_SYSTEM_PROMPT = """\
2
+ You are the Steward role currently used by the Assistant - the Human's interface to the system.
3
+
4
+ The Human can interact with the system only through the Assistant chat panel. The Human has no terminal, filesystem access, or direct execution surface. If a request requires reading files, running commands, editing code, browsing the network, or any other system interaction, you must open a workflow and create the appropriate agents to do the work rather than pushing the task back to the Human.
5
+
6
+ Your responsibilities:
7
+ - Understand the Human's intent
8
+ - Manage task boundaries at the Workspace level
9
+ - Turn the Human's execution request into a clear task brief for the workflow that will own it
10
+ - Directly manage system configuration using management tools when requested
11
+ - Wait for real results and present them back to the Human
12
+
13
+ ## Task Handoff
14
+
15
+ - Prefer the workflow-based control plane for execution work: `create_workflow` to open a task workspace with its bound Leader, `list_workflows` to inspect and reuse existing workspaces, and `delete_workflow` to remove a workspace that should no longer exist.
16
+ - When the Human asks to change a workflow's `allow_network` or `write_dirs`, use `set_permissions` to patch that workflow boundary directly.
17
+ - When a request requires real execution, choose or create the right workflow first, then hand the work to that workflow's Leader.
18
+ - Creating a workflow also creates its bound Leader. Do not leave a task workflow without a Leader.
19
+ - Do not directly design a workflow's internal Workflow Graph yourself. Once a workflow exists, that workflow's Leader owns its internal node creation, structure, and execution coordination.
20
+ - Do not directly assign execution work to a Worker or other ordinary task node as the default path. Even a simple execution task should enter the workflow through its Leader first.
21
+ - The first message you send to a Leader should be a task brief, not a raw copy of the Human's text. Include at least the task goal, expected artifact, success criteria, relevant context, constraints, and when the work should be escalated back to you for clarification.
22
+ - When continuing existing work, inspect the current workflows with `list_workflows` before creating a new one. Reuse the existing workflow when the Human is clearly referring to ongoing work.
23
+ - When the Human explicitly asks to remove a workflow or a finished workspace should be cleaned up, inspect with `list_workflows` and then use `delete_workflow`.
24
+ - After creating a new workflow, immediately dispatch the first task brief to its Leader with `send`.
25
+ - When a newly created or newly selected Leader is waiting for its next brief, keep using `send` until every intended Leader has been dispatched.
26
+ - Custom roles may also exist; choose them when the task clearly matches.
27
+ - Use `list_roles` when you need to inspect built-in or custom role details before choosing what to create.
28
+
29
+ ## System Management
30
+
31
+ - You can manage system configuration directly without creating an agent
32
+ - When the Human asks about current system configuration or wants to change providers, roles, settings, or prompts, use the corresponding management tool directly
33
+ - When the Human asks to change an existing workflow's network or writable-directory boundary, use `set_permissions` directly instead of delegating that boundary change to the workflow's Leader
34
+
35
+ ## Security Boundary
36
+
37
+ - Apply least privilege
38
+ - Only specify `write_dirs` when the task needs file writes, and keep them as narrow as possible
39
+ - Only set `allow_network=true` when the task needs network access
40
+ - Only grant the tools required for the task
41
+
42
+ ## Workflow
43
+
44
+ 1. Receive the Human's message
45
+ 2. If the message is just casual conversation, a greeting, or common knowledge that needs no system interaction, answer directly without creating an agent
46
+ 3. If the message is a system configuration request, use the corresponding management tool directly
47
+ 4. If role, workflow, or tool availability is uncertain, use `list_roles`, `list_workflows`, and `list_tools` to inspect the current options before acting
48
+ 5. Otherwise: open or choose a workflow and hand the execution brief to that workflow's Leader
49
+ 6. Immediately send each new or newly selected Leader its next brief with `send`, including the concrete objective, expected output, relevant constraints, and escalation conditions
50
+ 7. If a brief status update is helpful, keep it short and action-oriented, such as "正在查看"
51
+ 8. After delegating, use `idle` to wait for messages from connected agents when you have no immediate next action
52
+ 9. When a Leader reports back, present the real result to the Human
53
+
54
+ ## Behavior Rules
55
+
56
+ - Do not personally execute system tasks
57
+ - Do not directly design or rewire a workflow's internal Workflow Graph once a Leader owns that workflow
58
+ - Do not explain internal Workflow Graph mechanics unless the Human explicitly asks
59
+ - Do not ask whether you should create a workflow and agents once that decision is clear; do it directly
60
+ - Do not invent results; wait for the delegated agent's real reply
61
+ - Do not re-send a task to a node that has already been dispatched or has already reported back
62
+ - Do not insert tool calls such as `contacts` between dispatch responses while some planned Leaders are still waiting for their next brief
63
+ - If the Human sends a new message while you are waiting, handle the new message instead of automatically idling again
64
+ """
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any, Protocol
5
+
6
+ from flowent.models import LLMResponse, ModelInfo
7
+ from flowent.settings import ModelParams
8
+
9
+
10
+ class LLMProvider(Protocol):
11
+ def chat(
12
+ self,
13
+ messages: list[dict[str, Any]],
14
+ tools: list[dict[str, Any]] | None = None,
15
+ on_chunk: Callable[[str, str], None] | None = None,
16
+ register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
17
+ model_params: ModelParams | None = None,
18
+ ) -> LLMResponse: ...
19
+
20
+ def list_models(
21
+ self,
22
+ register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
23
+ ) -> list[ModelInfo]: ...
@@ -0,0 +1,468 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ import uuid
6
+ from collections.abc import Callable
7
+ from typing import Any
8
+
9
+ from loguru import logger
10
+
11
+ from flowent.model_metadata import build_model_info
12
+ from flowent.models import LLMResponse, LLMUsage, ModelInfo
13
+ from flowent.models import ToolCallResult as ToolCall
14
+ from flowent.network import (
15
+ RequestException,
16
+ create_http_session,
17
+ read_response_text,
18
+ response_looks_like_html,
19
+ truncate_text,
20
+ )
21
+ from flowent.providers import LLMProvider
22
+ from flowent.providers.content import collapse_parts_to_text, to_anthropic_content
23
+ from flowent.providers.errors import (
24
+ build_access_blocked_error,
25
+ build_network_error,
26
+ build_status_error,
27
+ )
28
+ from flowent.providers.headers import merge_headers
29
+ from flowent.providers.sse import iter_sse_json
30
+ from flowent.settings import ModelParams
31
+
32
+
33
+ def _extract_usage_value(usage: dict[str, Any], key: str) -> int | None:
34
+ value = usage.get(key)
35
+ if isinstance(value, bool) or not isinstance(value, int):
36
+ return None
37
+ return value
38
+
39
+
40
+ class AnthropicProvider(LLMProvider):
41
+ def __init__(
42
+ self,
43
+ provider_name: str,
44
+ api_base_url: str,
45
+ api_key: str = "",
46
+ headers: dict[str, str] | None = None,
47
+ model: str = "",
48
+ request_timeout_seconds: float = 120.0,
49
+ ) -> None:
50
+ self._provider_name = provider_name
51
+ self._api_base_url = api_base_url.rstrip("/")
52
+ self._api_key = api_key
53
+ self._header_overrides = dict(headers or {})
54
+ self._model = model
55
+ self._request_timeout_seconds = request_timeout_seconds
56
+ self._client = create_http_session(timeout=self._request_timeout_seconds)
57
+
58
+ def _headers(self) -> dict[str, str]:
59
+ return merge_headers(
60
+ {
61
+ "Content-Type": "application/json",
62
+ "x-api-key": self._api_key,
63
+ "anthropic-version": "2023-06-01",
64
+ },
65
+ self._header_overrides,
66
+ )
67
+
68
+ def _convert_messages(
69
+ self,
70
+ messages: list[dict[str, Any]],
71
+ ) -> tuple[str | None, list[dict[str, Any]]]:
72
+ system_text: list[str] = []
73
+ converted: list[dict[str, Any]] = []
74
+ pending_tool_results: list[dict[str, Any]] = []
75
+
76
+ def flush_tool_results() -> None:
77
+ if not pending_tool_results:
78
+ return
79
+ converted.append(
80
+ {
81
+ "role": "user",
82
+ "content": list(pending_tool_results),
83
+ }
84
+ )
85
+ pending_tool_results.clear()
86
+
87
+ for msg in messages:
88
+ role = msg.get("role")
89
+
90
+ if role == "system":
91
+ content = msg.get("content")
92
+ if content is not None:
93
+ system_text.append(
94
+ content if isinstance(content, str) else json.dumps(content),
95
+ )
96
+ continue
97
+
98
+ if role == "tool":
99
+ content = msg.get("content")
100
+ if content is None:
101
+ tool_content = ""
102
+ elif isinstance(content, str):
103
+ tool_content = content
104
+ else:
105
+ tool_content = json.dumps(content)
106
+
107
+ pending_tool_results.append(
108
+ {
109
+ "type": "tool_result",
110
+ "tool_use_id": msg.get("tool_call_id", ""),
111
+ "content": tool_content,
112
+ }
113
+ )
114
+ continue
115
+
116
+ flush_tool_results()
117
+
118
+ if role == "assistant" and msg.get("tool_calls"):
119
+ content_blocks: list[dict[str, Any]] = []
120
+ content = msg.get("content")
121
+ assistant_text = collapse_parts_to_text(content)
122
+ if assistant_text:
123
+ content_blocks.append({"type": "text", "text": assistant_text})
124
+
125
+ for tool_call in msg.get("tool_calls", []):
126
+ fn = tool_call.get("function", {})
127
+ raw_arguments = fn.get("arguments", "{}")
128
+
129
+ if isinstance(raw_arguments, str):
130
+ try:
131
+ parsed_arguments = (
132
+ json.loads(raw_arguments) if raw_arguments else {}
133
+ )
134
+ except json.JSONDecodeError:
135
+ parsed_arguments = {}
136
+ elif isinstance(raw_arguments, dict):
137
+ parsed_arguments = raw_arguments
138
+ else:
139
+ parsed_arguments = {}
140
+
141
+ if not isinstance(parsed_arguments, dict):
142
+ parsed_arguments = {}
143
+
144
+ content_blocks.append(
145
+ {
146
+ "type": "tool_use",
147
+ "id": tool_call.get("id", ""),
148
+ "name": fn.get("name", ""),
149
+ "input": parsed_arguments,
150
+ }
151
+ )
152
+
153
+ converted.append({"role": "assistant", "content": content_blocks})
154
+ continue
155
+
156
+ if role == "user":
157
+ converted.append(
158
+ {
159
+ "role": "user",
160
+ "content": to_anthropic_content(
161
+ msg.get("content"),
162
+ allow_images=True,
163
+ ),
164
+ }
165
+ )
166
+ continue
167
+
168
+ converted.append(
169
+ {"role": role, "content": collapse_parts_to_text(msg.get("content"))}
170
+ )
171
+
172
+ flush_tool_results()
173
+
174
+ system = "\n\n".join(system_text) if system_text else None
175
+ return system, converted
176
+
177
+ def _convert_tools(self, tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
178
+ result = []
179
+ for tool in tools:
180
+ fn = tool.get("function", {})
181
+ result.append(
182
+ {
183
+ "name": fn.get("name", ""),
184
+ "description": fn.get("description", ""),
185
+ "input_schema": fn.get("parameters", {}),
186
+ },
187
+ )
188
+ return result
189
+
190
+ def chat(
191
+ self,
192
+ messages: list[dict[str, Any]],
193
+ tools: list[dict[str, Any]] | None = None,
194
+ on_chunk: Callable[[str, str], None] | None = None,
195
+ register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
196
+ model_params: ModelParams | None = None,
197
+ ) -> LLMResponse:
198
+ url = f"{self._api_base_url}/messages"
199
+ system, converted_messages = self._convert_messages(messages)
200
+
201
+ payload: dict[str, Any] = {
202
+ "model": self._model,
203
+ "messages": converted_messages,
204
+ "max_tokens": (
205
+ model_params.max_output_tokens
206
+ if model_params is not None
207
+ and model_params.max_output_tokens is not None
208
+ else 8192
209
+ ),
210
+ "stream": True,
211
+ }
212
+ if model_params is not None:
213
+ if model_params.temperature is not None:
214
+ payload["temperature"] = model_params.temperature
215
+ if model_params.top_p is not None:
216
+ payload["top_p"] = model_params.top_p
217
+ if system:
218
+ payload["system"] = system
219
+ if tools:
220
+ payload["tools"] = self._convert_tools(tools)
221
+
222
+ logger.debug(
223
+ "[{}] Anthropic chat request: model={}, messages={}, tools={}",
224
+ self._provider_name,
225
+ self._model,
226
+ len(converted_messages),
227
+ len(tools) if tools else 0,
228
+ )
229
+
230
+ t0 = time.perf_counter()
231
+
232
+ content_parts: list[str] = []
233
+ thinking_parts: list[str] = []
234
+ tool_calls_accum: dict[int, dict[str, Any]] = {}
235
+ current_block_idx = -1
236
+ event_count = 0
237
+ input_tokens: int | None = None
238
+ output_tokens: int | None = None
239
+ cached_input_tokens: int | None = None
240
+ cache_read_tokens: int | None = None
241
+ cache_write_tokens: int | None = None
242
+ usage_details: dict[str, int] = {}
243
+ raw_usage: dict[str, Any] | None = None
244
+ client = self._client
245
+ if register_interrupt is not None:
246
+ register_interrupt(client.close)
247
+
248
+ try:
249
+ with client.stream(
250
+ "POST",
251
+ url,
252
+ headers=self._headers(),
253
+ json=payload,
254
+ ) as response:
255
+ if register_interrupt is not None:
256
+ register_interrupt(response.close)
257
+ if response_looks_like_html(response):
258
+ raise build_access_blocked_error(
259
+ provider_name=self._provider_name,
260
+ provider_type="anthropic",
261
+ model=self._model,
262
+ base_url=self._api_base_url,
263
+ status_code=response.status_code,
264
+ detail=truncate_text(read_response_text(response)),
265
+ )
266
+ if response.status_code != 200:
267
+ body = truncate_text(read_response_text(response))
268
+ elapsed = time.perf_counter() - t0
269
+ logger.error(
270
+ "LLM API error [provider={}, model={}, type=anthropic]: {} - {} ({:.2f}s)",
271
+ self._provider_name,
272
+ self._model,
273
+ response.status_code,
274
+ body[:500],
275
+ elapsed,
276
+ )
277
+ raise build_status_error(
278
+ provider_name=self._provider_name,
279
+ provider_type="anthropic",
280
+ model=self._model,
281
+ base_url=self._api_base_url,
282
+ status_code=response.status_code,
283
+ body=body,
284
+ )
285
+
286
+ for event in iter_sse_json(response):
287
+ event_count += 1
288
+ event_type = event.get("type", "")
289
+
290
+ if event_type == "message_start":
291
+ message = event.get("message", {})
292
+ usage = message.get("usage", {})
293
+ if isinstance(usage, dict):
294
+ raw_usage = dict(usage)
295
+ input_tokens = _extract_usage_value(usage, "input_tokens")
296
+ cache_read_tokens = _extract_usage_value(
297
+ usage, "cache_read_input_tokens"
298
+ )
299
+ cache_write_tokens = _extract_usage_value(
300
+ usage, "cache_creation_input_tokens"
301
+ )
302
+ cached_input_tokens = cache_read_tokens
303
+ if cached_input_tokens is None:
304
+ cached_input_tokens = cache_write_tokens
305
+ for key in (
306
+ "cache_creation_input_tokens",
307
+ "cache_read_input_tokens",
308
+ ):
309
+ value = _extract_usage_value(usage, key)
310
+ if value is not None:
311
+ usage_details[key] = value
312
+ continue
313
+
314
+ if event_type == "content_block_start":
315
+ current_block_idx += 1
316
+ block = event.get("content_block", {})
317
+ if block.get("type") == "tool_use":
318
+ tool_calls_accum[current_block_idx] = {
319
+ "id": block.get("id", str(uuid.uuid4())),
320
+ "name": block.get("name", ""),
321
+ "arguments": "",
322
+ }
323
+
324
+ elif event_type == "content_block_delta":
325
+ delta = event.get("delta", {})
326
+ delta_type = delta.get("type", "")
327
+
328
+ if delta_type == "text_delta":
329
+ text = delta.get("text", "")
330
+ if text:
331
+ content_parts.append(text)
332
+ if on_chunk:
333
+ on_chunk("content", text)
334
+
335
+ elif delta_type == "thinking_delta":
336
+ thinking = delta.get("thinking", "")
337
+ if thinking:
338
+ thinking_parts.append(thinking)
339
+ if on_chunk:
340
+ on_chunk("thinking", thinking)
341
+
342
+ elif delta_type == "input_json_delta":
343
+ partial = delta.get("partial_json", "")
344
+ if current_block_idx in tool_calls_accum:
345
+ tool_calls_accum[current_block_idx]["arguments"] += (
346
+ partial
347
+ )
348
+
349
+ elif event_type == "message_delta":
350
+ usage = event.get("usage", {})
351
+ if isinstance(usage, dict):
352
+ next_output_tokens = _extract_usage_value(
353
+ usage, "output_tokens"
354
+ )
355
+ if next_output_tokens is not None:
356
+ output_tokens = next_output_tokens
357
+
358
+ elif event_type == "message_stop":
359
+ break
360
+ except RequestException as exc:
361
+ elapsed = time.perf_counter() - t0
362
+ logger.warning(
363
+ "LLM API transport error [provider={}, model={}, type=anthropic]: {} ({:.2f}s)",
364
+ self._provider_name,
365
+ self._model,
366
+ exc,
367
+ elapsed,
368
+ )
369
+ raise build_network_error(
370
+ provider_name=self._provider_name,
371
+ provider_type="anthropic",
372
+ model=self._model,
373
+ base_url=self._api_base_url,
374
+ error=exc,
375
+ ) from exc
376
+ finally:
377
+ close_client = getattr(client, "close", None)
378
+ if callable(close_client):
379
+ close_client()
380
+ self._client = create_http_session(
381
+ timeout=self._request_timeout_seconds
382
+ )
383
+
384
+ elapsed = time.perf_counter() - t0
385
+ content = "".join(content_parts) or None
386
+ thinking = "".join(thinking_parts) or None
387
+ usage = None
388
+ if input_tokens is not None or output_tokens is not None:
389
+ usage = LLMUsage(
390
+ total_tokens=(input_tokens or 0) + (output_tokens or 0),
391
+ input_tokens=input_tokens,
392
+ output_tokens=output_tokens,
393
+ cached_input_tokens=cached_input_tokens,
394
+ cache_read_tokens=cache_read_tokens,
395
+ cache_write_tokens=cache_write_tokens,
396
+ details=usage_details,
397
+ )
398
+
399
+ logger.debug(
400
+ "[{}] Anthropic chat done: {:.2f}s, events={}, content_len={}, thinking_len={}, tool_calls={}",
401
+ self._provider_name,
402
+ elapsed,
403
+ event_count,
404
+ len(content) if content else 0,
405
+ len(thinking) if thinking else 0,
406
+ len(tool_calls_accum),
407
+ )
408
+
409
+ if tool_calls_accum:
410
+ tool_calls = []
411
+ for _, acc in sorted(tool_calls_accum.items()):
412
+ args_str = acc["arguments"]
413
+ try:
414
+ arguments = json.loads(args_str) if args_str else {}
415
+ except json.JSONDecodeError:
416
+ arguments = {}
417
+ tool_calls.append(
418
+ ToolCall(id=acc["id"], name=acc["name"], arguments=arguments),
419
+ )
420
+ return LLMResponse(
421
+ content=content,
422
+ tool_calls=tool_calls,
423
+ thinking=thinking,
424
+ usage=usage,
425
+ raw_usage=raw_usage,
426
+ )
427
+
428
+ return LLMResponse(
429
+ content=content or "",
430
+ thinking=thinking,
431
+ usage=usage,
432
+ raw_usage=raw_usage,
433
+ )
434
+
435
+ def list_models(
436
+ self,
437
+ register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
438
+ ) -> list[ModelInfo]:
439
+ url = f"{self._api_base_url}/models"
440
+ client = self._client
441
+ close_client = getattr(client, "close", None)
442
+ if register_interrupt is not None and callable(close_client):
443
+ register_interrupt(close_client)
444
+ try:
445
+ resp = client.get(url, headers=self._headers())
446
+ resp.raise_for_status()
447
+ data = resp.json()
448
+ models = data.get("data", [])
449
+ return [
450
+ build_model_info(
451
+ provider_type="anthropic",
452
+ model_id=m["id"],
453
+ )
454
+ for m in models
455
+ ]
456
+ except Exception as e:
457
+ logger.error(
458
+ "Failed to list models [provider={}, type=anthropic]: {}",
459
+ self._provider_name,
460
+ e,
461
+ )
462
+ return []
463
+ finally:
464
+ if callable(close_client):
465
+ close_client()
466
+ self._client = create_http_session(
467
+ timeout=self._request_timeout_seconds
468
+ )
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Final
4
+
5
+ PROVIDER_VERSION_SUFFIXES: Final[dict[str, str]] = {
6
+ "openai_compatible": "/v1",
7
+ "openai_responses": "/v1",
8
+ "anthropic": "/v1",
9
+ "gemini": "/v1beta",
10
+ }
11
+
12
+ PROVIDER_REQUEST_PATHS: Final[dict[str, str]] = {
13
+ "openai_compatible": "/chat/completions",
14
+ "openai_responses": "/responses",
15
+ "anthropic": "/messages",
16
+ "gemini": "/models/{model}:streamGenerateContent",
17
+ }
18
+
19
+ _KNOWN_SUFFIXES: Final[tuple[str, ...]] = tuple(
20
+ sorted(set(PROVIDER_VERSION_SUFFIXES.values()), key=len, reverse=True)
21
+ )
22
+
23
+
24
+ def _normalize_raw_base_url(base_url: str) -> str:
25
+ normalized = base_url.strip().rstrip("/")
26
+ if not normalized:
27
+ raise ValueError("Provider base_url is required")
28
+ return normalized
29
+
30
+
31
+ def _normalize_provider_type(provider_type: str) -> str:
32
+ normalized = provider_type.strip().lower()
33
+ if normalized not in PROVIDER_VERSION_SUFFIXES:
34
+ raise ValueError(f"Unknown provider type: {provider_type}")
35
+ return normalized
36
+
37
+
38
+ def resolve_provider_base_url(provider_type: str, base_url: str) -> str:
39
+ normalized_type = _normalize_provider_type(provider_type)
40
+ normalized_base_url = _normalize_raw_base_url(base_url)
41
+ expected_suffix = PROVIDER_VERSION_SUFFIXES[normalized_type]
42
+ lower_base_url = normalized_base_url.lower()
43
+
44
+ for suffix in _KNOWN_SUFFIXES:
45
+ if not lower_base_url.endswith(suffix):
46
+ continue
47
+ if suffix != expected_suffix:
48
+ raise ValueError(
49
+ f"Provider base_url suffix '{suffix}' does not match type "
50
+ f"'{normalized_type}' (expected '{expected_suffix}')"
51
+ )
52
+ return normalized_base_url
53
+
54
+ return f"{normalized_base_url}{expected_suffix}"
55
+
56
+
57
+ def build_provider_request_preview(provider_type: str, base_url: str) -> str:
58
+ normalized_type = _normalize_provider_type(provider_type)
59
+ resolved_base_url = resolve_provider_base_url(normalized_type, base_url)
60
+ return f"{resolved_base_url}{PROVIDER_REQUEST_PATHS[normalized_type]}"