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,769 @@
1
+ """Skill object model.
2
+
3
+ Skill objects validate local skill roots, preserve SKILL.md frontmatter metadata,
4
+ and provide stable local/backend roots for staging and runtime loading.
5
+
6
+ Authors:
7
+ Saul Sayers (saul.sayers@gdplabs.id)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import copy
13
+ import hashlib
14
+ import os
15
+ import posixpath
16
+ import shlex
17
+ from dataclasses import dataclass
18
+ from enum import StrEnum
19
+ from importlib import util as importlib_util
20
+ from pathlib import Path, PurePosixPath
21
+ from typing import Any
22
+
23
+ import yaml
24
+ from pydantic import BaseModel, Field
25
+
26
+ try:
27
+ # Pydantic v2 preferred config
28
+ from pydantic import ConfigDict # type: ignore
29
+ except ImportError: # pragma: no cover
30
+ ConfigDict = None # type: ignore
31
+
32
+ from aip_agents.common.callable_introspection import callable_accepts_keyword
33
+ from aip_agents.skills.errors import SkillExecutionError
34
+ from aip_agents.skills.installer import SkillInstaller
35
+ from aip_agents.skills.validation import validate_skill_root
36
+
37
+ _SKILL_MD_FILENAME = "SKILL.md"
38
+ _MISSING_POLICY = object()
39
+ _MALFORMED_FRONTMATTER_POLICY = object()
40
+ _DENY_POLICY_MARKERS = {"none", "null", "false", "deny", "deny-all"}
41
+ _SKILL_MD_FILENAME = "SKILL.md"
42
+
43
+
44
+ class Skill(BaseModel):
45
+ """Represents an installed skill.
46
+
47
+ Attributes:
48
+ name (str): Skill name (derived from folder name).
49
+ description (str | None): Optional metadata. Defaults to None.
50
+ version (str | None): Optional metadata. Defaults to None.
51
+ tags (tuple[str, ...]): Optional metadata. Defaults to ().
52
+ triggers (tuple[str, ...]): Optional metadata. Defaults to ().
53
+ source (str | None): The install source, when applicable. Defaults to None.
54
+ local_root_path (Path): Local filesystem path to the skill root.
55
+ backend_root (str): Deterministic backend root path (`/skills/<name>`).
56
+ """
57
+
58
+ name: str
59
+ local_root_path: Path
60
+ source: str | None = None
61
+ description: str | None = None
62
+ version: str | None = None
63
+ author: str | None = None
64
+ license: str | None = None
65
+ tags: tuple[str, ...] = ()
66
+ triggers: tuple[str, ...] = ()
67
+ required_credential_files: tuple[str, ...] = ()
68
+ metadata: dict[str, Any] = Field(default_factory=dict)
69
+ source_type: str = "generated_fixture"
70
+ trust_label: str = "generated_fixture"
71
+ content_hash: str | None = None
72
+
73
+ if ConfigDict is not None:
74
+ # Pydantic v2 style config
75
+ model_config = ConfigDict(frozen=True) # type: ignore[assignment]
76
+ else: # pragma: no cover
77
+
78
+ class Config: # type: ignore[no-redef]
79
+ """Pydantic v1 fallback config for Skill."""
80
+
81
+ allow_mutation = False
82
+
83
+ @property
84
+ def local_root(self) -> str:
85
+ """Local filesystem root as a string path.
86
+
87
+ Returns:
88
+ str: Local skill root path.
89
+ """
90
+ return str(self.local_root_path)
91
+
92
+ @property
93
+ def provenance(self) -> dict[str, Any]:
94
+ """Return source and trust context for diagnostics and harness payloads."""
95
+ return {
96
+ "source": self.source or str(self.local_root_path),
97
+ "source_type": self.source_type,
98
+ "trust_label": self.trust_label,
99
+ "local_root": str(self.local_root_path),
100
+ "content_hash": self.content_hash,
101
+ }
102
+
103
+ @property
104
+ def backend_root(self) -> str:
105
+ """Backend root path used for staging (`/skills/<skill-name>`).
106
+
107
+ Returns:
108
+ str: Backend root path.
109
+ """
110
+ return f"/skills/{self.name}"
111
+
112
+ @property
113
+ def skill_md_path(self) -> Path:
114
+ """Return the path to the required SKILL.md file.
115
+
116
+ Returns:
117
+ Path: SKILL.md file path.
118
+ """
119
+ return self.local_root_path / _SKILL_MD_FILENAME
120
+
121
+ def allowed_scripts_policy(self) -> dict[str, Any]:
122
+ """Return the normalized `allowed-scripts` policy from SKILL.md."""
123
+ frontmatter = self._read_frontmatter()
124
+ raw_policy = self._frontmatter_value(frontmatter, "allowed-scripts", "allowed_scripts", default=_MISSING_POLICY)
125
+ return self.normalize_allowed_scripts_policy(raw_policy, skill_name=self.name)
126
+
127
+ def setup_readiness(self, *, backend: Any | None = None) -> dict[str, Any]:
128
+ """Report skill setup readiness without reading credential file contents."""
129
+ frontmatter = self._read_frontmatter()
130
+ policy = self.allowed_scripts_policy()
131
+ credential_files = self._normalize_string_sequence(
132
+ self._frontmatter_value(
133
+ frontmatter,
134
+ "required_credential_files",
135
+ "required-credential-files",
136
+ default=[],
137
+ ),
138
+ field_name="required_credential_files",
139
+ )
140
+ optional_dependencies = self._normalize_string_sequence(
141
+ self._frontmatter_value(
142
+ frontmatter,
143
+ "optional_dependencies",
144
+ "optional-dependencies",
145
+ default=[],
146
+ ),
147
+ field_name="optional_dependencies",
148
+ )
149
+ scripts = self._script_paths_for_readiness(policy)
150
+
151
+ credential_checks = [self._credential_file_check(path) for path in credential_files]
152
+ dependency_checks = [self._optional_dependency_check(name) for name in optional_dependencies]
153
+ script_checks = [self._script_file_check(path) for path in scripts]
154
+ backend_check = self._backend_execution_check(backend, needs_execution=bool(scripts))
155
+
156
+ blocking_checks = [*credential_checks, *dependency_checks, *script_checks]
157
+ ready = policy["valid"] and all(check["status"] != "missing" for check in blocking_checks)
158
+ ready = ready and backend_check["status"] not in ("unsupported", "not_checked")
159
+
160
+ return {
161
+ "skill_name": self.name,
162
+ "ready": ready,
163
+ "policy": policy,
164
+ "credential_files": credential_checks,
165
+ "scripts": script_checks,
166
+ "optional_dependencies": dependency_checks,
167
+ "backend_execution": backend_check,
168
+ }
169
+
170
+ @classmethod
171
+ def from_path(cls, path: str | os.PathLike[str]) -> Skill:
172
+ """Create a Skill object from an already-installed local directory.
173
+
174
+ Args:
175
+ path (str | os.PathLike[str]): Path to a skill directory.
176
+
177
+ Returns:
178
+ Skill: The validated skill object.
179
+
180
+ Raises:
181
+ SkillValidationError: If the path is not a valid skill.
182
+ """
183
+ skill_root = Path(path).expanduser()
184
+ validate_skill_root(skill_root)
185
+ frontmatter = _read_skill_frontmatter(skill_root / _SKILL_MD_FILENAME)
186
+ return cls(
187
+ name=skill_root.name,
188
+ local_root_path=skill_root,
189
+ description=_optional_text(frontmatter.get("description")),
190
+ version=_optional_text(frontmatter.get("version")),
191
+ author=_optional_text(frontmatter.get("author")),
192
+ license=_optional_text(frontmatter.get("license")),
193
+ tags=_string_tuple(frontmatter.get("tags")),
194
+ triggers=_string_tuple(frontmatter.get("triggers")),
195
+ required_credential_files=_string_tuple(
196
+ frontmatter.get("required_credential_files") or frontmatter.get("required-credential-files")
197
+ ),
198
+ metadata=frontmatter,
199
+ source_type="local_path",
200
+ trust_label="explicit_local_path",
201
+ content_hash=_skill_md_hash(skill_root / _SKILL_MD_FILENAME),
202
+ )
203
+
204
+ @classmethod
205
+ async def from_github(
206
+ cls,
207
+ *,
208
+ source: str,
209
+ destination_root: str | os.PathLike[str],
210
+ token: str | None = None,
211
+ ) -> Skill:
212
+ """Install a skill from GitHub and return a Skill object.
213
+
214
+ Args:
215
+ source (str): GitHub tree URL pointing at a skill directory.
216
+ destination_root (str | os.PathLike[str]): Installation destination root
217
+ (e.g. `.agents/skills`).
218
+ token (str | None, optional): GitHub token used for private repos. Defaults to None.
219
+
220
+ Returns:
221
+ Skill: The installed and validated skill.
222
+
223
+ Raises:
224
+ SkillInstallError: If installation fails.
225
+ SkillValidationError: If the installed result is invalid.
226
+ """
227
+ installer = SkillInstaller(token=token)
228
+ installed_root = await installer.install_github_tree(source=source, destination_root=destination_root)
229
+
230
+ installed_skill = cls.from_path(installed_root)
231
+ return cls(
232
+ name=installed_skill.name,
233
+ local_root_path=installed_skill.local_root_path,
234
+ source=source,
235
+ description=installed_skill.description,
236
+ version=installed_skill.version,
237
+ author=installed_skill.author,
238
+ license=installed_skill.license,
239
+ tags=installed_skill.tags,
240
+ triggers=installed_skill.triggers,
241
+ required_credential_files=installed_skill.required_credential_files,
242
+ metadata=copy.deepcopy(installed_skill.metadata),
243
+ source_type="github_url",
244
+ trust_label="installed_cache",
245
+ content_hash=installed_skill.content_hash,
246
+ )
247
+
248
+ def execute_skill_script(
249
+ self,
250
+ *,
251
+ backend: Any,
252
+ script_relative_path: str,
253
+ args: list[str] | tuple[str, ...] | None = None,
254
+ timeout_seconds: int | None = None,
255
+ default_timeout_seconds: int = 30,
256
+ max_timeout_seconds: int = 300,
257
+ ) -> dict[str, Any]:
258
+ """Execute a staged skill script through backend execute support.
259
+
260
+ Args:
261
+ backend: Backend instance exposing execute().
262
+ script_relative_path: Relative script path within the skill root.
263
+ args: Optional command-line arguments.
264
+ timeout_seconds: Optional timeout override.
265
+ default_timeout_seconds: Default timeout when override is omitted.
266
+ max_timeout_seconds: Maximum accepted timeout.
267
+
268
+ Returns:
269
+ dict[str, Any]: Deterministic, serializable execution payload.
270
+
271
+ Raises:
272
+ SkillExecutionError: If execution is unavailable or input is invalid.
273
+ """
274
+ normalized_relative_path = self._normalize_script_relative_path(script_relative_path)
275
+ script_path = self._resolve_backend_script_path(normalized_relative_path)
276
+ self._enforce_allowed_script(normalized_relative_path, script_path)
277
+ self._ensure_local_script_exists(normalized_relative_path, script_path)
278
+
279
+ execute_fn = getattr(backend, "execute", None)
280
+ if not callable(execute_fn):
281
+ denial = self._execution_denial(
282
+ script_relative_path=normalized_relative_path,
283
+ script_path=script_path,
284
+ policy_reason="unsupported_backend",
285
+ )
286
+ raise SkillExecutionError(
287
+ "Execution not available: backend does not expose execute().",
288
+ denial=denial,
289
+ )
290
+
291
+ allow_execute = getattr(backend, "allow_execute", None)
292
+ if allow_execute is False:
293
+ denial = self._execution_denial(
294
+ script_relative_path=normalized_relative_path,
295
+ script_path=script_path,
296
+ policy_reason="backend_execute_disabled",
297
+ )
298
+ raise SkillExecutionError("Execution not available: backend execute() is disabled.", denial=denial)
299
+
300
+ normalized_args = tuple(str(arg) for arg in (args or []))
301
+ command = self._build_command(script_path, normalized_args)
302
+ resolved_timeout = self._resolve_timeout(timeout_seconds, default_timeout_seconds, max_timeout_seconds)
303
+
304
+ if callable_accepts_keyword(execute_fn, "timeout"):
305
+ result = execute_fn(command, timeout=resolved_timeout)
306
+ else:
307
+ result = execute_fn(command)
308
+
309
+ return {
310
+ "skill_name": self.name,
311
+ "script_relative_path": normalized_relative_path,
312
+ "script_path": script_path,
313
+ "command": command,
314
+ "timeout_seconds": resolved_timeout,
315
+ "stdout": str(getattr(result, "stdout", "")),
316
+ "stderr": str(getattr(result, "stderr", "")),
317
+ "exit_code": int(getattr(result, "exit_code", -1)),
318
+ "truncated": bool(getattr(result, "truncated", False)),
319
+ }
320
+
321
+ def _resolve_backend_script_path(self, script_relative_path: str) -> str:
322
+ """Resolve and validate a script path under `/skills/<skill-name>/`.
323
+
324
+ Args:
325
+ script_relative_path: Relative script path.
326
+
327
+ Returns:
328
+ str: Canonical backend script path.
329
+
330
+ Raises:
331
+ SkillExecutionError: If path escapes skill root or is invalid.
332
+ """
333
+ normalized_relative_path = self._normalize_script_relative_path(script_relative_path)
334
+
335
+ skill_root = PurePosixPath(self.backend_root)
336
+ candidate_path = PurePosixPath(posixpath.normpath(posixpath.join(str(skill_root), normalized_relative_path)))
337
+ candidate_path_str = str(candidate_path)
338
+ skill_root_str = str(skill_root)
339
+ if not (candidate_path_str == skill_root_str or candidate_path_str.startswith(f"{skill_root_str}/")):
340
+ raise SkillExecutionError("Resolved script path escapes the skill root.")
341
+
342
+ return candidate_path_str
343
+
344
+ @staticmethod
345
+ def _canonicalize_skill_path(path: str, skill_name: str) -> str:
346
+ """Canonicalize a skill path to skill-relative form.
347
+
348
+ Strips current-skill path prefixes (.agents/skills/<name>/,
349
+ skills/<name>/, /skills/<name>/) to produce a clean skill-relative
350
+ path for comparison or resolution across both authoring and runtime
351
+ conventions.
352
+
353
+ Args:
354
+ path: Raw path string in any supported form.
355
+ skill_name: The expected skill name for validation.
356
+
357
+ Returns:
358
+ str: Canonical skill-relative path.
359
+
360
+ Raises:
361
+ SkillExecutionError: If the path is empty, uses an absolute or
362
+ .agents path for a different skill, or is an absolute path in
363
+ an unsupported form.
364
+ """
365
+ stripped = str(path).strip()
366
+ if not stripped:
367
+ raise SkillExecutionError("Path is required.")
368
+ normalized = posixpath.normpath(stripped.replace("\\", "/"))
369
+
370
+ # Strip runtime absolute prefix: /skills/<skill_name>/
371
+ runtime_prefix = f"/skills/{skill_name}"
372
+ if normalized.startswith("/"):
373
+ if normalized == runtime_prefix:
374
+ normalized = "" # pragma: no cover
375
+ elif normalized.startswith(f"{runtime_prefix}/"):
376
+ normalized = normalized[len(runtime_prefix) + 1 :]
377
+ elif normalized.startswith("/skills/"):
378
+ remainder = normalized[len("/skills/") :]
379
+ referenced_name = remainder.split("/", 1)[0]
380
+ raise SkillExecutionError(
381
+ f"script_relative_path references skill '{referenced_name}', "
382
+ f"not the current skill '{skill_name}'."
383
+ )
384
+ else:
385
+ raise SkillExecutionError("Path must be relative, not absolute.")
386
+
387
+ # Strip authoring-time prefix: .agents/skills/<skill_name>/
388
+ agents_prefix = f".agents/skills/{skill_name}"
389
+ if normalized == agents_prefix:
390
+ normalized = "" # pragma: no cover
391
+ elif normalized.startswith(f"{agents_prefix}/"):
392
+ normalized = normalized[len(agents_prefix) + 1 :]
393
+
394
+ # Strip runtime-relative prefix: skills/<skill_name>/
395
+ skill_prefix = f"skills/{skill_name}"
396
+ if normalized == skill_prefix:
397
+ normalized = ""
398
+ elif normalized.startswith(f"{skill_prefix}/"):
399
+ normalized = normalized[len(skill_prefix) + 1 :]
400
+
401
+ # Reject .agents/skills/<other>/ authoring paths.
402
+ _reject_wrong_skill_path(normalized, skill_name)
403
+
404
+ return normalized
405
+
406
+ def _normalize_script_relative_path(self, script_relative_path: str) -> str:
407
+ normalized = self._canonicalize_skill_path(script_relative_path, self.name)
408
+ if normalized in {"", "."}:
409
+ raise SkillExecutionError("script_relative_path must point to a concrete file.")
410
+ if normalized == ".." or normalized.startswith("../"):
411
+ raise SkillExecutionError("script_relative_path cannot traverse outside the skill directory.")
412
+ return normalized
413
+
414
+ def _enforce_allowed_script(self, script_relative_path: str, script_path: str) -> None:
415
+ policy = self.allowed_scripts_policy()
416
+ allowed = policy["mode"] == "allow_all" or script_relative_path in policy["allowed_scripts"]
417
+ if policy["mode"] == "deny_all":
418
+ allowed = False
419
+ if allowed:
420
+ return
421
+
422
+ denial = self._execution_denial(
423
+ script_relative_path=script_relative_path,
424
+ script_path=script_path,
425
+ policy_reason=str(policy["reason"]),
426
+ policy=policy,
427
+ )
428
+ raise SkillExecutionError(f"Skill script execution denied by policy: {policy['reason']}.", denial=denial)
429
+
430
+ def _ensure_local_script_exists(self, script_relative_path: str, script_path: str) -> None:
431
+ if (self.local_root_path / script_relative_path).is_file():
432
+ return
433
+ denial = self._execution_denial(
434
+ script_relative_path=script_relative_path,
435
+ script_path=script_path,
436
+ policy_reason="missing_script",
437
+ )
438
+ raise SkillExecutionError(
439
+ f"Skill script execution denied: script not found at {script_relative_path}.",
440
+ denial=denial,
441
+ )
442
+
443
+ def _execution_denial(
444
+ self,
445
+ *,
446
+ script_relative_path: str,
447
+ script_path: str,
448
+ policy_reason: str,
449
+ policy: dict[str, Any] | None = None,
450
+ ) -> dict[str, Any]:
451
+ resolved_policy = policy or self.allowed_scripts_policy()
452
+ return {
453
+ "skill_name": self.name,
454
+ "requested_script": script_relative_path,
455
+ "requested_resource": script_path,
456
+ "policy_mode": resolved_policy["mode"],
457
+ "allowed_scripts": resolved_policy["allowed_scripts"],
458
+ "policy_reason": policy_reason,
459
+ }
460
+
461
+ def _read_frontmatter(self) -> dict[str, Any]:
462
+ try:
463
+ content = self.skill_md_path.read_text(encoding="utf-8")
464
+ except OSError:
465
+ return {}
466
+ if not content.startswith("---"):
467
+ return {}
468
+ start = content.find("\n")
469
+ end = content.find("\n---", start + 1)
470
+ if start == -1 or end == -1:
471
+ return {"allowed-scripts": _MALFORMED_FRONTMATTER_POLICY}
472
+ try:
473
+ loaded = yaml.safe_load(content[start + 1 : end])
474
+ except yaml.YAMLError:
475
+ return {"allowed-scripts": _MALFORMED_FRONTMATTER_POLICY}
476
+ return loaded if isinstance(loaded, dict) else {"allowed-scripts": _MALFORMED_FRONTMATTER_POLICY}
477
+
478
+ @staticmethod
479
+ def _frontmatter_value(frontmatter: dict[str, Any], *keys: str, default: Any) -> Any:
480
+ for key in keys:
481
+ if key in frontmatter:
482
+ return frontmatter[key]
483
+ return default
484
+
485
+ @staticmethod
486
+ def _parse_allowed_scripts_str(raw_value: str) -> list[str] | None:
487
+ raw_value = raw_value.strip()
488
+ if raw_value == "*":
489
+ return ["*"]
490
+ if raw_value.lower() in _DENY_POLICY_MARKERS or not raw_value:
491
+ return []
492
+ return [item for part in raw_value.split(",") for item in part.split()]
493
+
494
+ @staticmethod
495
+ def _parse_allowed_scripts_list(raw_policy: list[Any]) -> list[str] | None:
496
+ if not raw_policy:
497
+ return []
498
+ if not all(isinstance(item, str) for item in raw_policy):
499
+ return None
500
+ return raw_policy
501
+
502
+ @staticmethod
503
+ def _normalize_script_items(raw_items: list[str], *, skill_name: str) -> dict[str, Any] | None:
504
+ normalized = []
505
+ for item in raw_items:
506
+ try:
507
+ normalized_item = Skill._canonicalize_skill_path(item, skill_name)
508
+ except SkillExecutionError:
509
+ return None
510
+ if normalized_item in {"", ".", ".."} or normalized_item.startswith("../"):
511
+ return None # pragma: no cover
512
+ normalized.append(normalized_item)
513
+ if "*" in normalized:
514
+ return Skill._allow_all_policy(skill_name, reason="allowed_scripts_allow_all")
515
+ return {
516
+ "skill_name": skill_name,
517
+ "mode": "allow_list",
518
+ "allowed_scripts": sorted(set(normalized)),
519
+ "valid": True,
520
+ "reason": "allowed_scripts_allow_list",
521
+ }
522
+
523
+ @staticmethod
524
+ def _allow_all_policy(skill_name: str, *, reason: str) -> dict[str, Any]:
525
+ return {
526
+ "skill_name": skill_name,
527
+ "mode": "allow_all",
528
+ "allowed_scripts": ["*"],
529
+ "valid": True,
530
+ "reason": reason,
531
+ }
532
+
533
+ @staticmethod
534
+ def normalize_allowed_scripts_policy(raw_policy: Any, *, skill_name: str) -> dict[str, Any]:
535
+ """Normalize frontmatter `allowed-scripts` into an enforcement contract."""
536
+ if raw_policy is _MISSING_POLICY:
537
+ return Skill._allow_all_policy(skill_name, reason="missing_allowed_scripts_defaults_to_allow_all")
538
+ if raw_policy is _MALFORMED_FRONTMATTER_POLICY:
539
+ return Skill._deny_all_policy(skill_name, reason="malformed_frontmatter", valid=False)
540
+ if raw_policy is None:
541
+ return Skill._deny_all_policy(skill_name, reason="allowed_scripts_denies_all")
542
+
543
+ if isinstance(raw_policy, str):
544
+ raw_items = Skill._parse_allowed_scripts_str(raw_policy)
545
+ elif isinstance(raw_policy, list):
546
+ raw_items = Skill._parse_allowed_scripts_list(raw_policy)
547
+ else:
548
+ raw_items = None
549
+
550
+ if raw_items is None:
551
+ return Skill._deny_all_policy(skill_name, reason="malformed_allowed_scripts", valid=False)
552
+ if not raw_items:
553
+ return Skill._deny_all_policy(skill_name, reason="allowed_scripts_denies_all")
554
+
555
+ result = Skill._normalize_script_items(raw_items, skill_name=skill_name)
556
+ if result is not None:
557
+ return result
558
+ return Skill._deny_all_policy(skill_name, reason="malformed_allowed_scripts", valid=False)
559
+
560
+ @staticmethod
561
+ def missing_allowed_scripts_policy_marker() -> object:
562
+ """Return the internal sentinel used for absent `allowed-scripts` metadata."""
563
+ return _MISSING_POLICY
564
+
565
+ @staticmethod
566
+ def _deny_all_policy(skill_name: str, *, reason: str, valid: bool = True) -> dict[str, Any]:
567
+ return {
568
+ "skill_name": skill_name,
569
+ "mode": "deny_all",
570
+ "allowed_scripts": [],
571
+ "valid": valid,
572
+ "reason": reason,
573
+ }
574
+
575
+ @staticmethod
576
+ def _normalize_string_sequence(raw_value: Any, *, field_name: str) -> list[str]:
577
+ if raw_value in (None, ""):
578
+ return []
579
+ if isinstance(raw_value, str):
580
+ trimmed = raw_value.strip()
581
+ return [trimmed] if trimmed else []
582
+ if isinstance(raw_value, list) and all(isinstance(item, str) for item in raw_value):
583
+ return [item.strip() for item in raw_value if item.strip()]
584
+ return []
585
+
586
+ def _script_paths_for_readiness(self, policy: dict[str, Any]) -> list[str]:
587
+ if policy["mode"] == "allow_list":
588
+ return list(policy["allowed_scripts"])
589
+ scripts_root = self.local_root_path / "scripts"
590
+ if not scripts_root.exists():
591
+ return []
592
+ return sorted(
593
+ path.relative_to(self.local_root_path).as_posix() for path in scripts_root.rglob("*") if path.is_file()
594
+ )
595
+
596
+ def _credential_file_check(self, path: str) -> dict[str, Any]:
597
+ expanded_path = Path(path).expanduser()
598
+ if not expanded_path.is_absolute():
599
+ expanded_path = self.local_root_path / expanded_path
600
+ return {"path": path, "status": "present" if expanded_path.is_file() else "missing"}
601
+
602
+ def _script_file_check(self, path: str) -> dict[str, Any]:
603
+ try:
604
+ normalized_path = self._normalize_script_relative_path(path)
605
+ except SkillExecutionError as exc:
606
+ return {"path": path, "status": "missing", "reason": str(exc)}
607
+ return {
608
+ "path": normalized_path,
609
+ "status": "present" if (self.local_root_path / normalized_path).is_file() else "missing",
610
+ }
611
+
612
+ @staticmethod
613
+ def _optional_dependency_check(name: str) -> dict[str, Any]:
614
+ import_name = name
615
+ for separator in ("[", "<", ">", "=", "!", "~"):
616
+ import_name = import_name.split(separator, 1)[0]
617
+ import_name = import_name.strip().replace("-", "_")
618
+ try:
619
+ found = bool(import_name) and importlib_util.find_spec(import_name) is not None
620
+ except (ModuleNotFoundError, ValueError):
621
+ found = False
622
+ return {"name": name, "status": "present" if found else "missing"}
623
+
624
+ @staticmethod
625
+ def _backend_execution_check(backend: Any | None, *, needs_execution: bool) -> dict[str, Any]:
626
+ if not needs_execution:
627
+ return {"status": "not_required"}
628
+ if backend is None:
629
+ return {"status": "not_checked"}
630
+ execute_fn = getattr(backend, "execute", None)
631
+ if not callable(execute_fn) or getattr(backend, "allow_execute", None) is False:
632
+ return {"status": "unsupported"}
633
+ return {"status": "supported"}
634
+
635
+ @staticmethod
636
+ def _resolve_timeout(timeout_seconds: int | None, default_timeout_seconds: int, max_timeout_seconds: int) -> int:
637
+ """Resolve timeout bounded by configured min/max values."""
638
+ default_timeout = max(1, int(default_timeout_seconds))
639
+ max_timeout = max(1, int(max_timeout_seconds))
640
+ if timeout_seconds is None:
641
+ return min(default_timeout, max_timeout)
642
+ return max(1, min(int(timeout_seconds), max_timeout))
643
+
644
+ @staticmethod
645
+ def _execute_accepts_timeout(execute_fn: Any) -> bool:
646
+ """Return whether execute callable accepts a timeout keyword."""
647
+ return callable_accepts_keyword(execute_fn, "timeout")
648
+
649
+ @staticmethod
650
+ def _build_command(script_path: str, args: tuple[str, ...]) -> str:
651
+ """Build a deterministic Python command for script execution."""
652
+ parts = ["python", script_path, *args]
653
+ return " ".join(shlex.quote(part) for part in parts)
654
+
655
+
656
+ def _reject_wrong_skill_path(path: str, current_skill_name: str) -> None:
657
+ """Reject .agents/skills/<other>/ paths that reference a different skill.
658
+
659
+ Args:
660
+ path (str): Normalized path to inspect. Relative skills/<x>/ paths are
661
+ treated as local subdirectories after current-skill prefix stripping.
662
+ current_skill_name (str): The expected skill name.
663
+
664
+ Raises:
665
+ SkillExecutionError: If the path references a different skill.
666
+ """
667
+ # Check .agents/skills/<other-skill>/... without the current skill prefix
668
+ agents_other = ".agents/skills/"
669
+ if path.startswith(agents_other):
670
+ remainder = path[len(agents_other) :]
671
+ next_slash = remainder.find("/")
672
+ if next_slash > 0:
673
+ referenced_name = remainder[:next_slash]
674
+ if referenced_name != current_skill_name:
675
+ raise SkillExecutionError(
676
+ f"script_relative_path references skill '{referenced_name}', "
677
+ f"not the current skill '{current_skill_name}'."
678
+ )
679
+
680
+
681
+ class ResourceType(StrEnum):
682
+ """Type of resource contained in a staged skill manifest."""
683
+
684
+ INSTRUCTION = "instruction"
685
+ REFERENCE = "reference"
686
+ EXAMPLE = "example"
687
+ SCRIPT = "script"
688
+ ASSET = "asset"
689
+
690
+
691
+ @dataclass
692
+ class ResourceEntry:
693
+ """Metadata for one staged skill resource."""
694
+
695
+ backend_path: str
696
+ relative_path: str
697
+ resource_type: ResourceType
698
+ size_bytes: int
699
+ is_binary: bool
700
+ summary: str | None = None
701
+
702
+
703
+ @dataclass
704
+ class ResourceManifest:
705
+ """Deterministic manifest of resources staged for one skill."""
706
+
707
+ skill_name: str
708
+ resources: list[ResourceEntry]
709
+
710
+
711
+ def _read_skill_frontmatter(skill_md_path: Path) -> dict[str, Any]:
712
+ try:
713
+ content = skill_md_path.read_text(encoding="utf-8")
714
+ except OSError:
715
+ return {}
716
+ frontmatter = _extract_frontmatter(content)
717
+ if frontmatter is None:
718
+ return {}
719
+ try:
720
+ parsed = yaml.safe_load(frontmatter)
721
+ except yaml.YAMLError:
722
+ return {}
723
+ if not isinstance(parsed, dict):
724
+ return {}
725
+ return dict(parsed)
726
+
727
+
728
+ def _extract_frontmatter(content: str) -> str | None:
729
+ lines = content.splitlines(keepends=True)
730
+ if not lines or not _is_frontmatter_delimiter(lines[0]):
731
+ return None
732
+
733
+ body: list[str] = []
734
+ for line in lines[1:]:
735
+ if _is_frontmatter_delimiter(line):
736
+ return "".join(body)
737
+ body.append(line)
738
+
739
+ return None
740
+
741
+
742
+ def _is_frontmatter_delimiter(line: str) -> bool:
743
+ stripped = line.rstrip("\r\n")
744
+ return stripped.strip() == "---" and stripped == stripped.lstrip()
745
+
746
+
747
+ def _skill_md_hash(skill_md_path: Path) -> str | None:
748
+ try:
749
+ data = skill_md_path.read_bytes()
750
+ except OSError:
751
+ return None
752
+ return f"sha256:{hashlib.sha256(data).hexdigest()}"
753
+
754
+
755
+ def _optional_text(value: Any) -> str | None:
756
+ if value is None:
757
+ return None
758
+ text = str(value).strip()
759
+ return text or None
760
+
761
+
762
+ def _string_tuple(value: Any) -> tuple[str, ...]:
763
+ if value is None:
764
+ return ()
765
+ if isinstance(value, str):
766
+ return (value.strip(),) if value.strip() else ()
767
+ if isinstance(value, list | tuple):
768
+ return tuple(str(item).strip() for item in value if str(item).strip())
769
+ return (str(value).strip(),) if str(value).strip() else ()