aip-agents-binary 0.0.0__py3-none-any.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 (447) hide show
  1. aip_agents/__init__.py +66 -0
  2. aip_agents/a2a/__init__.py +19 -0
  3. aip_agents/a2a/server/__init__.py +10 -0
  4. aip_agents/a2a/server/base_executor.py +1125 -0
  5. aip_agents/a2a/server/google_adk_executor.py +198 -0
  6. aip_agents/a2a/server/langflow_executor.py +180 -0
  7. aip_agents/a2a/server/langgraph_executor.py +270 -0
  8. aip_agents/a2a/types.py +232 -0
  9. aip_agents/a2ui/__init__.py +26 -0
  10. aip_agents/a2ui/catalog_provider.py +109 -0
  11. aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
  12. aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
  13. aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
  14. aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
  15. aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
  16. aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
  17. aip_agents/a2ui/middleware.py +754 -0
  18. aip_agents/a2ui/models.py +153 -0
  19. aip_agents/a2ui/public_url.py +138 -0
  20. aip_agents/a2ui/stream_filter.py +67 -0
  21. aip_agents/agent/__init__.py +72 -0
  22. aip_agents/agent/base_agent.py +1437 -0
  23. aip_agents/agent/base_langgraph_agent.py +4048 -0
  24. aip_agents/agent/google_adk_agent.py +932 -0
  25. aip_agents/agent/google_adk_constants.py +11 -0
  26. aip_agents/agent/graph_runtime.py +185 -0
  27. aip_agents/agent/hitl/__init__.py +24 -0
  28. aip_agents/agent/hitl/config.py +28 -0
  29. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  30. aip_agents/agent/hitl/manager.py +532 -0
  31. aip_agents/agent/hitl/models.py +18 -0
  32. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  33. aip_agents/agent/hitl/prompt/base.py +42 -0
  34. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  35. aip_agents/agent/hitl/registry.py +149 -0
  36. aip_agents/agent/interface.py +138 -0
  37. aip_agents/agent/interfaces.py +65 -0
  38. aip_agents/agent/langflow_agent.py +464 -0
  39. aip_agents/agent/langgraph_react_agent.py +3741 -0
  40. aip_agents/agent/model_invocation_adapter.py +346 -0
  41. aip_agents/agent/system_instruction_context.py +34 -0
  42. aip_agents/agent/tool_execution_engine.py +587 -0
  43. aip_agents/audio_interface/__init__.py +30 -0
  44. aip_agents/audio_interface/audio_agent_adapter.py +254 -0
  45. aip_agents/audio_interface/config.py +146 -0
  46. aip_agents/audio_interface/errors.py +15 -0
  47. aip_agents/audio_interface/livekit_audio_session.py +504 -0
  48. aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
  49. aip_agents/audio_interface/session_factory.py +40 -0
  50. aip_agents/clients/__init__.py +10 -0
  51. aip_agents/clients/langflow/__init__.py +10 -0
  52. aip_agents/clients/langflow/client.py +477 -0
  53. aip_agents/clients/langflow/types.py +18 -0
  54. aip_agents/common/__init__.py +5 -0
  55. aip_agents/common/callable_introspection.py +17 -0
  56. aip_agents/constants.py +23 -0
  57. aip_agents/context/__init__.py +5 -0
  58. aip_agents/context/budget.py +237 -0
  59. aip_agents/context/compaction.py +339 -0
  60. aip_agents/context/metadata.py +169 -0
  61. aip_agents/context/offload.py +449 -0
  62. aip_agents/context/preparation.py +196 -0
  63. aip_agents/context/prompt.py +61 -0
  64. aip_agents/context/runtime.py +149 -0
  65. aip_agents/context/session_recall.py +69 -0
  66. aip_agents/context/state.py +48 -0
  67. aip_agents/context/utils.py +44 -0
  68. aip_agents/credentials/manager.py +132 -0
  69. aip_agents/examples/__init__.py +5 -0
  70. aip_agents/examples/audio_livekit_session.py +116 -0
  71. aip_agents/examples/compare_streaming_client.py +783 -0
  72. aip_agents/examples/compare_streaming_server.py +141 -0
  73. aip_agents/examples/gl_connectors_smoke.py +148 -0
  74. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  75. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  76. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  77. aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
  78. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  79. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  80. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  81. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  82. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  83. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
  84. aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
  85. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
  86. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  87. aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
  88. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  89. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  90. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
  91. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  92. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  93. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  94. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  95. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  96. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  97. aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
  98. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
  99. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
  100. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  101. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  102. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
  103. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  104. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
  105. aip_agents/examples/hello_world_filesystem.py +162 -0
  106. aip_agents/examples/hello_world_google_adk.py +41 -0
  107. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  108. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  109. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  110. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  111. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  112. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  113. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  114. aip_agents/examples/hello_world_langchain.py +28 -0
  115. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  116. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  117. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  118. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  119. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  120. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  121. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  122. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  123. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  124. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  125. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  126. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  127. aip_agents/examples/hello_world_langgraph.py +39 -0
  128. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
  129. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  130. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  131. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  132. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  133. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  134. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  135. aip_agents/examples/hello_world_langgraph_skills.py +62 -0
  136. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  137. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  138. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  139. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  140. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  141. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  142. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  143. aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
  144. aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
  145. aip_agents/examples/hello_world_pii_logger.py +21 -0
  146. aip_agents/examples/hello_world_ptc.py +49 -0
  147. aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
  148. aip_agents/examples/hello_world_sentry.py +133 -0
  149. aip_agents/examples/hello_world_step_limits.py +273 -0
  150. aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
  151. aip_agents/examples/hello_world_tool_output_client.py +55 -0
  152. aip_agents/examples/hello_world_tool_output_server.py +113 -0
  153. aip_agents/examples/hitl_demo.py +724 -0
  154. aip_agents/examples/mcp_configs/configs.py +63 -0
  155. aip_agents/examples/mcp_servers/common.py +76 -0
  156. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  157. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  158. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  159. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  160. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  161. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  162. aip_agents/examples/pii_demo_langgraph_server.py +125 -0
  163. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  164. aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
  165. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  166. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
  167. aip_agents/examples/tools/__init__.py +27 -0
  168. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  169. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  170. aip_agents/examples/tools/data_generator_tool.py +103 -0
  171. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  172. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  173. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  174. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  175. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  176. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  177. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  178. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  179. aip_agents/examples/tools/multiply_tool.py +43 -0
  180. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  181. aip_agents/examples/tools/random_chart_tool.py +142 -0
  182. aip_agents/examples/tools/serper_tool.py +202 -0
  183. aip_agents/examples/tools/stock_tools.py +82 -0
  184. aip_agents/examples/tools/table_generator_tool.py +167 -0
  185. aip_agents/examples/tools/time_tool.py +82 -0
  186. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  187. aip_agents/guardrails/__init__.py +83 -0
  188. aip_agents/guardrails/engines/__init__.py +69 -0
  189. aip_agents/guardrails/engines/base.py +90 -0
  190. aip_agents/guardrails/engines/nemo.py +101 -0
  191. aip_agents/guardrails/engines/phrase_matcher.py +113 -0
  192. aip_agents/guardrails/exceptions.py +39 -0
  193. aip_agents/guardrails/manager.py +163 -0
  194. aip_agents/guardrails/middleware.py +199 -0
  195. aip_agents/guardrails/schemas.py +63 -0
  196. aip_agents/guardrails/utils.py +45 -0
  197. aip_agents/integration/__init__.py +43 -0
  198. aip_agents/integration/agent.py +58 -0
  199. aip_agents/integration/guardrails.py +52 -0
  200. aip_agents/integration/hitl.py +71 -0
  201. aip_agents/integration/ptc.py +95 -0
  202. aip_agents/integration/skills.py +37 -0
  203. aip_agents/integration/storage.py +81 -0
  204. aip_agents/integration/version.py +103 -0
  205. aip_agents/mcp/__init__.py +1 -0
  206. aip_agents/mcp/client/__init__.py +60 -0
  207. aip_agents/mcp/client/base_mcp_client.py +377 -0
  208. aip_agents/mcp/client/connection_manager.py +231 -0
  209. aip_agents/mcp/client/errors.py +119 -0
  210. aip_agents/mcp/client/google_adk/__init__.py +45 -0
  211. aip_agents/mcp/client/google_adk/client.py +383 -0
  212. aip_agents/mcp/client/langchain/__init__.py +11 -0
  213. aip_agents/mcp/client/langchain/client.py +265 -0
  214. aip_agents/mcp/client/persistent_session.py +704 -0
  215. aip_agents/mcp/client/session_pool.py +358 -0
  216. aip_agents/mcp/client/transports.py +288 -0
  217. aip_agents/mcp/utils/__init__.py +7 -0
  218. aip_agents/mcp/utils/config_validator.py +139 -0
  219. aip_agents/memory/__init__.py +14 -0
  220. aip_agents/memory/adapters/__init__.py +10 -0
  221. aip_agents/memory/adapters/base_adapter.py +815 -0
  222. aip_agents/memory/adapters/mem0.py +286 -0
  223. aip_agents/memory/base.py +84 -0
  224. aip_agents/memory/constants.py +49 -0
  225. aip_agents/memory/factory.py +86 -0
  226. aip_agents/memory/guidance.py +20 -0
  227. aip_agents/memory/simple_memory.py +47 -0
  228. aip_agents/middleware/__init__.py +65 -0
  229. aip_agents/middleware/backends/__init__.py +72 -0
  230. aip_agents/middleware/backends/in_memory.py +578 -0
  231. aip_agents/middleware/backends/local_disk.py +1203 -0
  232. aip_agents/middleware/backends/protocol.py +353 -0
  233. aip_agents/middleware/backends/sandbox.py +1431 -0
  234. aip_agents/middleware/backends/sandbox_files.py +113 -0
  235. aip_agents/middleware/backends/utils.py +293 -0
  236. aip_agents/middleware/base.py +239 -0
  237. aip_agents/middleware/filesystem.py +690 -0
  238. aip_agents/middleware/manager.py +291 -0
  239. aip_agents/middleware/memory.py +532 -0
  240. aip_agents/middleware/pii_middleware.py +192 -0
  241. aip_agents/middleware/schema.py +19 -0
  242. aip_agents/middleware/skills.py +872 -0
  243. aip_agents/middleware/task_delegation.py +1656 -0
  244. aip_agents/middleware/todolist.py +276 -0
  245. aip_agents/middleware/tools/__init__.py +23 -0
  246. aip_agents/middleware/tools/edit_file.py +70 -0
  247. aip_agents/middleware/tools/execute.py +428 -0
  248. aip_agents/middleware/tools/grep_file.py +79 -0
  249. aip_agents/middleware/tools/ls.py +71 -0
  250. aip_agents/middleware/tools/read_file.py +239 -0
  251. aip_agents/middleware/tools/skill_load.py +193 -0
  252. aip_agents/middleware/tools/skill_resource.py +338 -0
  253. aip_agents/middleware/tools/write_file.py +114 -0
  254. aip_agents/middleware/vision.py +322 -0
  255. aip_agents/ptc/__init__.py +87 -0
  256. aip_agents/ptc/custom_tools.py +511 -0
  257. aip_agents/ptc/custom_tools_payload.py +499 -0
  258. aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
  259. aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
  260. aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
  261. aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
  262. aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
  263. aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
  264. aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
  265. aip_agents/ptc/doc_gen.py +122 -0
  266. aip_agents/ptc/exceptions.py +57 -0
  267. aip_agents/ptc/executor.py +627 -0
  268. aip_agents/ptc/mcp/__init__.py +45 -0
  269. aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
  270. aip_agents/ptc/mcp/templates/__init__.py +1 -0
  271. aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
  272. aip_agents/ptc/naming.py +430 -0
  273. aip_agents/ptc/payload.py +26 -0
  274. aip_agents/ptc/prompt_builder.py +674 -0
  275. aip_agents/ptc/ptc_helper.py +16 -0
  276. aip_agents/ptc/sandbox_bridge.py +297 -0
  277. aip_agents/ptc/template_utils.py +33 -0
  278. aip_agents/ptc/templates/__init__.py +1 -0
  279. aip_agents/ptc/templates/ptc_helper.py.template +134 -0
  280. aip_agents/ptc/tool_def_helpers.py +101 -0
  281. aip_agents/ptc/tool_enrichment.py +163 -0
  282. aip_agents/resilience/__init__.py +10 -0
  283. aip_agents/resilience/base.py +46 -0
  284. aip_agents/resilience/tool.py +1661 -0
  285. aip_agents/sandbox/__init__.py +62 -0
  286. aip_agents/sandbox/defaults.py +220 -0
  287. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
  288. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
  289. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
  290. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
  291. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
  292. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
  293. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
  294. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
  295. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
  296. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
  297. aip_agents/sandbox/e2b_runtime.py +27 -0
  298. aip_agents/sandbox/image_registry.py +98 -0
  299. aip_agents/sandbox/providers/__init__.py +76 -0
  300. aip_agents/sandbox/providers/base.py +392 -0
  301. aip_agents/sandbox/providers/bedrock.py +28 -0
  302. aip_agents/sandbox/providers/e2b.py +230 -0
  303. aip_agents/sandbox/providers/opensandbox.py +163 -0
  304. aip_agents/sandbox/sandbox_runtime.py +967 -0
  305. aip_agents/sandbox/template_builder.py +240 -0
  306. aip_agents/sandbox/types.py +141 -0
  307. aip_agents/sandbox/validation.py +50 -0
  308. aip_agents/schema/__init__.py +106 -0
  309. aip_agents/schema/a2a.py +58 -0
  310. aip_agents/schema/agent.py +117 -0
  311. aip_agents/schema/artifacts.py +75 -0
  312. aip_agents/schema/attachments.py +425 -0
  313. aip_agents/schema/hitl.py +157 -0
  314. aip_agents/schema/langgraph.py +47 -0
  315. aip_agents/schema/model_id.py +103 -0
  316. aip_agents/schema/model_usage.py +50 -0
  317. aip_agents/schema/runtime_metadata.py +78 -0
  318. aip_agents/schema/step_limit.py +108 -0
  319. aip_agents/schema/storage.py +40 -0
  320. aip_agents/schema/tool_payloads.py +68 -0
  321. aip_agents/sentry/__init__.py +11 -0
  322. aip_agents/sentry/sentry.py +267 -0
  323. aip_agents/skills/__init__.py +30 -0
  324. aip_agents/skills/errors.py +110 -0
  325. aip_agents/skills/installer.py +479 -0
  326. aip_agents/skills/models.py +769 -0
  327. aip_agents/skills/stager.py +224 -0
  328. aip_agents/skills/validation.py +53 -0
  329. aip_agents/storage/__init__.py +41 -0
  330. aip_agents/storage/base.py +85 -0
  331. aip_agents/storage/clients/__init__.py +12 -0
  332. aip_agents/storage/clients/minio_client.py +318 -0
  333. aip_agents/storage/config.py +62 -0
  334. aip_agents/storage/providers/__init__.py +15 -0
  335. aip_agents/storage/providers/base.py +106 -0
  336. aip_agents/storage/providers/memory.py +114 -0
  337. aip_agents/storage/providers/object_storage.py +214 -0
  338. aip_agents/tools/__init__.py +65 -0
  339. aip_agents/tools/agent_schedule_tool.py +392 -0
  340. aip_agents/tools/browser_use/__init__.py +83 -0
  341. aip_agents/tools/browser_use/action_parser.py +104 -0
  342. aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
  343. aip_agents/tools/browser_use/llm_config.py +120 -0
  344. aip_agents/tools/browser_use/minio_storage.py +203 -0
  345. aip_agents/tools/browser_use/schemas.py +121 -0
  346. aip_agents/tools/browser_use/session.py +13 -0
  347. aip_agents/tools/browser_use/session_errors.py +132 -0
  348. aip_agents/tools/browser_use/steel_session_recording.py +321 -0
  349. aip_agents/tools/browser_use/streaming.py +263 -0
  350. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  351. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  352. aip_agents/tools/browser_use/types.py +37 -0
  353. aip_agents/tools/code_sandbox/__init__.py +48 -0
  354. aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
  355. aip_agents/tools/code_sandbox/constant.py +13 -0
  356. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
  357. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
  358. aip_agents/tools/computer_use/__init__.py +75 -0
  359. aip_agents/tools/computer_use/computer_use_tool.py +910 -0
  360. aip_agents/tools/computer_use/schemas.py +78 -0
  361. aip_agents/tools/computer_use/streaming.py +198 -0
  362. aip_agents/tools/computer_use/types.py +32 -0
  363. aip_agents/tools/constants.py +177 -0
  364. aip_agents/tools/date_range_tool.py +554 -0
  365. aip_agents/tools/document_loader/__init__.py +44 -0
  366. aip_agents/tools/document_loader/base_reader.py +302 -0
  367. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  368. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  369. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  370. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  371. aip_agents/tools/execute_ptc_code.py +582 -0
  372. aip_agents/tools/gl_connector/__init__.py +5 -0
  373. aip_agents/tools/gl_connector/tool.py +518 -0
  374. aip_agents/tools/gl_connector_tools.py +170 -0
  375. aip_agents/tools/memory_search/__init__.py +29 -0
  376. aip_agents/tools/memory_search/base.py +213 -0
  377. aip_agents/tools/memory_search/mem0.py +424 -0
  378. aip_agents/tools/memory_search/schema.py +81 -0
  379. aip_agents/tools/memory_search_tool.py +34 -0
  380. aip_agents/tools/streaming_common.py +577 -0
  381. aip_agents/tools/time_tool.py +117 -0
  382. aip_agents/tools/tool_config_injector.py +300 -0
  383. aip_agents/tools/web_search/__init__.py +15 -0
  384. aip_agents/tools/web_search/serper_tool.py +187 -0
  385. aip_agents/types/__init__.py +73 -0
  386. aip_agents/types/a2a_events.py +13 -0
  387. aip_agents/utils/__init__.py +91 -0
  388. aip_agents/utils/a2a_connector.py +1758 -0
  389. aip_agents/utils/artifact_helpers.py +542 -0
  390. aip_agents/utils/artifacts/__init__.py +20 -0
  391. aip_agents/utils/artifacts/tracker.py +367 -0
  392. aip_agents/utils/attachment_helpers.py +178 -0
  393. aip_agents/utils/constants.py +22 -0
  394. aip_agents/utils/datetime/__init__.py +34 -0
  395. aip_agents/utils/datetime/normalization.py +231 -0
  396. aip_agents/utils/datetime/timezone.py +206 -0
  397. aip_agents/utils/env_loader.py +27 -0
  398. aip_agents/utils/event_handler_registry.py +58 -0
  399. aip_agents/utils/file_prompt_utils.py +276 -0
  400. aip_agents/utils/file_watchers/__init__.py +33 -0
  401. aip_agents/utils/file_watchers/base.py +46 -0
  402. aip_agents/utils/file_watchers/e2b.py +128 -0
  403. aip_agents/utils/file_watchers/factory.py +39 -0
  404. aip_agents/utils/file_watchers/ls_diff.py +88 -0
  405. aip_agents/utils/file_watchers/sandbox.py +66 -0
  406. aip_agents/utils/file_watchers/watchdog.py +97 -0
  407. aip_agents/utils/final_response_builder.py +211 -0
  408. aip_agents/utils/formatter_llm_client.py +258 -0
  409. aip_agents/utils/langgraph/__init__.py +19 -0
  410. aip_agents/utils/langgraph/converter.py +399 -0
  411. aip_agents/utils/langgraph/delegation_context.py +22 -0
  412. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  413. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  414. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  415. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
  416. aip_agents/utils/langgraph/tool_output_management.py +1047 -0
  417. aip_agents/utils/logger.py +195 -0
  418. aip_agents/utils/metadata/__init__.py +27 -0
  419. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  420. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  421. aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
  422. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  423. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  424. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  425. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  426. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  427. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  428. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  429. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  430. aip_agents/utils/metadata_helper.py +363 -0
  431. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  432. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  433. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  434. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  435. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  436. aip_agents/utils/pii/__init__.py +29 -0
  437. aip_agents/utils/pii/pii_handler.py +555 -0
  438. aip_agents/utils/pii/pii_helper.py +225 -0
  439. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  440. aip_agents/utils/reference_helper.py +273 -0
  441. aip_agents/utils/sse_chunk_transformer.py +836 -0
  442. aip_agents/utils/step_limit_manager.py +265 -0
  443. aip_agents/utils/token_usage_helper.py +531 -0
  444. aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
  445. aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
  446. aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
  447. aip_agents_binary-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,690 @@
