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,499 @@
1
+ """Custom LangChain tools payload generation for PTC sandbox.
2
+
3
+ This module generates the sandbox payload (wrapper modules, registry, sources)
4
+ that allows LLM-generated code to call custom LangChain tools inside the sandbox.
5
+
6
+ The payload includes:
7
+ - tools/custom/__init__.py: Package init re-exporting all tool wrappers
8
+ - tools/custom/<tool_name>.py: Per-tool wrapper module with sync call function
9
+ - tools/custom_sources/__init__.py: Package init for file tool sources
10
+ - tools/custom_sources/<tool_name>/__init__.py: File tool source (copied from user)
11
+ - tools/custom_registry.py: Factory functions to build tool instances
12
+ - tools/custom_invoke.py: Safe invoke helper for calling tool.ainvoke
13
+ - tools/custom_defaults.json: Per-run tool config (uploaded each run)
14
+
15
+ Authors:
16
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ from dataclasses import dataclass, field
23
+ from pathlib import Path
24
+
25
+ from aip_agents.ptc.custom_tools import (
26
+ PTCCustomToolConfig,
27
+ PTCToolDef,
28
+ )
29
+ from aip_agents.ptc.naming import (
30
+ CustomToolRuntimeParamSpec,
31
+ project_custom_tool_signature,
32
+ sanitize_function_name,
33
+ )
34
+ from aip_agents.ptc.payload import SandboxPayload
35
+ from aip_agents.ptc.template_utils import render_template
36
+ from aip_agents.utils.logger import get_logger
37
+
38
+ _TEMPLATE_PACKAGE = "aip_agents.ptc.custom_tools_templates"
39
+
40
+ logger = get_logger(__name__)
41
+
42
+ _SAFE_ANNOTATIONS = frozenset({"str", "int", "float", "bool", "list", "dict", "Any", "None"})
43
+
44
+
45
+ @dataclass
46
+ class CustomToolPayloadResult:
47
+ """Result of custom tool payload generation.
48
+
49
+ Attributes:
50
+ payload: The sandbox payload with files to upload.
51
+ tool_names: List of sanitized tool names available in sandbox.
52
+ """
53
+
54
+ payload: SandboxPayload
55
+ tool_names: list[str] = field(default_factory=list)
56
+
57
+
58
+ def _validate_tool_configs(
59
+ tool_configs: dict[str, dict[str, object]] | None,
60
+ configured_tools: list[PTCToolDef],
61
+ ) -> dict[str, dict[str, object]]:
62
+ """Validate tool_configs and return sanitized values.
63
+
64
+ Args:
65
+ tool_configs: Optional per-tool config values to validate.
66
+ configured_tools: List of configured tool definitions.
67
+
68
+ Returns:
69
+ Validated tool_configs with only known tools and JSON-serializable dict values.
70
+ """
71
+ if not tool_configs:
72
+ return {}
73
+
74
+ if not isinstance(tool_configs, dict):
75
+ logger.warning("tool_configs must be a dict mapping tool names to dict values")
76
+ return {}
77
+
78
+ configured_names = {t.get("name", "") for t in configured_tools}
79
+ validated: dict[str, dict[str, object]] = {}
80
+
81
+ for tool_name, tool_config in tool_configs.items():
82
+ if not isinstance(tool_name, str):
83
+ logger.warning(f"tool_configs contains non-string tool name: {tool_name!r}")
84
+ continue
85
+ if tool_name not in configured_names:
86
+ logger.warning(f"tool_configs contains config for unknown tool: {tool_name}")
87
+ continue
88
+ if not isinstance(tool_config, dict):
89
+ logger.warning(f"tool_configs for tool '{tool_name}' must be a dict")
90
+ continue
91
+ if any(not isinstance(key, str) for key in tool_config.keys()):
92
+ logger.warning(f"tool_configs for tool '{tool_name}' must use string keys")
93
+ continue
94
+ try:
95
+ json.dumps(tool_config)
96
+ except (TypeError, ValueError) as exc:
97
+ logger.warning(f"tool_configs for tool '{tool_name}' must be JSON-serializable: {exc}")
98
+ continue
99
+ validated[tool_name] = tool_config
100
+
101
+ return validated
102
+
103
+
104
+ def build_custom_tools_payload(
105
+ config: PTCCustomToolConfig,
106
+ tool_configs: dict[str, dict[str, object]] | None = None,
107
+ ) -> CustomToolPayloadResult:
108
+ """Build sandbox payload for custom LangChain tools.
109
+
110
+ Args:
111
+ config: Custom tool configuration with tool definitions.
112
+ tool_configs: Optional per-tool config values to write to defaults.json.
113
+
114
+ Returns:
115
+ CustomToolPayloadResult with payload and available tool names.
116
+ """
117
+ result = CustomToolPayloadResult(payload=SandboxPayload())
118
+
119
+ if not config.enabled or not config.tools:
120
+ return result
121
+
122
+ # Collect sanitized tool names
123
+ sanitized_tools: list[tuple[str, PTCToolDef]] = []
124
+ for tool in config.tools:
125
+ name = tool.get("name", "")
126
+ sanitized_name = sanitize_function_name(name)
127
+ sanitized_tools.append((sanitized_name, tool))
128
+
129
+ # Sort for deterministic output
130
+ sanitized_tools.sort(key=lambda x: x[0])
131
+ result.tool_names = [name for name, _ in sanitized_tools]
132
+
133
+ validated_tool_configs = _validate_tool_configs(tool_configs, config.tools)
134
+
135
+ # Generate tools/custom/__init__.py
136
+ result.payload.files["tools/custom/__init__.py"] = _generate_custom_init(sanitized_tools)
137
+
138
+ # Generate tools/custom/<tool_name>.py for each tool
139
+ for sanitized_name, tool in sanitized_tools:
140
+ wrapper_content = _generate_tool_wrapper(sanitized_name, tool)
141
+ result.payload.files[f"tools/custom/{sanitized_name}.py"] = wrapper_content
142
+
143
+ # Generate tools/custom_sources/__init__.py
144
+ file_tools = [(name, tool) for name, tool in sanitized_tools if tool.get("kind") == "file"]
145
+ result.payload.files["tools/custom_sources/__init__.py"] = _generate_custom_sources_init(file_tools)
146
+
147
+ # Generate tools/custom_sources/<tool_name>/__init__.py for file tools
148
+ for sanitized_name, tool in file_tools:
149
+ source_content = _read_file_tool_source(tool)
150
+ result.payload.files[f"tools/custom_sources/{sanitized_name}/__init__.py"] = source_content
151
+
152
+ # Generate tools/custom_registry.py
153
+ result.payload.files["tools/custom_registry.py"] = _generate_registry(sanitized_tools)
154
+
155
+ # Generate tools/custom_invoke.py
156
+ result.payload.files["tools/custom_invoke.py"] = _generate_invoke_helper()
157
+
158
+ # Generate tools/custom_defaults.json (per-run file, always re-uploaded).
159
+ # Key the JSON by the sanitized tool name (matching the name baked into
160
+ # the generated wrapper and ``build_runtime_config`` lookup) so tools
161
+ # configured with hyphens or other non-Python-identifier characters
162
+ # still resolve to their defaults at runtime.
163
+ defaults = {sanitize_function_name(name): value for name, value in validated_tool_configs.items()}
164
+ result.payload.per_run_files["tools/custom_defaults.json"] = json.dumps(defaults, indent=2)
165
+
166
+ # Bundle package sources from package_path
167
+ for sanitized_name, tool in sanitized_tools:
168
+ if tool.get("kind") == "package":
169
+ package_path = tool.get("package_path")
170
+ if package_path:
171
+ _bundle_package_source(result.payload, package_path, config.bundle_roots)
172
+
173
+ return result
174
+
175
+
176
+ def _generate_custom_init(sanitized_tools: list[tuple[str, PTCToolDef]]) -> str:
177
+ """Generate tools/custom/__init__.py content.
178
+
179
+ Args:
180
+ sanitized_tools: List of (sanitized_name, tool_def) tuples.
181
+
182
+ Returns:
183
+ Python source code for the __init__.py module.
184
+ """
185
+ # Sort for deterministic output
186
+ sorted_names = sorted(name for name, _ in sanitized_tools)
187
+
188
+ all_list = ", ".join(f'"{name}"' for name in sorted_names)
189
+
190
+ return render_template(
191
+ _TEMPLATE_PACKAGE,
192
+ "custom_init.py.template",
193
+ {
194
+ "all_list": all_list,
195
+ },
196
+ )
197
+
198
+
199
+ def _generate_tool_wrapper(sanitized_name: str, tool: PTCToolDef) -> str:
200
+ """Generate tools/custom/<tool_name>.py wrapper module.
201
+
202
+ Args:
203
+ sanitized_name: Sanitized tool name for function/module.
204
+ tool: Tool definition.
205
+
206
+ Returns:
207
+ Python source code for the wrapper module.
208
+ """
209
+ original_name = tool.get("name", sanitized_name)
210
+ schema = tool.get("input_schema", {"type": "object", "properties": {}})
211
+ projection = project_custom_tool_signature(schema)
212
+ required_params = tuple(field.param_name for field in projection.field_specs if field.required)
213
+ optional_params = tuple(field.param_name for field in projection.field_specs if not field.required)
214
+
215
+ return render_template(
216
+ _TEMPLATE_PACKAGE,
217
+ "custom_wrapper.py.template",
218
+ {
219
+ "original_name": original_name,
220
+ "sanitized_name": sanitized_name,
221
+ "canonical_params": projection.canonical_params,
222
+ "schema_to_param": repr(projection.schema_to_param),
223
+ "param_to_schema": repr(projection.param_to_schema),
224
+ "required_params": repr(required_params),
225
+ "optional_params": repr(optional_params),
226
+ "runtime_signature_params": _render_runtime_signature_params(projection.runtime_params),
227
+ },
228
+ )
229
+
230
+
231
+ def _is_safe_annotation(expr: str) -> bool:
232
+ """Check if annotation expression is safe.
233
+
234
+ Safe annotations are:
235
+ - Single safe type: str, int, float, bool, list, dict, Any, None
236
+ - Union of safe types using | (e.g., str | None, str | int, int | str | None)
237
+
238
+ This validates the ENTIRE expression, not just a prefix, to prevent
239
+ bypasses like "str | __import__('os').system('x')".
240
+ """
241
+ if expr in _SAFE_ANNOTATIONS:
242
+ return True
243
+
244
+ parts = [part.strip() for part in expr.split("|")]
245
+ if len(parts) <= 1 or any(not part for part in parts):
246
+ return False
247
+
248
+ return all(part in _SAFE_ANNOTATIONS for part in parts)
249
+
250
+
251
+ def _sanitize_annotation(param: CustomToolRuntimeParamSpec) -> str:
252
+ """Return safe annotation expression for a runtime parameter."""
253
+ annotation = param.annotation_expr
254
+ if _is_safe_annotation(annotation):
255
+ return annotation
256
+
257
+ logger.warning(f"Potentially unsafe annotation '{annotation}' in parameter '{param.name}', using 'Any'")
258
+ return "Any"
259
+
260
+
261
+ def _is_safe_numeric_literal(default_expr: str) -> bool:
262
+ """Return True if expression is a safe signed integer or float literal."""
263
+ stripped = default_expr.replace("-", "", 1).replace("+", "", 1)
264
+ return stripped.count(".") <= 1 and stripped.replace(".", "").isdigit()
265
+
266
+
267
+ def _sanitize_default_fragment(param: CustomToolRuntimeParamSpec) -> str:
268
+ """Return rendered default fragment for inspect.Parameter declaration."""
269
+ if not param.has_default:
270
+ return ""
271
+
272
+ default_expr = param.default_expr if param.default_expr is not None else "None"
273
+ if default_expr not in ("None", "True", "False") and not _is_safe_numeric_literal(default_expr):
274
+ logger.warning(f"Potentially unsafe default '{default_expr}' in parameter '{param.name}', using 'None'")
275
+ default_expr = "None"
276
+
277
+ return f", default={default_expr}"
278
+
279
+
280
+ def _render_runtime_signature_params(runtime_params: list[CustomToolRuntimeParamSpec]) -> str:
281
+ """Render inspect.Parameter entries for generated wrapper signatures.
282
+
283
+ Sanitizes annotation and default expressions to prevent code injection.
284
+ Unsafe values are replaced with safe defaults and logged.
285
+ """
286
+ rendered_params: list[str] = []
287
+
288
+ for param in runtime_params:
289
+ annotation = _sanitize_annotation(param)
290
+ default_fragment = _sanitize_default_fragment(param)
291
+
292
+ rendered_params.append(
293
+ "inspect.Parameter("
294
+ f'"{param.name}", '
295
+ f"inspect.Parameter.{param.kind}, "
296
+ f"annotation={annotation}{default_fragment}"
297
+ ")"
298
+ )
299
+
300
+ return ",\n ".join(rendered_params)
301
+
302
+
303
+ def _generate_custom_sources_init(file_tools: list[tuple[str, PTCToolDef]]) -> str:
304
+ """Generate tools/custom_sources/__init__.py content.
305
+
306
+ Args:
307
+ file_tools: List of (sanitized_name, tool_def) tuples for file tools.
308
+
309
+ Returns:
310
+ Python source code for the __init__.py module.
311
+ """
312
+ sorted_names = sorted(name for name, _ in file_tools)
313
+ all_list = ", ".join(f'"{name}"' for name in sorted_names)
314
+
315
+ return render_template(
316
+ _TEMPLATE_PACKAGE,
317
+ "custom_sources_init.py.template",
318
+ {"all_list": all_list},
319
+ )
320
+
321
+
322
+ def _read_file_tool_source(tool: PTCToolDef) -> str:
323
+ """Read source code from a file tool.
324
+
325
+ Args:
326
+ tool: File tool definition with file_path.
327
+
328
+ Returns:
329
+ Source code content.
330
+
331
+ Raises:
332
+ FileNotFoundError: If file does not exist.
333
+ """
334
+ file_path = tool.get("file_path", "")
335
+ path = Path(file_path)
336
+ return path.read_text(encoding="utf-8")
337
+
338
+
339
+ def _generate_registry(sanitized_tools: list[tuple[str, PTCToolDef]]) -> str:
340
+ """Generate tools/custom_registry.py content.
341
+
342
+ Args:
343
+ sanitized_tools: List of (sanitized_name, tool_def) tuples.
344
+
345
+ Returns:
346
+ Python source code for the registry module.
347
+ """
348
+ build_functions: list[str] = []
349
+
350
+ for sanitized_name, tool in sorted(sanitized_tools, key=lambda x: x[0]):
351
+ kind = tool.get("kind")
352
+ class_name = tool.get("class_name", "")
353
+
354
+ if kind == "package":
355
+ import_path = tool.get("import_path", "")
356
+ import_statement = f"from {import_path} import {class_name}"
357
+ elif kind == "file":
358
+ import_statement = f"from tools.custom_sources.{sanitized_name} import {class_name}"
359
+ else:
360
+ import_statement = ""
361
+
362
+ # Generate build function
363
+ build_func = _generate_build_function(sanitized_name, class_name, import_statement)
364
+ build_functions.append(build_func)
365
+
366
+ build_functions_str = "\n\n".join(build_functions)
367
+
368
+ return render_template(
369
+ _TEMPLATE_PACKAGE,
370
+ "custom_registry.py.template",
371
+ {
372
+ "build_functions": build_functions_str,
373
+ },
374
+ )
375
+
376
+
377
+ def _generate_build_function(sanitized_name: str, class_name: str, import_statement: str = "") -> str:
378
+ """Generate a build function for a single tool.
379
+
380
+ Args:
381
+ sanitized_name: Sanitized tool name.
382
+ class_name: Tool class name.
383
+ import_statement: Import statement for the tool class, rendered inside the build function.
384
+
385
+ Returns:
386
+ Python source code for the build function.
387
+ """
388
+ return render_template(
389
+ _TEMPLATE_PACKAGE,
390
+ "custom_build_function.py.template",
391
+ {
392
+ "sanitized_name": sanitized_name,
393
+ "class_name": class_name,
394
+ "import_statement": import_statement,
395
+ },
396
+ )
397
+
398
+
399
+ def _generate_invoke_helper() -> str:
400
+ """Generate tools/custom_invoke.py content.
401
+
402
+ The structured-output constants are injected from the host-side executor so the
403
+ sandbox helper and the host reader/cleanup cannot drift apart.
404
+ """
405
+ from aip_agents.ptc.executor import STRUCTURED_OUTPUT_DIR, STRUCTURED_OUTPUT_ENV
406
+
407
+ return render_template(
408
+ _TEMPLATE_PACKAGE,
409
+ "custom_invoke.py.template",
410
+ {
411
+ "structured_output_env": STRUCTURED_OUTPUT_ENV,
412
+ "structured_output_dir": STRUCTURED_OUTPUT_DIR,
413
+ },
414
+ )
415
+
416
+
417
+ def _bundle_package_source(
418
+ payload: SandboxPayload,
419
+ package_path: str,
420
+ bundle_roots: list[str],
421
+ ) -> None:
422
+ """Bundle package source files into payload.
423
+
424
+ Copies Python files from package_path into the payload, preserving
425
+ the directory structure relative to the source root.
426
+
427
+ For standard layouts, files are relative to the bundle root.
428
+ For src/ layouts, files are relative to the src/ directory so that
429
+ imports work correctly (e.g., `from my_pkg import ...`).
430
+
431
+ Args:
432
+ payload: Sandbox payload to add files to.
433
+ package_path: Path to the package directory.
434
+ bundle_roots: List of allowed bundle roots.
435
+ """
436
+ pkg_path = Path(package_path).resolve()
437
+
438
+ # Find which bundle root contains this path
439
+ bundle_root = None
440
+ for root in bundle_roots:
441
+ root_path = Path(root).resolve()
442
+ try:
443
+ pkg_path.relative_to(root_path)
444
+ bundle_root = root_path
445
+ break
446
+ except ValueError:
447
+ continue
448
+
449
+ if bundle_root is None:
450
+ # Path validation should have caught this earlier
451
+ return
452
+
453
+ # Check for src/ subdirectory (common package layout)
454
+ # When src/ exists, use it as the base for relative paths so imports work
455
+ src_path = pkg_path / "src"
456
+ if src_path.is_dir():
457
+ # For src/ layout: copy from src/ and calculate paths relative to src/
458
+ # This ensures `from my_pkg import ...` works when packages/ is on sys.path
459
+ _copy_python_files(payload, src_path, src_path)
460
+ else:
461
+ # For flat layout: copy from pkg_path relative to bundle_root
462
+ _copy_python_files(payload, pkg_path, bundle_root)
463
+
464
+
465
+ def _copy_python_files(
466
+ payload: SandboxPayload,
467
+ source_dir: Path,
468
+ bundle_root: Path,
469
+ ) -> None:
470
+ """Copy Python files from source directory to payload.
471
+
472
+ Args:
473
+ payload: Sandbox payload to add files to.
474
+ source_dir: Source directory to copy from.
475
+ bundle_root: Bundle root for relative path calculation.
476
+ """
477
+ for py_file in source_dir.rglob("*.py"):
478
+ # Skip __pycache__ and similar
479
+ if "__pycache__" in py_file.parts:
480
+ continue
481
+ if ".egg-info" in str(py_file):
482
+ continue
483
+
484
+ # Calculate relative path from bundle root
485
+ try:
486
+ rel_path = py_file.relative_to(bundle_root)
487
+ except ValueError:
488
+ # File is not under bundle root
489
+ continue
490
+
491
+ # Read and add to payload
492
+ try:
493
+ content = py_file.read_text(encoding="utf-8")
494
+ # Use packages/ prefix for bundled sources
495
+ payload_path = f"packages/{rel_path.as_posix()}"
496
+ payload.files[payload_path] = content
497
+ except Exception as exc:
498
+ logger.warning(f"Skipping unreadable file '{py_file}': {exc}")
499
+ continue
@@ -0,0 +1 @@
1
+ """Templates for custom LangChain tool payload generation."""
@@ -0,0 +1,24 @@
1
+ def build_$sanitized_name() -> BaseTool:
2
+ """Build an instance of $class_name.
3
+
4
+ Returns:
5
+ Configured tool instance.
6
+ """
7
+ $import_statement
8
+ # Check if the class is already an instance (module-level singleton)
9
+ tool_or_class = $class_name
10
+ if isinstance(tool_or_class, BaseTool):
11
+ tool = tool_or_class
12
+ else:
13
+ try:
14
+ tool = tool_or_class()
15
+ except TypeError as e:
16
+ raise TypeError(
17
+ f"Failed to instantiate $class_name(). "
18
+ "Constructor args are not supported in MVP. "
19
+ "Use tool_config_schema for runtime configuration. "
20
+ f"Original error: {e}"
21
+ ) from e
22
+
23
+ _configure_tool(tool, "$sanitized_name")
24
+ return tool
@@ -0,0 +1,26 @@
1
+ """Generated custom tools package for PTC sandbox execution.
2
+
3
+ This package provides access to custom LangChain tools configured for this agent.
4
+ Import tools directly:
5
+
6
+ from tools.custom import tool_name
7
+
8
+ Or from specific modules:
9
+
10
+ from tools.custom.tool_name import tool_name
11
+ """
12
+
13
+ from importlib import import_module
14
+ from typing import Any
15
+
16
+ __all__ = [$all_list]
17
+
18
+
19
+ def __getattr__(name: str) -> Any:
20
+ """Lazy-load custom tool wrapper functions on first access."""
21
+ if name in __all__:
22
+ module = import_module(f"tools.custom.{name}")
23
+ tool = getattr(module, name)
24
+ globals()[name] = tool
25
+ return tool
26
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,149 @@
1
+ """Safe invoke helper for custom tools.
2
+
3
+ This module provides a sync wrapper around tool.ainvoke for use in PTC code.
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import sys
10
+ import threading
11
+ from typing import Any
12
+
13
+ from langchain_core.tools import BaseTool
14
+
15
+ try:
16
+ from langgraph.types import Command
17
+ except Exception: # pragma: no cover - sandbox dependency guard
18
+ Command = None
19
+
20
+ _STRUCTURED_OUTPUT_ENV = "$structured_output_env"
21
+ _STRUCTURED_OUTPUT_DIR = "$structured_output_dir"
22
+ _STRUCTURED_OUTPUT_LOCK = threading.Lock()
23
+ _SUPPORTED_UPDATE_FIELDS = ("metadata", "artifacts", "pii_mapping", "usage_metadata")
24
+ _INTENTIONALLY_LOCAL_FIELDS = ("result",)
25
+
26
+
27
+ def run_tool(tool: BaseTool, kwargs: dict[str, Any], config: dict[str, Any] | None = None) -> Any:
28
+ """Run a tool's ainvoke method synchronously.
29
+
30
+ Args:
31
+ tool: Tool instance to invoke.
32
+ kwargs: Arguments to pass to the tool.
33
+ config: Optional runtime config (e.g. {"metadata": ...}) forwarded
34
+ to tool.ainvoke.
35
+
36
+ Returns:
37
+ Tool execution result.
38
+ """
39
+ result = _run_async_safely(tool.ainvoke(kwargs, config=config))
40
+ _write_command_update(result)
41
+ return result
42
+
43
+
44
+ def _write_command_update(result: Any) -> None:
45
+ """Write supported Command.update fields to the host-provided JSONL side channel."""
46
+ if Command is None or not isinstance(result, Command):
47
+ return
48
+
49
+ update = getattr(result, "update", None)
50
+ if not isinstance(update, dict):
51
+ return
52
+
53
+ unsupported_fields = sorted(set(update) - set(_SUPPORTED_UPDATE_FIELDS) - set(_INTENTIONALLY_LOCAL_FIELDS))
54
+ if unsupported_fields:
55
+ fields = ", ".join(unsupported_fields)
56
+ print(
57
+ f"Warning: PTC custom tool Command.update fields are not supported and were not bridged: {fields}",
58
+ file=sys.stderr,
59
+ )
60
+
61
+ serializable_update: dict[str, Any] = {}
62
+ for field in _SUPPORTED_UPDATE_FIELDS:
63
+ if field not in update:
64
+ continue
65
+ try:
66
+ json.dumps(update[field])
67
+ except (TypeError, ValueError):
68
+ print(
69
+ f"Warning: PTC custom tool Command.update field is not JSON-serializable and was not bridged: {field}",
70
+ file=sys.stderr,
71
+ )
72
+ continue
73
+ serializable_update[field] = update[field]
74
+
75
+ if not serializable_update:
76
+ return
77
+
78
+ path = _get_structured_output_path()
79
+ if not path:
80
+ return
81
+
82
+ try:
83
+ # E2B's command API may read/cleanup from a different sandbox user than
84
+ # execute_code. Keep the side-channel non-world-writable while allowing
85
+ # that command plane to traverse/read the executor-generated file.
86
+ os.makedirs(_STRUCTURED_OUTPUT_DIR, mode=0o755, exist_ok=True)
87
+ os.chmod(_STRUCTURED_OUTPUT_DIR, 0o755)
88
+ line = json.dumps(serializable_update, separators=(",", ":")) + "\n"
89
+ with _STRUCTURED_OUTPUT_LOCK:
90
+ with open(path, "a", encoding="utf-8") as file:
91
+ file.write(line)
92
+ os.chmod(path, 0o644)
93
+ except OSError as exc:
94
+ print(f"Warning: failed to write PTC structured output side-channel: {exc}", file=sys.stderr)
95
+
96
+
97
+ def _get_structured_output_path() -> str | None:
98
+ """Return the current host-provided side-channel path when it is reserved for PTC."""
99
+ path = os.environ.get(_STRUCTURED_OUTPUT_ENV)
100
+ if not path:
101
+ return None
102
+ normalized_path = os.path.normpath(path)
103
+ if os.path.dirname(normalized_path) != _STRUCTURED_OUTPUT_DIR:
104
+ return None
105
+ name = os.path.basename(normalized_path)
106
+ if not name.endswith(".jsonl"):
107
+ return None
108
+ stem = name[:-len(".jsonl")]
109
+ if len(stem) != 32 or any(char not in "0123456789abcdef" for char in stem):
110
+ return None
111
+ return normalized_path
112
+
113
+
114
+ def _run_async_safely(coro: Any) -> Any:
115
+ """Run an async coroutine safely in sync context.
116
+
117
+ Handles the case where we may or may not be in an async context.
118
+
119
+ Args:
120
+ coro: Coroutine to run.
121
+
122
+ Returns:
123
+ Result of the coroutine.
124
+ """
125
+ try:
126
+ asyncio.get_running_loop()
127
+ except RuntimeError:
128
+ # No running loop - create new one
129
+ return asyncio.run(coro)
130
+
131
+ # Already in async context - run in a separate thread with a new loop
132
+ import concurrent.futures
133
+
134
+ def run_in_new_loop() -> Any:
135
+ """Run coroutine in a new event loop in a thread."""
136
+ new_loop = asyncio.new_event_loop()
137
+ asyncio.set_event_loop(new_loop)
138
+ try:
139
+ result = new_loop.run_until_complete(coro)
140
+ new_loop.run_until_complete(new_loop.shutdown_asyncgens())
141
+ if hasattr(new_loop, "shutdown_default_executor"):
142
+ new_loop.run_until_complete(new_loop.shutdown_default_executor())
143
+ return result
144
+ finally:
145
+ new_loop.close()
146
+
147
+ with concurrent.futures.ThreadPoolExecutor() as executor:
148
+ future = executor.submit(run_in_new_loop)
149
+ return future.result()