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.
- aip_agents/__init__.py +66 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/base_executor.py +1125 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/a2ui/__init__.py +26 -0
- aip_agents/a2ui/catalog_provider.py +109 -0
- aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
- aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
- aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
- aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
- aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
- aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
- aip_agents/a2ui/middleware.py +754 -0
- aip_agents/a2ui/models.py +153 -0
- aip_agents/a2ui/public_url.py +138 -0
- aip_agents/a2ui/stream_filter.py +67 -0
- aip_agents/agent/__init__.py +72 -0
- aip_agents/agent/base_agent.py +1437 -0
- aip_agents/agent/base_langgraph_agent.py +4048 -0
- aip_agents/agent/google_adk_agent.py +932 -0
- aip_agents/agent/google_adk_constants.py +11 -0
- aip_agents/agent/graph_runtime.py +185 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langgraph_react_agent.py +3741 -0
- aip_agents/agent/model_invocation_adapter.py +346 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/agent/tool_execution_engine.py +587 -0
- aip_agents/audio_interface/__init__.py +30 -0
- aip_agents/audio_interface/audio_agent_adapter.py +254 -0
- aip_agents/audio_interface/config.py +146 -0
- aip_agents/audio_interface/errors.py +15 -0
- aip_agents/audio_interface/livekit_audio_session.py +504 -0
- aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
- aip_agents/audio_interface/session_factory.py +40 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/common/__init__.py +5 -0
- aip_agents/common/callable_introspection.py +17 -0
- aip_agents/constants.py +23 -0
- aip_agents/context/__init__.py +5 -0
- aip_agents/context/budget.py +237 -0
- aip_agents/context/compaction.py +339 -0
- aip_agents/context/metadata.py +169 -0
- aip_agents/context/offload.py +449 -0
- aip_agents/context/preparation.py +196 -0
- aip_agents/context/prompt.py +61 -0
- aip_agents/context/runtime.py +149 -0
- aip_agents/context/session_recall.py +69 -0
- aip_agents/context/state.py +48 -0
- aip_agents/context/utils.py +44 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/audio_livekit_session.py +116 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_server.py +141 -0
- aip_agents/examples/gl_connectors_smoke.py +148 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_filesystem.py +162 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_skills.py +62 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
- aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_ptc.py +49 -0
- aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
- aip_agents/examples/hello_world_tool_output_client.py +55 -0
- aip_agents/examples/hello_world_tool_output_server.py +113 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/mcp_configs/configs.py +63 -0
- aip_agents/examples/mcp_servers/common.py +76 -0
- aip_agents/examples/mcp_servers/mcp_name.py +29 -0
- aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
- aip_agents/examples/mcp_servers/mcp_time.py +10 -0
- aip_agents/examples/pii_demo_langgraph_client.py +69 -0
- aip_agents/examples/pii_demo_langgraph_server.py +125 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/multiply_tool.py +43 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/guardrails/__init__.py +83 -0
- aip_agents/guardrails/engines/__init__.py +69 -0
- aip_agents/guardrails/engines/base.py +90 -0
- aip_agents/guardrails/engines/nemo.py +101 -0
- aip_agents/guardrails/engines/phrase_matcher.py +113 -0
- aip_agents/guardrails/exceptions.py +39 -0
- aip_agents/guardrails/manager.py +163 -0
- aip_agents/guardrails/middleware.py +199 -0
- aip_agents/guardrails/schemas.py +63 -0
- aip_agents/guardrails/utils.py +45 -0
- aip_agents/integration/__init__.py +43 -0
- aip_agents/integration/agent.py +58 -0
- aip_agents/integration/guardrails.py +52 -0
- aip_agents/integration/hitl.py +71 -0
- aip_agents/integration/ptc.py +95 -0
- aip_agents/integration/skills.py +37 -0
- aip_agents/integration/storage.py +81 -0
- aip_agents/integration/version.py +103 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/client/__init__.py +60 -0
- aip_agents/mcp/client/base_mcp_client.py +377 -0
- aip_agents/mcp/client/connection_manager.py +231 -0
- aip_agents/mcp/client/errors.py +119 -0
- aip_agents/mcp/client/google_adk/__init__.py +45 -0
- aip_agents/mcp/client/google_adk/client.py +383 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/persistent_session.py +704 -0
- aip_agents/mcp/client/session_pool.py +358 -0
- aip_agents/mcp/client/transports.py +288 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/base_adapter.py +815 -0
- aip_agents/memory/adapters/mem0.py +286 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/middleware/__init__.py +65 -0
- aip_agents/middleware/backends/__init__.py +72 -0
- aip_agents/middleware/backends/in_memory.py +578 -0
- aip_agents/middleware/backends/local_disk.py +1203 -0
- aip_agents/middleware/backends/protocol.py +353 -0
- aip_agents/middleware/backends/sandbox.py +1431 -0
- aip_agents/middleware/backends/sandbox_files.py +113 -0
- aip_agents/middleware/backends/utils.py +293 -0
- aip_agents/middleware/base.py +239 -0
- aip_agents/middleware/filesystem.py +690 -0
- aip_agents/middleware/manager.py +291 -0
- aip_agents/middleware/memory.py +532 -0
- aip_agents/middleware/pii_middleware.py +192 -0
- aip_agents/middleware/schema.py +19 -0
- aip_agents/middleware/skills.py +872 -0
- aip_agents/middleware/task_delegation.py +1656 -0
- aip_agents/middleware/todolist.py +276 -0
- aip_agents/middleware/tools/__init__.py +23 -0
- aip_agents/middleware/tools/edit_file.py +70 -0
- aip_agents/middleware/tools/execute.py +428 -0
- aip_agents/middleware/tools/grep_file.py +79 -0
- aip_agents/middleware/tools/ls.py +71 -0
- aip_agents/middleware/tools/read_file.py +239 -0
- aip_agents/middleware/tools/skill_load.py +193 -0
- aip_agents/middleware/tools/skill_resource.py +338 -0
- aip_agents/middleware/tools/write_file.py +114 -0
- aip_agents/middleware/vision.py +322 -0
- aip_agents/ptc/__init__.py +87 -0
- aip_agents/ptc/custom_tools.py +511 -0
- aip_agents/ptc/custom_tools_payload.py +499 -0
- aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
- aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
- aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
- aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
- aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
- aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
- aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/exceptions.py +57 -0
- aip_agents/ptc/executor.py +627 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
- aip_agents/ptc/naming.py +430 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/prompt_builder.py +674 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/sandbox_bridge.py +297 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/ptc/tool_def_helpers.py +101 -0
- aip_agents/ptc/tool_enrichment.py +163 -0
- aip_agents/resilience/__init__.py +10 -0
- aip_agents/resilience/base.py +46 -0
- aip_agents/resilience/tool.py +1661 -0
- aip_agents/sandbox/__init__.py +62 -0
- aip_agents/sandbox/defaults.py +220 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
- aip_agents/sandbox/e2b_runtime.py +27 -0
- aip_agents/sandbox/image_registry.py +98 -0
- aip_agents/sandbox/providers/__init__.py +76 -0
- aip_agents/sandbox/providers/base.py +392 -0
- aip_agents/sandbox/providers/bedrock.py +28 -0
- aip_agents/sandbox/providers/e2b.py +230 -0
- aip_agents/sandbox/providers/opensandbox.py +163 -0
- aip_agents/sandbox/sandbox_runtime.py +967 -0
- aip_agents/sandbox/template_builder.py +240 -0
- aip_agents/sandbox/types.py +141 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/schema/__init__.py +106 -0
- aip_agents/schema/a2a.py +58 -0
- aip_agents/schema/agent.py +117 -0
- aip_agents/schema/artifacts.py +75 -0
- aip_agents/schema/attachments.py +425 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/langgraph.py +47 -0
- aip_agents/schema/model_id.py +103 -0
- aip_agents/schema/model_usage.py +50 -0
- aip_agents/schema/runtime_metadata.py +78 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/schema/tool_payloads.py +68 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/sentry.py +267 -0
- aip_agents/skills/__init__.py +30 -0
- aip_agents/skills/errors.py +110 -0
- aip_agents/skills/installer.py +479 -0
- aip_agents/skills/models.py +769 -0
- aip_agents/skills/stager.py +224 -0
- aip_agents/skills/validation.py +53 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/tools/__init__.py +65 -0
- aip_agents/tools/agent_schedule_tool.py +392 -0
- aip_agents/tools/browser_use/__init__.py +83 -0
- aip_agents/tools/browser_use/action_parser.py +104 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/minio_storage.py +203 -0
- aip_agents/tools/browser_use/schemas.py +121 -0
- aip_agents/tools/browser_use/session.py +13 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/steel_session_recording.py +321 -0
- aip_agents/tools/browser_use/streaming.py +263 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/types.py +37 -0
- aip_agents/tools/code_sandbox/__init__.py +48 -0
- aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
- aip_agents/tools/computer_use/__init__.py +75 -0
- aip_agents/tools/computer_use/computer_use_tool.py +910 -0
- aip_agents/tools/computer_use/schemas.py +78 -0
- aip_agents/tools/computer_use/streaming.py +198 -0
- aip_agents/tools/computer_use/types.py +32 -0
- aip_agents/tools/constants.py +177 -0
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/execute_ptc_code.py +582 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/tool.py +518 -0
- aip_agents/tools/gl_connector_tools.py +170 -0
- aip_agents/tools/memory_search/__init__.py +29 -0
- aip_agents/tools/memory_search/base.py +213 -0
- aip_agents/tools/memory_search/mem0.py +424 -0
- aip_agents/tools/memory_search/schema.py +81 -0
- aip_agents/tools/memory_search_tool.py +34 -0
- aip_agents/tools/streaming_common.py +577 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/types/__init__.py +73 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/utils/__init__.py +91 -0
- aip_agents/utils/a2a_connector.py +1758 -0
- aip_agents/utils/artifact_helpers.py +542 -0
- aip_agents/utils/artifacts/__init__.py +20 -0
- aip_agents/utils/artifacts/tracker.py +367 -0
- aip_agents/utils/attachment_helpers.py +178 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/file_prompt_utils.py +276 -0
- aip_agents/utils/file_watchers/__init__.py +33 -0
- aip_agents/utils/file_watchers/base.py +46 -0
- aip_agents/utils/file_watchers/e2b.py +128 -0
- aip_agents/utils/file_watchers/factory.py +39 -0
- aip_agents/utils/file_watchers/ls_diff.py +88 -0
- aip_agents/utils/file_watchers/sandbox.py +66 -0
- aip_agents/utils/file_watchers/watchdog.py +97 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/formatter_llm_client.py +258 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/converter.py +399 -0
- aip_agents/utils/langgraph/delegation_context.py +22 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
- aip_agents/utils/langgraph/tool_output_management.py +1047 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata_helper.py +363 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/pii/__init__.py +29 -0
- aip_agents/utils/pii/pii_handler.py +555 -0
- aip_agents/utils/pii/pii_helper.py +225 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/sse_chunk_transformer.py +836 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/token_usage_helper.py +531 -0
- aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
- aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
- aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
- 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
|
+
]
|