1
+ """Filesystem middleware for agent file operations.
2
+
3
+ Provides file storage and management capabilities for agents, enabling them
4
+ to read, write, edit, and search files within isolated thread-specific storage.
5
+
6
+ TODO: When migrating to deepagents:
7
+ 1. Import BackendProtocol from deepagents.backends.protocol
8
+ 2. Keep InMemoryBackend or use deepagents.backends.StateBackend
9
+ 3. Tools will import from deepagents middleware tools
10
+
11
+ Authors:
12
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import abstractmethod
18
+ from functools import lru_cache
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from langchain_core.messages import ToolMessage
23
+ from langchain_core.tools import BaseTool
24
+ from pydantic import BaseModel, ConfigDict, Field
25
+
26
+ from aip_agents.common.callable_introspection import callable_accepts_keyword
27
+ from aip_agents.middleware.backends.in_memory import InMemoryBackend
28
+ from aip_agents.middleware.backends.local_disk import LocalDiskBackend
29
+ from aip_agents.middleware.backends.protocol import BackendProtocol
30
+ from aip_agents.middleware.backends.utils import sanitize_tool_call_id
31
+ from aip_agents.middleware.base import AgentMiddleware, ModelRequest
32
+ from aip_agents.middleware.tools.edit_file import EditFileTool
33
+ from aip_agents.middleware.tools.execute import ExecuteTool
34
+ from aip_agents.middleware.tools.grep_file import GrepTool
35
+ from aip_agents.middleware.tools.ls import LsTool
36
+ from aip_agents.middleware.tools.read_file import ReadFileTool
37
+ from aip_agents.middleware.tools.write_file import WriteFileTool
38
+ from aip_agents.sandbox.defaults import DEFAULT_SANDBOX_TEMPLATE
39
+ from aip_agents.utils.logger import get_logger
40
+
41
+ # Execution system prompt addition (shown when execute tool is available)
42
+ EXECUTION_SYSTEM_PROMPT = """## Execute Tool `execute`
43
+
44
+ You have access to an `execute` tool for running shell commands.
45
+ Use this tool to run commands, scripts, tests, builds, and other shell operations.
46
+
47
+ - execute: run a shell command (returns output and exit code)
48
+
49
+ ⚠️ Always handle errors gracefully. Read stderr if execution fails."""
50
+
51
+
52
+ def _backend_supports_execution(backend: BackendProtocol) -> bool:
53
+ """Check if backend supports command execution.
54
+
55
+ Handles both direct backends and CompositeBackend which may wrap
56
+ a sandbox backend as its default.
57
+
58
+ Args:
59
+ backend: The backend to check.
60
+
61
+ Returns:
62
+ True if the backend supports execution, False otherwise.
63
+ """
64
+ try:
65
+ composite_module = __import__(
66
+ "aip_agents.middleware.backends.composite",
67
+ fromlist=["CompositeBackend"],
68
+ )
69
+ composite_backend_cls = getattr(composite_module, "CompositeBackend")
70
+
71
+ if isinstance(backend, composite_backend_cls):
72
+ default = getattr(backend, "default", None)
73
+ if default is None:
74
+ return False
75
+ return _backend_supports_execution(default)
76
+ except ImportError:
77
+ # CompositeBackend is optional in this repository state.
78
+ # Keep compatibility with existing tests that use a class-name-based mock.
79
+ if getattr(backend, "__class__", None) and backend.__class__.__name__ == "CompositeBackend":
80
+ default = getattr(backend, "default", None)
81
+ if default is None:
82
+ return False
83
+ return _backend_supports_execution(default)
84
+
85
+ # For other backends, check if execute method exists
86
+ return hasattr(backend, "execute") and callable(getattr(backend, "execute"))
87
+
88
+
89
+ def _is_execution_enabled(backend: BackendProtocol) -> bool:
90
+ """Check if execution is enabled for this backend.
91
+
92
+ Even if a backend supports execution (has execute method),
93
+ it may be disabled via allow_execute flag.
94
+
95
+ Args:
96
+ backend: The backend to check.
97
+
98
+ Returns:
99
+ True if execution is enabled, False otherwise.
100
+ """
101
+ # Check if backend supports execution first
102
+ if not _backend_supports_execution(backend):
103
+ return False
104
+
105
+ try:
106
+ composite_module = __import__(
107
+ "aip_agents.middleware.backends.composite",
108
+ fromlist=["CompositeBackend"],
109
+ )
110
+ composite_backend_cls = getattr(composite_module, "CompositeBackend")
111
+
112
+ if isinstance(backend, composite_backend_cls):
113
+ default = getattr(backend, "default", None)
114
+ if default is None:
115
+ return False
116
+ return _is_execution_enabled(default)
117
+ except ImportError:
118
+ if getattr(backend, "__class__", None) and backend.__class__.__name__ == "CompositeBackend":
119
+ default = getattr(backend, "default", None)
120
+ if default is None:
121
+ return False
122
+ return _is_execution_enabled(default)
123
+
124
+ # Check if allow_execute is set to True (defaults to False for security)
125
+ return getattr(backend, "allow_execute", False)
126
+
127
+
128
+ @lru_cache(maxsize=128)
129
+ def _execute_accepts_timeout(backend_class: type) -> bool:
130
+ """Check whether a backend class's `execute` accepts a `timeout` kwarg.
131
+
132
+ Args:
133
+ backend_class: The backend class to inspect.
134
+
135
+ Returns:
136
+ True if the execute method accepts a timeout parameter, False otherwise.
137
+ """
138
+ execute_fn = getattr(backend_class, "execute", None)
139
+ return callable_accepts_keyword(execute_fn, "timeout")
140
+
141
+
142
+ __all__ = [
143
+ "FilesystemConfig",
144
+ "InMemoryConfig",
145
+ "LocalDiskConfig",
146
+ "SandboxConfig",
147
+ "FilesystemMiddleware",
148
+ "FILESYSTEM_SYSTEM_PROMPT",
149
+ "EXECUTION_SYSTEM_PROMPT",
150
+ "_backend_supports_execution",
151
+ "_is_execution_enabled",
152
+ "_execute_accepts_timeout",
153
+ ]
154
+
155
+ logger = get_logger(__name__)
156
+
157
+
158
+ class FilesystemConfig(BaseModel):
159
+ """Abstract base class for filesystem backend configurations.
160
+
161
+ Subclasses implement create_backend() to instantiate specific backends.
162
+ This allows users to configure filesystem storage with custom settings.
163
+
164
+ Example:
165
+ >>> config = InMemoryConfig()
166
+ >>> backend = config.create_backend()
167
+ >>> middleware = FilesystemMiddleware(backend=backend)
168
+
169
+ >>> config = LocalDiskConfig(base_directory="/tmp/agent_files")
170
+ >>> backend = config.create_backend()
171
+ >>> middleware = FilesystemMiddleware(backend=backend)
172
+ """
173
+
174
+ @abstractmethod
175
+ def create_backend(self) -> BackendProtocol:
176
+ """Create and return a backend instance based on this configuration.
177
+
178
+ Returns:
179
+ BackendProtocol: Configured backend instance ready for use.
180
+ """
181
+ ...
182
+
183
+
184
+ class InMemoryConfig(FilesystemConfig):
185
+ """Configuration for in-memory filesystem backend.
186
+
187
+ Stores all files in memory. Files are lost when the process exits.
188
+ Best for temporary storage and testing scenarios.
189
+
190
+ Example:
191
+ >>> config = InMemoryConfig()
192
+ >>> agent = LangGraphReactAgent(
193
+ ... name="test_agent",
194
+ ... instruction="Test",
195
+ ... filesystem=config
196
+ ... )
197
+ """
198
+
199
+ def create_backend(self) -> BackendProtocol:
200
+ """Create an in-memory backend.
201
+
202
+ Returns:
203
+ BackendProtocol: InMemoryBackend instance.
204
+ """
205
+ return InMemoryBackend()
206
+
207
+ model_config = ConfigDict(frozen=True)
208
+
209
+
210
+ class LocalDiskConfig(FilesystemConfig):
211
+ """Configuration for local disk filesystem backend.
212
+
213
+ Stores files on the local filesystem at the specified base path.
214
+ Files persist between process restarts.
215
+ Best for production use cases requiring file persistence.
216
+
217
+
218
+ Args:
219
+ base_directory: Root directory for file storage.
220
+
221
+ Example:
222
+ >>> import tempfile
223
+ >>> with tempfile.TemporaryDirectory() as tmpdir:
224
+ ... config = LocalDiskConfig(base_directory=tmpdir)
225
+ ... agent = LangGraphReactAgent(
226
+ ... name="file_agent",
227
+ ... instruction="Process files",
228
+ ... filesystem=config
229
+ ... )
230
+ """
231
+
232
+ base_directory: str | Path = Field(
233
+ ...,
234
+ description="Root directory for file storage.",
235
+ )
236
+ allow_execute: bool = Field(
237
+ default=False,
238
+ description="Whether execute() is enabled for this local disk backend.",
239
+ )
240
+ env: dict[str, str] | None = Field(
241
+ default=None,
242
+ description="Optional environment variables overlay for execute() calls.",
243
+ )
244
+
245
+ def create_backend(self) -> BackendProtocol:
246
+ """Create a local disk backend.
247
+
248
+ Returns:
249
+ BackendProtocol: LocalDiskBackend instance configured with base_path.
250
+ """
251
+ return LocalDiskBackend(
252
+ base_directory=Path(self.base_directory),
253
+ allow_execute=self.allow_execute,
254
+ env=self.env,
255
+ )
256
+
257
+ model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
258
+
259
+
260
+ class SandboxConfig(FilesystemConfig):
261
+ """Configuration for sandbox filesystem backend.
262
+
263
+ Stores files and command execution inside E2B sandbox environments.
264
+ """
265
+
266
+ template: str = Field(
267
+ default=DEFAULT_SANDBOX_TEMPLATE,
268
+ description="E2B template alias. Defaults to DEFAULT_SANDBOX_TEMPLATE.",
269
+ )
270
+ base_dir: str | None = Field(
271
+ default=None,
272
+ description=(
273
+ "Base workspace directory inside the sandbox. When None, resolved at "
274
+ "runtime to $HOME/workspace (falling back to /home/user/workspace) so "
275
+ "it is writable by the sandbox user."
276
+ ),
277
+ )
278
+ allow_execute: bool = Field(
279
+ default=True,
280
+ description="Whether execute() is enabled for this sandbox backend.",
281
+ )
282
+ env: dict[str, str] | None = Field(
283
+ default=None,
284
+ description="Optional environment variables forwarded to sandbox execute() calls.",
285
+ )
286
+ timeout_seconds: int = Field(
287
+ default=300,
288
+ ge=1,
289
+ description="Default runtime timeout in seconds for sandbox commands.",
290
+ )
291
+ auto_build_template: bool = Field(
292
+ default=True,
293
+ description="Whether to auto-build missing template alias before fallback.",
294
+ )
295
+
296
+ def create_backend(self) -> BackendProtocol:
297
+ """Create a sandbox backend.
298
+
299
+ Returns:
300
+ BackendProtocol: SandboxBackend configured from this model.
301
+
302
+ Raises:
303
+ ImportError: If sandbox dependencies (e.g., e2b) are not installed.
304
+ """
305
+ try:
306
+ from aip_agents.middleware.backends.sandbox import SandboxBackend
307
+ except ModuleNotFoundError as e:
308
+ missing_root = (e.name or "").split(".")[0]
309
+ if missing_root in {"e2b", "e2b_code_interpreter"}:
310
+ raise ImportError(
311
+ "SandboxBackend requires sandbox dependencies. Install with: pip install 'aip-agents[local]'"
312
+ ) from e
313
+ raise
314
+ return SandboxBackend(
315
+ template=self.template,
316
+ base_dir=self.base_dir,
317
+ allow_execute=self.allow_execute,
318
+ env=self.env,
319
+ timeout_seconds=self.timeout_seconds,
320
+ auto_build_template=self.auto_build_template,
321
+ )
322
+
323
+ model_config = ConfigDict(frozen=True)
324
+
325
+
326
+ NUM_CHARS_PER_TOKEN = 4
327
+ TOOL_TOKEN_LIMIT_BEFORE_EVICT = 20000
328
+ PREVIEW_HEAD_SIZE = 500
329
+ PREVIEW_TAIL_SIZE = 500
330
+ AUTO_SAVE_DIR = "/tool_outputs"
331
+
332
+ TOOLS_EXCLUDED_FROM_EVICTION = (
333
+ "ls",
334
+ "grep",
335
+ "read_file",
336
+ "edit_file",
337
+ "write_file",
338
+ )
339
+
340
+
341
+ FILESYSTEM_SYSTEM_PROMPT = """## Filesystem Tools `read_file`, `grep`, `ls`, `write_file`, `edit_file`
342
+
343
+ You have access to a filesystem which you can interact with using these tools.
344
+ All file paths must start with a /.
345
+
346
+ - read_file: read a file from the filesystem
347
+ - grep: search for text within files
348
+ - ls: list files in a directory (requires absolute path)
349
+ - write_file: write to a file in the filesystem
350
+ - edit_file: perform exact string replacements in existing files
351
+
352
+ ## Pagination Strategy
353
+
354
+ **CRITICAL: Choose ONE mode per file and never mix them in the same session.**
355
+
356
+ **Line-based** — for normal code/text files:
357
+ - `read_file(path, line_offset=0, limit=100)` → first 100 lines
358
+ - `read_file(path, line_offset=100, limit=100)` → next 100 lines
359
+ - Do NOT set `char_offset` or `max_chars`
360
+
361
+ **Character-based** — for large/long-line files (minified JS, JSON, CSV, etc.):
362
+ - `read_file(path, char_offset=0, max_chars=20000)` → first 20K chars
363
+ - `read_file(path, char_offset=20000, max_chars=20000)` → next 20K chars
364
+ - Do NOT set `line_offset` or `limit`
365
+ - Default chunk: 20000 characters; hard limit: 80000 characters per call
366
+
367
+ **How to decide:**
368
+ 1. If you know the file has long lines (JSON, minified, CSV) → use char-based immediately
369
+ 2. If a line-based read returns a truncation warning → copy the exact `char_offset` from that warning and continue with char-based
370
+ 3. Once you pick a mode for a file, continue with that mode until done
371
+
372
+ ## Auto-Save
373
+
374
+ Large tool outputs may be automatically saved to files. You will receive:
375
+ - The file path where content was saved
376
+ - A preview (head + tail) of the content
377
+ - Use `read_file` to access specific sections
378
+ """
379
+
380
+
381
+ class FilesystemMiddleware(AgentMiddleware):
382
+ """Middleware providing filesystem capabilities for agents.
383
+
384
+ Manages file storage and retrieval for agents using a pluggable backend.
385
+ Provides hooks for lifecycle management and system prompt enhancement
386
+ with file context information.
387
+
388
+ Thread Safety:
389
+ Uses the underlying backend's thread safety mechanisms.
390
+ Each thread_id has isolated storage.
391
+
392
+ Attributes:
393
+ tools: List of file operation tools (empty in PR 001, populated in 002-004).
394
+ system_prompt_additions: System prompt text explaining file access.
395
+ backend: Storage backend for file operations.
396
+ """
397
+
398
+ def __init__(
399
+ self,
400
+ backend: BackendProtocol | None = None,
401
+ tool_token_limit_before_evict: int | None = TOOL_TOKEN_LIMIT_BEFORE_EVICT,
402
+ max_execute_timeout: int = 300,
403
+ ) -> None:
404
+ """Initialize filesystem tools and tool-output eviction policy.
405
+
406
+ Args:
407
+ backend: Filesystem backend implementation.
408
+ If ``None``, ``InMemoryBackend`` is used.
409
+ tool_token_limit_before_evict: Token threshold used to
410
+ decide when tool output should be evicted to
411
+ ``/tool_outputs/<tool_call_id>.txt``. The middleware uses
412
+ ``NUM_CHARS_PER_TOKEN`` as an approximation.
413
+ Set to ``None`` to disable eviction.
414
+ max_execute_timeout: Maximum timeout in seconds for execute commands.
415
+ Defaults to 300. Only used if backend supports execution.
416
+
417
+ Notes:
418
+ Eviction is skipped for tools listed in
419
+ ``TOOLS_EXCLUDED_FROM_EVICTION``.
420
+ """
421
+ self.backend = backend if backend is not None else InMemoryBackend()
422
+ self.tools: list[BaseTool] = [
423
+ ReadFileTool(backend=self.backend),
424
+ GrepTool(backend=self.backend),
425
+ LsTool(backend=self.backend),
426
+ WriteFileTool(backend=self.backend),
427
+ EditFileTool(backend=self.backend),
428
+ ]
429
+
430
+ # Add execute tool if backend supports it AND execution is enabled
431
+ if _is_execution_enabled(self.backend):
432
+ self.tools.append(
433
+ ExecuteTool(
434
+ backend=self.backend,
435
+ max_execute_timeout=max_execute_timeout,
436
+ )
437
+ )
438
+ self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT + "\n\n" + EXECUTION_SYSTEM_PROMPT
439
+ else:
440
+ self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT
441
+
442
+ self.tool_token_limit_before_evict = tool_token_limit_before_evict
443
+
444
+ def replace_backend(self, backend: BackendProtocol, max_execute_timeout: int = 300) -> None:
445
+ """Replace the backend and rebuild all tools to point to it.
446
+
447
+ Called by the coordinator during sub-agent registration to inject a
448
+ shared backend. After this call, all tools will operate on the new
449
+ backend.
450
+
451
+ Args:
452
+ backend: The new backend instance to use. Must implement BackendProtocol.
453
+ max_execute_timeout: Maximum timeout in seconds for execute commands.
454
+ Defaults to 300. Only used if backend supports execution.
455
+ """
456
+ self.backend = backend
457
+ self.tools = [
458
+ ReadFileTool(backend=self.backend),
459
+ GrepTool(backend=self.backend),
460
+ LsTool(backend=self.backend),
461
+ WriteFileTool(backend=self.backend),
462
+ EditFileTool(backend=self.backend),
463
+ ]
464
+
465
+ # Add execute tool if backend supports it AND execution is enabled
466
+ if _is_execution_enabled(self.backend):
467
+ self.tools.append(
468
+ ExecuteTool(
469
+ backend=self.backend,
470
+ max_execute_timeout=max_execute_timeout,
471
+ )
472
+ )
473
+ self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT + "\n\n" + EXECUTION_SYSTEM_PROMPT
474
+ else:
475
+ self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT
476
+
477
+ def _is_tool_excluded_from_eviction(self, message: ToolMessage) -> bool:
478
+ """Check if the tool is excluded from eviction."""
479
+ tool_name = None
480
+ tool_calls = getattr(message, "tool_calls", None)
481
+ if isinstance(tool_calls, dict):
482
+ tool_name = tool_calls.get("name")
483
+
484
+ if not tool_name:
485
+ tool_name = getattr(message, "name", None)
486
+
487
+ return isinstance(tool_name, str) and tool_name in TOOLS_EXCLUDED_FROM_EVICTION
488
+
489
+ def _exceeds_eviction_limit(self, content: str) -> bool:
490
+ """Check if the content exceeds the eviction limit."""
491
+ if self.tool_token_limit_before_evict is None:
492
+ return False
493
+ return len(content) > NUM_CHARS_PER_TOKEN * self.tool_token_limit_before_evict
494
+
495
+ def _save_output(self, path: str, content: str, thread_id: str) -> None:
496
+ """Save the output to the backend.
497
+
498
+ Auto-saved files are saved to the ``"default"`` thread storage because
499
+ file tools (ReadFileTool, GrepTool, etc.) do not currently pass
500
+ ``thread_id`` to the backend. See issue #1158.
501
+ """
502
+ if isinstance(self.backend, InMemoryBackend):
503
+ self.backend.write(path, content, thread_id="default")
504
+ return
505
+ self.backend.write(path, content)
506
+
507
+ def _format_auto_saved_preview(self, file_path: str, content: str) -> str:
508
+ """Format the auto-saved preview."""
509
+ head = content[:PREVIEW_HEAD_SIZE]
510
+ tail = content[-PREVIEW_TAIL_SIZE:] if len(content) > PREVIEW_TAIL_SIZE else content
511
+ return (
512
+ f"Output saved to: {file_path}\n"
513
+ f"Preview (first {PREVIEW_HEAD_SIZE} chars): {head}...\n"
514
+ f"Preview (last {PREVIEW_TAIL_SIZE} chars): ...{tail}\n"
515
+ "Use read_file(path, char_offset=0, max_chars=20000) to read this file in chunks."
516
+ )
517
+
518
+ def _replace_tool_message_content(self, message: ToolMessage, content: str) -> ToolMessage:
519
+ """Replace the content of a tool message and update tool_calls output.
520
+
521
+ Args:
522
+ message: The original ToolMessage to update.
523
+ content: The new content to set in the message.
524
+
525
+ Returns:
526
+ A new ToolMessage instance with updated content and tool_calls.output.
527
+ """
528
+ # Update content and tool_calls.output for consistency
529
+ update_dict: dict[str, Any] = {"content": content}
530
+ if hasattr(message, "tool_calls") and isinstance(message.tool_calls, dict):
531
+ updated_tool_calls = dict(message.tool_calls)
532
+ updated_tool_calls["output"] = content
533
+ update_dict["tool_calls"] = updated_tool_calls
534
+ return message.model_copy(update=update_dict)
535
+
536
+ def before_model(self, state: dict[str, Any]) -> dict[str, Any]:
537
+ """Hook executed before model invocation.
538
+
539
+ Loads files from state into the backend storage for the current thread.
540
+ This restores the filesystem state from checkpointed data.
541
+
542
+ Args:
543
+ state: Current agent state containing messages and other context.
544
+ Expected keys:
545
+ - "files": Dictionary mapping file paths to file data (optional).
546
+ - "configurable": Dict containing "thread_id" for isolation.
547
+
548
+ Returns:
549
+ Empty dict (no state updates needed from this hook).
550
+ """
551
+ thread_id = state.get("configurable", {}).get("thread_id", "default")
552
+
553
+ if "files" in state and state["files"] is not None:
554
+ self.backend.set_files(state["files"], thread_id=thread_id)
555
+
556
+ return {}
557
+
558
+ def _evict_tool_messages(
559
+ self,
560
+ messages: list[Any],
561
+ thread_id: str,
562
+ ) -> tuple[list[Any], bool]:
563
+ """Evict large tool messages and return updated list.
564
+
565
+ Args:
566
+ messages: List of messages to process.
567
+ thread_id: Thread ID for backend storage.
568
+
569
+ Returns:
570
+ Tuple of (updated_messages, messages_changed).
571
+ """
572
+ updated_messages = []
573
+ messages_changed = False
574
+
575
+ for message in messages:
576
+ if not isinstance(message, ToolMessage):
577
+ updated_messages.append(message)
578
+ continue
579
+
580
+ content = message.content if isinstance(message.content, str) else str(message.content)
581
+ if self._is_tool_excluded_from_eviction(message):
582
+ updated_messages.append(message)
583
+ continue
584
+
585
+ tool_name = getattr(message, "name", "unknown")
586
+ threshold = (
587
+ NUM_CHARS_PER_TOKEN * self.tool_token_limit_before_evict
588
+ if self.tool_token_limit_before_evict is not None
589
+ else None
590
+ )
591
+ logger.debug(
592
+ "Checking tool output for eviction: tool=%s size=%d threshold=%s",
593
+ tool_name,
594
+ len(content),
595
+ threshold,
596
+ )
597
+
598
+ if not self._exceeds_eviction_limit(content):
599
+ updated_messages.append(message)
600
+ continue
601
+
602
+ raw_call_id = getattr(message, "tool_call_id", "") or "unknown"
603
+ safe_call_id = sanitize_tool_call_id(str(raw_call_id))
604
+ file_path = f"{AUTO_SAVE_DIR}/{safe_call_id}.txt"
605
+
606
+ self._save_output(file_path, content, thread_id)
607
+ preview_text = self._format_auto_saved_preview(file_path=file_path, content=content)
608
+ updated_messages.append(self._replace_tool_message_content(message=message, content=preview_text))
609
+ messages_changed = True
610
+
611
+ logger.info(
612
+ "Evicted tool output: tool=%s size=%d chars path=%s",
613
+ tool_name,
614
+ len(content),
615
+ file_path,
616
+ )
617
+
618
+ return updated_messages, messages_changed
619
+
620
+ def after_model(self, state: dict[str, Any]) -> dict[str, Any]:
621
+ """Hook executed after model invocation.
622
+
623
+ Persists files to backend for checkpointing. Note: message eviction
624
+ is handled in modify_model_request hook before LLM invocation.
625
+
626
+ Args:
627
+ state: Current agent state after model invocation.
628
+
629
+ Returns:
630
+ Dict with "files" key if files exist, or empty dict if no updates needed.
631
+ """
632
+ thread_id = state.get("configurable", {}).get("thread_id", "default")
633
+
634
+ updates: dict[str, Any] = {}
635
+
636
+ # Persist files to checkpoint only (message eviction happens in modify_model_request)
637
+ files = self.backend.get_files(thread_id=thread_id)
638
+ if files:
639
+ updates["files"] = files
640
+
641
+ return updates
642
+
643
+ def modify_model_request(self, request: ModelRequest, state: dict[str, Any]) -> ModelRequest:
644
+ """Modify the model request before invocation."""
645
+ messages = request.get("messages", [])
646
+ thread_id = state.get("configurable", {}).get("thread_id", "default")
647
+
648
+ updated_messages, messages_changed = self._evict_tool_messages(messages, thread_id)
649
+
650
+ if messages_changed:
651
+ request["messages"] = updated_messages
652
+
653
+ return request
654
+
655
+ async def abefore_model(self, state: dict[str, Any]) -> dict[str, Any]:
656
+ """Async version of before_model.
657
+
658
+ Args:
659
+ state: Current agent state containing messages and other context.
660
+
661
+ Returns:
662
+ Dict of state updates to merge into the agent state. Return empty dict
663
+ if no updates are needed.
664
+ """
665
+ return self.before_model(state)
666
+
667
+ async def aafter_model(self, state: dict[str, Any]) -> dict[str, Any]:
668
+ """Async version of after_model.
669
+
670
+ Args:
671
+ state: Current agent state after model invocation.
672
+
673
+ Returns:
674
+ Dict of state updates to merge into the agent state. Return empty dict
675
+ if no updates are needed.
676
+ """
677
+ thread_id = state.get("configurable", {}).get("thread_id", "default")
678
+
679
+ updates: dict[str, Any] = {}
680
+
681
+ aget_files = getattr(self.backend, "aget_files", None)
682
+ if callable(aget_files):
683
+ files = await aget_files(thread_id=thread_id)
684
+ else:
685
+ files = self.backend.get_files(thread_id=thread_id)
686
+
687
+ if files:
688
+ updates["files"] = files
689
+
690
+ return updates