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,240 @@
|
|
|
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 | None,
|
|
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=None if timeout is None else 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
|
+
raw_timeout = server_config.get("timeout")
|
|
115
|
+
timeout = None if raw_timeout is None else float(raw_timeout)
|
|
116
|
+
transport = server_config.get("transport", "streamable-http").lower().replace("_", "-")
|
|
117
|
+
max_retries = 3
|
|
118
|
+
|
|
119
|
+
last_error: Exception | None = None
|
|
120
|
+
backoff = 0.5
|
|
121
|
+
|
|
122
|
+
for attempt in range(max_retries):
|
|
123
|
+
try:
|
|
124
|
+
return await _execute_tool_call(url, transport, headers, timeout, tool_name, arguments)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
last_error = e
|
|
127
|
+
if attempt < max_retries - 1 and _is_transient_error(e):
|
|
128
|
+
await asyncio.sleep(backoff)
|
|
129
|
+
backoff *= 2
|
|
130
|
+
continue
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
raise RuntimeError(f"MCP tool call failed: {last_error}") from last_error
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _run_async(coro):
|
|
137
|
+
"""Run an async coroutine, handling existing event loops (e.g., in Jupyter/E2B).
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
coro: The coroutine to run.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
The result of the coroutine.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
loop = asyncio.get_running_loop()
|
|
147
|
+
except RuntimeError:
|
|
148
|
+
# No running loop, use asyncio.run()
|
|
149
|
+
return asyncio.run(coro)
|
|
150
|
+
|
|
151
|
+
# There's a running loop - we need to run in a separate thread
|
|
152
|
+
import concurrent.futures
|
|
153
|
+
|
|
154
|
+
def run_in_new_loop():
|
|
155
|
+
"""Run coroutine in a new event loop in a thread."""
|
|
156
|
+
new_loop = asyncio.new_event_loop()
|
|
157
|
+
asyncio.set_event_loop(new_loop)
|
|
158
|
+
try:
|
|
159
|
+
result = new_loop.run_until_complete(coro)
|
|
160
|
+
new_loop.run_until_complete(new_loop.shutdown_asyncgens())
|
|
161
|
+
if hasattr(new_loop, "shutdown_default_executor"):
|
|
162
|
+
new_loop.run_until_complete(new_loop.shutdown_default_executor())
|
|
163
|
+
return result
|
|
164
|
+
finally:
|
|
165
|
+
new_loop.close()
|
|
166
|
+
|
|
167
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
168
|
+
future = executor.submit(run_in_new_loop)
|
|
169
|
+
return future.result()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def call_tool(server_name: str, tool_name: str, arguments: dict[str, Any]) -> Any:
|
|
173
|
+
"""Call an MCP tool (sync wrapper).
|
|
174
|
+
|
|
175
|
+
This is a synchronous wrapper around the async MCP SDK for easier use
|
|
176
|
+
in LLM-generated code. Handles both regular scripts and Jupyter/E2B environments.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
server_name: Name of the MCP server.
|
|
180
|
+
tool_name: Name of the tool to call.
|
|
181
|
+
arguments: Arguments to pass to the tool.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Normalized tool result.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
RuntimeError: If the tool call fails.
|
|
188
|
+
"""
|
|
189
|
+
return _run_async(_call_tool_async(server_name, tool_name, arguments))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _normalize_result(result: Any) -> Any:
|
|
193
|
+
"""Normalize MCP tool result.
|
|
194
|
+
|
|
195
|
+
Normalization rules:
|
|
196
|
+
- If result has content array with single text item: return text (JSON-parsed if possible)
|
|
197
|
+
- Otherwise return structured object with text and non_text lists
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
result: MCP CallToolResult object.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Normalized result value.
|
|
204
|
+
"""
|
|
205
|
+
# Handle MCP SDK result object
|
|
206
|
+
content = getattr(result, "content", [])
|
|
207
|
+
if not content:
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
text_contents: list[str] = []
|
|
211
|
+
non_text_contents: list[Any] = []
|
|
212
|
+
|
|
213
|
+
for item in content:
|
|
214
|
+
item_type = getattr(item, "type", None)
|
|
215
|
+
if item_type == "text":
|
|
216
|
+
text_contents.append(getattr(item, "text", ""))
|
|
217
|
+
else:
|
|
218
|
+
# Convert non-text content to dict for serialization
|
|
219
|
+
non_text_contents.append({
|
|
220
|
+
"type": item_type,
|
|
221
|
+
**{k: v for k, v in vars(item).items() if k != "type"}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
# Single text content, no non-text content
|
|
225
|
+
if len(text_contents) == 1 and not non_text_contents:
|
|
226
|
+
text = text_contents[0]
|
|
227
|
+
# Try to parse as JSON
|
|
228
|
+
stripped = text.strip()
|
|
229
|
+
if stripped.startswith(("{", "[")):
|
|
230
|
+
try:
|
|
231
|
+
return json.loads(text)
|
|
232
|
+
except json.JSONDecodeError:
|
|
233
|
+
pass
|
|
234
|
+
return text
|
|
235
|
+
|
|
236
|
+
# Return structured object for complex results
|
|
237
|
+
return {
|
|
238
|
+
"text": text_contents,
|
|
239
|
+
"non_text": non_text_contents,
|
|
240
|
+
}
|
aip_agents/ptc/naming.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
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 dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
# Reserved module names that cannot be used for server packages.
|
|
16
|
+
RESERVED_MODULE_NAMES = frozenset({"ptc_helper", "mcp_client", "custom"})
|
|
17
|
+
|
|
18
|
+
# Default placeholder for example values.
|
|
19
|
+
DEFAULT_EXAMPLE_PLACEHOLDER = '"example"'
|
|
20
|
+
|
|
21
|
+
# Shared kwargs signature fragment.
|
|
22
|
+
KWARGS_ANY_PARAMS = "**kwargs: Any"
|
|
23
|
+
RESERVED_CUSTOM_PARAM_NAMES = frozenset({"kwargs"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class CustomToolFieldSpec:
|
|
28
|
+
"""Projected field metadata for a custom tool schema property."""
|
|
29
|
+
|
|
30
|
+
schema_key: str
|
|
31
|
+
param_name: str
|
|
32
|
+
type_hint: str
|
|
33
|
+
required: bool
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class CustomToolRuntimeParamSpec:
|
|
38
|
+
"""Runtime signature metadata for inspect.Signature reconstruction."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
kind: str
|
|
42
|
+
annotation_expr: str
|
|
43
|
+
has_default: bool = False
|
|
44
|
+
default_expr: str | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class CustomToolSignatureProjection:
|
|
49
|
+
"""Canonical custom-tool signature projection derived from JSON schema."""
|
|
50
|
+
|
|
51
|
+
field_specs: list[CustomToolFieldSpec]
|
|
52
|
+
canonical_params: str
|
|
53
|
+
runtime_params: list[CustomToolRuntimeParamSpec]
|
|
54
|
+
schema_to_param: dict[str, str]
|
|
55
|
+
param_to_schema: dict[str, str]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def sanitize_module_name(name: str) -> str:
|
|
59
|
+
"""Sanitize server name for use as Python module name.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
name: Original server name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Valid Python module name (lowercase, underscored).
|
|
66
|
+
"""
|
|
67
|
+
# Replace non-alphanumeric chars with underscore
|
|
68
|
+
sanitized = re.sub(r"[^a-zA-Z0-9]", "_", name)
|
|
69
|
+
# Remove leading digits
|
|
70
|
+
sanitized = re.sub(r"^\d+", "", sanitized)
|
|
71
|
+
# Remove consecutive underscores
|
|
72
|
+
sanitized = re.sub(r"_+", "_", sanitized)
|
|
73
|
+
# Strip leading/trailing underscores
|
|
74
|
+
sanitized = sanitized.strip("_")
|
|
75
|
+
# Ensure not empty
|
|
76
|
+
if not sanitized:
|
|
77
|
+
sanitized = "server"
|
|
78
|
+
sanitized = sanitized.lower()
|
|
79
|
+
# Avoid Python keywords
|
|
80
|
+
if keyword.iskeyword(sanitized):
|
|
81
|
+
sanitized = f"{sanitized}_"
|
|
82
|
+
return sanitized
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def sanitize_module_name_with_reserved(name: str) -> str:
|
|
86
|
+
"""Sanitize server name, avoiding reserved module names.
|
|
87
|
+
|
|
88
|
+
If the sanitized name collides with a reserved name (e.g., ptc_helper),
|
|
89
|
+
appends '_mcp' suffix to avoid collision.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
name: Original server name.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Valid Python module name that does not collide with reserved names.
|
|
96
|
+
"""
|
|
97
|
+
sanitized = sanitize_module_name(name)
|
|
98
|
+
if sanitized in RESERVED_MODULE_NAMES:
|
|
99
|
+
return f"{sanitized}_mcp"
|
|
100
|
+
return sanitized
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def sanitize_function_name(name: str) -> str:
|
|
104
|
+
"""Sanitize tool name for use as Python function name.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
name: Original tool name.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Valid Python function name (lowercase, underscored).
|
|
111
|
+
"""
|
|
112
|
+
return sanitize_module_name(name)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def sanitize_param_name(name: str) -> str:
|
|
116
|
+
"""Sanitize parameter name for use as Python parameter.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
name: Original parameter name (e.g., userId, user-id).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Valid Python parameter name (lowercase, underscored).
|
|
123
|
+
"""
|
|
124
|
+
return sanitize_module_name(name)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_valid_identifier(name: str) -> bool:
|
|
128
|
+
"""Check if a string is a valid Python identifier and not a keyword.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
name: Candidate identifier.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True if name is a valid identifier and not a keyword.
|
|
135
|
+
"""
|
|
136
|
+
return name.isidentifier() and not keyword.iskeyword(name)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _json_scalar_type_to_python(json_type: Any) -> str:
|
|
140
|
+
"""Convert a scalar JSON schema type token into a Python type hint token."""
|
|
141
|
+
type_map = {
|
|
142
|
+
"string": "str",
|
|
143
|
+
"integer": "int",
|
|
144
|
+
"number": "float",
|
|
145
|
+
"boolean": "bool",
|
|
146
|
+
"array": "list",
|
|
147
|
+
"object": "dict",
|
|
148
|
+
"null": "None",
|
|
149
|
+
}
|
|
150
|
+
return type_map.get(json_type, "Any") if isinstance(json_type, str) else "Any"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _json_union_type_to_python(json_types: list[Any]) -> str:
|
|
154
|
+
"""Convert JSON schema union type list into a Python type hint expression."""
|
|
155
|
+
if not json_types:
|
|
156
|
+
return "Any"
|
|
157
|
+
|
|
158
|
+
converted_types: list[str] = []
|
|
159
|
+
seen: set[str] = set()
|
|
160
|
+
has_none = False
|
|
161
|
+
|
|
162
|
+
for json_type in json_types:
|
|
163
|
+
python_type = _json_scalar_type_to_python(json_type)
|
|
164
|
+
if python_type == "None":
|
|
165
|
+
has_none = True
|
|
166
|
+
continue
|
|
167
|
+
if python_type in seen:
|
|
168
|
+
continue
|
|
169
|
+
seen.add(python_type)
|
|
170
|
+
converted_types.append(python_type)
|
|
171
|
+
|
|
172
|
+
if has_none:
|
|
173
|
+
converted_types.append("None")
|
|
174
|
+
|
|
175
|
+
if not converted_types:
|
|
176
|
+
return "Any"
|
|
177
|
+
|
|
178
|
+
return " | ".join(converted_types)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def json_type_to_python(json_type: str | list) -> str:
|
|
182
|
+
"""Convert JSON schema type to Python type hint.
|
|
183
|
+
|
|
184
|
+
Handles union types (list form) by converting each type and joining with |.
|
|
185
|
+
For example, ["string", "null"] -> "str | None".
|
|
186
|
+
|
|
187
|
+
Edge cases handled:
|
|
188
|
+
- Empty list returns "Any"
|
|
189
|
+
- Duplicate types are deduplicated while preserving order
|
|
190
|
+
- "null" is normalized to end of union (e.g., ["null", "string"] -> "str | None")
|
|
191
|
+
- Unknown types map to "Any" (deduplicated if multiple)
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
json_type: JSON schema type (string or list of strings for union types).
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Python type hint string.
|
|
198
|
+
"""
|
|
199
|
+
if isinstance(json_type, list):
|
|
200
|
+
return _json_union_type_to_python(json_type)
|
|
201
|
+
|
|
202
|
+
return _json_scalar_type_to_python(json_type)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def schema_to_params(schema: dict[str, Any]) -> str:
|
|
206
|
+
"""Convert JSON schema to Python function parameters.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
schema: JSON schema for tool input.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Function parameter string.
|
|
213
|
+
"""
|
|
214
|
+
properties = schema.get("properties", {})
|
|
215
|
+
required = set(schema.get("required", []))
|
|
216
|
+
|
|
217
|
+
if not properties:
|
|
218
|
+
return KWARGS_ANY_PARAMS
|
|
219
|
+
|
|
220
|
+
params: list[str] = []
|
|
221
|
+
optional_params: list[str] = []
|
|
222
|
+
|
|
223
|
+
for prop_name, prop_schema in properties.items():
|
|
224
|
+
safe_name = sanitize_param_name(prop_name)
|
|
225
|
+
type_hint = json_type_to_python(prop_schema.get("type", "any"))
|
|
226
|
+
|
|
227
|
+
if prop_name in required:
|
|
228
|
+
params.append(f"{safe_name}: {type_hint}")
|
|
229
|
+
else:
|
|
230
|
+
optional_params.append(f"{safe_name}: {_optional_type_hint(type_hint)} = None")
|
|
231
|
+
|
|
232
|
+
# Required params first, then optional
|
|
233
|
+
all_params = params + optional_params
|
|
234
|
+
return ", ".join(all_params) if all_params else KWARGS_ANY_PARAMS
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _optional_type_hint(type_hint: str) -> str:
|
|
238
|
+
"""Return nullable hint while preserving explicit None type.
|
|
239
|
+
|
|
240
|
+
Avoids duplicating None if the type hint already includes it
|
|
241
|
+
(e.g., "str | None" stays as "str | None", not "str | None | None").
|
|
242
|
+
|
|
243
|
+
Also deduplicates if None appears multiple times in the union
|
|
244
|
+
(e.g., "str | None | None" becomes "str | None").
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
type_hint: The base type hint string.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Type hint string that includes None.
|
|
251
|
+
"""
|
|
252
|
+
normalized = type_hint.strip()
|
|
253
|
+
if not normalized:
|
|
254
|
+
return "Any | None"
|
|
255
|
+
|
|
256
|
+
if normalized == "None":
|
|
257
|
+
return "None"
|
|
258
|
+
|
|
259
|
+
parts = [p.strip() for p in normalized.split("|") if p.strip()]
|
|
260
|
+
if not parts:
|
|
261
|
+
return "Any | None"
|
|
262
|
+
|
|
263
|
+
# If None is already in the union, deduplicate and return
|
|
264
|
+
if "None" in parts:
|
|
265
|
+
seen = set()
|
|
266
|
+
deduplicated: list[str] = []
|
|
267
|
+
for part in parts:
|
|
268
|
+
if part not in seen:
|
|
269
|
+
seen.add(part)
|
|
270
|
+
deduplicated.append(part)
|
|
271
|
+
return " | ".join(deduplicated)
|
|
272
|
+
|
|
273
|
+
return " | ".join([*parts, "None"])
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _empty_custom_tool_signature_projection() -> CustomToolSignatureProjection:
|
|
277
|
+
"""Return default projection for schema without properties."""
|
|
278
|
+
return CustomToolSignatureProjection(
|
|
279
|
+
field_specs=[],
|
|
280
|
+
canonical_params=KWARGS_ANY_PARAMS,
|
|
281
|
+
runtime_params=[
|
|
282
|
+
CustomToolRuntimeParamSpec(
|
|
283
|
+
name="kwargs",
|
|
284
|
+
kind="VAR_KEYWORD",
|
|
285
|
+
annotation_expr="Any",
|
|
286
|
+
)
|
|
287
|
+
],
|
|
288
|
+
schema_to_param={},
|
|
289
|
+
param_to_schema={},
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _validate_custom_param_name(
|
|
294
|
+
schema_key: str,
|
|
295
|
+
param_name: str,
|
|
296
|
+
param_to_schema: dict[str, str],
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Validate reserved-name and sanitization-collision constraints."""
|
|
299
|
+
if param_name in RESERVED_CUSTOM_PARAM_NAMES:
|
|
300
|
+
raise ValueError(f"Custom tool schema parameter '{schema_key}' maps to reserved name '{param_name}'")
|
|
301
|
+
|
|
302
|
+
prior_schema_key = param_to_schema.get(param_name)
|
|
303
|
+
if prior_schema_key and prior_schema_key != schema_key:
|
|
304
|
+
raise ValueError(
|
|
305
|
+
"Custom tool schema parameter sanitization collision: "
|
|
306
|
+
f"'{prior_schema_key}' and '{schema_key}' both map to '{param_name}'"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _build_canonical_custom_params(field_specs: list[CustomToolFieldSpec]) -> str:
|
|
311
|
+
"""Build keyword-only canonical parameter signature string."""
|
|
312
|
+
canonical_parts = ["*"]
|
|
313
|
+
for field in field_specs:
|
|
314
|
+
if field.required:
|
|
315
|
+
canonical_parts.append(f"{field.param_name}: {field.type_hint}")
|
|
316
|
+
continue
|
|
317
|
+
canonical_parts.append(f"{field.param_name}: {_optional_type_hint(field.type_hint)} = None")
|
|
318
|
+
canonical_parts.append(KWARGS_ANY_PARAMS)
|
|
319
|
+
return ", ".join(canonical_parts)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def project_custom_tool_signature(schema: dict[str, Any]) -> CustomToolSignatureProjection:
|
|
323
|
+
"""Project schema into a canonical custom-tool signature contract.
|
|
324
|
+
|
|
325
|
+
This helper is custom-tool specific and intentionally independent from
|
|
326
|
+
`schema_to_params(...)` so MCP behavior remains unchanged.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
schema: JSON schema for custom tool input.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Canonical projection for rendering and runtime introspection.
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
ValueError: If parameter sanitization causes collisions.
|
|
336
|
+
"""
|
|
337
|
+
properties = schema.get("properties", {})
|
|
338
|
+
required = set(schema.get("required", []))
|
|
339
|
+
|
|
340
|
+
if not properties:
|
|
341
|
+
return _empty_custom_tool_signature_projection()
|
|
342
|
+
|
|
343
|
+
field_specs: list[CustomToolFieldSpec] = []
|
|
344
|
+
runtime_params: list[CustomToolRuntimeParamSpec] = []
|
|
345
|
+
schema_to_param: dict[str, str] = {}
|
|
346
|
+
param_to_schema: dict[str, str] = {}
|
|
347
|
+
|
|
348
|
+
for schema_key, prop_schema in properties.items():
|
|
349
|
+
param_name = sanitize_param_name(schema_key)
|
|
350
|
+
_validate_custom_param_name(schema_key, param_name, param_to_schema)
|
|
351
|
+
|
|
352
|
+
type_hint = json_type_to_python(prop_schema.get("type", "any"))
|
|
353
|
+
is_required = schema_key in required
|
|
354
|
+
|
|
355
|
+
field_specs.append(
|
|
356
|
+
CustomToolFieldSpec(
|
|
357
|
+
schema_key=schema_key,
|
|
358
|
+
param_name=param_name,
|
|
359
|
+
type_hint=type_hint,
|
|
360
|
+
required=is_required,
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
runtime_params.append(
|
|
364
|
+
CustomToolRuntimeParamSpec(
|
|
365
|
+
name=param_name,
|
|
366
|
+
kind="KEYWORD_ONLY",
|
|
367
|
+
annotation_expr=type_hint if is_required else _optional_type_hint(type_hint),
|
|
368
|
+
has_default=not is_required,
|
|
369
|
+
default_expr="None" if not is_required else None,
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
schema_to_param[schema_key] = param_name
|
|
373
|
+
param_to_schema[param_name] = schema_key
|
|
374
|
+
|
|
375
|
+
runtime_params.append(
|
|
376
|
+
CustomToolRuntimeParamSpec(
|
|
377
|
+
name="kwargs",
|
|
378
|
+
kind="VAR_KEYWORD",
|
|
379
|
+
annotation_expr="Any",
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return CustomToolSignatureProjection(
|
|
384
|
+
field_specs=field_specs,
|
|
385
|
+
canonical_params=_build_canonical_custom_params(field_specs),
|
|
386
|
+
runtime_params=runtime_params,
|
|
387
|
+
schema_to_param=schema_to_param,
|
|
388
|
+
param_to_schema=param_to_schema,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def example_value_from_schema(
|
|
393
|
+
prop_schema: dict[str, Any],
|
|
394
|
+
default_placeholder: str = DEFAULT_EXAMPLE_PLACEHOLDER,
|
|
395
|
+
) -> str:
|
|
396
|
+
"""Return an example value for a schema property.
|
|
397
|
+
|
|
398
|
+
Prefers schema-provided examples, defaults, or enums, and falls back
|
|
399
|
+
to type-based placeholders.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
prop_schema: Property schema from JSON schema.
|
|
403
|
+
default_placeholder: Placeholder value to use for unknown types.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Example value as a Python literal string.
|
|
407
|
+
"""
|
|
408
|
+
if prop_schema.get("examples"):
|
|
409
|
+
return repr(prop_schema["examples"][0])
|
|
410
|
+
if "default" in prop_schema:
|
|
411
|
+
return repr(prop_schema["default"])
|
|
412
|
+
if prop_schema.get("enum"):
|
|
413
|
+
return repr(prop_schema["enum"][0])
|
|
414
|
+
|
|
415
|
+
prop_type = prop_schema.get("type", "string")
|
|
416
|
+
type_placeholders = {
|
|
417
|
+
"integer": "1",
|
|
418
|
+
"number": "1.0",
|
|
419
|
+
"boolean": "True",
|
|
420
|
+
"array": "[]",
|
|
421
|
+
"object": "{}",
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if prop_type in type_placeholders:
|
|
425
|
+
return type_placeholders[prop_type]
|
|
426
|
+
|
|
427
|
+
if prop_type == "string":
|
|
428
|
+
return default_placeholder
|
|
429
|
+
|
|
430
|
+
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)
|