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,627 @@
1
+ """PTC Executor implementations.
2
+
3
+ This module provides the sandboxed executor for Programmatic Tool Calling.
4
+
5
+ Authors:
6
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import hashlib
13
+ import inspect
14
+ import json
15
+ import shlex
16
+ from dataclasses import dataclass, field
17
+ from pathlib import PurePosixPath
18
+ from typing import TYPE_CHECKING, Any
19
+ from uuid import uuid4
20
+
21
+ from aip_agents.mcp.client.base_mcp_client import BaseMCPClient
22
+ from aip_agents.ptc.custom_tools import PTCCustomToolConfig
23
+ from aip_agents.ptc.exceptions import PTCToolError
24
+ from aip_agents.ptc.prompt_builder import PromptConfig
25
+ from aip_agents.sandbox.defaults import DEFAULT_PTC_PACKAGES, DEFAULT_PTC_TEMPLATE
26
+ from aip_agents.utils.logger import get_logger
27
+
28
+ # Lazy import to avoid circular dependencies
29
+ # These are only needed for PTCSandboxExecutor
30
+ try:
31
+ from aip_agents.ptc.sandbox_bridge import build_sandbox_payload, wrap_ptc_code
32
+ from aip_agents.sandbox.types import SandboxExecutionResult
33
+
34
+ _SANDBOX_DEPS_AVAILABLE = True
35
+ except ImportError:
36
+ _SANDBOX_DEPS_AVAILABLE = False
37
+
38
+ logger = get_logger(__name__)
39
+
40
+ STRUCTURED_OUTPUT_ENV = "AIP_AGENTS_PTC_STRUCTURED_OUTPUT_PATH"
41
+ STRUCTURED_OUTPUT_DIR = "/home/user/.aip_agents_ptc/structured_outputs"
42
+ MAX_STRUCTURED_OUTPUT_BYTES = 10 * 1024 * 1024
43
+ MAX_STRUCTURED_OUTPUT_TOTAL_BYTES = 10 * MAX_STRUCTURED_OUTPUT_BYTES
44
+
45
+ if TYPE_CHECKING:
46
+ from aip_agents.middleware.backends.sandbox import SandboxBackend
47
+ from aip_agents.ptc.payload import SandboxPayload
48
+ from aip_agents.sandbox.types import PTCStructuredOutput
49
+
50
+
51
+ @dataclass
52
+ class PTCSandboxConfig:
53
+ """Configuration for PTC sandbox executor.
54
+
55
+ Attributes:
56
+ enabled: Whether PTC is enabled. When False, PTC is disabled.
57
+ default_tool_timeout: Optional default timeout per nested MCP tool call in seconds.
58
+ When None, PTC does not inject a default nested tool-call timeout.
59
+ sandbox_template: Optional E2B sandbox template ID. Ignored for runtime
60
+ selection when filesystem_backend is set; the backend's template is
61
+ the source of truth for the shared sandbox runtime.
62
+ sandbox_timeout: Sandbox lease timeout in seconds (VM TTL). This is not
63
+ the same clock as a single code execution budget.
64
+ execution_timeout: Per-run PTC execution timeout in seconds. When unset,
65
+ it inherits sandbox_timeout for backward compatibility.
66
+ package_install_timeout: Timeout in seconds for sandbox package installation.
67
+ When None, the runtime default applies.
68
+ auto_build_template: Whether to automatically build the sandbox template if it's missing.
69
+ ptc_packages: List of packages to install in sandbox. Defaults to DEFAULT_PTC_PACKAGES.
70
+ prompt: Prompt configuration for PTC usage guidance.
71
+ custom_tools: Configuration for custom LangChain tools in sandbox.
72
+ filesystem_backend: Optional caller-provided filesystem backend for shared sandbox execution.
73
+ When set, PTC configures packages on that shared backend runtime on
74
+ first execution and leaves runtime cleanup to the backend owner.
75
+ """
76
+
77
+ enabled: bool = False
78
+ default_tool_timeout: float | None = None
79
+ sandbox_template: str | None = DEFAULT_PTC_TEMPLATE
80
+ sandbox_timeout: float = 3600.0
81
+ execution_timeout: float | None = None
82
+ package_install_timeout: float | None = None
83
+ auto_build_template: bool = False
84
+ ptc_packages: list[str] | None = field(default_factory=lambda: list(DEFAULT_PTC_PACKAGES))
85
+ prompt: PromptConfig = field(default_factory=PromptConfig)
86
+ custom_tools: PTCCustomToolConfig = field(default_factory=PTCCustomToolConfig)
87
+ filesystem_backend: SandboxBackend | None = None
88
+
89
+ def __post_init__(self) -> None:
90
+ """Validate timeout relationships after dataclass initialization."""
91
+ if self.execution_timeout is None:
92
+ return
93
+ if self.execution_timeout <= 0:
94
+ msg = "PTCSandboxConfig.execution_timeout must be > 0."
95
+ raise ValueError(msg)
96
+ if self.execution_timeout > self.sandbox_timeout:
97
+ msg = "PTCSandboxConfig.execution_timeout must be <= sandbox_timeout."
98
+ raise ValueError(msg)
99
+
100
+ def effective_execution_timeout(self) -> float:
101
+ """Return the per-run execution timeout, inheriting sandbox_timeout when unset."""
102
+ timeout = self.sandbox_timeout if self.execution_timeout is None else self.execution_timeout
103
+ return float(timeout)
104
+
105
+
106
+ class PTCSandboxExecutor:
107
+ r"""Executes PTC code inside an E2B sandbox.
108
+
109
+ This executor is used for LLM-generated code that requires sandboxing.
110
+ It builds a sandbox payload (MCP server config + generated tool modules)
111
+ and executes the code using the E2B runtime.
112
+
113
+ Static bundle caching:
114
+ The executor tracks whether the static bundle (wrappers, registry, sources)
115
+ has been uploaded. On the first run, it uploads the full payload. On subsequent
116
+ runs, it only uploads per-run files (e.g., tools/custom_defaults.json) to
117
+ reduce upload overhead.
118
+
119
+ If the sandbox is destroyed/recreated, call reset_static_bundle() to force
120
+ a full re-upload on the next execution.
121
+
122
+ Thread-safety:
123
+ The static bundle upload is guarded by an asyncio.Lock. Concurrent calls will
124
+ serialize while the initial upload completes. Per-run diff/execute/cache-update
125
+ is guarded by an executor-local asyncio.Lock to keep dedupe state consistent.
126
+
127
+ Example:
128
+ runtime = E2BSandboxRuntime()
129
+ executor = PTCSandboxExecutor(mcp_client, runtime)
130
+ result = await executor.execute_code("from tools.yfinance import get_stock\\nprint(get_stock('AAPL'))")
131
+ """
132
+
133
+ def __init__(
134
+ self,
135
+ mcp_client: BaseMCPClient | None,
136
+ runtime: Any,
137
+ config: PTCSandboxConfig | None = None,
138
+ owns_runtime: bool = True,
139
+ ) -> None:
140
+ """Initialize PTCSandboxExecutor.
141
+
142
+ Args:
143
+ mcp_client: The MCP client with configured servers. Can be None for custom-only configs.
144
+ runtime: The E2B sandbox runtime instance.
145
+ config: Optional sandbox executor configuration.
146
+ owns_runtime: Whether this executor owns and may clean up the runtime.
147
+
148
+ Raises:
149
+ ImportError: If sandbox dependencies are not available.
150
+ """
151
+ if not _SANDBOX_DEPS_AVAILABLE:
152
+ raise ImportError(
153
+ "Sandbox dependencies not available. "
154
+ "PTCSandboxExecutor requires sandbox_bridge and e2b_runtime modules."
155
+ )
156
+
157
+ self._mcp_client = mcp_client
158
+ self._runtime = runtime
159
+ self._config = config or PTCSandboxConfig()
160
+ self._owns_runtime = owns_runtime
161
+ self._static_bundle_uploaded = False
162
+ self._per_run_hash_cache: dict[str, str] = {}
163
+ self._bundle_lock = asyncio.Lock()
164
+ self._per_run_lock = asyncio.Lock()
165
+ self._cleanup_timeout_s = 5.0
166
+ self._entrypoint_file_path = f".aip_ptc_entrypoint_{id(self):x}.py"
167
+
168
+ async def _refresh_runtime_state(self) -> None:
169
+ """Refresh shared runtimes and reset upload caches when they change."""
170
+ refresh_runtime = getattr(self._runtime, "refresh_runtime", None)
171
+ if not callable(refresh_runtime):
172
+ return
173
+ runtime_changed = refresh_runtime()
174
+ if asyncio.iscoroutine(runtime_changed):
175
+ runtime_changed = await runtime_changed
176
+ if runtime_changed:
177
+ logger.info("PTC runtime changed, resetting static bundle state")
178
+ self._reset_cached_upload_state()
179
+
180
+ def _clear_upload_caches(self) -> None:
181
+ """Clear static and per-run upload caches."""
182
+ self._static_bundle_uploaded = False
183
+ self._per_run_hash_cache.clear()
184
+
185
+ def _reset_cached_upload_state(self) -> None:
186
+ """Reset static/per-run upload cache state."""
187
+ self._clear_upload_caches()
188
+
189
+ def _reset_bundle_state_if_inactive(self) -> None:
190
+ """Reset cached bundle state when the runtime is inactive."""
191
+ if self._runtime.is_active:
192
+ return
193
+ if not self._static_bundle_uploaded and not self._per_run_hash_cache:
194
+ return
195
+
196
+ logger.info("Runtime is inactive, resetting static bundle state")
197
+ self._reset_cached_upload_state()
198
+
199
+ def _build_per_run_hashes(self, payload: SandboxPayload) -> dict[str, str]:
200
+ """Build deterministic hash map for per-run files."""
201
+ return {
202
+ file_path: hashlib.sha256(content.encode("utf-8")).hexdigest()
203
+ for file_path, content in payload.per_run_files.items()
204
+ }
205
+
206
+ async def _cleanup_runtime_for_removed_paths(self, removed_paths: set[str]) -> None:
207
+ """Cleanup runtime when per-run path shrink is detected.
208
+
209
+ Raises:
210
+ RuntimeError: If cleanup cannot be confirmed as successful.
211
+ """
212
+ logger.info(f"Per-run path shrink detected ({sorted(removed_paths)}), running conservative cleanup")
213
+ if not self._owns_runtime:
214
+ cleanup_removed_paths = getattr(self._runtime, "cleanup_removed_paths", None)
215
+ if not callable(cleanup_removed_paths):
216
+ raise RuntimeError("Caller-owned runtime cannot remove stale PTC files")
217
+ cleanup_result = cleanup_removed_paths(removed_paths)
218
+ if asyncio.iscoroutine(cleanup_result):
219
+ await cleanup_result
220
+ self._reset_cached_upload_state()
221
+ return
222
+ try:
223
+ cleanup_result = await asyncio.wait_for(
224
+ self._runtime.cleanup(),
225
+ timeout=self._cleanup_timeout_s,
226
+ )
227
+ except TimeoutError as exc:
228
+ raise RuntimeError("Runtime cleanup timed out") from exc
229
+ if isinstance(cleanup_result, bool) and not cleanup_result:
230
+ raise RuntimeError("Runtime cleanup reported failure")
231
+ self._reset_cached_upload_state()
232
+
233
+ def _should_include_packages_path(self) -> bool:
234
+ """Check if the packages path should be added to sys.path."""
235
+ custom_tools = self._config.custom_tools
236
+ if not custom_tools.enabled or not custom_tools.tools:
237
+ return False
238
+
239
+ return any(tool.get("package_path") or tool.get("kind") == "file" for tool in custom_tools.tools)
240
+
241
+ async def _build_payload(self, tool_configs: dict[str, dict] | None) -> SandboxPayload:
242
+ """Build the sandbox payload for execution."""
243
+ logger.info("Building sandbox payload")
244
+ return await build_sandbox_payload(
245
+ self._mcp_client,
246
+ self._config.default_tool_timeout,
247
+ custom_tools_config=self._config.custom_tools,
248
+ tool_configs=tool_configs,
249
+ )
250
+
251
+ def _wrap_code(self, code: str) -> str:
252
+ """Wrap code with required imports and setup."""
253
+ logger.info("Wrapping PTC code")
254
+ return wrap_ptc_code(code, include_packages_path=self._should_include_packages_path())
255
+
256
+ def _inject_structured_output_env(self, wrapped_code: str, path: str) -> str:
257
+ """Set the structured-output path inside the sandbox process.
258
+
259
+ Some E2B/gllm transport combinations have dropped per-call env values
260
+ under pytest-asyncio session-loop reuse. The host still passes env to
261
+ runtimes that honor it, but also sets this internal variable in the
262
+ executed Python process so the custom-tool helper sees the same path.
263
+ """
264
+ return (
265
+ "# PTC structured output bridge - host generated\n"
266
+ "import os as _ptc_structured_output_os\n"
267
+ f"_ptc_structured_output_os.environ[{json.dumps(STRUCTURED_OUTPUT_ENV)}] = {json.dumps(path)}\n"
268
+ + wrapped_code
269
+ )
270
+
271
+ def _execution_timeout(self) -> float:
272
+ """Return the per-run execution timeout for PTC code."""
273
+ return self._config.effective_execution_timeout()
274
+
275
+ def _entrypoint_path(self, wrapped_code: str) -> str:
276
+ """Build the executor-specific entrypoint path for command execution."""
277
+ _ = wrapped_code
278
+ return self._entrypoint_file_path
279
+
280
+ def _runtime_prefers_python_file_execution(self) -> bool:
281
+ return getattr(self._runtime, "prefer_python_file_execution", False) is True and callable(
282
+ getattr(self._runtime, "execute_python_file", None)
283
+ )
284
+
285
+ async def _execute_runtime(
286
+ self,
287
+ *,
288
+ wrapped_code: str,
289
+ entrypoint_path: str,
290
+ files: dict[str, str] | None,
291
+ env: dict[str, str],
292
+ ) -> SandboxExecutionResult:
293
+ execution_timeout = self._execution_timeout()
294
+ if self._runtime_prefers_python_file_execution():
295
+ return await self._runtime.execute_python_file(
296
+ code=wrapped_code,
297
+ entrypoint_path=entrypoint_path,
298
+ timeout=execution_timeout,
299
+ files=files,
300
+ env=env,
301
+ template=self._config.sandbox_template,
302
+ )
303
+ return await self._runtime.execute(
304
+ code=wrapped_code,
305
+ timeout=execution_timeout,
306
+ files=files,
307
+ env=env,
308
+ template=self._config.sandbox_template,
309
+ )
310
+
311
+ async def _execute_payload(
312
+ self,
313
+ payload: SandboxPayload,
314
+ wrapped_code: str,
315
+ *,
316
+ entrypoint_path: str | None = None,
317
+ upload_static_bundle: bool,
318
+ ) -> SandboxExecutionResult:
319
+ """Execute the wrapped code with the provided payload."""
320
+ entrypoint_path = entrypoint_path or self._entrypoint_path(wrapped_code)
321
+ if upload_static_bundle:
322
+ logger.info("Uploading static bundle and per-run files")
323
+ files_to_upload = {**payload.files, **payload.per_run_files}
324
+ else:
325
+ logger.debug("Static bundle already uploaded, uploading only per-run files")
326
+ files_to_upload = dict(payload.per_run_files)
327
+ if self._runtime_prefers_python_file_execution():
328
+ files_to_upload[entrypoint_path] = wrapped_code
329
+
330
+ logger.info(
331
+ "Executing code in sandbox "
332
+ f"(sandbox_timeout: {self._config.sandbox_timeout}s, execution_timeout: {self._execution_timeout()}s)"
333
+ )
334
+ return await self._execute_runtime(
335
+ wrapped_code=wrapped_code,
336
+ entrypoint_path=entrypoint_path,
337
+ files=files_to_upload if files_to_upload else None,
338
+ env=payload.env,
339
+ )
340
+
341
+ def _new_structured_output_path(self) -> str:
342
+ """Create an executor-controlled side-channel path for this run."""
343
+ return f"{STRUCTURED_OUTPUT_DIR}/{uuid4().hex}.jsonl"
344
+
345
+ def _is_valid_structured_output_path(self, path: str) -> bool:
346
+ """Validate that a side-channel path is under the reserved directory."""
347
+ parsed = PurePosixPath(path)
348
+ if parsed.parent != PurePosixPath(STRUCTURED_OUTPUT_DIR) or not parsed.name.endswith(".jsonl"):
349
+ return False
350
+ stem = parsed.name[: -len(".jsonl")]
351
+ return len(stem) == 32 and all(char in "0123456789abcdef" for char in stem)
352
+
353
+ async def _read_structured_output_text(self, path: str) -> str | None:
354
+ """Read bounded structured output text from a reserved side-channel file."""
355
+ quoted_path = shlex.quote(path)
356
+ async with asyncio.timeout(self._cleanup_timeout_s):
357
+ command_result = self._runtime.execute_command(
358
+ f"test -f {quoted_path} && head -c {MAX_STRUCTURED_OUTPUT_TOTAL_BYTES} {quoted_path} || true",
359
+ )
360
+ if not inspect.isawaitable(command_result):
361
+ logger.warning("Skipping PTC structured output read because runtime command was not awaitable")
362
+ return None
363
+ result = await command_result
364
+ if result.exit_code != 0 or not result.stdout:
365
+ return None
366
+ return result.stdout
367
+
368
+ @staticmethod
369
+ def _structured_output_lines(stdout: str) -> list[str]:
370
+ """Split JSONL output while dropping an invalid truncated final record."""
371
+ lines = stdout.splitlines()
372
+ if not lines or len(stdout.encode("utf-8")) < MAX_STRUCTURED_OUTPUT_TOTAL_BYTES:
373
+ return lines
374
+ if stdout.endswith(("\n", "\r")):
375
+ return lines # pragma: no cover
376
+
377
+ trailing_line = lines[-1]
378
+ if len(trailing_line.encode("utf-8")) > MAX_STRUCTURED_OUTPUT_BYTES:
379
+ return lines # pragma: no cover
380
+ try:
381
+ json.loads(trailing_line)
382
+ except json.JSONDecodeError:
383
+ logger.warning("Skipping truncated PTC structured output line")
384
+ return lines[:-1]
385
+ return lines # pragma: no cover
386
+
387
+ @staticmethod
388
+ def _parse_structured_output_line(line: str) -> tuple[dict[str, Any] | None, str | None]:
389
+ """Parse one bounded structured output JSONL record and surface skipped records."""
390
+ line_size = len(line.encode("utf-8"))
391
+ if line_size > MAX_STRUCTURED_OUTPUT_BYTES:
392
+ warning = (
393
+ "Skipped oversized PTC structured output line "
394
+ f"({line_size} bytes exceeds {MAX_STRUCTURED_OUTPUT_BYTES} byte limit)"
395
+ )
396
+ logger.warning(warning)
397
+ return None, warning
398
+ try:
399
+ item = json.loads(line)
400
+ except json.JSONDecodeError:
401
+ logger.warning("Skipping malformed PTC structured output line")
402
+ return None, None
403
+ return (item, None) if isinstance(item, dict) else (None, None)
404
+
405
+ async def _read_structured_output(self, path: str) -> PTCStructuredOutput:
406
+ """Read and aggregate supported structured updates from the reserved JSONL file."""
407
+ from aip_agents.sandbox.types import PTCStructuredOutput
408
+
409
+ output = PTCStructuredOutput()
410
+ if not self._is_valid_structured_output_path(path):
411
+ logger.warning("Skipping invalid PTC structured output path")
412
+ return output
413
+
414
+ stdout = await self._read_structured_output_text(path)
415
+ if not stdout:
416
+ return output
417
+
418
+ for line in self._structured_output_lines(stdout):
419
+ item, warning = self._parse_structured_output_line(line)
420
+ if warning is not None:
421
+ output.add_warning(warning)
422
+ if item is not None:
423
+ output.merge(item)
424
+ return output
425
+
426
+ async def _cleanup_structured_output(self, path: str) -> None:
427
+ """Best-effort cleanup of the reserved side-channel file."""
428
+ if not self._is_valid_structured_output_path(path):
429
+ return
430
+ try:
431
+ async with asyncio.timeout(self._cleanup_timeout_s):
432
+ command_result = self._runtime.execute_command(f"rm -f {shlex.quote(path)}")
433
+ if not inspect.isawaitable(command_result):
434
+ return
435
+ result = await command_result
436
+ exit_code = getattr(result, "exit_code", 0)
437
+ if not isinstance(exit_code, int) or exit_code == 0:
438
+ return
439
+
440
+ # In E2B, execute_command can run as a different sandbox user than
441
+ # execute_code. If it can read but not unlink a user-owned file, run
442
+ # the cleanup in the same Python execution plane that created it.
443
+ cleanup_code = (
444
+ "import os\n"
445
+ f"path = {json.dumps(path)}\n"
446
+ "try:\n"
447
+ " os.remove(path)\n"
448
+ "except FileNotFoundError:\n"
449
+ " pass\n"
450
+ )
451
+ execute_result = self._runtime.execute(code=cleanup_code, timeout=self._cleanup_timeout_s)
452
+ if inspect.isawaitable(execute_result):
453
+ await execute_result
454
+ except Exception as exc:
455
+ logger.warning(f"Failed to cleanup PTC structured output side-channel: {exc}")
456
+
457
+ async def _attach_structured_output(
458
+ self,
459
+ result: SandboxExecutionResult,
460
+ path: str,
461
+ ) -> SandboxExecutionResult:
462
+ """Attach parsed structured output and cleanup its side-channel file."""
463
+ try:
464
+ result.structured_output = await self._read_structured_output(path)
465
+ except Exception as exc:
466
+ logger.warning(f"Failed to read PTC structured output side-channel: {exc}")
467
+ finally:
468
+ await self._cleanup_structured_output(path)
469
+ return result
470
+
471
+ def _changed_per_run_files(
472
+ self,
473
+ payload: SandboxPayload,
474
+ current_hashes: dict[str, str],
475
+ entrypoint_path: str,
476
+ wrapped_code: str,
477
+ ) -> dict[str, str]:
478
+ """Return changed per-run files plus entrypoint when file execution is active."""
479
+ changed_per_run_files = {
480
+ file_path: content
481
+ for file_path, content in payload.per_run_files.items()
482
+ if self._per_run_hash_cache.get(file_path) != current_hashes[file_path]
483
+ }
484
+ if self._runtime_prefers_python_file_execution():
485
+ changed_per_run_files[entrypoint_path] = wrapped_code
486
+ return changed_per_run_files
487
+
488
+ async def _execute_incremental(
489
+ self,
490
+ payload: SandboxPayload,
491
+ wrapped_code: str,
492
+ current_hashes: dict[str, str],
493
+ entrypoint_path: str,
494
+ ) -> SandboxExecutionResult:
495
+ """Execute with only the changed per-run files (static bundle already uploaded)."""
496
+ changed_per_run_files = self._changed_per_run_files(payload, current_hashes, entrypoint_path, wrapped_code)
497
+ return await self._execute_runtime(
498
+ wrapped_code=wrapped_code,
499
+ entrypoint_path=entrypoint_path,
500
+ files=changed_per_run_files or None,
501
+ env=payload.env,
502
+ )
503
+
504
+ async def _finalize_cached_run(
505
+ self,
506
+ result: SandboxExecutionResult,
507
+ current_hashes: dict[str, str],
508
+ should_read_structured_output: bool,
509
+ structured_output_path: str,
510
+ ) -> SandboxExecutionResult:
511
+ """Update per-run hash cache and attach structured output if enabled."""
512
+ self._per_run_hash_cache = current_hashes
513
+ if should_read_structured_output:
514
+ return await self._attach_structured_output(result, structured_output_path)
515
+ return result
516
+
517
+ async def _execute_with_bundle(
518
+ self,
519
+ payload: SandboxPayload,
520
+ wrapped_code: str,
521
+ entrypoint_path: str | None = None,
522
+ ) -> SandboxExecutionResult:
523
+ """Execute code using the cached static bundle when possible."""
524
+ structured_output_path = self._new_structured_output_path()
525
+ should_read_structured_output = self._config.custom_tools.enabled and bool(self._config.custom_tools.tools)
526
+ if should_read_structured_output:
527
+ payload.env = {**payload.env, STRUCTURED_OUTPUT_ENV: structured_output_path}
528
+ wrapped_code = self._inject_structured_output_env(wrapped_code, structured_output_path)
529
+ entrypoint_path = entrypoint_path or self._entrypoint_path(wrapped_code)
530
+
531
+ async with self._per_run_lock:
532
+ try:
533
+ current_hashes = self._build_per_run_hashes(payload)
534
+ removed_paths = set(self._per_run_hash_cache) - set(current_hashes)
535
+ if removed_paths:
536
+ try:
537
+ await self._cleanup_runtime_for_removed_paths(removed_paths)
538
+ except Exception:
539
+ self._reset_cached_upload_state()
540
+ raise
541
+
542
+ if self._static_bundle_uploaded:
543
+ result = await self._execute_incremental(payload, wrapped_code, current_hashes, entrypoint_path)
544
+ return await self._finalize_cached_run(
545
+ result, current_hashes, should_read_structured_output, structured_output_path
546
+ )
547
+
548
+ async with self._bundle_lock:
549
+ if self._static_bundle_uploaded:
550
+ result = await self._execute_incremental(payload, wrapped_code, current_hashes, entrypoint_path)
551
+ return await self._finalize_cached_run(
552
+ result, current_hashes, should_read_structured_output, structured_output_path
553
+ )
554
+
555
+ result = await self._execute_payload(
556
+ payload,
557
+ wrapped_code,
558
+ entrypoint_path=entrypoint_path,
559
+ upload_static_bundle=True,
560
+ )
561
+ self._update_static_bundle_cache()
562
+ return await self._finalize_cached_run(
563
+ result, current_hashes, should_read_structured_output, structured_output_path
564
+ )
565
+ except Exception:
566
+ if should_read_structured_output:
567
+ await self._cleanup_structured_output(structured_output_path)
568
+ raise
569
+
570
+ def _update_static_bundle_cache(self) -> None:
571
+ """Update cached bundle state after a successful upload."""
572
+ self._static_bundle_uploaded = True
573
+ logger.debug("Static bundle successfully uploaded and cached")
574
+
575
+ def _log_execution_result(self, result: SandboxExecutionResult) -> None:
576
+ """Log execution results based on exit status."""
577
+ if result.exit_code == 0:
578
+ logger.info("Sandbox execution completed successfully")
579
+ else:
580
+ logger.warning(f"Sandbox execution failed with exit code {result.exit_code}")
581
+
582
+ async def execute_code(
583
+ self,
584
+ code: str,
585
+ tool_configs: dict[str, dict] | None = None,
586
+ ) -> SandboxExecutionResult:
587
+ """Execute code inside the sandbox with MCP access.
588
+
589
+ This method:
590
+ 1. Builds the sandbox payload (MCP config + generated tool modules + custom tools)
591
+ 2. Wraps the user code with necessary imports and setup
592
+ 3. Executes the code in the E2B sandbox
593
+ 4. Returns the execution result (stdout/stderr/exit_code)
594
+
595
+ Args:
596
+ code: Python code to execute in the sandbox.
597
+ tool_configs: Optional per-tool config values for custom LangChain tools.
598
+ These are merged with agent defaults and written to custom_defaults.json.
599
+
600
+ Returns:
601
+ SandboxExecutionResult with stdout, stderr, and exit_code.
602
+
603
+ Raises:
604
+ PTCToolError: If sandbox execution fails.
605
+ """
606
+ try:
607
+ await self._refresh_runtime_state()
608
+ self._reset_bundle_state_if_inactive()
609
+ payload = await self._build_payload(tool_configs)
610
+ wrapped_code = self._wrap_code(code)
611
+ entrypoint_path = self._entrypoint_path(wrapped_code)
612
+ result = await self._execute_with_bundle(payload, wrapped_code, entrypoint_path)
613
+ self._log_execution_result(result)
614
+ return result
615
+
616
+ except Exception as exc:
617
+ logger.error(f"Sandbox execution failed: {exc}")
618
+ raise PTCToolError(f"Sandbox execution failed: {exc}") from exc
619
+
620
+ def reset_static_bundle(self) -> None:
621
+ """Reset the static bundle upload state.
622
+
623
+ Call this method when the sandbox is destroyed/recreated to force
624
+ a full re-upload of the static bundle on the next execution.
625
+ """
626
+ self._clear_upload_caches()
627
+ logger.debug("Static bundle state reset, next execution will re-upload full payload")
@@ -0,0 +1,45 @@
1
+ """Programmatic Tool Calling (PTC) module for MCP tools.
2
+
3
+ This module provides programmatic tool calling capabilities for MCP tools,
4
+ allowing code-based tool invocation instead of JSON tool calls.
5
+
6
+ Authors:
7
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
8
+ """
9
+
10
+ from aip_agents.ptc.exceptions import PTCError, PTCToolError
11
+ from aip_agents.ptc.mcp.sandbox_bridge import (
12
+ ServerConfig,
13
+ build_mcp_payload,
14
+ )
15
+ from aip_agents.ptc.naming import (
16
+ json_type_to_python,
17
+ sanitize_function_name,
18
+ sanitize_module_name,
19
+ sanitize_param_name,
20
+ schema_to_params,
21
+ )
22
+ from aip_agents.ptc.payload import SandboxPayload
23
+ from aip_agents.ptc.prompt_builder import (
24
+ build_ptc_prompt,
25
+ compute_ptc_prompt_hash,
26
+ )
27
+
28
+ __all__ = [
29
+ # Exceptions
30
+ "PTCError",
31
+ "PTCToolError",
32
+ # Naming utilities
33
+ "json_type_to_python",
34
+ "sanitize_function_name",
35
+ "sanitize_module_name",
36
+ "sanitize_param_name",
37
+ "schema_to_params",
38
+ # Prompt builder
39
+ "build_ptc_prompt",
40
+ "compute_ptc_prompt_hash",
41
+ # Sandbox Bridge
42
+ "SandboxPayload",
43
+ "ServerConfig",
44
+ "build_mcp_payload",
45
+ ]