flowent 0.0.7 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/README.md +0 -3
  2. package/backend/README.md +0 -3
  3. package/backend/pyproject.toml +2 -8
  4. package/backend/src/flowent/__init__.py +6 -2
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/agent.py +213 -3173
  17. package/backend/src/flowent/cli.py +19 -24
  18. package/backend/src/flowent/context.py +127 -0
  19. package/backend/src/flowent/llm.py +256 -0
  20. package/backend/src/flowent/logging.py +170 -129
  21. package/backend/src/flowent/main.py +321 -70
  22. package/backend/src/flowent/patch.py +182 -0
  23. package/backend/src/flowent/paths.py +11 -0
  24. package/backend/src/flowent/sandbox.py +214 -40
  25. package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  26. package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  27. package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  28. package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
  29. package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
  30. package/backend/src/flowent/static/flowent.png +0 -0
  31. package/backend/src/flowent/static/index.html +5 -25
  32. package/backend/src/flowent/storage.py +302 -0
  33. package/backend/src/flowent/tools.py +376 -0
  34. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  35. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  36. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  37. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  38. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  39. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/test_agent_tools.py +477 -0
  41. package/backend/tests/test_health.py +12 -0
  42. package/backend/tests/test_llm_providers.py +113 -0
  43. package/backend/tests/test_logging.py +182 -0
  44. package/backend/tests/test_persistence.py +125 -0
  45. package/backend/tests/test_workspace_chat.py +578 -0
  46. package/backend/uv.lock +803 -99
  47. package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  48. package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  49. package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  50. package/dist/frontend/assets/index-C76K95ty.js +81 -0
  51. package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
  52. package/dist/frontend/flowent.png +0 -0
  53. package/dist/frontend/index.html +5 -25
  54. package/package.json +1 -2
  55. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  75. package/backend/src/flowent/access.py +0 -247
  76. package/backend/src/flowent/assistant_commands.py +0 -115
  77. package/backend/src/flowent/channels/__init__.py +0 -3
  78. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/channels/telegram.py +0 -615
  81. package/backend/src/flowent/config.py +0 -14
  82. package/backend/src/flowent/dev.py +0 -3
  83. package/backend/src/flowent/events.py +0 -157
  84. package/backend/src/flowent/graph_runtime.py +0 -60
  85. package/backend/src/flowent/graph_service.py +0 -2401
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/model_metadata.py +0 -102
  88. package/backend/src/flowent/models/__init__.py +0 -125
  89. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/agent.py +0 -34
  103. package/backend/src/flowent/models/base.py +0 -24
  104. package/backend/src/flowent/models/blueprint.py +0 -176
  105. package/backend/src/flowent/models/content.py +0 -164
  106. package/backend/src/flowent/models/delta.py +0 -44
  107. package/backend/src/flowent/models/event.py +0 -51
  108. package/backend/src/flowent/models/graph.py +0 -472
  109. package/backend/src/flowent/models/history.py +0 -272
  110. package/backend/src/flowent/models/llm.py +0 -62
  111. package/backend/src/flowent/models/message.py +0 -33
  112. package/backend/src/flowent/models/tab.py +0 -85
  113. package/backend/src/flowent/models/todo.py +0 -10
  114. package/backend/src/flowent/network.py +0 -146
  115. package/backend/src/flowent/observability_service.py +0 -218
  116. package/backend/src/flowent/prompts/__init__.py +0 -67
  117. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/common.py +0 -250
  121. package/backend/src/flowent/prompts/steward.py +0 -64
  122. package/backend/src/flowent/providers/__init__.py +0 -23
  123. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/anthropic.py +0 -468
  138. package/backend/src/flowent/providers/base_url.py +0 -60
  139. package/backend/src/flowent/providers/configuration.py +0 -189
  140. package/backend/src/flowent/providers/content.py +0 -122
  141. package/backend/src/flowent/providers/errors.py +0 -223
  142. package/backend/src/flowent/providers/gateway.py +0 -169
  143. package/backend/src/flowent/providers/gemini.py +0 -447
  144. package/backend/src/flowent/providers/headers.py +0 -20
  145. package/backend/src/flowent/providers/management.py +0 -96
  146. package/backend/src/flowent/providers/ollama.py +0 -293
  147. package/backend/src/flowent/providers/openai.py +0 -422
  148. package/backend/src/flowent/providers/openai_responses.py +0 -655
  149. package/backend/src/flowent/providers/registry.py +0 -144
  150. package/backend/src/flowent/providers/sse.py +0 -31
  151. package/backend/src/flowent/providers/thinking.py +0 -79
  152. package/backend/src/flowent/registry.py +0 -73
  153. package/backend/src/flowent/role_management.py +0 -270
  154. package/backend/src/flowent/routes/__init__.py +0 -26
  155. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  156. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/access.py +0 -48
  168. package/backend/src/flowent/routes/assistant.py +0 -158
  169. package/backend/src/flowent/routes/image_assets.py +0 -33
  170. package/backend/src/flowent/routes/meta.py +0 -28
  171. package/backend/src/flowent/routes/nodes.py +0 -423
  172. package/backend/src/flowent/routes/prompts.py +0 -46
  173. package/backend/src/flowent/routes/providers_route.py +0 -365
  174. package/backend/src/flowent/routes/roles.py +0 -207
  175. package/backend/src/flowent/routes/settings.py +0 -379
  176. package/backend/src/flowent/routes/tabs.py +0 -298
  177. package/backend/src/flowent/routes/ws.py +0 -33
  178. package/backend/src/flowent/runtime.py +0 -160
  179. package/backend/src/flowent/security.py +0 -37
  180. package/backend/src/flowent/settings.py +0 -2112
  181. package/backend/src/flowent/settings_management.py +0 -394
  182. package/backend/src/flowent/state_db.py +0 -108
  183. package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
  184. package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
  185. package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
  186. package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
  187. package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
  188. package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
  189. package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
  190. package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
  191. package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  192. package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
  193. package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
  194. package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
  195. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  196. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  197. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  198. package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
  199. package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
  200. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  201. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  202. package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
  203. package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
  204. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  205. package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
  206. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  207. package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
  208. package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
  209. package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
  210. package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
  211. package/backend/src/flowent/static/favicon.svg +0 -4
  212. package/backend/src/flowent/tools/__init__.py +0 -176
  213. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  214. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  215. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  216. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  217. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  218. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/connect.py +0 -100
  236. package/backend/src/flowent/tools/contacts.py +0 -22
  237. package/backend/src/flowent/tools/create_agent.py +0 -191
  238. package/backend/src/flowent/tools/create_tab.py +0 -61
  239. package/backend/src/flowent/tools/delete_tab.py +0 -39
  240. package/backend/src/flowent/tools/edit.py +0 -142
  241. package/backend/src/flowent/tools/exec.py +0 -118
  242. package/backend/src/flowent/tools/fetch.py +0 -85
  243. package/backend/src/flowent/tools/idle.py +0 -27
  244. package/backend/src/flowent/tools/list_roles.py +0 -68
  245. package/backend/src/flowent/tools/list_tabs.py +0 -100
  246. package/backend/src/flowent/tools/list_tools.py +0 -28
  247. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  248. package/backend/src/flowent/tools/manage_providers.py +0 -220
  249. package/backend/src/flowent/tools/manage_roles.py +0 -275
  250. package/backend/src/flowent/tools/manage_settings.py +0 -326
  251. package/backend/src/flowent/tools/read.py +0 -152
  252. package/backend/src/flowent/tools/send.py +0 -68
  253. package/backend/src/flowent/tools/set_permissions.py +0 -99
  254. package/backend/src/flowent/tools/sleep.py +0 -41
  255. package/backend/src/flowent/tools/todo.py +0 -51
  256. package/backend/src/flowent/workspace_store.py +0 -479
  257. package/backend/tests/__init__.py +0 -0
  258. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  259. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/conftest.py +0 -6
  261. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  265. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/conftest.py +0 -29
  271. package/backend/tests/integration/api/test_access_api.py +0 -182
  272. package/backend/tests/integration/api/test_assistant_api.py +0 -422
  273. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  274. package/backend/tests/integration/api/test_meta_api.py +0 -32
  275. package/backend/tests/integration/api/test_nodes_api.py +0 -787
  276. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  277. package/backend/tests/integration/api/test_roles_api.py +0 -228
  278. package/backend/tests/integration/api/test_tabs_api.py +0 -688
  279. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  280. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  281. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
  288. package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
  289. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  291. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/logging/test_logging.py +0 -132
  293. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  295. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  297. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  303. package/backend/tests/unit/providers/test_errors.py +0 -68
  304. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  305. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  306. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  307. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  308. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  309. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
  314. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  315. package/backend/tests/unit/routes/test_roles_routes.py +0 -539
  316. package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
  317. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  319. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  321. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/security/test_security.py +0 -124
  323. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  324. package/backend/tests/unit/settings/test_settings_roles.py +0 -703
  325. package/backend/tests/unit/test_access.py +0 -45
  326. package/backend/tests/unit/test_cli.py +0 -102
  327. package/backend/tests/unit/test_graph_runtime.py +0 -72
  328. package/backend/tests/unit/test_network.py +0 -51
  329. package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
  330. package/backend/tests/unit/test_workspace_store.py +0 -228
  331. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  332. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  334. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  335. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  336. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  337. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  338. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  339. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  340. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  346. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
  347. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  348. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  349. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  350. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  351. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
  352. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  353. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  354. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  355. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  356. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  357. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  358. package/backend/tests/unit/tools/test_tool_registry.py +0 -199
  359. package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
  360. package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
  361. package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
  362. package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
  363. package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
  364. package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
  365. package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
  366. package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
  367. package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  368. package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
  369. package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
  370. package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
  371. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  372. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  373. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  374. package/dist/frontend/assets/index--o_0fv0N.css +0 -1
  375. package/dist/frontend/assets/index-C9HuekJm.js +0 -10
  376. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  377. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  378. package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
  379. package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
  380. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  381. package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
  382. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  383. package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
  384. package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
  385. package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
  386. package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
  387. package/dist/frontend/favicon.svg +0 -4
