aip-agents-binary 0.6.0__py3-none-any.whl → 0.6.2__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/agent/langgraph_react_agent.py +194 -2
- aip_agents/examples/hello_world_ptc.py +49 -0
- aip_agents/ptc/__init__.py +48 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/exceptions.py +39 -0
- aip_agents/ptc/executor.py +143 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
- aip_agents/ptc/naming.py +184 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/prompt_builder.py +571 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/sandbox_bridge.py +58 -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/sandbox/__init__.py +43 -0
- aip_agents/sandbox/defaults.py +9 -0
- aip_agents/sandbox/e2b_runtime.py +267 -0
- aip_agents/sandbox/template_builder.py +131 -0
- aip_agents/sandbox/types.py +24 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/tools/__init__.py +2 -0
- aip_agents/tools/execute_ptc_code.py +308 -0
- {aip_agents_binary-0.6.0.dist-info → aip_agents_binary-0.6.2.dist-info}/METADATA +1 -1
- {aip_agents_binary-0.6.0.dist-info → aip_agents_binary-0.6.2.dist-info}/RECORD +30 -282
- aip_agents/__init__.pyi +0 -19
- aip_agents/a2a/__init__.pyi +0 -3
- aip_agents/a2a/server/__init__.pyi +0 -4
- aip_agents/a2a/server/base_executor.pyi +0 -73
- aip_agents/a2a/server/google_adk_executor.pyi +0 -51
- aip_agents/a2a/server/langflow_executor.pyi +0 -43
- aip_agents/a2a/server/langgraph_executor.pyi +0 -47
- aip_agents/a2a/types.pyi +0 -132
- aip_agents/agent/__init__.pyi +0 -9
- aip_agents/agent/base_agent.pyi +0 -221
- aip_agents/agent/base_langgraph_agent.pyi +0 -233
- aip_agents/agent/google_adk_agent.pyi +0 -141
- aip_agents/agent/google_adk_constants.pyi +0 -3
- aip_agents/agent/hitl/__init__.pyi +0 -6
- aip_agents/agent/hitl/config.pyi +0 -15
- aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +0 -42
- aip_agents/agent/hitl/manager.pyi +0 -200
- aip_agents/agent/hitl/models.pyi +0 -3
- aip_agents/agent/hitl/prompt/__init__.pyi +0 -4
- aip_agents/agent/hitl/prompt/base.pyi +0 -24
- aip_agents/agent/hitl/prompt/deferred.pyi +0 -30
- aip_agents/agent/hitl/registry.pyi +0 -101
- aip_agents/agent/interface.pyi +0 -81
- aip_agents/agent/interfaces.pyi +0 -44
- aip_agents/agent/langflow_agent.pyi +0 -133
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +0 -49
- aip_agents/agent/langgraph_react_agent.pyi +0 -131
- aip_agents/agent/system_instruction_context.pyi +0 -13
- aip_agents/clients/__init__.pyi +0 -4
- aip_agents/clients/langflow/__init__.pyi +0 -4
- aip_agents/clients/langflow/client.pyi +0 -140
- aip_agents/clients/langflow/types.pyi +0 -7
- aip_agents/constants.pyi +0 -7
- aip_agents/examples/__init__.pyi +0 -0
- aip_agents/examples/compare_streaming_client.pyi +0 -48
- aip_agents/examples/compare_streaming_server.pyi +0 -18
- aip_agents/examples/demo_memory_recall.pyi +0 -58
- aip_agents/examples/hello_world_a2a_google_adk_client.pyi +0 -9
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +0 -9
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +0 -9
- aip_agents/examples/hello_world_a2a_google_adk_server.pyi +0 -15
- aip_agents/examples/hello_world_a2a_langchain_client.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +0 -15
- aip_agents/examples/hello_world_a2a_langchain_server.pyi +0 -15
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +0 -15
- aip_agents/examples/hello_world_a2a_langflow_client.pyi +0 -9
- aip_agents/examples/hello_world_a2a_langflow_server.pyi +0 -14
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +0 -16
- aip_agents/examples/hello_world_a2a_langgraph_client.pyi +0 -9
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +0 -9
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +0 -2
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +0 -9
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +0 -5
- aip_agents/examples/hello_world_a2a_langgraph_server.pyi +0 -14
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +0 -15
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +0 -15
- aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +0 -48
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.pyi +0 -48
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +0 -45
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +0 -5
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +0 -15
- aip_agents/examples/hello_world_google_adk.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_mcp_http.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +0 -5
- aip_agents/examples/hello_world_google_adk_stream.pyi +0 -5
- aip_agents/examples/hello_world_langchain.pyi +0 -5
- aip_agents/examples/hello_world_langchain_lm_invoker.pyi +0 -2
- aip_agents/examples/hello_world_langchain_mcp_http.pyi +0 -5
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +0 -16
- aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +0 -5
- aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +0 -18
- aip_agents/examples/hello_world_langchain_mcp_sse.pyi +0 -5
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +0 -5
- aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +0 -5
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +0 -5
- aip_agents/examples/hello_world_langchain_stream.pyi +0 -5
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +0 -5
- aip_agents/examples/hello_world_langflow_agent.pyi +0 -35
- aip_agents/examples/hello_world_langgraph.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_mcp_http.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_stream.pyi +0 -5
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +0 -5
- aip_agents/examples/hello_world_model_switch_cli.pyi +0 -30
- aip_agents/examples/hello_world_multi_agent_adk.pyi +0 -6
- aip_agents/examples/hello_world_multi_agent_langchain.pyi +0 -5
- aip_agents/examples/hello_world_multi_agent_langgraph.pyi +0 -5
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +0 -5
- aip_agents/examples/hello_world_pii_logger.pyi +0 -5
- aip_agents/examples/hello_world_sentry.pyi +0 -21
- aip_agents/examples/hello_world_step_limits.pyi +0 -17
- aip_agents/examples/hello_world_stock_a2a_server.pyi +0 -17
- aip_agents/examples/hello_world_tool_output_client.pyi +0 -5
- aip_agents/examples/hello_world_tool_output_server.pyi +0 -19
- aip_agents/examples/hitl_demo.pyi +0 -67
- aip_agents/examples/pii_demo_langgraph_client.pyi +0 -5
- aip_agents/examples/pii_demo_langgraph_server.pyi +0 -20
- aip_agents/examples/pii_demo_multi_agent_client.pyi +0 -5
- aip_agents/examples/pii_demo_multi_agent_server.pyi +0 -40
- aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +0 -5
- aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +0 -19
- aip_agents/examples/tools/__init__.pyi +0 -9
- aip_agents/examples/tools/adk_arithmetic_tools.pyi +0 -24
- aip_agents/examples/tools/adk_weather_tool.pyi +0 -18
- aip_agents/examples/tools/data_generator_tool.pyi +0 -15
- aip_agents/examples/tools/data_visualization_tool.pyi +0 -19
- aip_agents/examples/tools/image_artifact_tool.pyi +0 -26
- aip_agents/examples/tools/langchain_arithmetic_tools.pyi +0 -17
- aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +0 -20
- aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +0 -25
- aip_agents/examples/tools/langchain_weather_tool.pyi +0 -19
- aip_agents/examples/tools/langgraph_streaming_tool.pyi +0 -43
- aip_agents/examples/tools/mock_retrieval_tool.pyi +0 -13
- aip_agents/examples/tools/pii_demo_tools.pyi +0 -54
- aip_agents/examples/tools/random_chart_tool.pyi +0 -20
- aip_agents/examples/tools/serper_tool.pyi +0 -16
- aip_agents/examples/tools/stock_tools.pyi +0 -36
- aip_agents/examples/tools/table_generator_tool.pyi +0 -22
- aip_agents/examples/tools/time_tool.pyi +0 -15
- aip_agents/examples/tools/weather_forecast_tool.pyi +0 -14
- aip_agents/guardrails/__init__.pyi +0 -6
- aip_agents/guardrails/engines/__init__.pyi +0 -4
- aip_agents/guardrails/engines/base.pyi +0 -61
- aip_agents/guardrails/engines/nemo.pyi +0 -46
- aip_agents/guardrails/engines/phrase_matcher.pyi +0 -48
- aip_agents/guardrails/exceptions.pyi +0 -23
- aip_agents/guardrails/manager.pyi +0 -42
- aip_agents/guardrails/middleware.pyi +0 -87
- aip_agents/guardrails/schemas.pyi +0 -43
- aip_agents/guardrails/utils.pyi +0 -19
- aip_agents/mcp/__init__.pyi +0 -0
- aip_agents/mcp/client/__init__.pyi +0 -5
- aip_agents/mcp/client/base_mcp_client.pyi +0 -148
- aip_agents/mcp/client/connection_manager.pyi +0 -51
- aip_agents/mcp/client/google_adk/__init__.pyi +0 -3
- aip_agents/mcp/client/google_adk/client.pyi +0 -75
- aip_agents/mcp/client/langchain/__init__.pyi +0 -3
- aip_agents/mcp/client/langchain/client.pyi +0 -48
- aip_agents/mcp/client/persistent_session.pyi +0 -122
- aip_agents/mcp/client/session_pool.pyi +0 -101
- aip_agents/mcp/client/transports.pyi +0 -132
- aip_agents/mcp/utils/__init__.pyi +0 -0
- aip_agents/mcp/utils/config_validator.pyi +0 -82
- aip_agents/memory/__init__.pyi +0 -5
- aip_agents/memory/adapters/__init__.pyi +0 -4
- aip_agents/memory/adapters/base_adapter.pyi +0 -150
- aip_agents/memory/adapters/mem0.pyi +0 -22
- aip_agents/memory/base.pyi +0 -60
- aip_agents/memory/constants.pyi +0 -25
- aip_agents/memory/factory.pyi +0 -24
- aip_agents/memory/guidance.pyi +0 -3
- aip_agents/memory/simple_memory.pyi +0 -23
- aip_agents/middleware/__init__.pyi +0 -5
- aip_agents/middleware/base.pyi +0 -75
- aip_agents/middleware/manager.pyi +0 -84
- aip_agents/middleware/todolist.pyi +0 -125
- aip_agents/schema/__init__.pyi +0 -9
- aip_agents/schema/a2a.pyi +0 -40
- aip_agents/schema/agent.pyi +0 -65
- aip_agents/schema/hitl.pyi +0 -89
- aip_agents/schema/langgraph.pyi +0 -28
- aip_agents/schema/model_id.pyi +0 -54
- aip_agents/schema/step_limit.pyi +0 -63
- aip_agents/schema/storage.pyi +0 -21
- aip_agents/sentry/__init__.pyi +0 -3
- aip_agents/sentry/sentry.pyi +0 -48
- aip_agents/storage/__init__.pyi +0 -8
- aip_agents/storage/base.pyi +0 -58
- aip_agents/storage/clients/__init__.pyi +0 -3
- aip_agents/storage/clients/minio_client.pyi +0 -137
- aip_agents/storage/config.pyi +0 -29
- aip_agents/storage/providers/__init__.pyi +0 -5
- aip_agents/storage/providers/base.pyi +0 -88
- aip_agents/storage/providers/memory.pyi +0 -79
- aip_agents/storage/providers/object_storage.pyi +0 -98
- aip_agents/tools/__init__.pyi +0 -9
- aip_agents/tools/browser_use/__init__.pyi +0 -14
- aip_agents/tools/browser_use/action_parser.pyi +0 -18
- aip_agents/tools/browser_use/browser_use_tool.pyi +0 -50
- aip_agents/tools/browser_use/llm_config.pyi +0 -52
- aip_agents/tools/browser_use/minio_storage.pyi +0 -109
- aip_agents/tools/browser_use/schemas.pyi +0 -32
- aip_agents/tools/browser_use/session.pyi +0 -4
- aip_agents/tools/browser_use/session_errors.pyi +0 -53
- aip_agents/tools/browser_use/steel_session_recording.pyi +0 -63
- aip_agents/tools/browser_use/streaming.pyi +0 -81
- aip_agents/tools/browser_use/structured_data_parser.pyi +0 -86
- aip_agents/tools/browser_use/structured_data_recovery.pyi +0 -43
- aip_agents/tools/browser_use/types.pyi +0 -45
- aip_agents/tools/code_sandbox/__init__.pyi +0 -3
- aip_agents/tools/code_sandbox/constant.pyi +0 -4
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +0 -102
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +0 -29
- aip_agents/tools/constants.pyi +0 -138
- aip_agents/tools/document_loader/__init__.pyi +0 -7
- aip_agents/tools/document_loader/base_reader.pyi +0 -75
- aip_agents/tools/document_loader/docx_reader_tool.pyi +0 -10
- aip_agents/tools/document_loader/excel_reader_tool.pyi +0 -26
- aip_agents/tools/document_loader/pdf_reader_tool.pyi +0 -11
- aip_agents/tools/document_loader/pdf_splitter.pyi +0 -18
- aip_agents/tools/gl_connector/__init__.pyi +0 -3
- aip_agents/tools/gl_connector/tool.pyi +0 -74
- aip_agents/tools/gl_connector_tools.pyi +0 -39
- aip_agents/tools/memory_search/__init__.pyi +0 -5
- aip_agents/tools/memory_search/base.pyi +0 -69
- aip_agents/tools/memory_search/mem0.pyi +0 -19
- aip_agents/tools/memory_search/schema.pyi +0 -15
- aip_agents/tools/memory_search_tool.pyi +0 -3
- aip_agents/tools/time_tool.pyi +0 -16
- aip_agents/tools/tool_config_injector.pyi +0 -26
- aip_agents/tools/web_search/__init__.pyi +0 -3
- aip_agents/tools/web_search/serper_tool.pyi +0 -19
- aip_agents/types/__init__.pyi +0 -36
- aip_agents/types/a2a_events.pyi +0 -3
- aip_agents/utils/__init__.pyi +0 -11
- aip_agents/utils/a2a_connector.pyi +0 -146
- aip_agents/utils/artifact_helpers.pyi +0 -203
- aip_agents/utils/constants.pyi +0 -10
- aip_agents/utils/datetime/__init__.pyi +0 -4
- aip_agents/utils/datetime/normalization.pyi +0 -95
- aip_agents/utils/datetime/timezone.pyi +0 -48
- aip_agents/utils/env_loader.pyi +0 -10
- aip_agents/utils/event_handler_registry.pyi +0 -23
- aip_agents/utils/file_prompt_utils.pyi +0 -21
- aip_agents/utils/final_response_builder.pyi +0 -34
- aip_agents/utils/formatter_llm_client.pyi +0 -71
- aip_agents/utils/langgraph/__init__.pyi +0 -3
- aip_agents/utils/langgraph/converter.pyi +0 -49
- aip_agents/utils/langgraph/tool_managers/__init__.pyi +0 -5
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +0 -35
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +0 -48
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +0 -56
- aip_agents/utils/langgraph/tool_output_management.pyi +0 -329
- aip_agents/utils/logger.pyi +0 -60
- aip_agents/utils/metadata/__init__.pyi +0 -5
- aip_agents/utils/metadata/activity_metadata_helper.pyi +0 -25
- aip_agents/utils/metadata/activity_narrative/__init__.pyi +0 -7
- aip_agents/utils/metadata/activity_narrative/builder.pyi +0 -35
- aip_agents/utils/metadata/activity_narrative/constants.pyi +0 -10
- aip_agents/utils/metadata/activity_narrative/context.pyi +0 -32
- aip_agents/utils/metadata/activity_narrative/formatters.pyi +0 -48
- aip_agents/utils/metadata/activity_narrative/utils.pyi +0 -12
- aip_agents/utils/metadata/schemas/__init__.pyi +0 -4
- aip_agents/utils/metadata/schemas/activity_schema.pyi +0 -18
- aip_agents/utils/metadata/schemas/thinking_schema.pyi +0 -20
- aip_agents/utils/metadata/thinking_metadata_helper.pyi +0 -4
- aip_agents/utils/metadata_helper.pyi +0 -117
- aip_agents/utils/name_preprocessor/__init__.pyi +0 -6
- aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +0 -52
- aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +0 -38
- aip_agents/utils/name_preprocessor/name_preprocessor.pyi +0 -41
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +0 -34
- aip_agents/utils/pii/__init__.pyi +0 -5
- aip_agents/utils/pii/pii_handler.pyi +0 -96
- aip_agents/utils/pii/pii_helper.pyi +0 -78
- aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +0 -73
- aip_agents/utils/reference_helper.pyi +0 -81
- aip_agents/utils/sse_chunk_transformer.pyi +0 -166
- aip_agents/utils/step_limit_manager.pyi +0 -112
- aip_agents/utils/token_usage_helper.pyi +0 -60
- {aip_agents_binary-0.6.0.dist-info → aip_agents_binary-0.6.2.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.6.0.dist-info → aip_agents_binary-0.6.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""PTC Discovery Helper Module.
|
|
2
|
+
|
|
3
|
+
This module provides discovery functions for exploring available PTC tools
|
|
4
|
+
inside the PTC sandbox. Use these functions to find packages, list tools,
|
|
5
|
+
and get detailed documentation.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from tools.ptc_helper import list_packages, list_tools, describe_tool
|
|
9
|
+
|
|
10
|
+
# List all available packages
|
|
11
|
+
packages = list_packages()
|
|
12
|
+
|
|
13
|
+
# List tools in a package
|
|
14
|
+
tools = list_tools("package_name")
|
|
15
|
+
|
|
16
|
+
# Get tool documentation
|
|
17
|
+
doc = describe_tool("package_name", "tool_name")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import difflib
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
# Load the index at module import time
|
|
26
|
+
_INDEX_PATH = os.path.join(os.path.dirname(__file__), "ptc_index.json")
|
|
27
|
+
_DOCS_DIR = os.path.join(os.path.dirname(__file__), "docs")
|
|
28
|
+
|
|
29
|
+
_index: dict[str, Any] = {}
|
|
30
|
+
if os.path.exists(_INDEX_PATH):
|
|
31
|
+
with open(_INDEX_PATH, "r") as f:
|
|
32
|
+
_index = json.load(f)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _suggest_closest(name: str, valid_names: list[str], kind: str = "name") -> str:
|
|
36
|
+
"""Generate a suggestion message for closest match.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
name: The name that was not found.
|
|
40
|
+
valid_names: List of valid names to match against.
|
|
41
|
+
kind: Type of name (package or tool) for the error message.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Suggestion string or empty string if no close match.
|
|
45
|
+
"""
|
|
46
|
+
matches = difflib.get_close_matches(name, valid_names, n=1, cutoff=0.6)
|
|
47
|
+
if matches:
|
|
48
|
+
return f" Did you mean '{matches[0]}'?"
|
|
49
|
+
return ""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def list_packages() -> list[str]:
|
|
53
|
+
"""List all available package names.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Sorted list of sanitized package names.
|
|
57
|
+
"""
|
|
58
|
+
packages = _index.get("packages", {})
|
|
59
|
+
return sorted(packages.keys())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def list_tools(package: str) -> list[dict[str, str]]:
|
|
63
|
+
"""List tools available in a package.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
package: Sanitized package name (e.g., 'deepwiki').
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of tool info dicts with 'name' keys.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If package is not found.
|
|
73
|
+
"""
|
|
74
|
+
packages = _index.get("packages", {})
|
|
75
|
+
if package not in packages:
|
|
76
|
+
valid = list(packages.keys())
|
|
77
|
+
suggestion = _suggest_closest(package, valid, "package")
|
|
78
|
+
raise ValueError(f"Unknown package '{package}'.{suggestion}")
|
|
79
|
+
|
|
80
|
+
pkg_data = packages[package]
|
|
81
|
+
tools = pkg_data.get("tools", [])
|
|
82
|
+
return [{"name": t["name"]} for t in tools]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def describe_tool(package: str, tool: str) -> dict[str, Any]:
|
|
86
|
+
"""Get detailed documentation for a tool.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
package: Sanitized package name.
|
|
90
|
+
tool: Sanitized tool name.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dict with 'name', 'signature', 'doc', and 'doc_path' keys.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If package or tool is not found.
|
|
97
|
+
"""
|
|
98
|
+
packages = _index.get("packages", {})
|
|
99
|
+
if package not in packages:
|
|
100
|
+
valid = list(packages.keys())
|
|
101
|
+
suggestion = _suggest_closest(package, valid, "package")
|
|
102
|
+
raise ValueError(f"Unknown package '{package}'.{suggestion}")
|
|
103
|
+
|
|
104
|
+
pkg_data = packages[package]
|
|
105
|
+
tools = pkg_data.get("tools", [])
|
|
106
|
+
tool_names = [t["name"] for t in tools]
|
|
107
|
+
|
|
108
|
+
if tool not in tool_names:
|
|
109
|
+
suggestion = _suggest_closest(tool, tool_names, "tool")
|
|
110
|
+
raise ValueError(f"Unknown tool '{tool}' in package '{package}'.{suggestion}")
|
|
111
|
+
|
|
112
|
+
tool_info = next(t for t in tools if t["name"] == tool)
|
|
113
|
+
doc_path = tool_info.get("doc_path", "")
|
|
114
|
+
|
|
115
|
+
doc_content = ""
|
|
116
|
+
if doc_path:
|
|
117
|
+
full_path = os.path.join(os.path.dirname(__file__), doc_path.replace("tools/", ""))
|
|
118
|
+
if os.path.exists(full_path):
|
|
119
|
+
with open(full_path, "r") as f:
|
|
120
|
+
doc_content = f.read()
|
|
121
|
+
else:
|
|
122
|
+
doc_content = f"# {tool}\n\n**Signature:** `{tool_info.get('signature', f'{tool}(**kwargs: Any)')}`"
|
|
123
|
+
else:
|
|
124
|
+
doc_content = f"# {tool}\n\n**Signature:** `{tool_info.get('signature', f'{tool}(**kwargs: Any)')}`"
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"name": tool,
|
|
128
|
+
"signature": tool_info.get("signature", f"{tool}(**kwargs)"),
|
|
129
|
+
"doc": doc_content,
|
|
130
|
+
"doc_path": doc_path,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
__all__ = ["list_packages", "list_tools", "describe_tool"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# flake8: noqa: F401
|
|
2
|
+
"""Sandbox module for isolated code execution.
|
|
3
|
+
|
|
4
|
+
This module provides abstractions for running code in sandboxed environments.
|
|
5
|
+
All components support lazy loading to work with optional dependencies (e2b).
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from aip_agents.sandbox.e2b_runtime import E2BSandboxRuntime
|
|
15
|
+
from aip_agents.sandbox.template_builder import ensure_ptc_template
|
|
16
|
+
from aip_agents.sandbox.types import SandboxExecutionResult
|
|
17
|
+
|
|
18
|
+
_IMPORT_MAP = {
|
|
19
|
+
"E2BSandboxRuntime": "aip_agents.sandbox.e2b_runtime",
|
|
20
|
+
"ensure_ptc_template": "aip_agents.sandbox.template_builder",
|
|
21
|
+
"SandboxExecutionResult": "aip_agents.sandbox.types",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_cache: dict[str, Any] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def __getattr__(name: str) -> Any:
|
|
28
|
+
"""Lazy import components on first access."""
|
|
29
|
+
if name in _cache:
|
|
30
|
+
return _cache[name]
|
|
31
|
+
|
|
32
|
+
if name in _IMPORT_MAP:
|
|
33
|
+
try:
|
|
34
|
+
module = __import__(_IMPORT_MAP[name], fromlist=[name])
|
|
35
|
+
_cache[name] = getattr(module, name)
|
|
36
|
+
return _cache[name]
|
|
37
|
+
except ImportError as e:
|
|
38
|
+
raise ImportError(f"Failed to import {name}. Install with: pip install aip-agents[local]") from e
|
|
39
|
+
|
|
40
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = list(_IMPORT_MAP.keys())
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""E2B Sandbox Runtime for PTC.
|
|
2
|
+
|
|
3
|
+
This module provides direct E2B SDK integration for sandbox code execution.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from e2b_code_interpreter import AsyncSandbox, OutputMessage
|
|
10
|
+
|
|
11
|
+
from aip_agents.sandbox.defaults import DEFAULT_PTC_PACKAGES, DEFAULT_PTC_TEMPLATE
|
|
12
|
+
from aip_agents.sandbox.types import SandboxExecutionResult
|
|
13
|
+
from aip_agents.sandbox.validation import validate_package_names
|
|
14
|
+
from aip_agents.utils.logger import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
SANDBOX_NOT_INITIALIZED_ERROR = "Sandbox not initialized"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class E2BSandboxRuntime:
|
|
22
|
+
"""E2B Sandbox runtime for executing code in isolated environments.
|
|
23
|
+
|
|
24
|
+
This runtime manages per-run sandbox lifecycle:
|
|
25
|
+
- Create sandbox on first execute
|
|
26
|
+
- Reuse sandbox for subsequent executes
|
|
27
|
+
- Destroy sandbox on cleanup
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
runtime = E2BSandboxRuntime()
|
|
31
|
+
result = await runtime.execute(
|
|
32
|
+
code="print('Hello')",
|
|
33
|
+
timeout=60.0,
|
|
34
|
+
files={"tools/mcp.py": "# MCP client code"},
|
|
35
|
+
)
|
|
36
|
+
await runtime.cleanup()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
template: str | None = None,
|
|
42
|
+
ptc_packages: list[str] | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Initialize E2B sandbox runtime.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
template: Optional E2B template ID for custom sandbox environments.
|
|
48
|
+
ptc_packages: Packages to install in sandbox. If None or empty, skip install.
|
|
49
|
+
"""
|
|
50
|
+
self._template = template
|
|
51
|
+
self._ptc_packages = ptc_packages
|
|
52
|
+
self._sandbox: AsyncSandbox | None = None
|
|
53
|
+
self._sandbox_created_with_template = False
|
|
54
|
+
|
|
55
|
+
async def execute(
|
|
56
|
+
self,
|
|
57
|
+
code: str,
|
|
58
|
+
*,
|
|
59
|
+
timeout: float = 300.0,
|
|
60
|
+
files: dict[str, str] | None = None,
|
|
61
|
+
env: dict[str, str] | None = None,
|
|
62
|
+
template: str | None = None,
|
|
63
|
+
) -> SandboxExecutionResult:
|
|
64
|
+
"""Execute code inside the sandbox.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
code: Python code to execute.
|
|
68
|
+
timeout: Execution timeout in seconds.
|
|
69
|
+
files: Files to upload to the sandbox (path -> content).
|
|
70
|
+
env: Environment variables to set.
|
|
71
|
+
template: Optional template override for this execution.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
SandboxExecutionResult with stdout, stderr, and exit_code.
|
|
75
|
+
"""
|
|
76
|
+
# Create sandbox if not exists
|
|
77
|
+
if self._sandbox is None:
|
|
78
|
+
await self._create_sandbox(template or self._template)
|
|
79
|
+
|
|
80
|
+
# Upload files if provided
|
|
81
|
+
if files:
|
|
82
|
+
await self._upload_files(files)
|
|
83
|
+
|
|
84
|
+
# Execute code
|
|
85
|
+
return await self._run_code(code, timeout, env)
|
|
86
|
+
|
|
87
|
+
async def cleanup(self) -> None:
|
|
88
|
+
"""Destroy the sandbox and release resources."""
|
|
89
|
+
if self._sandbox is not None:
|
|
90
|
+
try:
|
|
91
|
+
logger.info("Destroying E2B sandbox")
|
|
92
|
+
await self._sandbox.kill()
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.warning(f"Error destroying sandbox: {e}")
|
|
95
|
+
finally:
|
|
96
|
+
self._sandbox = None
|
|
97
|
+
|
|
98
|
+
self._reset_async_transport()
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def is_active(self) -> bool:
|
|
102
|
+
"""Check if a sandbox is currently active."""
|
|
103
|
+
return self._sandbox is not None
|
|
104
|
+
|
|
105
|
+
def _reset_async_transport(self) -> None:
|
|
106
|
+
try:
|
|
107
|
+
from e2b.api.client_async import AsyncTransportWithLogger
|
|
108
|
+
|
|
109
|
+
AsyncTransportWithLogger.singleton = None
|
|
110
|
+
except Exception:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
def _should_skip_default_ptc_install(self, template: str | None) -> bool:
|
|
114
|
+
if not self._sandbox_created_with_template:
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
if template != DEFAULT_PTC_TEMPLATE:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
return self._ptc_packages == list(DEFAULT_PTC_PACKAGES)
|
|
121
|
+
|
|
122
|
+
async def _create_sandbox(self, template: str | None = None) -> None:
|
|
123
|
+
"""Create a new E2B sandbox.
|
|
124
|
+
|
|
125
|
+
Implements canonical runtime rules:
|
|
126
|
+
- If template provided, try creating sandbox with template
|
|
127
|
+
- On any error, fall back to default sandbox (without template)
|
|
128
|
+
- Install ptc_packages regardless of template usage (even after fallback)
|
|
129
|
+
|
|
130
|
+
Note: Package installation occurs after sandbox creation, so fallback
|
|
131
|
+
to default sandbox does not skip package installation.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
template: Optional template ID.
|
|
135
|
+
"""
|
|
136
|
+
logger.info(f"Creating E2B sandbox (template={template})")
|
|
137
|
+
|
|
138
|
+
async def create_default_sandbox() -> None:
|
|
139
|
+
self._sandbox = await AsyncSandbox.create()
|
|
140
|
+
logger.info(f"E2B sandbox created (default): {self._sandbox.sandbox_id}")
|
|
141
|
+
|
|
142
|
+
if template:
|
|
143
|
+
try:
|
|
144
|
+
self._sandbox = await AsyncSandbox.create(template=template)
|
|
145
|
+
self._sandbox_created_with_template = True
|
|
146
|
+
logger.info(f"E2B sandbox created: {self._sandbox.sandbox_id}")
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.warning(f"Template creation failed ({template}): {e}")
|
|
149
|
+
logger.info("Falling back to default sandbox")
|
|
150
|
+
self._sandbox_created_with_template = False
|
|
151
|
+
await create_default_sandbox()
|
|
152
|
+
else:
|
|
153
|
+
self._sandbox_created_with_template = False
|
|
154
|
+
await create_default_sandbox()
|
|
155
|
+
|
|
156
|
+
# Install ptc_packages if non-empty
|
|
157
|
+
if self._ptc_packages:
|
|
158
|
+
if self._should_skip_default_ptc_install(template):
|
|
159
|
+
logger.info("Skipping PTC package install (default template already includes defaults)")
|
|
160
|
+
else:
|
|
161
|
+
await self._install_ptc_packages()
|
|
162
|
+
|
|
163
|
+
async def _install_ptc_packages(self) -> None:
|
|
164
|
+
"""Install PTC packages in the sandbox."""
|
|
165
|
+
if self._sandbox is None:
|
|
166
|
+
raise RuntimeError(SANDBOX_NOT_INITIALIZED_ERROR)
|
|
167
|
+
|
|
168
|
+
# Validate all packages before constructing command
|
|
169
|
+
validate_package_names(self._ptc_packages)
|
|
170
|
+
|
|
171
|
+
# Note: packages_str is safe because ptc_packages is a controlled list from
|
|
172
|
+
# configuration, not user input. E2B SDK's commands.run() only accepts str,
|
|
173
|
+
# not list, so string joining is required.
|
|
174
|
+
packages_str = " ".join(self._ptc_packages)
|
|
175
|
+
logger.info(f"Installing PTC packages in sandbox: {packages_str}")
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
result = await self._sandbox.commands.run(
|
|
179
|
+
f"pip install -q {packages_str}",
|
|
180
|
+
timeout=120,
|
|
181
|
+
)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Error installing PTC packages: {e}")
|
|
184
|
+
raise
|
|
185
|
+
|
|
186
|
+
if result.exit_code != 0:
|
|
187
|
+
logger.error(f"Failed to install PTC packages: {result.stderr}")
|
|
188
|
+
raise RuntimeError(f"Failed to install PTC packages: {result.stderr}")
|
|
189
|
+
|
|
190
|
+
logger.info("PTC packages installed successfully")
|
|
191
|
+
|
|
192
|
+
async def _upload_files(self, files: dict[str, str]) -> None:
|
|
193
|
+
"""Upload files to the sandbox.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
files: Mapping of path -> content.
|
|
197
|
+
"""
|
|
198
|
+
if self._sandbox is None:
|
|
199
|
+
raise RuntimeError(SANDBOX_NOT_INITIALIZED_ERROR)
|
|
200
|
+
|
|
201
|
+
for path, content in files.items():
|
|
202
|
+
logger.debug(f"Uploading file to sandbox: {path}")
|
|
203
|
+
await self._sandbox.files.write(path, content)
|
|
204
|
+
|
|
205
|
+
async def _run_code(
|
|
206
|
+
self,
|
|
207
|
+
code: str,
|
|
208
|
+
timeout: float,
|
|
209
|
+
env: dict[str, str] | None = None,
|
|
210
|
+
) -> SandboxExecutionResult:
|
|
211
|
+
"""Run code in the sandbox.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
code: Python code to execute.
|
|
215
|
+
timeout: Execution timeout in seconds.
|
|
216
|
+
env: Environment variables.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
SandboxExecutionResult with execution output.
|
|
220
|
+
"""
|
|
221
|
+
if self._sandbox is None:
|
|
222
|
+
raise RuntimeError(SANDBOX_NOT_INITIALIZED_ERROR)
|
|
223
|
+
|
|
224
|
+
stdout_lines: list[str] = []
|
|
225
|
+
stderr_lines: list[str] = []
|
|
226
|
+
|
|
227
|
+
def on_stdout(msg: OutputMessage) -> None:
|
|
228
|
+
if hasattr(msg, "line"):
|
|
229
|
+
stdout_lines.append(msg.line)
|
|
230
|
+
|
|
231
|
+
def on_stderr(msg: OutputMessage) -> None:
|
|
232
|
+
if hasattr(msg, "line"):
|
|
233
|
+
stderr_lines.append(msg.line)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
execution = await self._sandbox.run_code(
|
|
237
|
+
code=code,
|
|
238
|
+
language="python",
|
|
239
|
+
on_stdout=on_stdout,
|
|
240
|
+
on_stderr=on_stderr,
|
|
241
|
+
envs=env,
|
|
242
|
+
timeout=timeout,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Determine exit code
|
|
246
|
+
exit_code = 0
|
|
247
|
+
if execution.error:
|
|
248
|
+
exit_code = 1
|
|
249
|
+
# Add error to stderr
|
|
250
|
+
error_msg = f"{execution.error.name}: {execution.error.value}"
|
|
251
|
+
if execution.error.traceback:
|
|
252
|
+
error_msg = f"{execution.error.traceback}\n{error_msg}"
|
|
253
|
+
stderr_lines.append(error_msg)
|
|
254
|
+
|
|
255
|
+
return SandboxExecutionResult(
|
|
256
|
+
stdout="\n".join(stdout_lines),
|
|
257
|
+
stderr="\n".join(stderr_lines),
|
|
258
|
+
exit_code=exit_code,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.error(f"Sandbox execution failed: {e}")
|
|
263
|
+
return SandboxExecutionResult(
|
|
264
|
+
stdout="",
|
|
265
|
+
stderr=str(e),
|
|
266
|
+
exit_code=1,
|
|
267
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Template builder for PTC sandbox templates.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for creating and managing E2B sandbox templates
|
|
4
|
+
for programmatic tool calling (PTC) environments.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from e2b import Template, default_build_logger
|
|
11
|
+
|
|
12
|
+
from aip_agents.sandbox.validation import validate_package_names
|
|
13
|
+
from aip_agents.utils.logger import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_ptc_template(base_template: str, ptc_packages: list[str] | None) -> Template:
|
|
19
|
+
"""Create a PTC template definition based on a base template.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
base_template: Base template alias to build from (e.g., "code-interpreter-v1").
|
|
23
|
+
ptc_packages: List of packages to install in the template.
|
|
24
|
+
If None or empty, skips pip install step.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Template: A configured template ready to be built.
|
|
28
|
+
"""
|
|
29
|
+
logger.info(f"Creating template from base: {base_template}")
|
|
30
|
+
template = Template().from_template(base_template)
|
|
31
|
+
|
|
32
|
+
if ptc_packages:
|
|
33
|
+
# Validate all packages before constructing command
|
|
34
|
+
validate_package_names(ptc_packages)
|
|
35
|
+
|
|
36
|
+
# Note: packages_str is safe because ptc_packages is a controlled list from
|
|
37
|
+
# configuration, not user input. Template.run_cmd() only accepts str.
|
|
38
|
+
packages_str = " ".join(ptc_packages)
|
|
39
|
+
logger.info(f"Installing packages: {packages_str}")
|
|
40
|
+
template.run_cmd(f"pip install -q {packages_str}")
|
|
41
|
+
|
|
42
|
+
return template
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _template_exists(template_id: str) -> bool:
|
|
46
|
+
"""Check if a template alias exists.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
template_id: The template alias to check.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
bool: True if alias exists, False otherwise.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
return Template.alias_exists(template_id)
|
|
56
|
+
except Exception:
|
|
57
|
+
logger.warning(f"Template alias check failed for: {template_id}")
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_template(template: Template, template_id: str) -> bool:
|
|
62
|
+
"""Build a template with the given alias.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
template: The template to build.
|
|
66
|
+
template_id: The alias to assign to the built template.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
bool: True if build succeeded, False otherwise.
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
logger.info(f"Building template: {template_id}")
|
|
73
|
+
Template.build(
|
|
74
|
+
template,
|
|
75
|
+
alias=template_id,
|
|
76
|
+
on_build_logs=default_build_logger(),
|
|
77
|
+
)
|
|
78
|
+
logger.info(f"Template built successfully: {template_id}")
|
|
79
|
+
return True
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warning(f"Template build failed for {template_id}: {e}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def ensure_ptc_template(
|
|
86
|
+
template_id: str,
|
|
87
|
+
base_template: str,
|
|
88
|
+
ptc_packages: list[str] | None,
|
|
89
|
+
force_rebuild: bool = False,
|
|
90
|
+
) -> str | None:
|
|
91
|
+
"""Ensure a PTC sandbox template exists, creating it if necessary.
|
|
92
|
+
|
|
93
|
+
This is an explicit helper that apps can call at startup to ensure the
|
|
94
|
+
template exists. It is never run implicitly by the SDK.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
template_id: Unique alias for the template (e.g., "aip-agents-ptc-v1").
|
|
98
|
+
base_template: Base template alias to build from
|
|
99
|
+
(e.g., "code-interpreter-v1").
|
|
100
|
+
ptc_packages: List of packages to install in the template.
|
|
101
|
+
If None or empty, skips pip install step.
|
|
102
|
+
force_rebuild: If True, rebuild even if alias exists.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The template_id on success, None if creation failed.
|
|
106
|
+
Never raises exceptions.
|
|
107
|
+
"""
|
|
108
|
+
# Fast path: template already exists and we're not forcing rebuild
|
|
109
|
+
if not force_rebuild and _template_exists(template_id):
|
|
110
|
+
logger.info(f"Template already exists: {template_id}")
|
|
111
|
+
return template_id
|
|
112
|
+
|
|
113
|
+
# Create and build the template
|
|
114
|
+
try:
|
|
115
|
+
template = create_ptc_template(base_template, ptc_packages)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.warning(f"Template creation failed for {template_id}: {e}")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# Build the template
|
|
121
|
+
is_success = _build_template(template, template_id)
|
|
122
|
+
if is_success:
|
|
123
|
+
return template_id
|
|
124
|
+
|
|
125
|
+
# Build failed. Check if template exists anyway (race condition: another
|
|
126
|
+
# process may have built it while we were trying)
|
|
127
|
+
if _template_exists(template_id):
|
|
128
|
+
logger.info(f"Template already exists after failed build: {template_id}")
|
|
129
|
+
return template_id
|
|
130
|
+
|
|
131
|
+
return None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Sandbox execution result types.
|
|
2
|
+
|
|
3
|
+
This module defines types for sandbox execution results.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class SandboxExecutionResult:
|
|
14
|
+
"""Result of a sandbox code execution.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
stdout: Standard output from the execution.
|
|
18
|
+
stderr: Standard error from the execution.
|
|
19
|
+
exit_code: Exit code (0 for success, non-zero for failure).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
stdout: str
|
|
23
|
+
stderr: str
|
|
24
|
+
exit_code: int
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Validation utilities for sandbox operations.
|
|
2
|
+
|
|
3
|
+
This module provides validation functions for sandbox-related operations
|
|
4
|
+
such as package name validation.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
_PACKAGE_SPEC_PATTERN = re.compile(
|
|
13
|
+
r"^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?"
|
|
14
|
+
r"(?:\[[A-Za-z0-9._-]+(?:,[A-Za-z0-9._-]+)*\])?"
|
|
15
|
+
r"(?:"
|
|
16
|
+
r"(?:==|!=|<=|>=|~=|<|>)[0-9][A-Za-z0-9.*+!_-]*"
|
|
17
|
+
r"(?:,(?:==|!=|<=|>=|~=|<|>)[0-9][A-Za-z0-9.*+!_-]*)*"
|
|
18
|
+
r")?$"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_package_name(package: str) -> bool:
|
|
23
|
+
"""Validate package name/specifier format for pip install.
|
|
24
|
+
|
|
25
|
+
Allows standard pip formats: package, package==version, package[extra].
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
package: Package name or specifier to validate.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
True if package name is valid, False otherwise.
|
|
32
|
+
"""
|
|
33
|
+
if not package:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
return bool(_PACKAGE_SPEC_PATTERN.fullmatch(package))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def validate_package_names(packages: list[str]) -> None:
|
|
40
|
+
"""Validate all package names in a list.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
packages: List of package names or specifiers to validate.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If any package name is invalid.
|
|
47
|
+
"""
|
|
48
|
+
for pkg in packages:
|
|
49
|
+
if not validate_package_name(pkg):
|
|
50
|
+
raise ValueError(f"Invalid package name format: {pkg}")
|
aip_agents/tools/__init__.py
CHANGED
|
@@ -46,8 +46,10 @@ _register_optional("aip_agents.tools.code_sandbox", "E2BCodeSandboxTool")
|
|
|
46
46
|
_register_optional("aip_agents.tools.document_loader", "DocxReaderTool")
|
|
47
47
|
_register_optional("aip_agents.tools.document_loader", "ExcelReaderTool")
|
|
48
48
|
_register_optional("aip_agents.tools.document_loader", "PDFReaderTool")
|
|
49
|
+
_register_optional("aip_agents.tools.execute_ptc_code", "create_execute_ptc_code_tool")
|
|
49
50
|
|
|
50
51
|
if TYPE_CHECKING:
|
|
51
52
|
from aip_agents.tools.browser_use import BrowserUseTool
|
|
52
53
|
from aip_agents.tools.code_sandbox import E2BCodeSandboxTool
|
|
53
54
|
from aip_agents.tools.document_loader import DocxReaderTool, ExcelReaderTool, PDFReaderTool
|
|
55
|
+
from aip_agents.tools.execute_ptc_code import create_execute_ptc_code_tool
|