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,117 @@
1
+ """Schema definitions for agent configuration types.
2
+
3
+ Authors:
4
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
5
+ Reinhart Linanda (reinhart.linanda@gdplabs.id)
6
+ """
7
+
8
+ from enum import StrEnum
9
+ from typing import Any
10
+
11
+ from a2a.types import AgentCard
12
+ from gllm_core.utils.retry import RetryConfig
13
+ from pydantic import BaseModel, Field
14
+
15
+ try:
16
+ # Pydantic v2 preferred config
17
+ from pydantic import ConfigDict # type: ignore
18
+ except Exception: # pragma: no cover
19
+ ConfigDict = None # type: ignore
20
+
21
+ __all__ = [
22
+ "CredentialType",
23
+ "StreamMode",
24
+ "HttpxClientOptions",
25
+ "A2AClientConfig",
26
+ "BaseAgentConfig",
27
+ "AgentConfig",
28
+ "LangflowAgentConfig",
29
+ ]
30
+
31
+
32
+ class CredentialType(StrEnum):
33
+ """Credential type enumeration for type safety and better developer experience."""
34
+
35
+ API_KEY = "api_key"
36
+ FILE = "file"
37
+ DICT = "dict"
38
+
39
+
40
+ class StreamMode(StrEnum):
41
+ """LangGraph stream modes for astream operations."""
42
+
43
+ VALUES = "values"
44
+ CUSTOM = "custom"
45
+ MESSAGES = "messages"
46
+
47
+
48
+ class HttpxClientOptions(BaseModel):
49
+ """Options for the HTTP client."""
50
+
51
+ timeout: float = 360.0
52
+ trust_env: bool = False
53
+ follow_redirects: bool = True
54
+
55
+ if ConfigDict is not None:
56
+ # Pydantic v2 style config
57
+ model_config = ConfigDict(extra="allow") # type: ignore[assignment]
58
+ else: # pragma: no cover
59
+
60
+ class Config: # type: ignore[no-redef]
61
+ """Pydantic v1 fallback config for HttpxClientOptions."""
62
+
63
+ extra = "allow"
64
+
65
+
66
+ class A2AClientConfig(BaseModel):
67
+ """Configuration for A2A client."""
68
+
69
+ discovery_urls: list[str] | None = None
70
+ known_agents: dict[str, AgentCard] = Field(default_factory=dict)
71
+ httpx_client_options: HttpxClientOptions | None = HttpxClientOptions()
72
+
73
+
74
+ class BaseAgentConfig(BaseModel):
75
+ """Base configuration for agent implementations."""
76
+
77
+ tools: list[Any] | None = None
78
+ default_hyperparameters: dict[str, Any] | None = None
79
+
80
+ if ConfigDict is not None:
81
+ model_config = ConfigDict(extra="allow") # type: ignore[assignment]
82
+ else: # pragma: no cover
83
+
84
+ class Config: # type: ignore[no-redef]
85
+ """Pydantic v1 fallback config for BaseAgentConfig."""
86
+
87
+ extra = "allow"
88
+
89
+
90
+ class AgentConfig(BaseAgentConfig):
91
+ """Configuration for agent implementations with language model settings."""
92
+
93
+ lm_name: str | None = None
94
+ lm_hyperparameters: dict[str, Any] | None = None
95
+ lm_provider: str | None = None
96
+ lm_base_url: str | None = None
97
+ lm_api_key: str | None = None
98
+ lm_credentials: str | dict[str, Any] | None = None
99
+ lm_retry_config: RetryConfig | None = None
100
+ a2ui: dict[str, Any] | None = None
101
+
102
+
103
+ class LangflowAgentConfig(BaseAgentConfig):
104
+ """Configuration for Langflow agent implementations."""
105
+
106
+ flow_id: str
107
+ base_url: str | None = None
108
+ api_key: str | None = None
109
+
110
+ if ConfigDict is not None:
111
+ model_config = ConfigDict(extra="allow") # type: ignore[assignment]
112
+ else: # pragma: no cover
113
+
114
+ class Config: # type: ignore[no-redef]
115
+ """Pydantic v1 fallback config for LangflowAgentConfig."""
116
+
117
+ extra = "allow"
@@ -0,0 +1,75 @@
1
+ """Shared typed contracts for artifact payloads exchanged across tool and agent boundaries.
2
+
3
+ These contracts codify the dict shapes already produced by
4
+ ``aip_agents.utils.artifact_helpers`` and ``aip_agents.utils.artifacts.tracker``
5
+ so downstream typing slices can import named structures instead of repeating
6
+ ``dict[str, Any]``. They describe the *serialized* artifact payload (the dict
7
+ form of ``aip_agents.a2a.types.Artifact`` plus an optional internal ``path``
8
+ used by the tracker); they do not replace the Pydantic ``Artifact`` model.
9
+
10
+ Authors:
11
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Required, TypedDict
17
+
18
+ __all__ = [
19
+ "ArtifactPayload",
20
+ "TrackedArtifact",
21
+ "ArtifactBuildError",
22
+ "ToolArtifactResponse",
23
+ "ToolMultiArtifactResponse",
24
+ ]
25
+
26
+
27
+ class ArtifactPayload(TypedDict, total=False):
28
+ """Serialized artifact payload produced by artifact helpers.
29
+
30
+ Mirrors ``aip_agents.a2a.types.Artifact.model_dump()``.
31
+
32
+ Wire-mandatory keys (always present when emitted by the A2A pipeline) are
33
+ marked ``Required``: ``artifact_type``, ``data``, ``name``, ``mime_type``.
34
+ ``description`` defaults to an empty string and ``metadata`` is omitted when
35
+ absent.
36
+ """
37
+
38
+ artifact_type: Required[str]
39
+ data: Required[str]
40
+ name: Required[str]
41
+ description: str
42
+ mime_type: Required[str]
43
+ metadata: dict[str, Any] | None
44
+
45
+
46
+ class TrackedArtifact(ArtifactPayload, total=False):
47
+ """``ArtifactPayload`` plus the tracker-only ``path`` provenance key.
48
+
49
+ Emitted by ``ArtifactTracker.build_artifacts``; ``path`` is internal and
50
+ must be stripped before serialization to the A2A wire contract
51
+ (``ArtifactPayload``).
52
+ """
53
+
54
+ path: str
55
+
56
+
57
+ class ArtifactBuildError(TypedDict):
58
+ """Error entry returned by ``ArtifactTracker.build_artifacts`` for a file that could not be turned into an artifact."""
59
+
60
+ path: str
61
+ reason: str
62
+
63
+
64
+ class ToolArtifactResponse(TypedDict):
65
+ """Single-artifact tool response shape returned by ``ArtifactHandler.create_artifact``."""
66
+
67
+ result: str
68
+ artifact: ArtifactPayload
69
+
70
+
71
+ class ToolMultiArtifactResponse(TypedDict):
72
+ """Multi-artifact tool response shape returned by artifact command helpers."""
73
+
74
+ result: str
75
+ artifacts: list[ArtifactPayload]
@@ -0,0 +1,425 @@
1
+ """Runtime attachment contracts for user inputs and generated artifacts.
2
+
3
+ Authors:
4
+ Raymond Christopher (raymond.christopher@gdplabs.id)
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import hashlib
11
+ import os
12
+ from enum import StrEnum
13
+ from typing import Any, Required, TypedDict
14
+ from urllib.parse import urlparse
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator
17
+
18
+ from aip_agents.a2a.types import MimeType, get_mime_type_from_filename
19
+
20
+
21
+ class AttachmentKind(StrEnum):
22
+ """Supported first-class attachment kinds."""
23
+
24
+ IMAGE = "image"
25
+ DOCUMENT = "document"
26
+
27
+
28
+ class AttachmentSource(StrEnum):
29
+ """Where an attachment reference originated."""
30
+
31
+ USER_INPUT = "user_input"
32
+ LOCAL_TOOL_OUTPUT = "local_tool_output"
33
+ SUB_AGENT_ARTIFACT = "sub_agent_artifact"
34
+ REMOTE_UPLOAD = "remote_upload"
35
+ PLATFORM_ARTIFACT = "platform_artifact"
36
+
37
+
38
+ _HTTP_SCHEME = "http"
39
+ _HTTP_PROTOCOL_PREFIXES = (f"{_HTTP_SCHEME}://", f"{_HTTP_SCHEME}s://")
40
+ SUPPORTED_IMAGE_MIME_TYPES = ("image/png", "image/jpeg", "image/webp")
41
+ SUPPORTED_DOCUMENT_MIME_TYPES = (
42
+ MimeType.APPLICATION_PDF,
43
+ MimeType.APPLICATION_MSWORD,
44
+ MimeType.APPLICATION_EXCEL,
45
+ MimeType.APPLICATION_DOCX,
46
+ MimeType.APPLICATION_XLSX,
47
+ )
48
+ MAX_IMAGE_ATTACHMENT_BYTES = 20 * 1024 * 1024
49
+ _UNSAFE_TRACE_METADATA_KEYS = {
50
+ "base64",
51
+ "bytes",
52
+ "data",
53
+ "data_ref",
54
+ "file_data",
55
+ "file_uri",
56
+ "local_path",
57
+ "path",
58
+ "presigned_url",
59
+ "raw",
60
+ "raw_data",
61
+ "s3_url",
62
+ "signed_url",
63
+ "url",
64
+ }
65
+
66
+
67
+ def _normalize_trace_metadata_key(key: object) -> str:
68
+ """Normalize metadata keys before trace-safety blocklist checks."""
69
+ return str(key).strip().lower().replace("-", "_").replace(" ", "_")
70
+
71
+
72
+ def _is_likely_local_path_ref(ref: str) -> bool:
73
+ """Return whether a ref explicitly names a local filesystem path."""
74
+ if os.path.isabs(ref):
75
+ return True
76
+ normalized_ref = ref.lower()
77
+ if normalized_ref.startswith(("file://", "./", "../", "~/")):
78
+ return True
79
+ if "\\" in ref:
80
+ return True
81
+ return len(ref) > 2 and ref[1] == ":" and ref[0].isalpha()
82
+
83
+
84
+ class AttachmentEntryPayload(TypedDict, total=False):
85
+ """Serialized attachment entry — the dict form of ``RuntimeAttachment``.
86
+
87
+ Represents the shape stored in metadata ``attachments`` lists and
88
+ ``attachment_registry`` dicts, and merged by
89
+ ``utils.attachment_helpers.merge_input_attachments``. Mirrors
90
+ ``RuntimeAttachment.model_dump()`` so registry/list consumers can use a
91
+ typed contract instead of ``dict[str, Any]``.
92
+
93
+ Identity fields always produced by ``model_dump()`` are marked ``Required``;
94
+ ``size_bytes`` and ``metadata`` are genuinely optional.
95
+ """
96
+
97
+ id: Required[str]
98
+ kind: Required[str]
99
+ source: Required[str]
100
+ filename: Required[str]
101
+ mime_type: Required[str]
102
+ data_ref: Required[str]
103
+ size_bytes: int | None
104
+ metadata: dict[str, Any] | None
105
+
106
+
107
+ class RuntimeAttachment(BaseModel):
108
+ """Safe runtime reference for an input attachment or generated artifact."""
109
+
110
+ model_config = ConfigDict(use_enum_values=True)
111
+
112
+ id: str
113
+ kind: AttachmentKind
114
+ source: AttachmentSource
115
+ filename: str
116
+ mime_type: str
117
+ size_bytes: int | None = Field(default=None, ge=0)
118
+ data_ref: str
119
+ metadata: dict[str, Any] | None = None
120
+
121
+ @field_validator("mime_type")
122
+ @classmethod
123
+ def validate_mime_type(cls, value: str, info: ValidationInfo) -> str:
124
+ """Apply MIME validation based on attachment kind."""
125
+ raw_kind = info.data.get("kind") if isinstance(info.data, dict) else None
126
+ kind = AttachmentKind(raw_kind) if raw_kind is not None else None
127
+ normalized_mime = normalize_mime_type(value)
128
+
129
+ if normalized_mime is None:
130
+ raise ValueError("attachment MIME type must be a non-empty string") # pragma: no cover
131
+ if kind == AttachmentKind.IMAGE and normalized_mime not in SUPPORTED_IMAGE_MIME_TYPES:
132
+ raise ValueError("image attachments must use a supported image MIME type")
133
+ if kind == AttachmentKind.DOCUMENT and normalized_mime not in SUPPORTED_DOCUMENT_MIME_TYPES:
134
+ raise ValueError("document attachments must use a supported document MIME type")
135
+ return normalized_mime
136
+
137
+ @field_validator("data_ref")
138
+ @classmethod
139
+ def validate_safe_data_ref(cls, value: str) -> str:
140
+ """Reject inline bytes, base64 data URIs, and likely signed URLs."""
141
+ normalized_value = value.lower()
142
+ if normalized_value.startswith("data:"):
143
+ raise ValueError("data_ref must not contain inline data")
144
+ if normalized_value.startswith(_HTTP_PROTOCOL_PREFIXES):
145
+ parsed = urlparse(value)
146
+ if parsed.query:
147
+ raise ValueError("data_ref must not contain signed URL query parameters")
148
+ if "@" in parsed.netloc:
149
+ raise ValueError("data_ref must not contain credentials")
150
+ return value
151
+
152
+
153
+ class AttachmentRegistry(BaseModel):
154
+ """Lookup container shared by user inputs and generated artifacts."""
155
+
156
+ attachments: dict[str, RuntimeAttachment] = Field(default_factory=dict)
157
+
158
+ def register(self, attachment: RuntimeAttachment) -> RuntimeAttachment:
159
+ """Register an attachment by ID and return the stored value."""
160
+ self.attachments[attachment.id] = attachment
161
+ return attachment
162
+
163
+ def resolve(self, attachment_id: str) -> RuntimeAttachment | None:
164
+ """Resolve an attachment by ID."""
165
+ return self.attachments.get(attachment_id)
166
+
167
+ def as_list(self) -> list[dict[str, Any]]:
168
+ """Return attachment entries as serializable dictionaries."""
169
+ return [attachment.model_dump() for attachment in self.attachments.values()]
170
+
171
+
172
+ def normalize_mime_type(mime_type: str | None) -> str | None:
173
+ """Normalize MIME strings for MIME support checks."""
174
+ if not isinstance(mime_type, str):
175
+ return None
176
+ normalized = mime_type.split(";", 1)[0].strip().lower()
177
+ return normalized or None
178
+
179
+
180
+ def normalize_image_mime_type(mime_type: str | None) -> str | None:
181
+ """Backward-compatible image MIME normalization helper."""
182
+ return normalize_mime_type(mime_type)
183
+
184
+
185
+ def is_supported_image_mime(mime_type: str | None) -> bool:
186
+ """Return whether the MIME type is supported for image attachments."""
187
+ return normalize_mime_type(mime_type) in SUPPORTED_IMAGE_MIME_TYPES
188
+
189
+
190
+ def is_supported_document_mime(mime_type: str | None) -> bool:
191
+ """Return whether the MIME type is supported for document attachments."""
192
+ return normalize_mime_type(mime_type) in SUPPORTED_DOCUMENT_MIME_TYPES
193
+
194
+
195
+ def create_runtime_attachment(
196
+ *,
197
+ attachment_id: str,
198
+ source: AttachmentSource | str,
199
+ filename: str,
200
+ mime_type: str,
201
+ data_ref: str,
202
+ kind: AttachmentKind | str = AttachmentKind.IMAGE,
203
+ size_bytes: int | None = None,
204
+ metadata: dict[str, Any] | None = None,
205
+ max_size_bytes: int = MAX_IMAGE_ATTACHMENT_BYTES,
206
+ ) -> RuntimeAttachment:
207
+ """Create a runtime attachment from caller-owned safe metadata."""
208
+ resolved_kind = AttachmentKind(kind)
209
+ normalized_mime = normalize_mime_type(mime_type)
210
+
211
+ if resolved_kind == AttachmentKind.IMAGE:
212
+ if normalized_mime is None or normalized_mime not in SUPPORTED_IMAGE_MIME_TYPES:
213
+ raise ValueError(f"unsupported image MIME type: {mime_type}")
214
+ if size_bytes is not None and size_bytes > max_size_bytes:
215
+ raise ValueError(f"image attachment {filename} exceeds {max_size_bytes} bytes")
216
+ elif resolved_kind == AttachmentKind.DOCUMENT:
217
+ if normalized_mime is None or normalized_mime not in SUPPORTED_DOCUMENT_MIME_TYPES:
218
+ raise ValueError(f"unsupported document MIME type: {mime_type}")
219
+
220
+ return RuntimeAttachment(
221
+ id=attachment_id,
222
+ kind=resolved_kind,
223
+ source=source,
224
+ filename=filename,
225
+ mime_type=normalized_mime,
226
+ size_bytes=size_bytes,
227
+ data_ref=data_ref,
228
+ metadata=metadata,
229
+ )
230
+
231
+
232
+ create_runtime_image_attachment = create_runtime_attachment
233
+
234
+
235
+ def merge_attachment_registry(
236
+ metadata: dict[str, Any] | None,
237
+ attachments: list[RuntimeAttachment | dict[str, Any]],
238
+ ) -> dict[str, Any]:
239
+ """Merge image attachments into runtime metadata and registry fields."""
240
+ merged = dict(metadata or {})
241
+ existing_attachments = merged.get("attachments") if isinstance(merged.get("attachments"), list) else []
242
+ existing_registry = merged.get("attachment_registry") if isinstance(merged.get("attachment_registry"), dict) else {}
243
+ attachment_list = [*existing_attachments]
244
+ registry = dict(existing_registry)
245
+
246
+ for attachment in attachments:
247
+ attachment_dict = attachment.model_dump() if isinstance(attachment, RuntimeAttachment) else dict(attachment)
248
+ attachment_id = attachment_dict.get("id")
249
+ if not isinstance(attachment_id, str) or not attachment_id:
250
+ continue
251
+ if attachment_id not in registry:
252
+ attachment_list.append(attachment_dict)
253
+ registry[attachment_id] = attachment_dict
254
+
255
+ merged["attachments"] = attachment_list
256
+ merged["attachment_registry"] = registry
257
+ return merged
258
+
259
+
260
+ def safe_image_data_ref(value: object) -> str | None:
261
+ """Return safe non-inline image refs while excluding signed URLs and local paths."""
262
+ if not isinstance(value, str):
263
+ return None
264
+ ref = value.strip()
265
+ normalized_ref = ref.lower()
266
+ if not ref or normalized_ref.startswith("data:"):
267
+ return None
268
+ if normalized_ref.startswith(_HTTP_PROTOCOL_PREFIXES):
269
+ parsed = urlparse(ref)
270
+ if parsed.query or "@" in parsed.netloc:
271
+ return None
272
+ return ref
273
+ if _is_likely_local_path_ref(ref):
274
+ return None
275
+ return ref
276
+
277
+
278
+ def _extract_trace_base_fields(attachment_dict: dict[str, Any]) -> dict[str, Any]:
279
+ """Copy stable attachment identity fields into a trace-safe payload."""
280
+ trace: dict[str, Any] = {}
281
+ for key in ("id", "kind", "source", "filename", "mime_type", "size_bytes"):
282
+ if key in attachment_dict:
283
+ trace[key] = attachment_dict[key]
284
+ return trace
285
+
286
+
287
+ def _sanitize_trace_metadata(metadata: dict[str, Any]) -> dict[str, Any]:
288
+ """Remove unsafe metadata keys and values before trace emission."""
289
+ safe_metadata: dict[str, Any] = {}
290
+ for key, value in metadata.items():
291
+ if _normalize_trace_metadata_key(key) in _UNSAFE_TRACE_METADATA_KEYS:
292
+ continue
293
+ if isinstance(value, str):
294
+ safe_value = safe_image_data_ref(value.strip())
295
+ if safe_value is None:
296
+ continue
297
+ value = safe_value
298
+ safe_metadata[key] = value
299
+ return safe_metadata
300
+
301
+
302
+ def sanitize_image_attachment_for_trace(attachment: RuntimeAttachment | dict[str, Any]) -> dict[str, Any]:
303
+ """Return trace-safe image metadata without local paths, signed URLs, or bytes."""
304
+ attachment_dict = attachment.model_dump() if isinstance(attachment, RuntimeAttachment) else dict(attachment)
305
+ trace = _extract_trace_base_fields(attachment_dict)
306
+
307
+ safe_ref = safe_image_data_ref(attachment_dict.get("data_ref"))
308
+ if safe_ref is not None:
309
+ trace["data_ref"] = safe_ref
310
+
311
+ metadata = attachment_dict.get("metadata")
312
+ if isinstance(metadata, dict):
313
+ safe_metadata = _sanitize_trace_metadata(metadata)
314
+ if safe_metadata:
315
+ trace["metadata"] = safe_metadata
316
+ return trace
317
+
318
+
319
+ def create_input_image_attachment(
320
+ *,
321
+ attachment_id: str,
322
+ path: str,
323
+ filename: str | None = None,
324
+ mime_type: str | None = None,
325
+ size_bytes: int | None = None,
326
+ data_ref: str | None = None,
327
+ source: AttachmentSource = AttachmentSource.USER_INPUT,
328
+ ) -> RuntimeAttachment:
329
+ """Create a safe image attachment reference for user-provided files."""
330
+ resolved_filename = filename or os.path.basename(path)
331
+ resolved_mime_type = mime_type or str(get_mime_type_from_filename(resolved_filename))
332
+ resolved_size = size_bytes if size_bytes is not None else _get_file_size(path)
333
+ return create_runtime_attachment(
334
+ attachment_id=attachment_id,
335
+ source=source,
336
+ filename=resolved_filename,
337
+ mime_type=resolved_mime_type,
338
+ data_ref=data_ref or path,
339
+ size_bytes=resolved_size,
340
+ )
341
+
342
+
343
+ def create_generated_image_attachment(
344
+ *,
345
+ artifact_name: str,
346
+ mime_type: str,
347
+ artifact_data: str,
348
+ source: AttachmentSource = AttachmentSource.LOCAL_TOOL_OUTPUT,
349
+ metadata: dict[str, Any] | None = None,
350
+ ) -> RuntimeAttachment:
351
+ """Create a safe image attachment reference for a generated artifact."""
352
+ digest = hashlib.sha256(f"{artifact_name}:{artifact_data}".encode()).hexdigest()
353
+ return create_runtime_attachment(
354
+ attachment_id=f"img_artifact_{digest[:12]}",
355
+ source=source,
356
+ filename=artifact_name,
357
+ mime_type=mime_type,
358
+ data_ref=f"artifact://sha256/{digest}",
359
+ size_bytes=_decoded_base64_size(artifact_data),
360
+ metadata=metadata,
361
+ )
362
+
363
+
364
+ def create_generated_document_attachment(
365
+ *,
366
+ artifact_name: str,
367
+ mime_type: str,
368
+ artifact_data: str,
369
+ source: AttachmentSource = AttachmentSource.LOCAL_TOOL_OUTPUT,
370
+ metadata: dict[str, Any] | None = None,
371
+ ) -> RuntimeAttachment:
372
+ """Create a safe document attachment reference for a generated artifact."""
373
+ digest = hashlib.sha256(f"{artifact_name}:{artifact_data}".encode()).hexdigest()
374
+ return create_runtime_attachment(
375
+ attachment_id=f"document_artifact_{digest[:12]}",
376
+ source=source,
377
+ filename=artifact_name,
378
+ mime_type=mime_type,
379
+ kind=AttachmentKind.DOCUMENT,
380
+ data_ref=f"artifact://sha256/{digest}",
381
+ size_bytes=_decoded_base64_size(artifact_data),
382
+ metadata=metadata,
383
+ )
384
+
385
+
386
+ def _get_file_size(path: str) -> int | None:
387
+ """Return file size when the local path is readable."""
388
+ try:
389
+ if os.path.isfile(path) and os.access(path, os.R_OK):
390
+ return os.path.getsize(path)
391
+ except OSError:
392
+ return None
393
+ return None
394
+
395
+
396
+ def _decoded_base64_size(value: str) -> int | None:
397
+ """Return decoded byte length for base64 content without exposing bytes."""
398
+ try:
399
+ return len(base64.b64decode(value, validate=True))
400
+ except Exception:
401
+ return None
402
+
403
+
404
+ __all__ = [
405
+ "AttachmentKind",
406
+ "AttachmentRegistry",
407
+ "AttachmentSource",
408
+ "AttachmentEntryPayload",
409
+ "RuntimeAttachment",
410
+ "MAX_IMAGE_ATTACHMENT_BYTES",
411
+ "SUPPORTED_IMAGE_MIME_TYPES",
412
+ "create_generated_image_attachment",
413
+ "create_generated_document_attachment",
414
+ "create_runtime_attachment",
415
+ "create_input_image_attachment",
416
+ "create_runtime_image_attachment",
417
+ "is_supported_image_mime",
418
+ "is_supported_document_mime",
419
+ "SUPPORTED_DOCUMENT_MIME_TYPES",
420
+ "normalize_mime_type",
421
+ "merge_attachment_registry",
422
+ "normalize_image_mime_type",
423
+ "safe_image_data_ref",
424
+ "sanitize_image_attachment_for_trace",
425
+ ]