aip-agents-binary 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. aip_agents/__init__.py +66 -0
  2. aip_agents/a2a/__init__.py +19 -0
  3. aip_agents/a2a/server/__init__.py +10 -0
  4. aip_agents/a2a/server/base_executor.py +1125 -0
  5. aip_agents/a2a/server/google_adk_executor.py +198 -0
  6. aip_agents/a2a/server/langflow_executor.py +180 -0
  7. aip_agents/a2a/server/langgraph_executor.py +270 -0
  8. aip_agents/a2a/types.py +232 -0
  9. aip_agents/a2ui/__init__.py +26 -0
  10. aip_agents/a2ui/catalog_provider.py +109 -0
  11. aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
  12. aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
  13. aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
  14. aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
  15. aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
  16. aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
  17. aip_agents/a2ui/middleware.py +754 -0
  18. aip_agents/a2ui/models.py +153 -0
  19. aip_agents/a2ui/public_url.py +138 -0
  20. aip_agents/a2ui/stream_filter.py +67 -0
  21. aip_agents/agent/__init__.py +72 -0
  22. aip_agents/agent/base_agent.py +1437 -0
  23. aip_agents/agent/base_langgraph_agent.py +4048 -0
  24. aip_agents/agent/google_adk_agent.py +932 -0
  25. aip_agents/agent/google_adk_constants.py +11 -0
  26. aip_agents/agent/graph_runtime.py +185 -0
  27. aip_agents/agent/hitl/__init__.py +24 -0
  28. aip_agents/agent/hitl/config.py +28 -0
  29. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  30. aip_agents/agent/hitl/manager.py +532 -0
  31. aip_agents/agent/hitl/models.py +18 -0
  32. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  33. aip_agents/agent/hitl/prompt/base.py +42 -0
  34. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  35. aip_agents/agent/hitl/registry.py +149 -0
  36. aip_agents/agent/interface.py +138 -0
  37. aip_agents/agent/interfaces.py +65 -0
  38. aip_agents/agent/langflow_agent.py +464 -0
  39. aip_agents/agent/langgraph_react_agent.py +3741 -0
  40. aip_agents/agent/model_invocation_adapter.py +346 -0
  41. aip_agents/agent/system_instruction_context.py +34 -0
  42. aip_agents/agent/tool_execution_engine.py +587 -0
  43. aip_agents/audio_interface/__init__.py +30 -0
  44. aip_agents/audio_interface/audio_agent_adapter.py +254 -0
  45. aip_agents/audio_interface/config.py +146 -0
  46. aip_agents/audio_interface/errors.py +15 -0
  47. aip_agents/audio_interface/livekit_audio_session.py +504 -0
  48. aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
  49. aip_agents/audio_interface/session_factory.py +40 -0
  50. aip_agents/clients/__init__.py +10 -0
  51. aip_agents/clients/langflow/__init__.py +10 -0
  52. aip_agents/clients/langflow/client.py +477 -0
  53. aip_agents/clients/langflow/types.py +18 -0
  54. aip_agents/common/__init__.py +5 -0
  55. aip_agents/common/callable_introspection.py +17 -0
  56. aip_agents/constants.py +23 -0
  57. aip_agents/context/__init__.py +5 -0
  58. aip_agents/context/budget.py +237 -0
  59. aip_agents/context/compaction.py +339 -0
  60. aip_agents/context/metadata.py +169 -0
  61. aip_agents/context/offload.py +449 -0
  62. aip_agents/context/preparation.py +196 -0
  63. aip_agents/context/prompt.py +61 -0
  64. aip_agents/context/runtime.py +149 -0
  65. aip_agents/context/session_recall.py +69 -0
  66. aip_agents/context/state.py +48 -0
  67. aip_agents/context/utils.py +44 -0
  68. aip_agents/credentials/manager.py +132 -0
  69. aip_agents/examples/__init__.py +5 -0
  70. aip_agents/examples/audio_livekit_session.py +116 -0
  71. aip_agents/examples/compare_streaming_client.py +783 -0
  72. aip_agents/examples/compare_streaming_server.py +141 -0
  73. aip_agents/examples/gl_connectors_smoke.py +148 -0
  74. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  75. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  76. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  77. aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
  78. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  79. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  80. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  81. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  82. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  83. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
  84. aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
  85. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
  86. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  87. aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
  88. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  89. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  90. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
  91. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  92. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  93. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  94. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  95. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  96. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  97. aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
  98. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
  99. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
  100. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  101. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  102. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
  103. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  104. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
  105. aip_agents/examples/hello_world_filesystem.py +162 -0
  106. aip_agents/examples/hello_world_google_adk.py +41 -0
  107. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  108. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  109. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  110. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  111. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  112. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  113. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  114. aip_agents/examples/hello_world_langchain.py +28 -0
  115. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  116. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  117. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  118. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  119. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  120. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  121. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  122. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  123. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  124. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  125. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  126. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  127. aip_agents/examples/hello_world_langgraph.py +39 -0
  128. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
  129. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  130. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  131. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  132. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  133. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  134. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  135. aip_agents/examples/hello_world_langgraph_skills.py +62 -0
  136. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  137. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  138. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  139. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  140. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  141. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  142. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  143. aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
  144. aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
  145. aip_agents/examples/hello_world_pii_logger.py +21 -0
  146. aip_agents/examples/hello_world_ptc.py +49 -0
  147. aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
  148. aip_agents/examples/hello_world_sentry.py +133 -0
  149. aip_agents/examples/hello_world_step_limits.py +273 -0
  150. aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
  151. aip_agents/examples/hello_world_tool_output_client.py +55 -0
  152. aip_agents/examples/hello_world_tool_output_server.py +113 -0
  153. aip_agents/examples/hitl_demo.py +724 -0
  154. aip_agents/examples/mcp_configs/configs.py +63 -0
  155. aip_agents/examples/mcp_servers/common.py +76 -0
  156. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  157. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  158. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  159. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  160. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  161. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  162. aip_agents/examples/pii_demo_langgraph_server.py +125 -0
  163. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  164. aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
  165. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  166. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
  167. aip_agents/examples/tools/__init__.py +27 -0
  168. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  169. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  170. aip_agents/examples/tools/data_generator_tool.py +103 -0
  171. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  172. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  173. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  174. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  175. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  176. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  177. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  178. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  179. aip_agents/examples/tools/multiply_tool.py +43 -0
  180. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  181. aip_agents/examples/tools/random_chart_tool.py +142 -0
  182. aip_agents/examples/tools/serper_tool.py +202 -0
  183. aip_agents/examples/tools/stock_tools.py +82 -0
  184. aip_agents/examples/tools/table_generator_tool.py +167 -0
  185. aip_agents/examples/tools/time_tool.py +82 -0
  186. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  187. aip_agents/guardrails/__init__.py +83 -0
  188. aip_agents/guardrails/engines/__init__.py +69 -0
  189. aip_agents/guardrails/engines/base.py +90 -0
  190. aip_agents/guardrails/engines/nemo.py +101 -0
  191. aip_agents/guardrails/engines/phrase_matcher.py +113 -0
  192. aip_agents/guardrails/exceptions.py +39 -0
  193. aip_agents/guardrails/manager.py +163 -0
  194. aip_agents/guardrails/middleware.py +199 -0
  195. aip_agents/guardrails/schemas.py +63 -0
  196. aip_agents/guardrails/utils.py +45 -0
  197. aip_agents/integration/__init__.py +43 -0
  198. aip_agents/integration/agent.py +58 -0
  199. aip_agents/integration/guardrails.py +52 -0
  200. aip_agents/integration/hitl.py +71 -0
  201. aip_agents/integration/ptc.py +95 -0
  202. aip_agents/integration/skills.py +37 -0
  203. aip_agents/integration/storage.py +81 -0
  204. aip_agents/integration/version.py +103 -0
  205. aip_agents/mcp/__init__.py +1 -0
  206. aip_agents/mcp/client/__init__.py +60 -0
  207. aip_agents/mcp/client/base_mcp_client.py +377 -0
  208. aip_agents/mcp/client/connection_manager.py +231 -0
  209. aip_agents/mcp/client/errors.py +119 -0
  210. aip_agents/mcp/client/google_adk/__init__.py +45 -0
  211. aip_agents/mcp/client/google_adk/client.py +383 -0
  212. aip_agents/mcp/client/langchain/__init__.py +11 -0
  213. aip_agents/mcp/client/langchain/client.py +265 -0
  214. aip_agents/mcp/client/persistent_session.py +704 -0
  215. aip_agents/mcp/client/session_pool.py +358 -0
  216. aip_agents/mcp/client/transports.py +288 -0
  217. aip_agents/mcp/utils/__init__.py +7 -0
  218. aip_agents/mcp/utils/config_validator.py +139 -0
  219. aip_agents/memory/__init__.py +14 -0
  220. aip_agents/memory/adapters/__init__.py +10 -0
  221. aip_agents/memory/adapters/base_adapter.py +815 -0
  222. aip_agents/memory/adapters/mem0.py +286 -0
  223. aip_agents/memory/base.py +84 -0
  224. aip_agents/memory/constants.py +49 -0
  225. aip_agents/memory/factory.py +86 -0
  226. aip_agents/memory/guidance.py +20 -0
  227. aip_agents/memory/simple_memory.py +47 -0
  228. aip_agents/middleware/__init__.py +65 -0
  229. aip_agents/middleware/backends/__init__.py +72 -0
  230. aip_agents/middleware/backends/in_memory.py +578 -0
  231. aip_agents/middleware/backends/local_disk.py +1203 -0
  232. aip_agents/middleware/backends/protocol.py +353 -0
  233. aip_agents/middleware/backends/sandbox.py +1431 -0
  234. aip_agents/middleware/backends/sandbox_files.py +113 -0
  235. aip_agents/middleware/backends/utils.py +293 -0
  236. aip_agents/middleware/base.py +239 -0
  237. aip_agents/middleware/filesystem.py +690 -0
  238. aip_agents/middleware/manager.py +291 -0
  239. aip_agents/middleware/memory.py +532 -0
  240. aip_agents/middleware/pii_middleware.py +192 -0
  241. aip_agents/middleware/schema.py +19 -0
  242. aip_agents/middleware/skills.py +872 -0
  243. aip_agents/middleware/task_delegation.py +1656 -0
  244. aip_agents/middleware/todolist.py +276 -0
  245. aip_agents/middleware/tools/__init__.py +23 -0
  246. aip_agents/middleware/tools/edit_file.py +70 -0
  247. aip_agents/middleware/tools/execute.py +428 -0
  248. aip_agents/middleware/tools/grep_file.py +79 -0
  249. aip_agents/middleware/tools/ls.py +71 -0
  250. aip_agents/middleware/tools/read_file.py +239 -0
  251. aip_agents/middleware/tools/skill_load.py +193 -0
  252. aip_agents/middleware/tools/skill_resource.py +338 -0
  253. aip_agents/middleware/tools/write_file.py +114 -0
  254. aip_agents/middleware/vision.py +322 -0
  255. aip_agents/ptc/__init__.py +87 -0
  256. aip_agents/ptc/custom_tools.py +511 -0
  257. aip_agents/ptc/custom_tools_payload.py +499 -0
  258. aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
  259. aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
  260. aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
  261. aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
  262. aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
  263. aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
  264. aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
  265. aip_agents/ptc/doc_gen.py +122 -0
  266. aip_agents/ptc/exceptions.py +57 -0
  267. aip_agents/ptc/executor.py +627 -0
  268. aip_agents/ptc/mcp/__init__.py +45 -0
  269. aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
  270. aip_agents/ptc/mcp/templates/__init__.py +1 -0
  271. aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
  272. aip_agents/ptc/naming.py +430 -0
  273. aip_agents/ptc/payload.py +26 -0
  274. aip_agents/ptc/prompt_builder.py +674 -0
  275. aip_agents/ptc/ptc_helper.py +16 -0
  276. aip_agents/ptc/sandbox_bridge.py +297 -0
  277. aip_agents/ptc/template_utils.py +33 -0
  278. aip_agents/ptc/templates/__init__.py +1 -0
  279. aip_agents/ptc/templates/ptc_helper.py.template +134 -0
  280. aip_agents/ptc/tool_def_helpers.py +101 -0
  281. aip_agents/ptc/tool_enrichment.py +163 -0
  282. aip_agents/resilience/__init__.py +10 -0
  283. aip_agents/resilience/base.py +46 -0
  284. aip_agents/resilience/tool.py +1661 -0
  285. aip_agents/sandbox/__init__.py +62 -0
  286. aip_agents/sandbox/defaults.py +220 -0
  287. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
  288. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
  289. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
  290. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
  291. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
  292. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
  293. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
  294. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
  295. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
  296. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
  297. aip_agents/sandbox/e2b_runtime.py +27 -0
  298. aip_agents/sandbox/image_registry.py +98 -0
  299. aip_agents/sandbox/providers/__init__.py +76 -0
  300. aip_agents/sandbox/providers/base.py +392 -0
  301. aip_agents/sandbox/providers/bedrock.py +28 -0
  302. aip_agents/sandbox/providers/e2b.py +230 -0
  303. aip_agents/sandbox/providers/opensandbox.py +163 -0
  304. aip_agents/sandbox/sandbox_runtime.py +967 -0
  305. aip_agents/sandbox/template_builder.py +240 -0
  306. aip_agents/sandbox/types.py +141 -0
  307. aip_agents/sandbox/validation.py +50 -0
  308. aip_agents/schema/__init__.py +106 -0
  309. aip_agents/schema/a2a.py +58 -0
  310. aip_agents/schema/agent.py +117 -0
  311. aip_agents/schema/artifacts.py +75 -0
  312. aip_agents/schema/attachments.py +425 -0
  313. aip_agents/schema/hitl.py +157 -0
  314. aip_agents/schema/langgraph.py +47 -0
  315. aip_agents/schema/model_id.py +103 -0
  316. aip_agents/schema/model_usage.py +50 -0
  317. aip_agents/schema/runtime_metadata.py +78 -0
  318. aip_agents/schema/step_limit.py +108 -0
  319. aip_agents/schema/storage.py +40 -0
  320. aip_agents/schema/tool_payloads.py +68 -0
  321. aip_agents/sentry/__init__.py +11 -0
  322. aip_agents/sentry/sentry.py +267 -0
  323. aip_agents/skills/__init__.py +30 -0
  324. aip_agents/skills/errors.py +110 -0
  325. aip_agents/skills/installer.py +479 -0
  326. aip_agents/skills/models.py +769 -0
  327. aip_agents/skills/stager.py +224 -0
  328. aip_agents/skills/validation.py +53 -0
  329. aip_agents/storage/__init__.py +41 -0
  330. aip_agents/storage/base.py +85 -0
  331. aip_agents/storage/clients/__init__.py +12 -0
  332. aip_agents/storage/clients/minio_client.py +318 -0
  333. aip_agents/storage/config.py +62 -0
  334. aip_agents/storage/providers/__init__.py +15 -0
  335. aip_agents/storage/providers/base.py +106 -0
  336. aip_agents/storage/providers/memory.py +114 -0
  337. aip_agents/storage/providers/object_storage.py +214 -0
  338. aip_agents/tools/__init__.py +65 -0
  339. aip_agents/tools/agent_schedule_tool.py +392 -0
  340. aip_agents/tools/browser_use/__init__.py +83 -0
  341. aip_agents/tools/browser_use/action_parser.py +104 -0
  342. aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
  343. aip_agents/tools/browser_use/llm_config.py +120 -0
  344. aip_agents/tools/browser_use/minio_storage.py +203 -0
  345. aip_agents/tools/browser_use/schemas.py +121 -0
  346. aip_agents/tools/browser_use/session.py +13 -0
  347. aip_agents/tools/browser_use/session_errors.py +132 -0
  348. aip_agents/tools/browser_use/steel_session_recording.py +321 -0
  349. aip_agents/tools/browser_use/streaming.py +263 -0
  350. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  351. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  352. aip_agents/tools/browser_use/types.py +37 -0
  353. aip_agents/tools/code_sandbox/__init__.py +48 -0
  354. aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
  355. aip_agents/tools/code_sandbox/constant.py +13 -0
  356. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
  357. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
  358. aip_agents/tools/computer_use/__init__.py +75 -0
  359. aip_agents/tools/computer_use/computer_use_tool.py +910 -0
  360. aip_agents/tools/computer_use/schemas.py +78 -0
  361. aip_agents/tools/computer_use/streaming.py +198 -0
  362. aip_agents/tools/computer_use/types.py +32 -0
  363. aip_agents/tools/constants.py +177 -0
  364. aip_agents/tools/date_range_tool.py +554 -0
  365. aip_agents/tools/document_loader/__init__.py +44 -0
  366. aip_agents/tools/document_loader/base_reader.py +302 -0
  367. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  368. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  369. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  370. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  371. aip_agents/tools/execute_ptc_code.py +582 -0
  372. aip_agents/tools/gl_connector/__init__.py +5 -0
  373. aip_agents/tools/gl_connector/tool.py +518 -0
  374. aip_agents/tools/gl_connector_tools.py +170 -0
  375. aip_agents/tools/memory_search/__init__.py +29 -0
  376. aip_agents/tools/memory_search/base.py +213 -0
  377. aip_agents/tools/memory_search/mem0.py +424 -0
  378. aip_agents/tools/memory_search/schema.py +81 -0
  379. aip_agents/tools/memory_search_tool.py +34 -0
  380. aip_agents/tools/streaming_common.py +577 -0
  381. aip_agents/tools/time_tool.py +117 -0
  382. aip_agents/tools/tool_config_injector.py +300 -0
  383. aip_agents/tools/web_search/__init__.py +15 -0
  384. aip_agents/tools/web_search/serper_tool.py +187 -0
  385. aip_agents/types/__init__.py +73 -0
  386. aip_agents/types/a2a_events.py +13 -0
  387. aip_agents/utils/__init__.py +91 -0
  388. aip_agents/utils/a2a_connector.py +1758 -0
  389. aip_agents/utils/artifact_helpers.py +542 -0
  390. aip_agents/utils/artifacts/__init__.py +20 -0
  391. aip_agents/utils/artifacts/tracker.py +367 -0
  392. aip_agents/utils/attachment_helpers.py +178 -0
  393. aip_agents/utils/constants.py +22 -0
  394. aip_agents/utils/datetime/__init__.py +34 -0
  395. aip_agents/utils/datetime/normalization.py +231 -0
  396. aip_agents/utils/datetime/timezone.py +206 -0
  397. aip_agents/utils/env_loader.py +27 -0
  398. aip_agents/utils/event_handler_registry.py +58 -0
  399. aip_agents/utils/file_prompt_utils.py +276 -0
  400. aip_agents/utils/file_watchers/__init__.py +33 -0
  401. aip_agents/utils/file_watchers/base.py +46 -0
  402. aip_agents/utils/file_watchers/e2b.py +128 -0
  403. aip_agents/utils/file_watchers/factory.py +39 -0
  404. aip_agents/utils/file_watchers/ls_diff.py +88 -0
  405. aip_agents/utils/file_watchers/sandbox.py +66 -0
  406. aip_agents/utils/file_watchers/watchdog.py +97 -0
  407. aip_agents/utils/final_response_builder.py +211 -0
  408. aip_agents/utils/formatter_llm_client.py +258 -0
  409. aip_agents/utils/langgraph/__init__.py +19 -0
  410. aip_agents/utils/langgraph/converter.py +399 -0
  411. aip_agents/utils/langgraph/delegation_context.py +22 -0
  412. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  413. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  414. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  415. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
  416. aip_agents/utils/langgraph/tool_output_management.py +1047 -0
  417. aip_agents/utils/logger.py +195 -0
  418. aip_agents/utils/metadata/__init__.py +27 -0
  419. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  420. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  421. aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
  422. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  423. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  424. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  425. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  426. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  427. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  428. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  429. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  430. aip_agents/utils/metadata_helper.py +363 -0
  431. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  432. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  433. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  434. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  435. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  436. aip_agents/utils/pii/__init__.py +29 -0
  437. aip_agents/utils/pii/pii_handler.py +555 -0
  438. aip_agents/utils/pii/pii_helper.py +225 -0
  439. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  440. aip_agents/utils/reference_helper.py +273 -0
  441. aip_agents/utils/sse_chunk_transformer.py +836 -0
  442. aip_agents/utils/step_limit_manager.py +265 -0
  443. aip_agents/utils/token_usage_helper.py +531 -0
  444. aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
  445. aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
  446. aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
  447. aip_agents_binary-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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
+ }
@@ -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)