aip-agents-binary 0.5.20__py3-none-manylinux_2_31_x86_64.whl

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 (546) hide show
  1. aip_agents/__init__.py +65 -0
  2. aip_agents/__init__.pyi +19 -0
  3. aip_agents/a2a/__init__.py +19 -0
  4. aip_agents/a2a/__init__.pyi +3 -0
  5. aip_agents/a2a/server/__init__.py +10 -0
  6. aip_agents/a2a/server/__init__.pyi +4 -0
  7. aip_agents/a2a/server/base_executor.py +1086 -0
  8. aip_agents/a2a/server/base_executor.pyi +73 -0
  9. aip_agents/a2a/server/google_adk_executor.py +198 -0
  10. aip_agents/a2a/server/google_adk_executor.pyi +51 -0
  11. aip_agents/a2a/server/langflow_executor.py +180 -0
  12. aip_agents/a2a/server/langflow_executor.pyi +43 -0
  13. aip_agents/a2a/server/langgraph_executor.py +270 -0
  14. aip_agents/a2a/server/langgraph_executor.pyi +47 -0
  15. aip_agents/a2a/types.py +232 -0
  16. aip_agents/a2a/types.pyi +132 -0
  17. aip_agents/agent/__init__.py +27 -0
  18. aip_agents/agent/__init__.pyi +9 -0
  19. aip_agents/agent/base_agent.py +970 -0
  20. aip_agents/agent/base_agent.pyi +221 -0
  21. aip_agents/agent/base_langgraph_agent.py +2942 -0
  22. aip_agents/agent/base_langgraph_agent.pyi +232 -0
  23. aip_agents/agent/google_adk_agent.py +926 -0
  24. aip_agents/agent/google_adk_agent.pyi +141 -0
  25. aip_agents/agent/google_adk_constants.py +6 -0
  26. aip_agents/agent/google_adk_constants.pyi +3 -0
  27. aip_agents/agent/hitl/__init__.py +24 -0
  28. aip_agents/agent/hitl/__init__.pyi +6 -0
  29. aip_agents/agent/hitl/config.py +28 -0
  30. aip_agents/agent/hitl/config.pyi +15 -0
  31. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  32. aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +42 -0
  33. aip_agents/agent/hitl/manager.py +532 -0
  34. aip_agents/agent/hitl/manager.pyi +200 -0
  35. aip_agents/agent/hitl/models.py +18 -0
  36. aip_agents/agent/hitl/models.pyi +3 -0
  37. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  38. aip_agents/agent/hitl/prompt/__init__.pyi +4 -0
  39. aip_agents/agent/hitl/prompt/base.py +42 -0
  40. aip_agents/agent/hitl/prompt/base.pyi +24 -0
  41. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  42. aip_agents/agent/hitl/prompt/deferred.pyi +30 -0
  43. aip_agents/agent/hitl/registry.py +149 -0
  44. aip_agents/agent/hitl/registry.pyi +101 -0
  45. aip_agents/agent/interface.py +138 -0
  46. aip_agents/agent/interface.pyi +81 -0
  47. aip_agents/agent/interfaces.py +65 -0
  48. aip_agents/agent/interfaces.pyi +44 -0
  49. aip_agents/agent/langflow_agent.py +464 -0
  50. aip_agents/agent/langflow_agent.pyi +133 -0
  51. aip_agents/agent/langgraph_memory_enhancer_agent.py +433 -0
  52. aip_agents/agent/langgraph_memory_enhancer_agent.pyi +49 -0
  53. aip_agents/agent/langgraph_react_agent.py +2514 -0
  54. aip_agents/agent/langgraph_react_agent.pyi +126 -0
  55. aip_agents/agent/system_instruction_context.py +34 -0
  56. aip_agents/agent/system_instruction_context.pyi +13 -0
  57. aip_agents/clients/__init__.py +10 -0
  58. aip_agents/clients/__init__.pyi +4 -0
  59. aip_agents/clients/langflow/__init__.py +10 -0
  60. aip_agents/clients/langflow/__init__.pyi +4 -0
  61. aip_agents/clients/langflow/client.py +477 -0
  62. aip_agents/clients/langflow/client.pyi +140 -0
  63. aip_agents/clients/langflow/types.py +18 -0
  64. aip_agents/clients/langflow/types.pyi +7 -0
  65. aip_agents/constants.py +23 -0
  66. aip_agents/constants.pyi +7 -0
  67. aip_agents/credentials/manager.py +132 -0
  68. aip_agents/examples/__init__.py +5 -0
  69. aip_agents/examples/__init__.pyi +0 -0
  70. aip_agents/examples/compare_streaming_client.py +783 -0
  71. aip_agents/examples/compare_streaming_client.pyi +48 -0
  72. aip_agents/examples/compare_streaming_server.py +142 -0
  73. aip_agents/examples/compare_streaming_server.pyi +18 -0
  74. aip_agents/examples/demo_memory_recall.py +401 -0
  75. aip_agents/examples/demo_memory_recall.pyi +58 -0
  76. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  77. aip_agents/examples/hello_world_a2a_google_adk_client.pyi +9 -0
  78. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  79. aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +9 -0
  80. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  81. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +9 -0
  82. aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
  83. aip_agents/examples/hello_world_a2a_google_adk_server.pyi +15 -0
  84. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  85. aip_agents/examples/hello_world_a2a_langchain_client.pyi +5 -0
  86. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  87. aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +5 -0
  88. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  89. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +5 -0
  90. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  91. aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +5 -0
  92. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  93. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +5 -0
  94. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
  95. aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +15 -0
  96. aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
  97. aip_agents/examples/hello_world_a2a_langchain_server.pyi +15 -0
  98. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
  99. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +15 -0
  100. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  101. aip_agents/examples/hello_world_a2a_langflow_client.pyi +9 -0
  102. aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
  103. aip_agents/examples/hello_world_a2a_langflow_server.pyi +14 -0
  104. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  105. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +5 -0
  106. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  107. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +5 -0
  108. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
  109. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +16 -0
  110. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  111. aip_agents/examples/hello_world_a2a_langgraph_client.pyi +9 -0
  112. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  113. aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +9 -0
  114. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  115. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +2 -0
  116. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  117. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +9 -0
  118. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  119. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +5 -0
  120. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  121. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +5 -0
  122. aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
  123. aip_agents/examples/hello_world_a2a_langgraph_server.pyi +14 -0
  124. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
  125. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +15 -0
  126. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
  127. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +15 -0
  128. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  129. aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +48 -0
  130. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  131. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.pyi +48 -0
  132. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
  133. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +45 -0
  134. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  135. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +5 -0
  136. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
  137. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +15 -0
  138. aip_agents/examples/hello_world_google_adk.py +41 -0
  139. aip_agents/examples/hello_world_google_adk.pyi +5 -0
  140. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  141. aip_agents/examples/hello_world_google_adk_mcp_http.pyi +5 -0
  142. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  143. aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +5 -0
  144. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  145. aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +5 -0
  146. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  147. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +5 -0
  148. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  149. aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +5 -0
  150. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  151. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +5 -0
  152. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  153. aip_agents/examples/hello_world_google_adk_stream.pyi +5 -0
  154. aip_agents/examples/hello_world_langchain.py +28 -0
  155. aip_agents/examples/hello_world_langchain.pyi +5 -0
  156. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  157. aip_agents/examples/hello_world_langchain_lm_invoker.pyi +2 -0
  158. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  159. aip_agents/examples/hello_world_langchain_mcp_http.pyi +5 -0
  160. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  161. aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +16 -0
  162. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  163. aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +5 -0
  164. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  165. aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +18 -0
  166. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  167. aip_agents/examples/hello_world_langchain_mcp_sse.pyi +5 -0
  168. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  169. aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +5 -0
  170. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  171. aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +5 -0
  172. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  173. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +5 -0
  174. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  175. aip_agents/examples/hello_world_langchain_stream.pyi +5 -0
  176. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  177. aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +5 -0
  178. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  179. aip_agents/examples/hello_world_langflow_agent.pyi +35 -0
  180. aip_agents/examples/hello_world_langgraph.py +39 -0
  181. aip_agents/examples/hello_world_langgraph.pyi +5 -0
  182. aip_agents/examples/hello_world_langgraph_bosa_twitter.py +41 -0
  183. aip_agents/examples/hello_world_langgraph_bosa_twitter.pyi +5 -0
  184. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  185. aip_agents/examples/hello_world_langgraph_mcp_http.pyi +5 -0
  186. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  187. aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +5 -0
  188. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  189. aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +5 -0
  190. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  191. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +5 -0
  192. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  193. aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +5 -0
  194. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  195. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +5 -0
  196. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  197. aip_agents/examples/hello_world_langgraph_stream.pyi +5 -0
  198. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  199. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +5 -0
  200. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  201. aip_agents/examples/hello_world_model_switch_cli.pyi +30 -0
  202. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  203. aip_agents/examples/hello_world_multi_agent_adk.pyi +6 -0
  204. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  205. aip_agents/examples/hello_world_multi_agent_langchain.pyi +5 -0
  206. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  207. aip_agents/examples/hello_world_multi_agent_langgraph.pyi +5 -0
  208. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  209. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +5 -0
  210. aip_agents/examples/hello_world_pii_logger.py +21 -0
  211. aip_agents/examples/hello_world_pii_logger.pyi +5 -0
  212. aip_agents/examples/hello_world_sentry.py +133 -0
  213. aip_agents/examples/hello_world_sentry.pyi +21 -0
  214. aip_agents/examples/hello_world_step_limits.py +273 -0
  215. aip_agents/examples/hello_world_step_limits.pyi +17 -0
  216. aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
  217. aip_agents/examples/hello_world_stock_a2a_server.pyi +17 -0
  218. aip_agents/examples/hello_world_tool_output_client.py +46 -0
  219. aip_agents/examples/hello_world_tool_output_client.pyi +5 -0
  220. aip_agents/examples/hello_world_tool_output_server.py +114 -0
  221. aip_agents/examples/hello_world_tool_output_server.pyi +19 -0
  222. aip_agents/examples/hitl_demo.py +724 -0
  223. aip_agents/examples/hitl_demo.pyi +67 -0
  224. aip_agents/examples/mcp_configs/configs.py +63 -0
  225. aip_agents/examples/mcp_servers/common.py +76 -0
  226. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  227. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  228. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  229. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  230. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  231. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  232. aip_agents/examples/pii_demo_langgraph_client.pyi +5 -0
  233. aip_agents/examples/pii_demo_langgraph_server.py +126 -0
  234. aip_agents/examples/pii_demo_langgraph_server.pyi +20 -0
  235. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  236. aip_agents/examples/pii_demo_multi_agent_client.pyi +5 -0
  237. aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
  238. aip_agents/examples/pii_demo_multi_agent_server.pyi +40 -0
  239. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  240. aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +5 -0
  241. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
  242. aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +19 -0
  243. aip_agents/examples/tools/__init__.py +27 -0
  244. aip_agents/examples/tools/__init__.pyi +9 -0
  245. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  246. aip_agents/examples/tools/adk_arithmetic_tools.pyi +24 -0
  247. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  248. aip_agents/examples/tools/adk_weather_tool.pyi +18 -0
  249. aip_agents/examples/tools/data_generator_tool.py +103 -0
  250. aip_agents/examples/tools/data_generator_tool.pyi +15 -0
  251. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  252. aip_agents/examples/tools/data_visualization_tool.pyi +19 -0
  253. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  254. aip_agents/examples/tools/image_artifact_tool.pyi +26 -0
  255. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  256. aip_agents/examples/tools/langchain_arithmetic_tools.pyi +17 -0
  257. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  258. aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +20 -0
  259. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  260. aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +25 -0
  261. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  262. aip_agents/examples/tools/langchain_weather_tool.pyi +19 -0
  263. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  264. aip_agents/examples/tools/langgraph_streaming_tool.pyi +43 -0
  265. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  266. aip_agents/examples/tools/mock_retrieval_tool.pyi +13 -0
  267. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  268. aip_agents/examples/tools/pii_demo_tools.pyi +54 -0
  269. aip_agents/examples/tools/random_chart_tool.py +142 -0
  270. aip_agents/examples/tools/random_chart_tool.pyi +20 -0
  271. aip_agents/examples/tools/serper_tool.py +202 -0
  272. aip_agents/examples/tools/serper_tool.pyi +16 -0
  273. aip_agents/examples/tools/stock_tools.py +82 -0
  274. aip_agents/examples/tools/stock_tools.pyi +36 -0
  275. aip_agents/examples/tools/table_generator_tool.py +167 -0
  276. aip_agents/examples/tools/table_generator_tool.pyi +22 -0
  277. aip_agents/examples/tools/time_tool.py +82 -0
  278. aip_agents/examples/tools/time_tool.pyi +15 -0
  279. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  280. aip_agents/examples/tools/weather_forecast_tool.pyi +14 -0
  281. aip_agents/executor/agent_executor.py +473 -0
  282. aip_agents/executor/base.py +48 -0
  283. aip_agents/mcp/__init__.py +1 -0
  284. aip_agents/mcp/__init__.pyi +0 -0
  285. aip_agents/mcp/client/__init__.py +14 -0
  286. aip_agents/mcp/client/__init__.pyi +5 -0
  287. aip_agents/mcp/client/base_mcp_client.py +369 -0
  288. aip_agents/mcp/client/base_mcp_client.pyi +148 -0
  289. aip_agents/mcp/client/connection_manager.py +193 -0
  290. aip_agents/mcp/client/connection_manager.pyi +48 -0
  291. aip_agents/mcp/client/google_adk/__init__.py +11 -0
  292. aip_agents/mcp/client/google_adk/__init__.pyi +3 -0
  293. aip_agents/mcp/client/google_adk/client.py +381 -0
  294. aip_agents/mcp/client/google_adk/client.pyi +75 -0
  295. aip_agents/mcp/client/langchain/__init__.py +11 -0
  296. aip_agents/mcp/client/langchain/__init__.pyi +3 -0
  297. aip_agents/mcp/client/langchain/client.py +265 -0
  298. aip_agents/mcp/client/langchain/client.pyi +48 -0
  299. aip_agents/mcp/client/persistent_session.py +359 -0
  300. aip_agents/mcp/client/persistent_session.pyi +113 -0
  301. aip_agents/mcp/client/session_pool.py +351 -0
  302. aip_agents/mcp/client/session_pool.pyi +101 -0
  303. aip_agents/mcp/client/transports.py +215 -0
  304. aip_agents/mcp/client/transports.pyi +123 -0
  305. aip_agents/mcp/utils/__init__.py +7 -0
  306. aip_agents/mcp/utils/__init__.pyi +0 -0
  307. aip_agents/mcp/utils/config_validator.py +139 -0
  308. aip_agents/mcp/utils/config_validator.pyi +82 -0
  309. aip_agents/memory/__init__.py +14 -0
  310. aip_agents/memory/__init__.pyi +5 -0
  311. aip_agents/memory/adapters/__init__.py +10 -0
  312. aip_agents/memory/adapters/__init__.pyi +4 -0
  313. aip_agents/memory/adapters/base_adapter.py +717 -0
  314. aip_agents/memory/adapters/base_adapter.pyi +150 -0
  315. aip_agents/memory/adapters/mem0.py +84 -0
  316. aip_agents/memory/adapters/mem0.pyi +22 -0
  317. aip_agents/memory/base.py +84 -0
  318. aip_agents/memory/base.pyi +60 -0
  319. aip_agents/memory/constants.py +49 -0
  320. aip_agents/memory/constants.pyi +25 -0
  321. aip_agents/memory/factory.py +86 -0
  322. aip_agents/memory/factory.pyi +24 -0
  323. aip_agents/memory/guidance.py +20 -0
  324. aip_agents/memory/guidance.pyi +3 -0
  325. aip_agents/memory/simple_memory.py +47 -0
  326. aip_agents/memory/simple_memory.pyi +23 -0
  327. aip_agents/middleware/__init__.py +17 -0
  328. aip_agents/middleware/__init__.pyi +5 -0
  329. aip_agents/middleware/base.py +88 -0
  330. aip_agents/middleware/base.pyi +71 -0
  331. aip_agents/middleware/manager.py +128 -0
  332. aip_agents/middleware/manager.pyi +80 -0
  333. aip_agents/middleware/todolist.py +274 -0
  334. aip_agents/middleware/todolist.pyi +125 -0
  335. aip_agents/schema/__init__.py +69 -0
  336. aip_agents/schema/__init__.pyi +9 -0
  337. aip_agents/schema/a2a.py +56 -0
  338. aip_agents/schema/a2a.pyi +40 -0
  339. aip_agents/schema/agent.py +111 -0
  340. aip_agents/schema/agent.pyi +65 -0
  341. aip_agents/schema/hitl.py +157 -0
  342. aip_agents/schema/hitl.pyi +89 -0
  343. aip_agents/schema/langgraph.py +37 -0
  344. aip_agents/schema/langgraph.pyi +28 -0
  345. aip_agents/schema/model_id.py +97 -0
  346. aip_agents/schema/model_id.pyi +54 -0
  347. aip_agents/schema/step_limit.py +108 -0
  348. aip_agents/schema/step_limit.pyi +63 -0
  349. aip_agents/schema/storage.py +40 -0
  350. aip_agents/schema/storage.pyi +21 -0
  351. aip_agents/sentry/__init__.py +11 -0
  352. aip_agents/sentry/__init__.pyi +3 -0
  353. aip_agents/sentry/sentry.py +151 -0
  354. aip_agents/sentry/sentry.pyi +48 -0
  355. aip_agents/storage/__init__.py +41 -0
  356. aip_agents/storage/__init__.pyi +8 -0
  357. aip_agents/storage/base.py +85 -0
  358. aip_agents/storage/base.pyi +58 -0
  359. aip_agents/storage/clients/__init__.py +12 -0
  360. aip_agents/storage/clients/__init__.pyi +3 -0
  361. aip_agents/storage/clients/minio_client.py +318 -0
  362. aip_agents/storage/clients/minio_client.pyi +137 -0
  363. aip_agents/storage/config.py +62 -0
  364. aip_agents/storage/config.pyi +29 -0
  365. aip_agents/storage/providers/__init__.py +15 -0
  366. aip_agents/storage/providers/__init__.pyi +5 -0
  367. aip_agents/storage/providers/base.py +106 -0
  368. aip_agents/storage/providers/base.pyi +88 -0
  369. aip_agents/storage/providers/memory.py +114 -0
  370. aip_agents/storage/providers/memory.pyi +79 -0
  371. aip_agents/storage/providers/object_storage.py +214 -0
  372. aip_agents/storage/providers/object_storage.pyi +98 -0
  373. aip_agents/tools/__init__.py +33 -0
  374. aip_agents/tools/__init__.pyi +13 -0
  375. aip_agents/tools/bosa_tools.py +105 -0
  376. aip_agents/tools/bosa_tools.pyi +37 -0
  377. aip_agents/tools/browser_use/__init__.py +82 -0
  378. aip_agents/tools/browser_use/__init__.pyi +14 -0
  379. aip_agents/tools/browser_use/action_parser.py +103 -0
  380. aip_agents/tools/browser_use/action_parser.pyi +18 -0
  381. aip_agents/tools/browser_use/browser_use_tool.py +1112 -0
  382. aip_agents/tools/browser_use/browser_use_tool.pyi +50 -0
  383. aip_agents/tools/browser_use/llm_config.py +120 -0
  384. aip_agents/tools/browser_use/llm_config.pyi +52 -0
  385. aip_agents/tools/browser_use/minio_storage.py +198 -0
  386. aip_agents/tools/browser_use/minio_storage.pyi +109 -0
  387. aip_agents/tools/browser_use/schemas.py +119 -0
  388. aip_agents/tools/browser_use/schemas.pyi +32 -0
  389. aip_agents/tools/browser_use/session.py +76 -0
  390. aip_agents/tools/browser_use/session.pyi +4 -0
  391. aip_agents/tools/browser_use/session_errors.py +132 -0
  392. aip_agents/tools/browser_use/session_errors.pyi +53 -0
  393. aip_agents/tools/browser_use/steel_session_recording.py +317 -0
  394. aip_agents/tools/browser_use/steel_session_recording.pyi +63 -0
  395. aip_agents/tools/browser_use/streaming.py +813 -0
  396. aip_agents/tools/browser_use/streaming.pyi +81 -0
  397. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  398. aip_agents/tools/browser_use/structured_data_parser.pyi +86 -0
  399. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  400. aip_agents/tools/browser_use/structured_data_recovery.pyi +43 -0
  401. aip_agents/tools/browser_use/types.py +78 -0
  402. aip_agents/tools/browser_use/types.pyi +45 -0
  403. aip_agents/tools/code_sandbox/__init__.py +26 -0
  404. aip_agents/tools/code_sandbox/__init__.pyi +3 -0
  405. aip_agents/tools/code_sandbox/constant.py +13 -0
  406. aip_agents/tools/code_sandbox/constant.pyi +4 -0
  407. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +257 -0
  408. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +86 -0
  409. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
  410. aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +29 -0
  411. aip_agents/tools/constants.py +165 -0
  412. aip_agents/tools/constants.pyi +135 -0
  413. aip_agents/tools/document_loader/__init__.py +44 -0
  414. aip_agents/tools/document_loader/__init__.pyi +7 -0
  415. aip_agents/tools/document_loader/base_reader.py +302 -0
  416. aip_agents/tools/document_loader/base_reader.pyi +75 -0
  417. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  418. aip_agents/tools/document_loader/docx_reader_tool.pyi +10 -0
  419. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  420. aip_agents/tools/document_loader/excel_reader_tool.pyi +26 -0
  421. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  422. aip_agents/tools/document_loader/pdf_reader_tool.pyi +11 -0
  423. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  424. aip_agents/tools/document_loader/pdf_splitter.pyi +18 -0
  425. aip_agents/tools/gl_connector/__init__.py +5 -0
  426. aip_agents/tools/gl_connector/__init__.pyi +3 -0
  427. aip_agents/tools/gl_connector/tool.py +351 -0
  428. aip_agents/tools/gl_connector/tool.pyi +74 -0
  429. aip_agents/tools/memory_search/__init__.py +22 -0
  430. aip_agents/tools/memory_search/__init__.pyi +5 -0
  431. aip_agents/tools/memory_search/base.py +200 -0
  432. aip_agents/tools/memory_search/base.pyi +69 -0
  433. aip_agents/tools/memory_search/mem0.py +258 -0
  434. aip_agents/tools/memory_search/mem0.pyi +19 -0
  435. aip_agents/tools/memory_search/schema.py +48 -0
  436. aip_agents/tools/memory_search/schema.pyi +15 -0
  437. aip_agents/tools/memory_search_tool.py +26 -0
  438. aip_agents/tools/memory_search_tool.pyi +3 -0
  439. aip_agents/tools/time_tool.py +117 -0
  440. aip_agents/tools/time_tool.pyi +16 -0
  441. aip_agents/tools/tool_config_injector.py +300 -0
  442. aip_agents/tools/tool_config_injector.pyi +26 -0
  443. aip_agents/tools/web_search/__init__.py +15 -0
  444. aip_agents/tools/web_search/__init__.pyi +3 -0
  445. aip_agents/tools/web_search/serper_tool.py +187 -0
  446. aip_agents/tools/web_search/serper_tool.pyi +19 -0
  447. aip_agents/types/__init__.py +70 -0
  448. aip_agents/types/__init__.pyi +36 -0
  449. aip_agents/types/a2a_events.py +13 -0
  450. aip_agents/types/a2a_events.pyi +3 -0
  451. aip_agents/utils/__init__.py +79 -0
  452. aip_agents/utils/__init__.pyi +11 -0
  453. aip_agents/utils/a2a_connector.py +1757 -0
  454. aip_agents/utils/a2a_connector.pyi +146 -0
  455. aip_agents/utils/artifact_helpers.py +502 -0
  456. aip_agents/utils/artifact_helpers.pyi +203 -0
  457. aip_agents/utils/constants.py +22 -0
  458. aip_agents/utils/constants.pyi +10 -0
  459. aip_agents/utils/datetime/__init__.py +34 -0
  460. aip_agents/utils/datetime/__init__.pyi +4 -0
  461. aip_agents/utils/datetime/normalization.py +231 -0
  462. aip_agents/utils/datetime/normalization.pyi +95 -0
  463. aip_agents/utils/datetime/timezone.py +206 -0
  464. aip_agents/utils/datetime/timezone.pyi +48 -0
  465. aip_agents/utils/env_loader.py +27 -0
  466. aip_agents/utils/env_loader.pyi +10 -0
  467. aip_agents/utils/event_handler_registry.py +58 -0
  468. aip_agents/utils/event_handler_registry.pyi +23 -0
  469. aip_agents/utils/file_prompt_utils.py +176 -0
  470. aip_agents/utils/file_prompt_utils.pyi +21 -0
  471. aip_agents/utils/final_response_builder.py +211 -0
  472. aip_agents/utils/final_response_builder.pyi +34 -0
  473. aip_agents/utils/formatter_llm_client.py +231 -0
  474. aip_agents/utils/formatter_llm_client.pyi +71 -0
  475. aip_agents/utils/langgraph/__init__.py +19 -0
  476. aip_agents/utils/langgraph/__init__.pyi +3 -0
  477. aip_agents/utils/langgraph/converter.py +128 -0
  478. aip_agents/utils/langgraph/converter.pyi +49 -0
  479. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  480. aip_agents/utils/langgraph/tool_managers/__init__.pyi +5 -0
  481. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  482. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +35 -0
  483. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  484. aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +48 -0
  485. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1071 -0
  486. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
  487. aip_agents/utils/langgraph/tool_output_management.py +967 -0
  488. aip_agents/utils/langgraph/tool_output_management.pyi +292 -0
  489. aip_agents/utils/logger.py +195 -0
  490. aip_agents/utils/logger.pyi +60 -0
  491. aip_agents/utils/metadata/__init__.py +27 -0
  492. aip_agents/utils/metadata/__init__.pyi +5 -0
  493. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  494. aip_agents/utils/metadata/activity_metadata_helper.pyi +25 -0
  495. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  496. aip_agents/utils/metadata/activity_narrative/__init__.pyi +7 -0
  497. aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
  498. aip_agents/utils/metadata/activity_narrative/builder.pyi +35 -0
  499. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  500. aip_agents/utils/metadata/activity_narrative/constants.pyi +10 -0
  501. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  502. aip_agents/utils/metadata/activity_narrative/context.pyi +32 -0
  503. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  504. aip_agents/utils/metadata/activity_narrative/formatters.pyi +48 -0
  505. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  506. aip_agents/utils/metadata/activity_narrative/utils.pyi +12 -0
  507. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  508. aip_agents/utils/metadata/schemas/__init__.pyi +4 -0
  509. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  510. aip_agents/utils/metadata/schemas/activity_schema.pyi +18 -0
  511. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  512. aip_agents/utils/metadata/schemas/thinking_schema.pyi +20 -0
  513. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  514. aip_agents/utils/metadata/thinking_metadata_helper.pyi +4 -0
  515. aip_agents/utils/metadata_helper.py +358 -0
  516. aip_agents/utils/metadata_helper.pyi +117 -0
  517. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  518. aip_agents/utils/name_preprocessor/__init__.pyi +6 -0
  519. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  520. aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +52 -0
  521. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  522. aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +38 -0
  523. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  524. aip_agents/utils/name_preprocessor/name_preprocessor.pyi +41 -0
  525. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  526. aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +34 -0
  527. aip_agents/utils/pii/__init__.py +25 -0
  528. aip_agents/utils/pii/__init__.pyi +5 -0
  529. aip_agents/utils/pii/pii_handler.py +397 -0
  530. aip_agents/utils/pii/pii_handler.pyi +96 -0
  531. aip_agents/utils/pii/pii_helper.py +207 -0
  532. aip_agents/utils/pii/pii_helper.pyi +78 -0
  533. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  534. aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +73 -0
  535. aip_agents/utils/reference_helper.py +273 -0
  536. aip_agents/utils/reference_helper.pyi +81 -0
  537. aip_agents/utils/sse_chunk_transformer.py +831 -0
  538. aip_agents/utils/sse_chunk_transformer.pyi +166 -0
  539. aip_agents/utils/step_limit_manager.py +265 -0
  540. aip_agents/utils/step_limit_manager.pyi +112 -0
  541. aip_agents/utils/token_usage_helper.py +156 -0
  542. aip_agents/utils/token_usage_helper.pyi +60 -0
  543. aip_agents_binary-0.5.20.dist-info/METADATA +681 -0
  544. aip_agents_binary-0.5.20.dist-info/RECORD +546 -0
  545. aip_agents_binary-0.5.20.dist-info/WHEEL +5 -0
  546. aip_agents_binary-0.5.20.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1112 @@
