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,72 @@
1
+ """Backend implementations for filesystem middleware.
2
+
3
+ This package provides backend implementations for filesystem storage,
4
+ including in-memory storage and protocol definitions.
5
+
6
+ Migration note for deepagents:
7
+ 1. Replace protocol imports with deepagents.backends.protocol
8
+ 2. Replace utils imports with deepagents.backends.utils
9
+ 3. InMemoryBackend can stay or be replaced with StateBackend
10
+
11
+ Authors:
12
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
13
+ """
14
+
15
+ from typing import TYPE_CHECKING, Any
16
+
17
+ from aip_agents.middleware.backends.in_memory import InMemoryBackend
18
+ from aip_agents.middleware.backends.local_disk import LocalDiskBackend
19
+ from aip_agents.middleware.backends.protocol import (
20
+ BackendProtocol,
21
+ EditResult,
22
+ FileInfo,
23
+ GrepMatch,
24
+ WriteResult,
25
+ )
26
+
27
+ if TYPE_CHECKING:
28
+ from aip_agents.middleware.backends.sandbox import SandboxBackend
29
+
30
+ from aip_agents.middleware.backends.utils import (
31
+ ensure_absolute_path,
32
+ format_content_with_line_numbers,
33
+ format_grep_matches,
34
+ perform_string_replacement,
35
+ sanitize_tool_call_id,
36
+ truncate_if_too_long,
37
+ )
38
+
39
+ __all__ = [
40
+ "InMemoryBackend",
41
+ "LocalDiskBackend",
42
+ "SandboxBackend",
43
+ "BackendProtocol",
44
+ "FileInfo",
45
+ "WriteResult",
46
+ "EditResult",
47
+ "GrepMatch",
48
+ "ensure_absolute_path",
49
+ "format_content_with_line_numbers",
50
+ "perform_string_replacement",
51
+ "format_grep_matches",
52
+ "truncate_if_too_long",
53
+ "sanitize_tool_call_id",
54
+ ]
55
+
56
+
57
+ def __getattr__(name: str) -> Any:
58
+ if name == "SandboxBackend":
59
+ try:
60
+ from aip_agents.middleware.backends.sandbox import (
61
+ SandboxBackend as SandboxBackendType,
62
+ )
63
+ except ModuleNotFoundError as exc:
64
+ raise ModuleNotFoundError(
65
+ "SandboxBackend requires the E2B optional dependency. Install with: pip install 'aip-agents[local]'"
66
+ ) from exc
67
+ return SandboxBackendType
68
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
69
+
70
+
71
+ def __dir__() -> list[str]:
72
+ return sorted(__all__)
@@ -0,0 +1,578 @@
1
+ """In-memory filesystem backend implementation.
2
+
3
+ This module provides InMemoryBackend, a BackendProtocol implementation
4
+ that stores files in memory with thread-isolated storage.
5
+
6
+ Migration note for deepagents:
7
+ 1. Import BackendProtocol and types from deepagents.backends.protocol
8
+ 2. Import utility functions from deepagents.backends.utils (same API)
9
+
10
+ Authors:
11
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ import threading
18
+ import time
19
+ from base64 import b64decode, b64encode
20
+ from typing import Any
21
+
22
+ from aip_agents.middleware.backends.protocol import (
23
+ BackendProtocol,
24
+ EditResult,
25
+ FileInfo,
26
+ GrepMatch,
27
+ WriteResult,
28
+ )
29
+ from aip_agents.middleware.backends.utils import (
30
+ format_content_with_line_numbers,
31
+ perform_string_replacement,
32
+ )
33
+
34
+ __all__ = ["InMemoryBackend"]
35
+
36
+ _TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S"
37
+ _BINARY_CONTENT_ENCODING = "base64"
38
+
39
+
40
+ class InMemoryBackend(BackendProtocol):
41
+ """In-memory filesystem backend.
42
+
43
+ Explicitly implements BackendProtocol with thread-isolated storage.
44
+ Stores files in memory per thread_id, making it suitable for
45
+ agent conversations where each thread needs isolated file storage.
46
+
47
+ Thread Safety:
48
+ All operations are protected by threading.RLock to ensure
49
+ thread-safe access to the shared storage.
50
+
51
+ Attributes:
52
+ _storage: Dictionary mapping thread_id to {path: FileData}.
53
+ _storage_lock: RLock protecting concurrent access to _storage.
54
+
55
+ Example:
56
+ >>> backend = InMemoryBackend()
57
+ >>> files = {"/test.txt": {"content": ["Hello"], "created_at": "2024-01-01", "modified_at": "2024-01-01"}}
58
+ >>> backend.set_files(files, thread_id="thread-1")
59
+ >>> retrieved = backend.get_files(thread_id="thread-1")
60
+ >>> "/test.txt" in retrieved
61
+ True
62
+ """
63
+
64
+ def __init__(self) -> None:
65
+ """Initialize with empty storage."""
66
+ self._storage: dict[str, dict[str, Any]] = {}
67
+ self._storage_lock = threading.RLock()
68
+
69
+ def set_files(self, files: dict[str, Any], thread_id: str = "default") -> None:
70
+ """Load files from state for a specific thread.
71
+
72
+ Called by FilesystemMiddleware.before_model to restore files
73
+ from checkpointed state into the backend.
74
+
75
+ Args:
76
+ files: Dictionary mapping file paths to FileData.
77
+ thread_id: Thread identifier for isolation. Defaults to "default".
78
+
79
+ Note:
80
+ Makes a copy of the files dict to prevent external modifications
81
+ affecting internal storage.
82
+ """
83
+ with self._storage_lock:
84
+ self._storage[thread_id] = {
85
+ path: self._deserialize_file_data(file_data) for path, file_data in files.items()
86
+ }
87
+
88
+ def get_files(self, thread_id: str = "default") -> dict[str, Any]:
89
+ """Get files for a specific thread.
90
+
91
+ Called by FilesystemMiddleware.after_model to save files
92
+ from backend into state for checkpointing.
93
+
94
+ Args:
95
+ thread_id: Thread identifier for isolation. Defaults to "default".
96
+
97
+ Returns:
98
+ Dictionary mapping file paths to FileData, or empty dict
99
+ if no files for this thread.
100
+
101
+ Note:
102
+ Returns a copy to prevent external modifications affecting
103
+ internal storage.
104
+ """
105
+ with self._storage_lock:
106
+ return {
107
+ path: self._serialize_file_data(file_data)
108
+ for path, file_data in self._storage.get(thread_id, {}).items()
109
+ }
110
+
111
+ @staticmethod
112
+ def _serialize_file_data(file_data: Any) -> Any:
113
+ """Return JSON-safe checkpoint data for one stored file entry."""
114
+ if not isinstance(file_data, dict):
115
+ return file_data # pragma: no cover
116
+ serialized = file_data.copy()
117
+ content = serialized.get("content")
118
+ if isinstance(content, bytes):
119
+ serialized["content"] = b64encode(content).decode("ascii")
120
+ serialized["encoding"] = _BINARY_CONTENT_ENCODING
121
+ return serialized
122
+
123
+ @staticmethod
124
+ def _deserialize_file_data(file_data: Any) -> Any:
125
+ """Restore one stored file entry from checkpoint data."""
126
+ if not isinstance(file_data, dict):
127
+ return file_data # pragma: no cover
128
+ restored = file_data.copy()
129
+ if restored.get("encoding") == _BINARY_CONTENT_ENCODING and isinstance(restored.get("content"), str):
130
+ restored["content"] = b64decode(restored["content"])
131
+ restored.pop("encoding", None)
132
+ restored["binary"] = True
133
+ return restored
134
+
135
+ def _get_thread_storage(self, thread_id: str = "default") -> dict[str, Any]:
136
+ """Get or create storage for a thread.
137
+
138
+ Args:
139
+ thread_id: Thread identifier.
140
+
141
+ Returns:
142
+ Storage dictionary for the thread.
143
+ """
144
+ with self._storage_lock:
145
+ if thread_id not in self._storage:
146
+ self._storage[thread_id] = {}
147
+ return self._storage[thread_id]
148
+
149
+ def _normalize_path(self, path: str) -> str:
150
+ """Normalize a path to absolute form.
151
+
152
+ Args:
153
+ path: Path to normalize.
154
+
155
+ Returns:
156
+ Normalized absolute path.
157
+ """
158
+ if not path.startswith("/"):
159
+ path = "/" + path
160
+ # Remove trailing slashes except for root
161
+ path = path.rstrip("/") or "/"
162
+ return path
163
+
164
+ @staticmethod
165
+ def _content_to_lines(content: Any) -> list[str]:
166
+ if isinstance(content, bytes):
167
+ raise ValueError("Binary content cannot be read as text") # pragma: no cover
168
+ if isinstance(content, str):
169
+ return content.split("\n") # pragma: no cover
170
+ return [str(line) for line in content]
171
+
172
+ @staticmethod
173
+ def _is_binary_entry(file_data: Any) -> bool:
174
+ """Return whether a stored file entry contains byte-backed content."""
175
+ if not isinstance(file_data, dict):
176
+ return False # pragma: no cover
177
+ return bool(file_data.get("binary")) or isinstance(file_data.get("content"), bytes)
178
+
179
+ @staticmethod
180
+ def _content_to_bytes(content: Any) -> bytes:
181
+ if isinstance(content, bytes):
182
+ return content
183
+ if isinstance(content, str):
184
+ return content.encode("utf-8") # pragma: no cover
185
+ return "\n".join(str(line) for line in content).encode("utf-8")
186
+
187
+ def write(
188
+ self,
189
+ file_path: str,
190
+ content: str | list[str],
191
+ thread_id: str = "default",
192
+ ) -> WriteResult:
193
+ """Write content to a file.
194
+
195
+ Args:
196
+ file_path: Path where to write the file.
197
+ content: Content to write (string or list of strings).
198
+ thread_id: Thread identifier for isolation. Defaults to "default".
199
+
200
+ Returns:
201
+ WriteResult indicating success.
202
+ """
203
+ path = self._normalize_path(file_path)
204
+ storage = self._get_thread_storage(thread_id)
205
+
206
+ # Normalize content to list of strings
207
+ if isinstance(content, str):
208
+ lines = content.split("\n")
209
+ else:
210
+ lines = list(content)
211
+
212
+ now = time.strftime(_TIMESTAMP_FORMAT)
213
+
214
+ with self._storage_lock:
215
+ is_new = path not in storage
216
+ storage[path] = {
217
+ "content": lines,
218
+ "binary": False,
219
+ "created_at": storage.get(path, {}).get("created_at", now) if not is_new else now,
220
+ "modified_at": now,
221
+ }
222
+
223
+ return WriteResult(path=path, success=True)
224
+
225
+ def write_bytes(
226
+ self,
227
+ file_path: str,
228
+ content: bytes,
229
+ thread_id: str = "default",
230
+ ) -> WriteResult:
231
+ """Write raw bytes to a file."""
232
+ path = self._normalize_path(file_path)
233
+ storage = self._get_thread_storage(thread_id)
234
+ now = time.strftime(_TIMESTAMP_FORMAT)
235
+
236
+ with self._storage_lock:
237
+ is_new = path not in storage
238
+ storage[path] = {
239
+ "content": bytes(content),
240
+ "binary": True,
241
+ "created_at": storage.get(path, {}).get("created_at", now) if not is_new else now,
242
+ "modified_at": now,
243
+ }
244
+
245
+ return WriteResult(path=path, success=True)
246
+
247
+ def read(
248
+ self,
249
+ file_path: str,
250
+ line_offset: int = 0,
251
+ limit: int = 2000,
252
+ *,
253
+ offset: int | None = None,
254
+ thread_id: str = "default",
255
+ ) -> str:
256
+ """Read a file's contents with line numbers.
257
+
258
+ Args:
259
+ file_path: Path to the file.
260
+ line_offset: Line number to start reading from (0-indexed).
261
+ limit: Maximum number of lines to read.
262
+ thread_id: Thread identifier for isolation. Defaults to "default".
263
+ offset: Deprecated alias for ``line_offset``.
264
+
265
+ Returns:
266
+ File content formatted with line numbers.
267
+
268
+ Raises:
269
+ FileNotFoundError: If file does not exist.
270
+ """
271
+ path = self._normalize_path(file_path)
272
+ storage = self._get_thread_storage(thread_id)
273
+
274
+ if offset is not None:
275
+ if line_offset != 0 and line_offset != offset:
276
+ raise ValueError("Conflicting values provided for line_offset and offset")
277
+ line_offset = offset
278
+
279
+ with self._storage_lock:
280
+ if path not in storage:
281
+ raise FileNotFoundError(f"File not found: {file_path}")
282
+
283
+ file_data = storage[path]
284
+ if self._is_binary_entry(file_data):
285
+ raise ValueError(f"File is binary and cannot be read as text: {file_path}")
286
+ lines = self._content_to_lines(file_data.get("content", []))
287
+
288
+ # Apply offset and limit
289
+ start = max(0, line_offset)
290
+ end = min(len(lines), start + limit)
291
+ selected_lines = lines[start:end]
292
+
293
+ content = "\n".join(selected_lines)
294
+ if not content:
295
+ return ""
296
+ return format_content_with_line_numbers(content, offset=start)
297
+
298
+ def read_bytes(self, file_path: str, thread_id: str = "default") -> bytes:
299
+ """Read a file's contents as raw bytes.
300
+
301
+ Args:
302
+ file_path: Path to the file.
303
+ thread_id: Thread identifier for isolation. Defaults to "default".
304
+
305
+ Returns:
306
+ File content as raw bytes (no formatting, no line numbers).
307
+
308
+ Raises:
309
+ FileNotFoundError: If file does not exist.
310
+ IsADirectoryError: If path is a directory (not supported in memory backend).
311
+ """
312
+ path = self._normalize_path(file_path)
313
+ storage = self._get_thread_storage(thread_id)
314
+
315
+ with self._storage_lock:
316
+ if path not in storage:
317
+ raise FileNotFoundError(f"File not found: {file_path}")
318
+
319
+ file_data = storage[path]
320
+ return self._content_to_bytes(file_data.get("content", []))
321
+
322
+ def read_chars(
323
+ self,
324
+ file_path: str,
325
+ char_offset: int = 0,
326
+ max_chars: int = 20000,
327
+ thread_id: str = "default",
328
+ ) -> str:
329
+ """Read a file's contents by character position.
330
+
331
+ Provides character-based pagination for reading large files
332
+ that may have very long single lines.
333
+
334
+ Args:
335
+ file_path: Path to the file.
336
+ char_offset: Character position to start from (0-indexed).
337
+ max_chars: Maximum number of characters to read.
338
+ thread_id: Thread identifier for isolation. Defaults to "default".
339
+
340
+ Returns:
341
+ File content without line numbers (raw characters).
342
+
343
+ Raises:
344
+ FileNotFoundError: If file does not exist.
345
+ """
346
+ path = self._normalize_path(file_path)
347
+ storage = self._get_thread_storage(thread_id)
348
+
349
+ with self._storage_lock:
350
+ if path not in storage:
351
+ raise FileNotFoundError(f"File not found: {file_path}")
352
+
353
+ file_data = storage[path]
354
+ if self._is_binary_entry(file_data):
355
+ raise ValueError(f"File is binary and cannot be read as text: {file_path}")
356
+ lines = self._content_to_lines(file_data.get("content", []))
357
+ full_content = "\n".join(lines)
358
+
359
+ # Apply char_offset and max_chars
360
+ start = max(0, char_offset)
361
+ if len(full_content) == 0:
362
+ return ""
363
+ if start >= len(full_content):
364
+ return "[End of file reached]"
365
+ end = min(len(full_content), start + max_chars)
366
+ selected_content = full_content[start:end]
367
+
368
+ return selected_content
369
+
370
+ def ls_info(self, path: str, thread_id: str = "default") -> list[FileInfo]:
371
+ """List files in a directory.
372
+
373
+ Args:
374
+ path: Directory path to list.
375
+ thread_id: Thread identifier for isolation. Defaults to "default".
376
+
377
+ Returns:
378
+ List of FileInfo objects.
379
+
380
+ Raises:
381
+ FileNotFoundError: If directory does not exist (treats root as always existing).
382
+ """
383
+ path = self._normalize_path(path)
384
+ storage = self._get_thread_storage(thread_id)
385
+
386
+ with self._storage_lock:
387
+ if path == "/":
388
+ # List all files (we don't have directories, just files)
389
+ result = []
390
+ for file_path, file_data in storage.items():
391
+ size = len(self._content_to_bytes(file_data.get("content", [])))
392
+ result.append(FileInfo(path=file_path, type="file", size=size))
393
+ return result
394
+ else:
395
+ # For non-root, check if any files exist with this prefix
396
+ prefix = path if path.endswith("/") else path + "/"
397
+ matching_files = [fp for fp in storage.keys() if fp.startswith(prefix) or fp == path]
398
+
399
+ if not matching_files:
400
+ raise FileNotFoundError(f"Directory not found: {path}")
401
+
402
+ result = []
403
+ for file_path in matching_files:
404
+ file_data = storage[file_path]
405
+ size = len(self._content_to_bytes(file_data.get("content", [])))
406
+ result.append(FileInfo(path=file_path, type="file", size=size))
407
+ return result
408
+
409
+ def _get_files_to_search(self, storage: dict[str, Any], path: str | None) -> list[str]:
410
+ """Get list of files to search based on path filter.
411
+
412
+ Args:
413
+ storage: Storage dictionary for the thread.
414
+ path: Directory to search in, or None for all files.
415
+
416
+ Returns:
417
+ List of file paths to search.
418
+
419
+ Raises:
420
+ FileNotFoundError: If path is specified but directory does not exist.
421
+ """
422
+ if path is None or path == "/":
423
+ return list(storage.keys())
424
+
425
+ normalized_path = self._normalize_path(path)
426
+ prefix = normalized_path if normalized_path.endswith("/") else normalized_path + "/"
427
+ files_to_search = [fp for fp in storage.keys() if fp.startswith(prefix) or fp == normalized_path]
428
+
429
+ if normalized_path != "/" and not files_to_search:
430
+ raise FileNotFoundError(f"Path not found: {path}")
431
+
432
+ return files_to_search
433
+
434
+ def _compile_pattern(self, pattern: str) -> re.Pattern[str]:
435
+ """Compile pattern as regex, falling back to literal if invalid.
436
+
437
+ Args:
438
+ pattern: Pattern to compile.
439
+
440
+ Returns:
441
+ Compiled regex pattern.
442
+ """
443
+ try:
444
+ return re.compile(pattern, re.IGNORECASE)
445
+ except re.error:
446
+ return re.compile(re.escape(pattern), re.IGNORECASE)
447
+
448
+ def grep(
449
+ self,
450
+ pattern: str,
451
+ path: str | None = None,
452
+ thread_id: str = "default",
453
+ ) -> list[GrepMatch]:
454
+ """Search for text pattern across files.
455
+
456
+ Args:
457
+ pattern: Text pattern to search for (literal string, not regex).
458
+ path: Directory to search in. Defaults to None (search all).
459
+ thread_id: Thread identifier for isolation. Defaults to "default".
460
+
461
+ Returns:
462
+ List of GrepMatch objects.
463
+
464
+ Raises:
465
+ FileNotFoundError: If path is specified but directory does not exist.
466
+ """
467
+ storage = self._get_thread_storage(thread_id)
468
+ matches = []
469
+
470
+ with self._storage_lock:
471
+ files_to_search = self._get_files_to_search(storage, path)
472
+ regex = self._compile_pattern(pattern)
473
+
474
+ for file_path in files_to_search:
475
+ file_data = storage.get(file_path, {})
476
+ if self._is_binary_entry(file_data):
477
+ continue
478
+ lines = self._content_to_lines(file_data.get("content", []))
479
+
480
+ for line_num, line in enumerate(lines, start=1):
481
+ if regex.search(line):
482
+ matches.append(
483
+ GrepMatch(
484
+ path=file_path,
485
+ line_number=line_num,
486
+ content=line,
487
+ )
488
+ )
489
+
490
+ return matches
491
+
492
+ # Alias grep_raw to grep for protocol compatibility
493
+ grep_raw = grep
494
+
495
+ def edit(
496
+ self,
497
+ file_path: str,
498
+ old_string: str,
499
+ new_string: str,
500
+ replace_all: bool = False,
501
+ thread_id: str = "default",
502
+ ) -> EditResult:
503
+ """Edit a file by replacing old_string with new_string.
504
+
505
+ Args:
506
+ file_path: Path to the file to edit.
507
+ old_string: String to find and replace.
508
+ new_string: String to replace with.
509
+ replace_all: If True, replace all occurrences. If False,
510
+ old_string must appear exactly once. Defaults to False.
511
+ thread_id: Thread identifier for isolation. Defaults to "default".
512
+
513
+ Returns:
514
+ EditResult with number of replacements.
515
+
516
+ Notes:
517
+ Returns errors via EditResult.error to match deepagents-style tool flow.
518
+ """
519
+ path = self._normalize_path(file_path)
520
+ storage = self._get_thread_storage(thread_id)
521
+
522
+ with self._storage_lock:
523
+ if path not in storage:
524
+ return EditResult(path=path, error=f"Error: File '{file_path}' not found", success=False)
525
+
526
+ file_data = storage[path]
527
+ if self._is_binary_entry(file_data):
528
+ return EditResult(
529
+ path=path, error=f"Error: '{file_path}' is binary and cannot be edited", success=False
530
+ )
531
+ lines = self._content_to_lines(file_data.get("content", []))
532
+ content = "\n".join(lines)
533
+
534
+ replacement = perform_string_replacement(content, old_string, new_string, replace_all)
535
+ if isinstance(replacement, str):
536
+ return EditResult(path=path, error=replacement, success=False)
537
+
538
+ new_content, occurrences = replacement
539
+ new_lines = new_content.split("\n")
540
+
541
+ # Update file
542
+ file_data["content"] = new_lines
543
+ file_data["binary"] = False
544
+ file_data["modified_at"] = time.strftime(_TIMESTAMP_FORMAT)
545
+
546
+ return EditResult(
547
+ path=path,
548
+ occurrences=occurrences,
549
+ success=True,
550
+ )
551
+
552
+ def glob_info(self, pattern: str, path: str = "/", thread_id: str = "default") -> list[FileInfo]:
553
+ """Find files matching a glob pattern.
554
+
555
+ Args:
556
+ pattern: Glob pattern to match (e.g., "**/*.py").
557
+ path: Base directory to search from. Defaults to "/".
558
+ thread_id: Thread identifier for isolation. Defaults to "default".
559
+
560
+ Returns:
561
+ List of FileInfo objects for matching files.
562
+ """
563
+ import fnmatch
564
+
565
+ storage = self._get_thread_storage(thread_id)
566
+ matches = []
567
+
568
+ # Convert glob pattern to something we can match
569
+ # Simple implementation: just check if filename matches
570
+ with self._storage_lock:
571
+ for file_path, file_data in storage.items():
572
+ # Check if file matches pattern
573
+ filename = file_path.split("/")[-1] if "/" in file_path else file_path
574
+ if fnmatch.fnmatch(filename, pattern) or fnmatch.fnmatch(file_path, pattern):
575
+ size = len(self._content_to_bytes(file_data.get("content", [])))
576
+ matches.append(FileInfo(path=file_path, type="file", size=size))
577
+
578
+ return matches