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,239 @@
|
|
|
1
|
+
"""MCP Client for sandbox PTC execution.
|
|
2
|
+
|
|
3
|
+
This module uses the official MCP Python SDK to call MCP tools from within
|
|
4
|
+
the E2B sandbox. It provides sync wrappers around the async SDK.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
from mcp import ClientSession
|
|
14
|
+
from mcp.client.streamable_http import streamable_http_client
|
|
15
|
+
from mcp.client.sse import sse_client
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_config() -> dict[str, Any]:
|
|
19
|
+
"""Load PTC config from ptc_config.json."""
|
|
20
|
+
config_path = os.path.join(os.path.dirname(__file__), "..", "ptc_config.json")
|
|
21
|
+
with open(config_path) as f:
|
|
22
|
+
return json.load(f)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_CONFIG: dict[str, Any] | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_config() -> dict[str, Any]:
|
|
29
|
+
"""Get cached config."""
|
|
30
|
+
global _CONFIG
|
|
31
|
+
if _CONFIG is None:
|
|
32
|
+
_CONFIG = load_config()
|
|
33
|
+
return _CONFIG
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_transient_error(exc: BaseException) -> bool:
|
|
37
|
+
"""Return True for retryable network errors."""
|
|
38
|
+
if isinstance(exc, asyncio.CancelledError):
|
|
39
|
+
return False
|
|
40
|
+
# Network-related errors that are retryable
|
|
41
|
+
if isinstance(exc, (httpx.HTTPError, OSError, ConnectionError, TimeoutError)):
|
|
42
|
+
return True
|
|
43
|
+
# MCP SDK wraps network errors in ExceptionGroup from anyio TaskGroups
|
|
44
|
+
if isinstance(exc, BaseExceptionGroup):
|
|
45
|
+
return any(_is_transient_error(sub) for sub in exc.exceptions)
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def _execute_tool_call(
|
|
50
|
+
url: str,
|
|
51
|
+
transport: str,
|
|
52
|
+
headers: dict[str, str] | None,
|
|
53
|
+
timeout: float,
|
|
54
|
+
tool_name: str,
|
|
55
|
+
arguments: dict[str, Any],
|
|
56
|
+
) -> Any:
|
|
57
|
+
"""Execute a single MCP tool call (no retry)."""
|
|
58
|
+
if transport == "sse":
|
|
59
|
+
async with sse_client(
|
|
60
|
+
url=url,
|
|
61
|
+
timeout=timeout,
|
|
62
|
+
sse_read_timeout=timeout * 2,
|
|
63
|
+
headers=headers,
|
|
64
|
+
) as (read_stream, write_stream):
|
|
65
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
66
|
+
await session.initialize()
|
|
67
|
+
result = await session.call_tool(tool_name, arguments)
|
|
68
|
+
return _normalize_result(result)
|
|
69
|
+
|
|
70
|
+
# Default: streamable-http
|
|
71
|
+
async with httpx.AsyncClient(
|
|
72
|
+
timeout=httpx.Timeout(timeout),
|
|
73
|
+
headers=headers,
|
|
74
|
+
) as http_client:
|
|
75
|
+
async with streamable_http_client(
|
|
76
|
+
url=url,
|
|
77
|
+
http_client=http_client,
|
|
78
|
+
) as (read_stream, write_stream, _):
|
|
79
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
80
|
+
await session.initialize()
|
|
81
|
+
result = await session.call_tool(tool_name, arguments)
|
|
82
|
+
return _normalize_result(result)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def _call_tool_async(
|
|
86
|
+
server_name: str,
|
|
87
|
+
tool_name: str,
|
|
88
|
+
arguments: dict[str, Any],
|
|
89
|
+
) -> Any:
|
|
90
|
+
"""Call an MCP tool asynchronously with retry.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
server_name: Name of the MCP server.
|
|
94
|
+
tool_name: Name of the tool to call.
|
|
95
|
+
arguments: Arguments to pass to the tool.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Normalized tool result.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
RuntimeError: If all retry attempts fail.
|
|
102
|
+
"""
|
|
103
|
+
config = get_config()
|
|
104
|
+
server_config = config["servers"].get(server_name)
|
|
105
|
+
if not server_config:
|
|
106
|
+
raise RuntimeError(f"Server '{server_name}' not found in config")
|
|
107
|
+
|
|
108
|
+
allowed = server_config.get("allowed_tools")
|
|
109
|
+
if allowed and tool_name not in allowed:
|
|
110
|
+
raise PermissionError(f"Tool '{tool_name}' not allowed on server '{server_name}'")
|
|
111
|
+
|
|
112
|
+
url = server_config["url"]
|
|
113
|
+
headers = server_config.get("headers") or None
|
|
114
|
+
timeout = float(server_config.get("timeout", 60.0))
|
|
115
|
+
transport = server_config.get("transport", "streamable-http").lower().replace("_", "-")
|
|
116
|
+
max_retries = 3
|
|
117
|
+
|
|
118
|
+
last_error: Exception | None = None
|
|
119
|
+
backoff = 0.5
|
|
120
|
+
|
|
121
|
+
for attempt in range(max_retries):
|
|
122
|
+
try:
|
|
123
|
+
return await _execute_tool_call(url, transport, headers, timeout, tool_name, arguments)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
last_error = e
|
|
126
|
+
if attempt < max_retries - 1 and _is_transient_error(e):
|
|
127
|
+
await asyncio.sleep(backoff)
|
|
128
|
+
backoff *= 2
|
|
129
|
+
continue
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
raise RuntimeError(f"MCP tool call failed: {last_error}") from last_error
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _run_async(coro):
|
|
136
|
+
"""Run an async coroutine, handling existing event loops (e.g., in Jupyter/E2B).
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
coro: The coroutine to run.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
The result of the coroutine.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
loop = asyncio.get_running_loop()
|
|
146
|
+
except RuntimeError:
|
|
147
|
+
# No running loop, use asyncio.run()
|
|
148
|
+
return asyncio.run(coro)
|
|
149
|
+
|
|
150
|
+
# There's a running loop - we need to run in a separate thread
|
|
151
|
+
import concurrent.futures
|
|
152
|
+
|
|
153
|
+
def run_in_new_loop():
|
|
154
|
+
"""Run coroutine in a new event loop in a thread."""
|
|
155
|
+
new_loop = asyncio.new_event_loop()
|
|
156
|
+
asyncio.set_event_loop(new_loop)
|
|
157
|
+
try:
|
|
158
|
+
result = new_loop.run_until_complete(coro)
|
|
159
|
+
new_loop.run_until_complete(new_loop.shutdown_asyncgens())
|
|
160
|
+
if hasattr(new_loop, "shutdown_default_executor"):
|
|
161
|
+
new_loop.run_until_complete(new_loop.shutdown_default_executor())
|
|
162
|
+
return result
|
|
163
|
+
finally:
|
|
164
|
+
new_loop.close()
|
|
165
|
+
|
|
166
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
167
|
+
future = executor.submit(run_in_new_loop)
|
|
168
|
+
return future.result()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def call_tool(server_name: str, tool_name: str, arguments: dict[str, Any]) -> Any:
|
|
172
|
+
"""Call an MCP tool (sync wrapper).
|
|
173
|
+
|
|
174
|
+
This is a synchronous wrapper around the async MCP SDK for easier use
|
|
175
|
+
in LLM-generated code. Handles both regular scripts and Jupyter/E2B environments.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
server_name: Name of the MCP server.
|
|
179
|
+
tool_name: Name of the tool to call.
|
|
180
|
+
arguments: Arguments to pass to the tool.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Normalized tool result.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
RuntimeError: If the tool call fails.
|
|
187
|
+
"""
|
|
188
|
+
return _run_async(_call_tool_async(server_name, tool_name, arguments))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _normalize_result(result: Any) -> Any:
|
|
192
|
+
"""Normalize MCP tool result.
|
|
193
|
+
|
|
194
|
+
Normalization rules:
|
|
195
|
+
- If result has content array with single text item: return text (JSON-parsed if possible)
|
|
196
|
+
- Otherwise return structured object with text and non_text lists
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
result: MCP CallToolResult object.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Normalized result value.
|
|
203
|
+
"""
|
|
204
|
+
# Handle MCP SDK result object
|
|
205
|
+
content = getattr(result, "content", [])
|
|
206
|
+
if not content:
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
text_contents: list[str] = []
|
|
210
|
+
non_text_contents: list[Any] = []
|
|
211
|
+
|
|
212
|
+
for item in content:
|
|
213
|
+
item_type = getattr(item, "type", None)
|
|
214
|
+
if item_type == "text":
|
|
215
|
+
text_contents.append(getattr(item, "text", ""))
|
|
216
|
+
else:
|
|
217
|
+
# Convert non-text content to dict for serialization
|
|
218
|
+
non_text_contents.append({
|
|
219
|
+
"type": item_type,
|
|
220
|
+
**{k: v for k, v in vars(item).items() if k != "type"}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
# Single text content, no non-text content
|
|
224
|
+
if len(text_contents) == 1 and not non_text_contents:
|
|
225
|
+
text = text_contents[0]
|
|
226
|
+
# Try to parse as JSON
|
|
227
|
+
stripped = text.strip()
|
|
228
|
+
if stripped.startswith(("{", "[")):
|
|
229
|
+
try:
|
|
230
|
+
return json.loads(text)
|
|
231
|
+
except json.JSONDecodeError:
|
|
232
|
+
pass
|
|
233
|
+
return text
|
|
234
|
+
|
|
235
|
+
# Return structured object for complex results
|
|
236
|
+
return {
|
|
237
|
+
"text": text_contents,
|
|
238
|
+
"non_text": non_text_contents,
|
|
239
|
+
}
|
aip_agents/ptc/naming.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Naming utilities for PTC module.
|
|
2
|
+
|
|
3
|
+
Shared naming helpers for sanitizing module, function, and parameter names
|
|
4
|
+
used in sandbox code generation and prompt building.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import keyword
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
# Reserved module names that cannot be used for server packages.
|
|
15
|
+
RESERVED_MODULE_NAMES = frozenset({"ptc_helper", "mcp_client"})
|
|
16
|
+
|
|
17
|
+
# Default placeholder for example values.
|
|
18
|
+
DEFAULT_EXAMPLE_PLACEHOLDER = '"example"'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def sanitize_module_name(name: str) -> str:
|
|
22
|
+
"""Sanitize server name for use as Python module name.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name: Original server name.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Valid Python module name (lowercase, underscored).
|
|
29
|
+
"""
|
|
30
|
+
# Replace non-alphanumeric chars with underscore
|
|
31
|
+
sanitized = re.sub(r"[^a-zA-Z0-9]", "_", name)
|
|
32
|
+
# Remove leading digits
|
|
33
|
+
sanitized = re.sub(r"^\d+", "", sanitized)
|
|
34
|
+
# Remove consecutive underscores
|
|
35
|
+
sanitized = re.sub(r"_+", "_", sanitized)
|
|
36
|
+
# Strip leading/trailing underscores
|
|
37
|
+
sanitized = sanitized.strip("_")
|
|
38
|
+
# Ensure not empty
|
|
39
|
+
if not sanitized:
|
|
40
|
+
sanitized = "server"
|
|
41
|
+
sanitized = sanitized.lower()
|
|
42
|
+
# Avoid Python keywords
|
|
43
|
+
if keyword.iskeyword(sanitized):
|
|
44
|
+
sanitized = f"{sanitized}_"
|
|
45
|
+
return sanitized
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def sanitize_module_name_with_reserved(name: str) -> str:
|
|
49
|
+
"""Sanitize server name, avoiding reserved module names.
|
|
50
|
+
|
|
51
|
+
If the sanitized name collides with a reserved name (e.g., ptc_helper),
|
|
52
|
+
appends '_mcp' suffix to avoid collision.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: Original server name.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Valid Python module name that does not collide with reserved names.
|
|
59
|
+
"""
|
|
60
|
+
sanitized = sanitize_module_name(name)
|
|
61
|
+
if sanitized in RESERVED_MODULE_NAMES:
|
|
62
|
+
return f"{sanitized}_mcp"
|
|
63
|
+
return sanitized
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def sanitize_function_name(name: str) -> str:
|
|
67
|
+
"""Sanitize tool name for use as Python function name.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
name: Original tool name.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Valid Python function name (lowercase, underscored).
|
|
74
|
+
"""
|
|
75
|
+
return sanitize_module_name(name)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def sanitize_param_name(name: str) -> str:
|
|
79
|
+
"""Sanitize parameter name for use as Python parameter.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
name: Original parameter name (e.g., userId, user-id).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Valid Python parameter name (lowercase, underscored).
|
|
86
|
+
"""
|
|
87
|
+
return sanitize_module_name(name)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def json_type_to_python(json_type: str | list) -> str:
|
|
91
|
+
"""Convert JSON schema type to Python type hint.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
json_type: JSON schema type.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Python type hint string.
|
|
98
|
+
"""
|
|
99
|
+
if isinstance(json_type, list):
|
|
100
|
+
return "Any"
|
|
101
|
+
|
|
102
|
+
type_map = {
|
|
103
|
+
"string": "str",
|
|
104
|
+
"integer": "int",
|
|
105
|
+
"number": "float",
|
|
106
|
+
"boolean": "bool",
|
|
107
|
+
"array": "list",
|
|
108
|
+
"object": "dict",
|
|
109
|
+
"null": "None",
|
|
110
|
+
}
|
|
111
|
+
return type_map.get(json_type, "Any")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def schema_to_params(schema: dict[str, Any]) -> str:
|
|
115
|
+
"""Convert JSON schema to Python function parameters.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
schema: JSON schema for tool input.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Function parameter string.
|
|
122
|
+
"""
|
|
123
|
+
properties = schema.get("properties", {})
|
|
124
|
+
required = set(schema.get("required", []))
|
|
125
|
+
|
|
126
|
+
if not properties:
|
|
127
|
+
return "**kwargs: Any"
|
|
128
|
+
|
|
129
|
+
params: list[str] = []
|
|
130
|
+
optional_params: list[str] = []
|
|
131
|
+
|
|
132
|
+
for prop_name, prop_schema in properties.items():
|
|
133
|
+
safe_name = sanitize_param_name(prop_name)
|
|
134
|
+
type_hint = json_type_to_python(prop_schema.get("type", "any"))
|
|
135
|
+
|
|
136
|
+
if prop_name in required:
|
|
137
|
+
params.append(f"{safe_name}: {type_hint}")
|
|
138
|
+
else:
|
|
139
|
+
optional_params.append(f"{safe_name}: {type_hint} | None = None")
|
|
140
|
+
|
|
141
|
+
# Required params first, then optional
|
|
142
|
+
all_params = params + optional_params
|
|
143
|
+
return ", ".join(all_params) if all_params else "**kwargs: Any"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def example_value_from_schema(
|
|
147
|
+
prop_schema: dict[str, Any],
|
|
148
|
+
default_placeholder: str = DEFAULT_EXAMPLE_PLACEHOLDER,
|
|
149
|
+
) -> str:
|
|
150
|
+
"""Return an example value for a schema property.
|
|
151
|
+
|
|
152
|
+
Prefers schema-provided examples, defaults, or enums, and falls back
|
|
153
|
+
to type-based placeholders.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
prop_schema: Property schema from JSON schema.
|
|
157
|
+
default_placeholder: Placeholder value to use for unknown types.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Example value as a Python literal string.
|
|
161
|
+
"""
|
|
162
|
+
if prop_schema.get("examples"):
|
|
163
|
+
return repr(prop_schema["examples"][0])
|
|
164
|
+
if "default" in prop_schema:
|
|
165
|
+
return repr(prop_schema["default"])
|
|
166
|
+
if prop_schema.get("enum"):
|
|
167
|
+
return repr(prop_schema["enum"][0])
|
|
168
|
+
|
|
169
|
+
prop_type = prop_schema.get("type", "string")
|
|
170
|
+
type_placeholders = {
|
|
171
|
+
"integer": "1",
|
|
172
|
+
"number": "1.0",
|
|
173
|
+
"boolean": "True",
|
|
174
|
+
"array": "[]",
|
|
175
|
+
"object": "{}",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if prop_type in type_placeholders:
|
|
179
|
+
return type_placeholders[prop_type]
|
|
180
|
+
|
|
181
|
+
if prop_type == "string":
|
|
182
|
+
return default_placeholder
|
|
183
|
+
|
|
184
|
+
return default_placeholder
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Shared payload types for PTC.
|
|
2
|
+
|
|
3
|
+
This module defines the payload structure used across PTC implementations
|
|
4
|
+
(MCP, custom tools, etc.).
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class SandboxPayload:
|
|
15
|
+
"""Payload to upload to the sandbox for PTC execution.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
files: Mapping of file path -> content to upload (static bundle).
|
|
19
|
+
per_run_files: Mapping of file path -> content to upload on every run.
|
|
20
|
+
These files are always re-uploaded, even when the static bundle is cached.
|
|
21
|
+
env: Environment variables to set in the sandbox.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
files: dict[str, str] = field(default_factory=dict)
|
|
25
|
+
per_run_files: dict[str, str] = field(default_factory=dict)
|
|
26
|
+
env: dict[str, str] = field(default_factory=dict)
|