1
+ """Tool to execute web automation tasks using browser-use framework.
2
+
3
+ This tool provides browser automation capabilities using the browser-use framework
4
+ with Steel session management and streaming support.
5
+
6
+ Streaming Contract
7
+
8
+ The tool emits streaming events with the following structure:
9
+
10
+ Thinking Markers
11
+ - **thinking_start**: Indicates beginning of a thinking phase
12
+ ```json
13
+ {
14
+ "event_type": "status_update",
15
+ "content": "Thinking start",
16
+ "thinking_and_activity_info": {
17
+ "data_type": "thinking_start",
18
+ "data_value": "",
19
+ "id": "default_thinking_id"
20
+ }
21
+ }
22
+ ```
23
+
24
+ - thinking: Contains actual thinking content
25
+ ```json
26
+ {
27
+ "event_type": "tool_result",
28
+ "content": "Completed go_to_url, extract_structured_data",
29
+ "thinking_and_activity_info": {
30
+ "data_type": "thinking",
31
+ "data_value": "**Starting fresh with the task...**",
32
+ "id": "default_thinking_id"
33
+ }
34
+ }
35
+ ```
36
+
37
+ - thinking_end: Marks end of thinking phase
38
+ ```json
39
+ {
40
+ "event_type": "status_update",
41
+ "content": "Thinking end",
42
+ "thinking_and_activity_info": {
43
+ "data_type": "thinking_end",
44
+ "data_value": "",
45
+ "id": "default_thinking_id"
46
+ }
47
+ }
48
+ ```
49
+
50
+ Iframe Activities
51
+
52
+ - Streaming URL: Live browser session URL
53
+ ```json
54
+ {
55
+ "event_type": "status_update",
56
+ "content": "Receive streaming URL",
57
+ "thinking_and_activity_info": {
58
+ "data_type": "activity",
59
+ "data_value": "{'type': 'iframe', 'message': 'https://steel.dev/...'}"
60
+ }
61
+ }
62
+ ```
63
+
64
+ - Recording URL: Session recording URL
65
+ ```json
66
+ {
67
+ "event_type": "status_update",
68
+ "content": "Receive recording URL",
69
+ "thinking_and_activity_info": {
70
+ "data_type": "activity",
71
+ "data_value": "{'type': 'iframe', 'message': 'http://minio/...'}"
72
+ }
73
+ }
74
+ ```
75
+
76
+ Event Flow
77
+
78
+ 1. Initialize session → Yield streaming URL
79
+ 2. For each agent step:
80
+ - thinking_start
81
+ - tool_result with thinking content (emitted between the markers)
82
+ - thinking_end
83
+ 3. Complete → Yield recording URL
84
+
85
+ Authors:
86
+ Reinhart Linanda (reinhart.linanda@gdplabs.id)
87
+ Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
88
+ Saul Sayerz (saul.sayerz@gdplabs.id)
89
+ Raymond Christopher (raymond.christopher@gdplabs.id)
90
+
91
+ References:
92
+ https://github.com/GDP-ADMIN/glair-rnd/tree/main/browser-use/steeldev
93
+ """
94
+
95
+ import asyncio
96
+ import json
97
+ from collections.abc import Callable
98
+ from typing import Any, Literal
99
+
100
+ from browser_use import Agent
101
+ from langchain_core.runnables import RunnableConfig
102
+ from langchain_core.tools import BaseTool
103
+ from pydantic import BaseModel
104
+ from steel import Steel
105
+ from steel.types import Session
106
+
107
+ from aip_agents.tools.browser_use import session_errors
108
+ from aip_agents.tools.browser_use.action_parser import ActionParser
109
+ from aip_agents.tools.browser_use.llm_config import build_browser_use_llm, configure_browser_use_environment
110
+ from aip_agents.tools.browser_use.schemas import BrowserUseToolConfig, BrowserUseToolInput
111
+ from aip_agents.tools.browser_use.session import BrowserSession
112
+ from aip_agents.tools.browser_use.steel_session_recording import SteelSessionRecorder
113
+ from aip_agents.tools.browser_use.streaming import (
114
+ create_error_response,
115
+ create_step_response,
116
+ generate_step_content,
117
+ generate_thinking_message,
118
+ yield_iframe_activity,
119
+ yield_status_message,
120
+ yield_thinking_marker,
121
+ )
122
+ from aip_agents.tools.browser_use.structured_data_parser import detect_structured_data_failure
123
+ from aip_agents.tools.browser_use.types import (
124
+ BrowserUseFatalError,
125
+ RetryDecision,
126
+ StreamingResponse,
127
+ StreamingState,
128
+ ToolCallInfo,
129
+ )
130
+ from aip_agents.utils.logger import get_logger
131
+
132
+ logger = get_logger(__name__)
133
+
134
+
135
+ class BrowserUseTool(BaseTool):
136
+ """Tool to execute web automation tasks using browser-use framework.
137
+
138
+ This tool provides step-by-step execution of browser automation tasks with detailed
139
+ logging of intermediate steps, including the agent's thinking process, goals, and
140
+ results at each step.
141
+ """
142
+
143
+ name: str = "browser_use_tool"
144
+ description: str = (
145
+ "Execute web automation tasks using browser-use framework. "
146
+ "Connects browser-use agent, and runs the specified task step by step. "
147
+ "Provides detailed logging of intermediate steps including agent thinking and goals. "
148
+ "Returns the task execution results with step-by-step information."
149
+ )
150
+ args_schema: type[BaseModel] = BrowserUseToolInput
151
+ tool_config_schema: type[BaseModel] = BrowserUseToolConfig
152
+
153
+ MAX_SESSION_RELEASE_RETRIES: int = 3
154
+ SESSION_RELEASE_SLEEP_TIME_IN_S: int = 10
155
+
156
+ def _run(self, task: str, config: RunnableConfig | None = None) -> str:
157
+ """Run the tool synchronously.
158
+
159
+ Args:
160
+ task (str): The task prompt for the AI agent to execute in the browser.
161
+ config (RunnableConfig): RunnableConfig containing tool configuration.
162
+
163
+ Returns:
164
+ str: The result of the task execution or an error message if the task fails.
165
+ """
166
+ return asyncio.run(self._arun(task, config))
167
+
168
+ async def _arun(self, task: str, config: RunnableConfig | None = None) -> str:
169
+ """Execute a web automation task using browser-use asynchronously.
170
+
171
+ This method creates a Steel browser session, initializes a browser-use agent with
172
+ the specified task, and executes the automation. It handles session cleanup and
173
+ error handling.
174
+
175
+ Args:
176
+ task (str): The task prompt for the AI agent to execute in the browser.
177
+ config (RunnableConfig): RunnableConfig containing tool configuration.
178
+
179
+ Returns:
180
+ str: A success message with the task result, or an error message if the task
181
+ execution fails. The result includes the final output from the browser-use
182
+ agent's execution.
183
+
184
+ Raises:
185
+ Exception: Any exception that occurs during task execution will be caught
186
+ and returned as an error message string.
187
+ """
188
+ tool_config = self._get_tool_config(config)
189
+
190
+ if not tool_config.browser_use_llm_openai_api_key:
191
+ return self._log_and_return("warning", "Browser-use LLM OpenAI API key is required.")
192
+ if not tool_config.browser_use_page_extraction_llm_openai_api_key:
193
+ return self._log_and_return("warning", "Browser-use Page Extraction OpenAI API key is required.")
194
+ if not tool_config.steel_api_key:
195
+ return self._log_and_return("warning", "Steel API key is required.")
196
+
197
+ client = self._init_steel_client(tool_config)
198
+
199
+ session = None
200
+ try:
201
+ session = self._create_steel_session(client, tool_config)
202
+ cdp_url = self._construct_cdp_url(session.id, tool_config)
203
+ agent = self._create_browser_use_agent(task, cdp_url, tool_config)
204
+ result = await agent.run()
205
+ return self._log_and_return("info", f"Task completed successfully!\nResult: {result.final_result()}")
206
+
207
+ except Exception as e:
208
+ return self._log_and_return("warning", f"Error during task execution: {str(e)}")
209
+ finally:
210
+ await self._release_session(session, client)
211
+
212
+ def _get_tool_config(self, config: RunnableConfig | None) -> BrowserUseToolConfig:
213
+ """Get the tool configuration.
214
+
215
+ Args:
216
+ config (RunnableConfig): RunnableConfig containing tool configuration.
217
+
218
+ Returns:
219
+ BrowserUseToolConfig: The tool configuration.
220
+ """
221
+ tool_config: BrowserUseToolConfig | None = None
222
+ if hasattr(self, "get_tool_config") and callable(self.get_tool_config):
223
+ tool_config = self.get_tool_config(config)
224
+ if not tool_config:
225
+ tool_config = BrowserUseToolConfig()
226
+ return tool_config
227
+
228
+ def _log_and_return(self, level: Literal["info", "warning"], message: str) -> str:
229
+ """Log a message and return it.
230
+
231
+ Args:
232
+ level (Literal["info", "warning"]): The log level.
233
+ message (str): The message to log and return.
234
+
235
+ Returns:
236
+ str: The logged message.
237
+ """
238
+ if level == "info":
239
+ logger.info(message)
240
+ elif level == "warning":
241
+ logger.warning(message)
242
+ return message
243
+
244
+ def _init_steel_client(self, tool_config: BrowserUseToolConfig) -> Steel:
245
+ """Initialize the Steel client.
246
+
247
+ Args:
248
+ tool_config: Tool configuration containing Steel settings.
249
+
250
+ Returns:
251
+ Steel: The Steel client.
252
+ """
253
+ return Steel(steel_api_key=tool_config.steel_api_key, base_url=tool_config.steel_base_url)
254
+
255
+ def _create_steel_session(self, client: Steel, tool_config: BrowserUseToolConfig) -> Session:
256
+ """Create the Steel session.
257
+
258
+ Args:
259
+ client: The Steel client.
260
+ tool_config: Tool configuration containing Steel session settings.
261
+
262
+ Returns:
263
+ Session: The Steel session.
264
+ """
265
+ return client.sessions.create(use_proxy=False, api_timeout=tool_config.steel_timeout_in_ms)
266
+
267
+ def _construct_cdp_url(self, session_id: str, tool_config: BrowserUseToolConfig) -> str:
268
+ """Construct the CDP URL for browser connection.
269
+
270
+ Args:
271
+ session_id: The Steel session ID.
272
+ tool_config: Tool configuration containing Steel WebSocket URL.
273
+
274
+ Returns:
275
+ str: The CDP URL.
276
+ """
277
+ return f"{tool_config.steel_ws_url}?apiKey={tool_config.steel_api_key}&sessionId={session_id}"
278
+
279
+ def _create_browser_use_agent(self, task: str, cdp_url: str, tool_config: BrowserUseToolConfig) -> Agent:
280
+ """Create OpenAI LLM and browser-use agent bound to the provided task and CDP URL.
281
+
282
+ Args:
283
+ task: The task prompt for the AI agent
284
+ cdp_url: The CDP URL for the browser session
285
+ tool_config: Tool configuration containing OpenAI model settings
286
+
287
+ Returns:
288
+ Agent: The browser-use agent
289
+ """
290
+ llm = build_browser_use_llm(
291
+ model=tool_config.browser_use_llm_openai_model,
292
+ reasoning_effort=tool_config.browser_use_llm_openai_reasoning_effort,
293
+ temperature=tool_config.browser_use_llm_openai_temperature,
294
+ api_key=tool_config.browser_use_llm_openai_api_key,
295
+ base_url=tool_config.browser_use_llm_openai_base_url,
296
+ )
297
+
298
+ page_extraction_llm = build_browser_use_llm(
299
+ model=tool_config.browser_use_page_extraction_llm_openai_model,
300
+ reasoning_effort=tool_config.browser_use_page_extraction_llm_openai_reasoning_effort,
301
+ temperature=tool_config.browser_use_page_extraction_llm_openai_temperature,
302
+ api_key=tool_config.browser_use_page_extraction_llm_openai_api_key,
303
+ base_url=tool_config.browser_use_page_extraction_llm_openai_base_url,
304
+ )
305
+
306
+ browser_session = BrowserSession(cdp_url=cdp_url)
307
+ # Mark the proxy-created session as owned so browser-use does not clone it with warnings.
308
+ setattr(browser_session, "_owns_browser_resources", True)
309
+
310
+ configure_browser_use_environment(
311
+ enable_cloud_sync=tool_config.browser_use_enable_cloud_sync,
312
+ logging_level=tool_config.browser_use_logging_level,
313
+ )
314
+
315
+ return Agent(
316
+ task=task,
317
+ llm=llm,
318
+ page_extraction_llm=page_extraction_llm,
319
+ browser_session=browser_session,
320
+ extend_system_message=tool_config.browser_use_extend_system_message,
321
+ vision_detail_level=tool_config.browser_use_vision_detail_level,
322
+ llm_timeout=tool_config.browser_use_llm_timeout_in_s,
323
+ step_timeout=tool_config.browser_use_step_timeout_in_s,
324
+ )
325
+
326
+ def _create_agent_session(
327
+ self,
328
+ client: Steel,
329
+ recorder: SteelSessionRecorder,
330
+ tool_config: BrowserUseToolConfig,
331
+ task: str,
332
+ ) -> tuple[Session, StreamingState, Agent]:
333
+ """Provision a Steel session, streaming state, and browser-use agent.
334
+
335
+ Args:
336
+ client: Initialized Steel SDK client.
337
+ recorder: Recorder responsible for generating session recordings.
338
+ tool_config: Tool configuration containing browser-use settings.
339
+ task: The textual task prompt the agent must execute.
340
+
341
+ Returns:
342
+ tuple[Session, StreamingState, Agent]: Provisioned Steel session,
343
+ streaming state metadata, and browser-use agent instance.
344
+ """
345
+ session = self._create_steel_session(client, tool_config)
346
+ streaming_state = self._create_streaming_state(session, recorder)
347
+ cdp_url = self._construct_cdp_url(session.id, tool_config)
348
+ agent = self._create_browser_use_agent(task, cdp_url, tool_config)
349
+ return session, streaming_state, agent
350
+
351
+ async def _retry_stream_events(self, task: str, tool_config: BrowserUseToolConfig):
352
+ """Yield streaming events while automatically retrying Steel sessions.
353
+
354
+ Args:
355
+ task: Textual task prompt.
356
+ tool_config: Tool configuration containing Steel/OpenAI settings.
357
+
358
+ Yields:
359
+ dict: Streaming events produced by the browser-use agent.
360
+ """
361
+ client = self._init_steel_client(tool_config)
362
+ recorder = SteelSessionRecorder(tool_config.steel_base_url, tool_config.steel_api_key)
363
+
364
+ retry_state = self._init_retry_state(tool_config)
365
+
366
+ while True:
367
+ state_holder: dict[str, StreamingState | None] = {"state": None}
368
+ store_state = self._make_state_store(state_holder)
369
+
370
+ try:
371
+ async for event in self._session_event_stream(
372
+ client,
373
+ recorder,
374
+ tool_config,
375
+ task,
376
+ store_state,
377
+ ):
378
+ yield event
379
+ return
380
+
381
+ except Exception as exc: # pragma: no cover - defensive orchestration
382
+ async for event in self._emit_retry_exception(
383
+ exc,
384
+ state_holder,
385
+ recorder,
386
+ tool_config,
387
+ retry_state,
388
+ ):
389
+ yield event
390
+
391
+ def _init_retry_state(self, tool_config: BrowserUseToolConfig) -> dict[str, int]:
392
+ """Initialize retry state tracking.
393
+
394
+ Args:
395
+ tool_config: Tool configuration containing retry settings.
396
+
397
+ Returns:
398
+ Dictionary with retry state counters.
399
+ """
400
+ return {
401
+ "retries_remaining": max(0, tool_config.browser_use_max_session_retries),
402
+ "attempted_retries": 0,
403
+ }
404
+
405
+ async def _handle_fatal_error_during_retry(
406
+ self,
407
+ fatal_error: BrowserUseFatalError,
408
+ state_holder: dict[str, StreamingState | None],
409
+ tool_config: BrowserUseToolConfig,
410
+ retry_state: dict[str, int],
411
+ ):
412
+ """Handle fatal errors during retry attempts.
413
+
414
+ Args:
415
+ fatal_error: The fatal error that occurred.
416
+ state_holder: Container for current streaming state.
417
+ tool_config: Tool configuration for retry logic.
418
+ retry_state: Current retry counters and state.
419
+ """
420
+ streaming_state = state_holder["state"]
421
+ decision, pending_event = await self._handle_retryable_fatal_error(
422
+ fatal_error,
423
+ streaming_state,
424
+ retry_state["retries_remaining"],
425
+ retry_state["attempted_retries"],
426
+ tool_config,
427
+ )
428
+ if pending_event:
429
+ yield pending_event
430
+ retry_state["retries_remaining"], retry_state["attempted_retries"] = self._apply_retry_decision(
431
+ fatal_error, decision
432
+ )
433
+
434
+ async def _handle_cancellation_during_retry(
435
+ self,
436
+ state_holder: dict[str, StreamingState | None],
437
+ recorder: SteelSessionRecorder,
438
+ ):
439
+ """Handle cancellation during retry attempts.
440
+
441
+ Args:
442
+ state_holder: Container for current streaming state.
443
+ recorder: Session recorder for cleanup operations.
444
+ """
445
+ streaming_state = state_holder["state"]
446
+ if streaming_state:
447
+ self._finalize_streaming_on_cancel(streaming_state, recorder)
448
+ if streaming_state.recording_url:
449
+ yield yield_iframe_activity(streaming_state.recording_url, "Receive recording URL")
450
+
451
+ async def _handle_unexpected_error_during_retry(
452
+ self,
453
+ exc: Exception,
454
+ state_holder: dict[str, StreamingState | None],
455
+ ):
456
+ """Handle unexpected errors during retry attempts.
457
+
458
+ Args:
459
+ exc: The unexpected exception that occurred.
460
+ state_holder: Container for current streaming state.
461
+ """
462
+ streaming_state = state_holder["state"]
463
+ error_url = streaming_state.recording_url if streaming_state else ""
464
+ yield create_error_response(f"Error during task execution: {str(exc)}", error_url)
465
+
466
+ @staticmethod
467
+ def _make_state_store(state_holder: dict[str, StreamingState | None]) -> Callable[[StreamingState], None]:
468
+ """Return a callback that records the latest streaming state.
469
+
470
+ Args:
471
+ state_holder: Mutable container storing the latest streaming state.
472
+ """
473
+
474
+ def store(streaming_state: StreamingState) -> None:
475
+ """Persist the latest streaming state in the shared holder.
476
+
477
+ Args:
478
+ streaming_state: Streaming state emitted by browser-use callbacks.
479
+ """
480
+ state_holder["state"] = streaming_state
481
+
482
+ return store
483
+
484
+ def _apply_retry_decision(
485
+ self, fatal_error: BrowserUseFatalError, decision: RetryDecision | None
486
+ ) -> tuple[int, int]:
487
+ """Update retry counters or re-raise when no retries remain.
488
+
489
+ Args:
490
+ fatal_error: The fatal error that triggered the retry decision.
491
+ decision: The retry decision containing retry counters, or None if no retries available.
492
+
493
+ Returns:
494
+ tuple[int, int]: A tuple containing (retries_remaining, attempted_retries).
495
+ """
496
+ if not decision:
497
+ raise fatal_error
498
+ return decision.retries_remaining, decision.attempted_retries
499
+
500
+ async def _emit_retry_exception(
501
+ self,
502
+ exc: Exception,
503
+ state_holder: dict[str, StreamingState | None],
504
+ recorder: SteelSessionRecorder,
505
+ tool_config: BrowserUseToolConfig,
506
+ retry_state: dict[str, int],
507
+ ):
508
+ """Normalize retry exception handling and yield resulting events.
509
+
510
+ Args:
511
+ exc: Exception raised during the streaming attempt.
512
+ state_holder: Container for current streaming state.
513
+ recorder: Session recorder for cleanup operations.
514
+ tool_config: Tool configuration containing retry settings.
515
+ retry_state: Mutable retry counters.
516
+ """
517
+ if isinstance(exc, asyncio.CancelledError):
518
+ async for event in self._handle_cancellation_during_retry(state_holder, recorder):
519
+ yield event
520
+ raise exc
521
+
522
+ if isinstance(exc, BrowserUseFatalError):
523
+ async for event in self._handle_fatal_error_during_retry(exc, state_holder, tool_config, retry_state):
524
+ yield event
525
+ return
526
+
527
+ async for event in self._handle_unexpected_error_during_retry(exc, state_holder):
528
+ yield event
529
+ raise BrowserUseFatalError(str(exc)) from exc
530
+
531
+ async def _session_event_stream(
532
+ self,
533
+ client: Steel,
534
+ recorder: SteelSessionRecorder,
535
+ tool_config: BrowserUseToolConfig,
536
+ task: str,
537
+ on_state_ready: Callable[[StreamingState], None],
538
+ ):
539
+ """Yield streaming events for a single Steel session attempt.
540
+
541
+ Args:
542
+ client: Steel SDK client instance.
543
+ recorder: Steel session recorder.
544
+ tool_config: Tool configuration for the agent.
545
+ task: Task prompt assigned to the agent.
546
+ on_state_ready: Callback invoked when streaming state is initialized.
547
+
548
+ Yields:
549
+ dict: Streaming events describing agent progress.
550
+ """
551
+ session: Session | None = None
552
+ streaming_state: StreamingState | None = None
553
+ try:
554
+ session, streaming_state, agent = self._create_agent_session(client, recorder, tool_config, task)
555
+ on_state_ready(streaming_state)
556
+ iframe_event = yield_iframe_activity(streaming_state.debug_url, "Receive streaming URL")
557
+ self._log_stream_event("iframe_start", iframe_event)
558
+ yield iframe_event
559
+ async for event in self._stream_agent_with_markers(agent, streaming_state, recorder):
560
+ self._log_stream_event("agent_event", event)
561
+ yield event
562
+ recording_event = self._recording_event(streaming_state)
563
+ if recording_event:
564
+ self._log_stream_event("recording_event", recording_event)
565
+ yield recording_event
566
+ finally:
567
+ await self._release_session(session, client)
568
+
569
+ async def _release_session(self, session: Session | None, client: Steel) -> None:
570
+ """Release a Steel browser session.
571
+
572
+ This method attempts to release the provided Steel session.
573
+ It retries the release operation up to MAX_SESSION_RELEASE_RETRIES times, waiting
574
+ SESSION_RELEASE_SLEEP_TIME_IN_S seconds between attempts. If the session is None, the method
575
+ returns immediately.
576
+
577
+ Args:
578
+ session (Session | None): The Steel session to release. If None, no action is taken.
579
+ client (Steel): The Steel client used to release the session.
580
+ """
581
+ if not session:
582
+ return
583
+
584
+ for attempt in range(self.MAX_SESSION_RELEASE_RETRIES):
585
+ try:
586
+ await asyncio.sleep(self.SESSION_RELEASE_SLEEP_TIME_IN_S)
587
+ client.sessions.release(session.id)
588
+ logger.info(f"Session {session.id} released")
589
+ break
590
+ except Exception as e:
591
+ if attempt == self.MAX_SESSION_RELEASE_RETRIES - 1:
592
+ logger.warning(f"Failed to release session after {self.MAX_SESSION_RELEASE_RETRIES} attempts: {e}")
593
+
594
+ async def arun_streaming(self, task: str = None, config: RunnableConfig | None = None, **kwargs):
595
+ """Execute a web automation task using browser-use asynchronously with streaming output.
596
+
597
+ This method creates a Steel browser session, initializes a browser-use agent with
598
+ the specified task, and executes the automation step by step, yielding results
599
+ in streaming fashion. Starts background recording after completion.
600
+
601
+ Args:
602
+ task (str, optional): The task prompt for the AI agent to execute in the browser.
603
+ If not provided, will attempt to extract from kwargs.
604
+ config (RunnableConfig): RunnableConfig containing tool configuration.
605
+ **kwargs: Additional parameters that may contain the task or other tool-specific arguments.
606
+
607
+ Yields:
608
+ dict: Step-by-step results in standardized StreamingResponse format.
609
+
610
+ Raises:
611
+ Exception: Any exception that occurs during task execution will be caught
612
+ and yielded as an error message.
613
+ """
614
+ if task is None:
615
+ task = kwargs.get("task")
616
+
617
+ tool_config = self._get_tool_config(config)
618
+
619
+ missing_key_error = self._missing_api_key_error(tool_config)
620
+ if missing_key_error:
621
+ yield create_error_response(missing_key_error)
622
+ return
623
+
624
+ async for event in self._retry_stream_events(task, tool_config):
625
+ yield event
626
+
627
+ @staticmethod
628
+ def _missing_api_key_error(tool_config: BrowserUseToolConfig) -> str | None:
629
+ """Return a descriptive error when required API keys are missing.
630
+
631
+ Args:
632
+ tool_config: The tool configuration containing API key settings.
633
+
634
+ Returns:
635
+ str | None: Error message if API keys are missing, otherwise None.
636
+ """
637
+ if not tool_config.browser_use_llm_openai_api_key:
638
+ return "Browser-use LLM OpenAI API key is required."
639
+ if not tool_config.browser_use_page_extraction_llm_openai_api_key:
640
+ return "Browser-use Page Extraction OpenAI API key is required."
641
+ if not tool_config.steel_api_key:
642
+ return "Steel API key is required."
643
+ return None
644
+
645
+ def _create_streaming_state(self, session: Session, recorder: SteelSessionRecorder) -> StreamingState:
646
+ """Build initial streaming state for a Steel session.
647
+
648
+ Args:
649
+ session: The Steel browser session to create streaming state for.
650
+ recorder: The Steel session recorder for video capture.
651
+
652
+ Returns:
653
+ StreamingState: The initialized streaming state with debug URL, recording URL, and session ID.
654
+ """
655
+ video_filename = recorder.generate_video_filename(session.id)
656
+ recording_url = recorder.minio_storage.get_file_url(video_filename) if recorder.minio_storage else ""
657
+ return StreamingState(debug_url=session.debug_url, recording_url=recording_url, session_id=session.id)
658
+
659
+ def _should_retry_session(self, error_message: str, retries_remaining: int) -> bool:
660
+ """Return True when the failure qualifies for an automatic Steel session retry.
661
+
662
+ Args:
663
+ error_message: The error message from the failed session.
664
+ retries_remaining: The number of retry attempts still available.
665
+
666
+ Returns:
667
+ bool: True if the error is recoverable and retries are available, False otherwise.
668
+ """
669
+ if retries_remaining <= 0:
670
+ return False
671
+ return session_errors.is_recoverable_message(error_message)
672
+
673
+ def _build_retry_decision(
674
+ self,
675
+ error_message: str,
676
+ retries_remaining: int,
677
+ attempted_retries: int,
678
+ tool_config: BrowserUseToolConfig,
679
+ ) -> RetryDecision | None:
680
+ """Create a retry decision payload when a Steel session disconnects.
681
+
682
+ Args:
683
+ error_message: Message describing the fatal error.
684
+ retries_remaining: Number of retries still allowed.
685
+ attempted_retries: Number of retries already attempted.
686
+ tool_config: Tool configuration containing retry settings.
687
+
688
+ Returns:
689
+ RetryDecision | None: A retry decision when the error is recoverable,
690
+ otherwise None.
691
+ """
692
+ if not self._should_retry_session(error_message, retries_remaining):
693
+ return None
694
+
695
+ new_retries = retries_remaining - 1
696
+ new_attempts = attempted_retries + 1
697
+ retry_total = tool_config.browser_use_max_session_retries or 1
698
+ retry_message = f"Steel session disconnected. Attempting automatic recovery ({new_attempts}/{retry_total})."
699
+ delay = max(0.0, tool_config.browser_use_session_retry_delay_in_s)
700
+ return RetryDecision(
701
+ retries_remaining=new_retries,
702
+ attempted_retries=new_attempts,
703
+ message=retry_message,
704
+ delay=delay,
705
+ )
706
+
707
+ def _recording_event(self, streaming_state: StreamingState | None) -> dict | None:
708
+ """Return an iframe activity event for successful runs.
709
+
710
+ Args:
711
+ streaming_state: Current streaming metadata, if available.
712
+
713
+ Returns:
714
+ dict | None: Iframe activity event when a recording is available.
715
+ """
716
+ if not streaming_state or streaming_state.terminal_error or not streaming_state.recording_url:
717
+ return None
718
+ return yield_iframe_activity(streaming_state.recording_url, "Receive recording URL")
719
+
720
+ async def _handle_retryable_fatal_error(
721
+ self,
722
+ fatal_error: BrowserUseFatalError,
723
+ streaming_state: StreamingState | None,
724
+ retries_remaining: int,
725
+ attempted_retries: int,
726
+ tool_config: BrowserUseToolConfig,
727
+ ) -> tuple[RetryDecision | None, dict | None]:
728
+ """Handle a fatal error by either yielding an error response or scheduling a retry.
729
+
730
+ Args:
731
+ fatal_error: The raised fatal exception.
732
+ streaming_state: Current streaming metadata, if any.
733
+ retries_remaining: Number of retries still allowed.
734
+ attempted_retries: Number of retries already attempted.
735
+ tool_config: Tool configuration containing retry settings.
736
+
737
+ Returns:
738
+ tuple[RetryDecision | None, dict | None]: Retry decision (None when unrecoverable)
739
+ and a streaming event to emit (error or status).
740
+ """
741
+ decision = self._build_retry_decision(
742
+ str(fatal_error),
743
+ retries_remaining,
744
+ attempted_retries,
745
+ tool_config,
746
+ )
747
+ if not decision:
748
+ error_url = streaming_state.recording_url if streaming_state else ""
749
+ return None, create_error_response(str(fatal_error), error_url)
750
+
751
+ status_event = yield_status_message(decision.message)
752
+ self._log_stream_event("retry_status", status_event)
753
+ if decision.delay:
754
+ await asyncio.sleep(decision.delay)
755
+ return decision, status_event
756
+
757
+ async def _stream_agent_with_markers(
758
+ self, agent: Agent, streaming_state: StreamingState, recorder: SteelSessionRecorder
759
+ ):
760
+ """Yield agent progress events surrounded by thinking markers.
761
+
762
+ Args:
763
+ self: The BrowserUseTool instance.
764
+ agent: The browser-use agent to execute steps with.
765
+ streaming_state: State management for the streaming operation.
766
+ recorder: The Steel session recorder for video capture.
767
+
768
+ Yields:
769
+ dict: Streaming events including thinking start/end markers and step results.
770
+ """
771
+ async for stream_item in self._execute_agent_steps_streaming(agent, streaming_state, recorder):
772
+ payload = stream_item["payload"]
773
+ wrap_markers = stream_item.get("wrap_markers", False)
774
+ label = stream_item.get("label", "step_result")
775
+
776
+ if wrap_markers:
777
+ start_marker = yield_thinking_marker("start")
778
+ self._log_stream_event(f"{label}_thinking_start", start_marker)
779
+ yield start_marker
780
+
781
+ self._log_stream_event(label, payload)
782
+ yield payload
783
+
784
+ end_marker = yield_thinking_marker("end")
785
+ self._log_stream_event(f"{label}_thinking_end", end_marker)
786
+ yield end_marker
787
+ else:
788
+ self._log_stream_event(label, payload)
789
+ yield payload
790
+
791
+ async def _execute_agent_steps_streaming(
792
+ self, agent: Agent, streaming_state: StreamingState, recorder: SteelSessionRecorder
793
+ ):
794
+ """Execute agent steps one by one and yield intermediate progress in streaming fashion.
795
+
796
+ This method uses the take_step approach to execute the agent's task step by step,
797
+ yielding each step's information in a standardized format. Starts background recording after completion.
798
+
799
+ Args:
800
+ agent (Agent): The browser-use agent to execute.
801
+ streaming_state (StreamingState): State management for the streaming operation.
802
+ recorder (SteelSessionRecorder): The Steel session recorder.
803
+
804
+ Yields:
805
+ dict: Step information in standardized StreamingResponse format.
806
+ """
807
+ while not streaming_state.is_complete:
808
+ events = await self._next_step_events(agent, streaming_state, recorder)
809
+ for event in events:
810
+ yield event
811
+ if streaming_state.is_complete:
812
+ break
813
+
814
+ async def _perform_agent_step(
815
+ self, agent: Agent, streaming_state: StreamingState, recorder: SteelSessionRecorder
816
+ ) -> StreamingResponse:
817
+ """Execute a single agent step and return its streaming payload.
818
+
819
+ Args:
820
+ agent: The browser-use agent currently executing the plan.
821
+ streaming_state: Mutable state tracking streaming progress and errors.
822
+ recorder: Steel recorder used to schedule background video exports.
823
+
824
+ Returns:
825
+ StreamingResponse: Serialized streaming payload for the current step.
826
+ """
827
+ is_done, _ = await agent.take_step()
828
+ streaming_state.step_count += 1
829
+
830
+ tool_calls = ActionParser.extract_actions(agent.state.last_model_output, agent.state.last_result)
831
+ logger.info("Browser-use raw tool calls: %s", [call.__dict__ for call in tool_calls])
832
+
833
+ error_message = self._resolve_step_error(agent, streaming_state, recorder, tool_calls)
834
+ if error_message:
835
+ raise BrowserUseFatalError(error_message)
836
+
837
+ if is_done:
838
+ self._update_completion_state(streaming_state, recorder)
839
+
840
+ tool_calls_dict = [{"name": tc.name, "args": tc.args, "output": tc.output} for tc in tool_calls]
841
+ content = generate_step_content(tool_calls, is_done)
842
+ thinking_message = await generate_thinking_message(content, tool_calls_dict, is_final=is_done)
843
+
844
+ step_response = create_step_response(agent, tool_calls, is_done, content, thinking_message)
845
+ logger.info("Browser-use step event: %s", step_response.to_dict())
846
+
847
+ self._log_stream_event("step_streaming_response", step_response)
848
+
849
+ return step_response
850
+
851
+ def _prepare_step_events(
852
+ self,
853
+ response: StreamingResponse,
854
+ streaming_state: StreamingState,
855
+ ) -> list[dict[str, Any]]:
856
+ """Construct serialized events for a streaming step.
857
+
858
+ Args:
859
+ response: Primary streaming response generated for the current step.
860
+ streaming_state: Streaming state describing overall progress/completion.
861
+
862
+ Returns:
863
+ List of serialized event dictionaries ready to be emitted.
864
+ """
865
+ wrap_markers = not streaming_state.is_complete
866
+ return [
867
+ {
868
+ "payload": response.to_dict(),
869
+ "wrap_markers": wrap_markers,
870
+ "label": "step_result",
871
+ }
872
+ ]
873
+
874
+ async def _next_step_events(
875
+ self,
876
+ agent: Agent,
877
+ streaming_state: StreamingState,
878
+ recorder: SteelSessionRecorder,
879
+ ) -> list[dict[str, Any]]:
880
+ """Execute a step and return serialized events, handling errors consistently.
881
+
882
+ Args:
883
+ agent: Browser-use agent executing the step.
884
+ streaming_state: Mutable streaming state metadata.
885
+ recorder: Recorder used for background capture scheduling.
886
+
887
+ Returns:
888
+ List of serialized event dictionaries produced by the step.
889
+ """
890
+ try:
891
+ response = await self._perform_agent_step(agent, streaming_state, recorder)
892
+ return self._prepare_step_events(response, streaming_state)
893
+ except asyncio.CancelledError as cancel:
894
+ self._finalize_streaming_on_cancel(streaming_state, recorder)
895
+ raise cancel
896
+ except BrowserUseFatalError:
897
+ raise
898
+ except Exception as error: # pragma: no cover - defensive path
899
+ error_message = f"Error in step execution: {error}"
900
+ raise BrowserUseFatalError(error_message) from error
901
+
902
+ def _resolve_step_error(
903
+ self,
904
+ agent: Agent,
905
+ streaming_state: StreamingState,
906
+ recorder: SteelSessionRecorder,
907
+ tool_calls: list[ToolCallInfo],
908
+ ) -> str | None:
909
+ """Handle terminal or extraction errors detected during a step.
910
+
911
+ Args:
912
+ agent: Browser-use agent with latest results.
913
+ streaming_state: Current streaming state object.
914
+ recorder: Recorder used to schedule background exports.
915
+ tool_calls: Tool calls extracted from the agent step.
916
+
917
+ Returns:
918
+ Error message when the step must terminate, otherwise ``None``.
919
+ """
920
+ terminal_error = self._detect_terminal_session_error(agent, tool_calls)
921
+ if terminal_error:
922
+ message = f"Browser session disconnected unexpectedly. Steel reported: {terminal_error}"
923
+ self._finalize_stream_with_error(streaming_state, recorder, message)
924
+ return message
925
+
926
+ extraction_error = detect_structured_data_failure(tool_calls, self._summarize_terminal_error)
927
+ if extraction_error:
928
+ self._finalize_stream_with_error(streaming_state, recorder, extraction_error)
929
+ return extraction_error
930
+
931
+ return None
932
+
933
+ def _finalize_stream_with_error(
934
+ self,
935
+ streaming_state: StreamingState,
936
+ recorder: SteelSessionRecorder,
937
+ message: str,
938
+ ) -> None:
939
+ """Mark the stream as complete due to an unrecoverable error.
940
+
941
+ Args:
942
+ streaming_state: Current streaming state to update.
943
+ recorder: Recorder used to schedule background exports.
944
+ message: Human-readable error message to persist.
945
+ """
946
+ streaming_state.terminal_error = message
947
+ streaming_state.is_complete = True
948
+ if streaming_state.session_id:
949
+ self._start_background_recording(recorder, streaming_state.session_id)
950
+ streaming_state.recording_started = True
951
+
952
+ def _update_completion_state(
953
+ self,
954
+ streaming_state: StreamingState,
955
+ recorder: SteelSessionRecorder,
956
+ ) -> None:
957
+ """Mark the streaming state as complete and schedule recording when needed.
958
+
959
+ Args:
960
+ streaming_state: Current streaming state to mutate.
961
+ recorder: Recorder used to kick off background exports.
962
+ """
963
+ streaming_state.is_complete = True
964
+ if streaming_state.session_id:
965
+ self._start_background_recording(recorder, streaming_state.session_id)
966
+ streaming_state.recording_started = True
967
+
968
+ def _detect_terminal_session_error(self, agent: Agent, tool_calls: list[ToolCallInfo]) -> str | None:
969
+ """Inspect tool outputs and agent results for terminal Steel session failures.
970
+
971
+ Args:
972
+ agent: The browser-use agent providing result context.
973
+ tool_calls: Tool call descriptors extracted for the current step.
974
+
975
+ Returns:
976
+ str | None: A concise terminal error summary when detected, otherwise None.
977
+ """
978
+ candidates = self._collect_session_messages(agent, tool_calls)
979
+
980
+ fatal_match = session_errors.find_fatal_message(candidates)
981
+ if fatal_match:
982
+ fatal_message, _ = fatal_match
983
+ return self._summarize_terminal_error(fatal_message)
984
+
985
+ self._log_session_warnings(candidates)
986
+
987
+ return None
988
+
989
+ def _collect_session_messages(self, agent: Agent, tool_calls: list[ToolCallInfo]) -> list[str]:
990
+ """Gather outputs and errors emitted during a step for analysis.
991
+
992
+ Args:
993
+ agent: Browser-use agent providing result context.
994
+ tool_calls: Tool calls extracted from the step execution.
995
+
996
+ Returns:
997
+ List of raw output/error messages produced during the step.
998
+ """
999
+ tool_outputs = [call.output for call in tool_calls if call.output]
1000
+ result_errors = []
1001
+ if agent.state.last_result:
1002
+ result_errors = [result.error for result in agent.state.last_result if result and result.error]
1003
+ return [message for message in (*tool_outputs, *result_errors) if message]
1004
+
1005
+ def _log_session_warnings(self, candidates: list[str]) -> None:
1006
+ """Emit debug logs for non-fatal Steel session warnings.
1007
+
1008
+ Args:
1009
+ candidates: Messages captured during the step to inspect for warnings.
1010
+ """
1011
+ logged_warnings: set[str] = set()
1012
+ for candidate in candidates:
1013
+ warning_name = session_errors.categorize_warning_message(candidate)
1014
+ if warning_name and warning_name not in logged_warnings:
1015
+ logger.debug("Detected session warning=%s; continuing stream.", warning_name)
1016
+ logged_warnings.add(warning_name)
1017
+
1018
+ def _detect_terminal_error_from_exception(self, error_message: str) -> str | None:
1019
+ """Detect terminal Steel session failures from raised exceptions.
1020
+
1021
+ Args:
1022
+ error_message: The exception message captured during step execution.
1023
+
1024
+ Returns:
1025
+ str | None: Terminal error summary when matched, otherwise None.
1026
+ """
1027
+ fatal_match = session_errors.find_fatal_message([error_message])
1028
+ if fatal_match:
1029
+ fatal_message, _ = fatal_match
1030
+ return self._summarize_terminal_error(fatal_message)
1031
+ warning_name = session_errors.categorize_warning_message(error_message)
1032
+ if warning_name:
1033
+ logger.debug(
1034
+ "Detected session warning=%s from exception; continuing stream.",
1035
+ warning_name,
1036
+ )
1037
+ return None
1038
+
1039
+ @staticmethod
1040
+ def _summarize_terminal_error(error_message: str, max_length: int = 200) -> str:
1041
+ """Produce a concise summary for terminal error messages.
1042
+
1043
+ Args:
1044
+ error_message: The original terminal error message.
1045
+ max_length: Maximum length for the summary text.
1046
+
1047
+ Returns:
1048
+ str: Sanitized and truncated error summary.
1049
+ """
1050
+ sanitized = " ".join(error_message.split())
1051
+ if len(sanitized) <= max_length:
1052
+ return sanitized
1053
+ return sanitized[: max_length - 1] + "…"
1054
+
1055
+ def _log_stream_event(self, label: str, event: dict[str, Any] | StreamingResponse) -> None:
1056
+ """Log streaming events to trace emission order during debugging.
1057
+
1058
+ Args:
1059
+ label: Descriptive label for the event source.
1060
+ event: Streaming payload (dict or StreamingResponse).
1061
+ """
1062
+ try:
1063
+ payload = event.to_dict() if isinstance(event, StreamingResponse) else event
1064
+ if isinstance(payload, dict):
1065
+ message = json.dumps(payload, default=str)
1066
+ else:
1067
+ message = repr(payload)
1068
+ logger.info("Streaming event (%s): %s", label, message)
1069
+ except Exception as error: # pragma: no cover - defensive logging
1070
+ logger.info("Streaming event (%s): logging failed: %s", label, error)
1071
+
1072
+ def _start_background_recording(self, recorder: SteelSessionRecorder, session_id: str) -> None:
1073
+ """Start background process to record and convert Steel session to video.
1074
+
1075
+ This method starts a background task to fetch rrweb events and convert them to video
1076
+ after the browser-use agent has completed its task. The recording happens asynchronously
1077
+ to avoid blocking the main execution flow.
1078
+
1079
+ Args:
1080
+ recorder: The Steel session recorder instance.
1081
+ session_id: The Steel session ID to record.
1082
+
1083
+ Note:
1084
+ The recording task is created using asyncio.create_task() to run in the
1085
+ background. This allows the main execution to continue while video
1086
+ generation happens asynchronously.
1087
+ """
1088
+ recording_task = asyncio.create_task(recorder.record_session_to_video(session_id=session_id))
1089
+ background_tasks: set[asyncio.Task[Any]] = getattr(self, "_background_tasks", set())
1090
+ background_tasks.add(recording_task)
1091
+ recording_task.add_done_callback(background_tasks.discard)
1092
+ self._background_tasks = background_tasks
1093
+
1094
+ def _finalize_streaming_on_cancel(
1095
+ self, streaming_state: StreamingState | None, recorder: SteelSessionRecorder
1096
+ ) -> None:
1097
+ """Mark streaming as complete and trigger recording when execution is cancelled.
1098
+
1099
+ Args:
1100
+ streaming_state: Current state container for streaming progress.
1101
+ recorder: Recorder instance responsible for exporting session recordings.
1102
+ """
1103
+ if not streaming_state or streaming_state.is_complete:
1104
+ return
1105
+
1106
+ streaming_state.is_complete = True
1107
+ if not streaming_state.terminal_error:
1108
+ streaming_state.terminal_error = "Execution cancelled by upstream request."
1109
+
1110
+ if streaming_state.session_id and not streaming_state.recording_started:
1111
+ self._start_background_recording(recorder, streaming_state.session_id)
1112
+ streaming_state.recording_started = True