@@ -1,655 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import base64
4
- import json
5
- import time
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 (
13
- LLMOutputImagePart,
14
- LLMOutputTextPart,
15
- LLMResponse,
16
- LLMUsage,
17
- ModelInfo,
18
- )
19
- from flowent.models import ToolCallResult as ToolCall
20
- from flowent.network import (
21
- RequestException,
22
- create_http_session,
23
- iter_response_lines,
24
- read_response_text,
25
- response_looks_like_html,
26
- truncate_text,
27
- )
28
- from flowent.providers import LLMProvider
29
- from flowent.providers.content import (
30
- collapse_parts_to_text,
31
- to_openai_responses_content,
32
- )
33
- from flowent.providers.errors import (
34
- build_access_blocked_error,
35
- build_network_error,
36
- build_status_error,
37
- )
38
- from flowent.providers.headers import merge_headers
39
- from flowent.settings import ModelParams
40
-
41
- REASONING_MODEL_PREFIXES = ("gpt-5", "o1", "o3", "o4")
42
- VERBOSITY_MODEL_PREFIXES = ("gpt-5",)
43
- REASONING_DELTA_EVENT_TYPES = {
44
- "response.reasoning.delta",
45
- "response.reasoning_text.delta",
46
- "response.reasoning_summary_text.delta",
47
- }
48
-
49
-
50
- def _supports_reasoning(model: str) -> bool:
51
- return model.startswith(REASONING_MODEL_PREFIXES)
52
-
53
-
54
- def _build_reasoning_config(
55
- model: str,
56
- model_params: ModelParams | None,
57
- ) -> dict[str, str] | None:
58
- if not _supports_reasoning(model) or model_params is None:
59
- return None
60
- effort = model_params.reasoning_effort
61
- if effort is None or effort == "none":
62
- return None
63
-
64
- return {
65
- "effort": effort,
66
- "summary": "detailed",
67
- }
68
-
69
-
70
- def _build_text_config(
71
- model: str,
72
- model_params: ModelParams | None,
73
- ) -> dict[str, object] | None:
74
- if model_params is None or not model.startswith(VERBOSITY_MODEL_PREFIXES):
75
- return None
76
- if not model_params.verbosity:
77
- return None
78
-
79
- return {
80
- "format": {"type": "text"},
81
- "verbosity": model_params.verbosity,
82
- }
83
-
84
-
85
- def _extract_reasoning_text_from_item(item: dict[str, Any]) -> str | None:
86
- parts: list[str] = []
87
-
88
- for key in ("summary", "content"):
89
- raw_value = item.get(key)
90
-
91
- if isinstance(raw_value, str):
92
- if raw_value.strip():
93
- parts.append(raw_value)
94
- continue
95
-
96
- if not isinstance(raw_value, list):
97
- continue
98
-
99
- for entry in raw_value:
100
- if not isinstance(entry, dict):
101
- continue
102
- text = entry.get("text")
103
- if isinstance(text, str) and text.strip():
104
- parts.append(text)
105
-
106
- if not parts:
107
- return None
108
-
109
- return "".join(parts)
110
-
111
-
112
- def _extract_reasoning_text_from_output(output: Any) -> str | None:
113
- if not isinstance(output, list):
114
- return None
115
-
116
- parts: list[str] = []
117
- for item in output:
118
- if not isinstance(item, dict):
119
- continue
120
- if item.get("type") != "reasoning":
121
- continue
122
- text = _extract_reasoning_text_from_item(item)
123
- if text:
124
- parts.append(text)
125
-
126
- if not parts:
127
- return None
128
-
129
- return "\n\n".join(parts)
130
-
131
-
132
- def _extract_reasoning_tokens(response: dict[str, Any]) -> int:
133
- usage = response.get("usage")
134
- if not isinstance(usage, dict):
135
- return 0
136
-
137
- output_tokens_details = usage.get("output_tokens_details")
138
- if not isinstance(output_tokens_details, dict):
139
- return 0
140
-
141
- tokens = output_tokens_details.get("reasoning_tokens")
142
- return tokens if isinstance(tokens, int) and tokens > 0 else 0
143
-
144
-
145
- def _collect_usage_details(
146
- value: dict[str, Any],
147
- *,
148
- prefix: str = "",
149
- ) -> dict[str, int]:
150
- details: dict[str, int] = {}
151
- for key, raw_item in value.items():
152
- detail_key = f"{prefix}.{key}" if prefix else key
153
- if isinstance(raw_item, bool):
154
- continue
155
- if isinstance(raw_item, int) and raw_item >= 0:
156
- details[detail_key] = raw_item
157
- continue
158
- if isinstance(raw_item, dict):
159
- details.update(_collect_usage_details(raw_item, prefix=detail_key))
160
- return details
161
-
162
-
163
- def _extract_usage(response: dict[str, Any]) -> LLMUsage | None:
164
- usage = response.get("usage")
165
- if not isinstance(usage, dict):
166
- return None
167
-
168
- total_tokens = usage.get("total_tokens")
169
- if isinstance(total_tokens, bool) or not isinstance(total_tokens, int):
170
- return None
171
-
172
- input_tokens = usage.get("input_tokens")
173
- if isinstance(input_tokens, bool) or not isinstance(input_tokens, int):
174
- input_tokens = None
175
-
176
- output_tokens = usage.get("output_tokens")
177
- if isinstance(output_tokens, bool) or not isinstance(output_tokens, int):
178
- output_tokens = None
179
-
180
- cached_input_tokens = usage.get("cached_input_tokens")
181
- if isinstance(cached_input_tokens, bool) or not isinstance(
182
- cached_input_tokens, int
183
- ):
184
- cached_input_tokens = None
185
- input_token_details = usage.get("input_tokens_details")
186
- if (
187
- cached_input_tokens is None
188
- and isinstance(input_token_details, dict)
189
- and isinstance(input_token_details.get("cached_tokens"), int)
190
- ):
191
- cached_input_tokens = input_token_details["cached_tokens"]
192
-
193
- details = _collect_usage_details(usage)
194
- for key in (
195
- "total_tokens",
196
- "input_tokens",
197
- "output_tokens",
198
- "cached_input_tokens",
199
- "input_tokens_details.cached_tokens",
200
- ):
201
- details.pop(key, None)
202
-
203
- return LLMUsage(
204
- total_tokens=total_tokens,
205
- input_tokens=input_tokens,
206
- output_tokens=output_tokens,
207
- cached_input_tokens=cached_input_tokens,
208
- cache_read_tokens=cached_input_tokens,
209
- details=details,
210
- )
211
-
212
-
213
- def _decode_output_image(item: dict[str, Any]) -> LLMOutputImagePart | None:
214
- encoded_data = (
215
- item.get("result") or item.get("image_base64") or item.get("b64_json")
216
- )
217
- if not isinstance(encoded_data, str) or not encoded_data:
218
- return None
219
- try:
220
- image_data = base64.b64decode(encoded_data)
221
- except ValueError:
222
- return None
223
- mime_type = item.get("mime_type")
224
- return LLMOutputImagePart(
225
- data=image_data,
226
- mime_type=mime_type if isinstance(mime_type, str) else "image/png",
227
- )
228
-
229
-
230
- def _extract_output_parts(output: Any) -> list[LLMOutputTextPart | LLMOutputImagePart]:
231
- if not isinstance(output, list):
232
- return []
233
-
234
- parts: list[LLMOutputTextPart | LLMOutputImagePart] = []
235
- for item in output:
236
- if not isinstance(item, dict):
237
- continue
238
- item_type = item.get("type")
239
- if item_type == "message":
240
- content = item.get("content")
241
- if not isinstance(content, list):
242
- continue
243
- for entry in content:
244
- if not isinstance(entry, dict):
245
- continue
246
- entry_type = entry.get("type")
247
- text = entry.get("text")
248
- if entry_type in {"output_text", "text"} and isinstance(text, str):
249
- parts.append(LLMOutputTextPart(text=text))
250
- elif item_type == "image_generation_call":
251
- image_part = _decode_output_image(item)
252
- if image_part is not None:
253
- parts.append(image_part)
254
- return parts
255
-
256
-
257
- def _format_reasoning_fallback(reasoning_tokens: int) -> str:
258
- if reasoning_tokens > 0:
259
- return f"Internal reasoning · {reasoning_tokens}"
260
-
261
- return "Internal reasoning"
262
-
263
-
264
- class OpenAIResponsesProvider(LLMProvider):
265
- def __init__(
266
- self,
267
- provider_name: str,
268
- api_base_url: str,
269
- api_key: str = "",
270
- headers: dict[str, str] | None = None,
271
- model: str = "",
272
- request_timeout_seconds: float = 120.0,
273
- ) -> None:
274
- self._provider_name = provider_name
275
- self._api_base_url = api_base_url.rstrip("/")
276
- self._api_key = api_key
277
- self._header_overrides = dict(headers or {})
278
- self._model = model
279
- self._request_timeout_seconds = request_timeout_seconds
280
- self._client = create_http_session(timeout=self._request_timeout_seconds)
281
-
282
- def _headers(self) -> dict[str, str]:
283
- headers: dict[str, str] = {"Content-Type": "application/json"}
284
- if self._api_key:
285
- headers["Authorization"] = f"Bearer {self._api_key}"
286
- return merge_headers(headers, self._header_overrides)
287
-
288
- def _convert_tools(self, tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
289
- result = []
290
- for t in tools:
291
- fn = t.get("function", {})
292
- result.append(
293
- {
294
- "type": "function",
295
- "name": fn.get("name", ""),
296
- "description": fn.get("description", ""),
297
- "parameters": fn.get("parameters", {}),
298
- }
299
- )
300
- return result
301
-
302
- def _convert_messages(
303
- self, messages: list[dict[str, Any]]
304
- ) -> tuple[str | None, list[dict[str, Any]]]:
305
- system_prompt: str | None = None
306
- input_items: list[dict[str, Any]] = []
307
-
308
- for msg in messages:
309
- role = msg.get("role")
310
- content = msg.get("content")
311
-
312
- if role == "system":
313
- system_prompt = content
314
- continue
315
-
316
- if role == "user":
317
- input_items.append(
318
- {
319
- "role": "user",
320
- "content": to_openai_responses_content(
321
- content,
322
- allow_images=True,
323
- ),
324
- }
325
- )
326
- elif role == "assistant":
327
- tool_calls = msg.get("tool_calls")
328
- if tool_calls:
329
- for tc in tool_calls:
330
- fn = tc.get("function", {})
331
- input_items.append(
332
- {
333
- "type": "function_call",
334
- "call_id": tc.get("id", ""),
335
- "name": fn.get("name", ""),
336
- "arguments": fn.get("arguments", "{}"),
337
- }
338
- )
339
- else:
340
- input_items.append(
341
- {
342
- "role": "assistant",
343
- "content": collapse_parts_to_text(content),
344
- }
345
- )
346
- elif role == "tool":
347
- input_items.append(
348
- {
349
- "type": "function_call_output",
350
- "call_id": msg.get("tool_call_id", ""),
351
- "output": content or "",
352
- }
353
- )
354
-
355
- return system_prompt, input_items
356
-
357
- def _supports_output_image(self) -> bool:
358
- model_info = build_model_info(
359
- provider_type="openai_responses",
360
- model_id=self._model,
361
- )
362
- return model_info.capabilities.output_image
363
-
364
- def chat(
365
- self,
366
- messages: list[dict[str, Any]],
367
- tools: list[dict[str, Any]] | None = None,
368
- on_chunk: Callable[[str, str], None] | None = None,
369
- register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
370
- model_params: ModelParams | None = None,
371
- ) -> LLMResponse:
372
- url = f"{self._api_base_url}/responses"
373
- system_prompt, input_items = self._convert_messages(messages)
374
-
375
- payload: dict[str, Any] = {
376
- "model": self._model,
377
- "input": input_items,
378
- "stream": True,
379
- }
380
- reasoning = _build_reasoning_config(self._model, model_params)
381
- if reasoning:
382
- payload["reasoning"] = reasoning
383
- text_config = _build_text_config(self._model, model_params)
384
- if text_config:
385
- payload["text"] = text_config
386
- if model_params is not None:
387
- if model_params.max_output_tokens is not None:
388
- payload["max_output_tokens"] = model_params.max_output_tokens
389
- if reasoning is None:
390
- if model_params.temperature is not None:
391
- payload["temperature"] = model_params.temperature
392
- if model_params.top_p is not None:
393
- payload["top_p"] = model_params.top_p
394
- if system_prompt:
395
- payload["instructions"] = system_prompt
396
- payload_tools = []
397
- if tools:
398
- payload_tools.extend(self._convert_tools(tools))
399
- if self._supports_output_image():
400
- payload_tools.append({"type": "image_generation"})
401
- if payload_tools:
402
- payload["tools"] = payload_tools
403
- payload["tool_choice"] = "auto"
404
-
405
- logger.debug(
406
- "[{}] OpenAI Responses chat request: model={}, input_items={}, tools={}",
407
- self._provider_name,
408
- self._model,
409
- len(input_items),
410
- len(tools) if tools else 0,
411
- )
412
-
413
- t0 = time.perf_counter()
414
- content_parts: list[str] = []
415
- thinking_parts: list[str] = []
416
- response_parts: list[LLMOutputTextPart | LLMOutputImagePart] | None = None
417
- tool_calls: list[ToolCall] = []
418
- chunk_count = 0
419
- saw_reasoning_item = False
420
- saw_reasoning_text = False
421
- reasoning_tokens = 0
422
- response_usage: LLMUsage | None = None
423
- raw_usage: dict[str, Any] | None = None
424
-
425
- current_tool: dict[str, Any] = {}
426
- client = self._client
427
- if register_interrupt is not None:
428
- register_interrupt(client.close)
429
-
430
- try:
431
- with client.stream(
432
- "POST",
433
- url,
434
- headers=self._headers(),
435
- json=payload,
436
- ) as response:
437
- if register_interrupt is not None:
438
- register_interrupt(response.close)
439
- if response_looks_like_html(response):
440
- raise build_access_blocked_error(
441
- provider_name=self._provider_name,
442
- provider_type="openai_responses",
443
- model=self._model,
444
- base_url=self._api_base_url,
445
- status_code=response.status_code,
446
- detail=truncate_text(read_response_text(response)),
447
- )
448
- if response.status_code != 200:
449
- body = truncate_text(read_response_text(response))
450
- elapsed = time.perf_counter() - t0
451
- logger.error(
452
- "LLM API error [provider={}, model={}, type=openai_responses]: {} - {} ({:.2f}s)",
453
- self._provider_name,
454
- self._model,
455
- response.status_code,
456
- body[:500],
457
- elapsed,
458
- )
459
- raise build_status_error(
460
- provider_name=self._provider_name,
461
- provider_type="openai_responses",
462
- model=self._model,
463
- base_url=self._api_base_url,
464
- status_code=response.status_code,
465
- body=body,
466
- )
467
-
468
- for line in iter_response_lines(response):
469
- if not line or line.startswith(":"):
470
- continue
471
- if not line.startswith("data: "):
472
- continue
473
- data_str = line[6:]
474
- if data_str.strip() == "[DONE]":
475
- break
476
-
477
- try:
478
- event = json.loads(data_str)
479
- except json.JSONDecodeError:
480
- continue
481
-
482
- chunk_count += 1
483
- event_type = event.get("type", "")
484
-
485
- if event_type in REASONING_DELTA_EVENT_TYPES:
486
- text = event.get("delta", "")
487
- if text:
488
- saw_reasoning_text = True
489
- thinking_parts.append(text)
490
- if on_chunk:
491
- on_chunk("thinking", text)
492
-
493
- elif event_type == "response.output_text.delta":
494
- text = event.get("delta", "")
495
- if text:
496
- content_parts.append(text)
497
- if on_chunk:
498
- on_chunk("content", text)
499
-
500
- elif event_type == "response.function_call_arguments.delta":
501
- delta = event.get("delta", "")
502
- current_tool.setdefault("arguments", "")
503
- current_tool["arguments"] += delta
504
-
505
- elif event_type == "response.output_item.added":
506
- item = event.get("item", {})
507
- if item.get("type") == "reasoning":
508
- saw_reasoning_item = True
509
- elif item.get("type") == "function_call":
510
- current_tool = {
511
- "id": item.get("call_id", ""),
512
- "name": item.get("name", ""),
513
- "arguments": "",
514
- }
515
-
516
- elif event_type == "response.output_item.done":
517
- item = event.get("item", {})
518
- if item.get("type") == "reasoning":
519
- saw_reasoning_item = True
520
- if not saw_reasoning_text:
521
- reasoning_text = _extract_reasoning_text_from_item(item)
522
- if reasoning_text:
523
- saw_reasoning_text = True
524
- thinking_parts.append(reasoning_text)
525
- if on_chunk:
526
- on_chunk("thinking", reasoning_text)
527
- elif item.get("type") == "function_call":
528
- call_id = item.get("call_id") or current_tool.get("id", "")
529
- name = item.get("name") or current_tool.get("name", "")
530
- args_str = item.get("arguments") or current_tool.get(
531
- "arguments", "{}"
532
- )
533
- try:
534
- args = json.loads(args_str)
535
- except json.JSONDecodeError:
536
- args = {}
537
- tool_calls.append(
538
- ToolCall(id=call_id, name=name, arguments=args)
539
- )
540
- current_tool = {}
541
-
542
- elif event_type == "response.completed":
543
- response_data = event.get("response", {})
544
- if isinstance(response_data, dict):
545
- reasoning_tokens = _extract_reasoning_tokens(response_data)
546
- response_usage = _extract_usage(response_data)
547
- usage_payload = response_data.get("usage")
548
- if isinstance(usage_payload, dict):
549
- raw_usage = dict(usage_payload)
550
- if not saw_reasoning_text:
551
- reasoning_text = _extract_reasoning_text_from_output(
552
- response_data.get("output")
553
- )
554
- if reasoning_text:
555
- saw_reasoning_text = True
556
- thinking_parts.append(reasoning_text)
557
- if on_chunk:
558
- on_chunk("thinking", reasoning_text)
559
- output_parts = _extract_output_parts(
560
- response_data.get("output")
561
- )
562
- if output_parts:
563
- response_parts = output_parts
564
- except RequestException as exc:
565
- elapsed = time.perf_counter() - t0
566
- logger.warning(
567
- "LLM API transport error [provider={}, model={}, type=openai_responses]: {} ({:.2f}s)",
568
- self._provider_name,
569
- self._model,
570
- exc,
571
- elapsed,
572
- )
573
- raise build_network_error(
574
- provider_name=self._provider_name,
575
- provider_type="openai_responses",
576
- model=self._model,
577
- base_url=self._api_base_url,
578
- error=exc,
579
- ) from exc
580
- finally:
581
- close_client = getattr(client, "close", None)
582
- if callable(close_client):
583
- close_client()
584
- self._client = create_http_session(
585
- timeout=self._request_timeout_seconds
586
- )
587
-
588
- elapsed = time.perf_counter() - t0
589
- content = "".join(content_parts) or None
590
- thinking = "".join(thinking_parts) or None
591
- if thinking is None and (saw_reasoning_item or reasoning_tokens > 0):
592
- thinking = _format_reasoning_fallback(reasoning_tokens)
593
-
594
- logger.debug(
595
- "[{}] OpenAI Responses chat done: {:.2f}s, chunks={}, content_len={}, thinking_len={}, tool_calls={}",
596
- self._provider_name,
597
- elapsed,
598
- chunk_count,
599
- len(content) if content else 0,
600
- len(thinking) if thinking else 0,
601
- len(tool_calls),
602
- )
603
-
604
- if tool_calls:
605
- return LLMResponse(
606
- content=content,
607
- parts=response_parts,
608
- tool_calls=tool_calls,
609
- thinking=thinking,
610
- usage=response_usage,
611
- raw_usage=raw_usage,
612
- )
613
-
614
- return LLMResponse(
615
- content=content or "",
616
- parts=response_parts,
617
- thinking=thinking,
618
- usage=response_usage,
619
- raw_usage=raw_usage,
620
- )
621
-
622
- def list_models(
623
- self,
624
- register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
625
- ) -> list[ModelInfo]:
626
- url = f"{self._api_base_url}/models"
627
- client = self._client
628
- close_client = getattr(client, "close", None)
629
- if register_interrupt is not None and callable(close_client):
630
- register_interrupt(close_client)
631
- try:
632
- resp = client.get(url, headers=self._headers())
633
- resp.raise_for_status()
634
- data = resp.json()
635
- models = data.get("data", [])
636
- return [
637
- build_model_info(
638
- provider_type="openai_responses",
639
- model_id=m["id"],
640
- )
641
- for m in models
642
- ]
643
- except Exception as e:
644
- logger.error(
645
- "Failed to list models [provider={}, type=openai_responses]: {}",
646
- self._provider_name,
647
- e,
648
- )
649
- return []
650
- finally:
651
- if callable(close_client):
652
- close_client()
653
- self._client = create_http_session(
654
- timeout=self._request_timeout_seconds
655
- )