aip-agents-binary 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aip_agents/__init__.py +66 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/base_executor.py +1125 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/a2ui/__init__.py +26 -0
- aip_agents/a2ui/catalog_provider.py +109 -0
- aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
- aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
- aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
- aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
- aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
- aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
- aip_agents/a2ui/middleware.py +754 -0
- aip_agents/a2ui/models.py +153 -0
- aip_agents/a2ui/public_url.py +138 -0
- aip_agents/a2ui/stream_filter.py +67 -0
- aip_agents/agent/__init__.py +72 -0
- aip_agents/agent/base_agent.py +1437 -0
- aip_agents/agent/base_langgraph_agent.py +4048 -0
- aip_agents/agent/google_adk_agent.py +932 -0
- aip_agents/agent/google_adk_constants.py +11 -0
- aip_agents/agent/graph_runtime.py +185 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langgraph_react_agent.py +3741 -0
- aip_agents/agent/model_invocation_adapter.py +346 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/agent/tool_execution_engine.py +587 -0
- aip_agents/audio_interface/__init__.py +30 -0
- aip_agents/audio_interface/audio_agent_adapter.py +254 -0
- aip_agents/audio_interface/config.py +146 -0
- aip_agents/audio_interface/errors.py +15 -0
- aip_agents/audio_interface/livekit_audio_session.py +504 -0
- aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
- aip_agents/audio_interface/session_factory.py +40 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/common/__init__.py +5 -0
- aip_agents/common/callable_introspection.py +17 -0
- aip_agents/constants.py +23 -0
- aip_agents/context/__init__.py +5 -0
- aip_agents/context/budget.py +237 -0
- aip_agents/context/compaction.py +339 -0
- aip_agents/context/metadata.py +169 -0
- aip_agents/context/offload.py +449 -0
- aip_agents/context/preparation.py +196 -0
- aip_agents/context/prompt.py +61 -0
- aip_agents/context/runtime.py +149 -0
- aip_agents/context/session_recall.py +69 -0
- aip_agents/context/state.py +48 -0
- aip_agents/context/utils.py +44 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/audio_livekit_session.py +116 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_server.py +141 -0
- aip_agents/examples/gl_connectors_smoke.py +148 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_filesystem.py +162 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_skills.py +62 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
- aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_ptc.py +49 -0
- aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
- aip_agents/examples/hello_world_tool_output_client.py +55 -0
- aip_agents/examples/hello_world_tool_output_server.py +113 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/mcp_configs/configs.py +63 -0
- aip_agents/examples/mcp_servers/common.py +76 -0
- aip_agents/examples/mcp_servers/mcp_name.py +29 -0
- aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
- aip_agents/examples/mcp_servers/mcp_time.py +10 -0
- aip_agents/examples/pii_demo_langgraph_client.py +69 -0
- aip_agents/examples/pii_demo_langgraph_server.py +125 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/multiply_tool.py +43 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/guardrails/__init__.py +83 -0
- aip_agents/guardrails/engines/__init__.py +69 -0
- aip_agents/guardrails/engines/base.py +90 -0
- aip_agents/guardrails/engines/nemo.py +101 -0
- aip_agents/guardrails/engines/phrase_matcher.py +113 -0
- aip_agents/guardrails/exceptions.py +39 -0
- aip_agents/guardrails/manager.py +163 -0
- aip_agents/guardrails/middleware.py +199 -0
- aip_agents/guardrails/schemas.py +63 -0
- aip_agents/guardrails/utils.py +45 -0
- aip_agents/integration/__init__.py +43 -0
- aip_agents/integration/agent.py +58 -0
- aip_agents/integration/guardrails.py +52 -0
- aip_agents/integration/hitl.py +71 -0
- aip_agents/integration/ptc.py +95 -0
- aip_agents/integration/skills.py +37 -0
- aip_agents/integration/storage.py +81 -0
- aip_agents/integration/version.py +103 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/client/__init__.py +60 -0
- aip_agents/mcp/client/base_mcp_client.py +377 -0
- aip_agents/mcp/client/connection_manager.py +231 -0
- aip_agents/mcp/client/errors.py +119 -0
- aip_agents/mcp/client/google_adk/__init__.py +45 -0
- aip_agents/mcp/client/google_adk/client.py +383 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/persistent_session.py +704 -0
- aip_agents/mcp/client/session_pool.py +358 -0
- aip_agents/mcp/client/transports.py +288 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/base_adapter.py +815 -0
- aip_agents/memory/adapters/mem0.py +286 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/middleware/__init__.py +65 -0
- aip_agents/middleware/backends/__init__.py +72 -0
- aip_agents/middleware/backends/in_memory.py +578 -0
- aip_agents/middleware/backends/local_disk.py +1203 -0
- aip_agents/middleware/backends/protocol.py +353 -0
- aip_agents/middleware/backends/sandbox.py +1431 -0
- aip_agents/middleware/backends/sandbox_files.py +113 -0
- aip_agents/middleware/backends/utils.py +293 -0
- aip_agents/middleware/base.py +239 -0
- aip_agents/middleware/filesystem.py +690 -0
- aip_agents/middleware/manager.py +291 -0
- aip_agents/middleware/memory.py +532 -0
- aip_agents/middleware/pii_middleware.py +192 -0
- aip_agents/middleware/schema.py +19 -0
- aip_agents/middleware/skills.py +872 -0
- aip_agents/middleware/task_delegation.py +1656 -0
- aip_agents/middleware/todolist.py +276 -0
- aip_agents/middleware/tools/__init__.py +23 -0
- aip_agents/middleware/tools/edit_file.py +70 -0
- aip_agents/middleware/tools/execute.py +428 -0
- aip_agents/middleware/tools/grep_file.py +79 -0
- aip_agents/middleware/tools/ls.py +71 -0
- aip_agents/middleware/tools/read_file.py +239 -0
- aip_agents/middleware/tools/skill_load.py +193 -0
- aip_agents/middleware/tools/skill_resource.py +338 -0
- aip_agents/middleware/tools/write_file.py +114 -0
- aip_agents/middleware/vision.py +322 -0
- aip_agents/ptc/__init__.py +87 -0
- aip_agents/ptc/custom_tools.py +511 -0
- aip_agents/ptc/custom_tools_payload.py +499 -0
- aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
- aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
- aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
- aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
- aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
- aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
- aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/exceptions.py +57 -0
- aip_agents/ptc/executor.py +627 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
- aip_agents/ptc/naming.py +430 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/prompt_builder.py +674 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/sandbox_bridge.py +297 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/ptc/tool_def_helpers.py +101 -0
- aip_agents/ptc/tool_enrichment.py +163 -0
- aip_agents/resilience/__init__.py +10 -0
- aip_agents/resilience/base.py +46 -0
- aip_agents/resilience/tool.py +1661 -0
- aip_agents/sandbox/__init__.py +62 -0
- aip_agents/sandbox/defaults.py +220 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
- aip_agents/sandbox/e2b_runtime.py +27 -0
- aip_agents/sandbox/image_registry.py +98 -0
- aip_agents/sandbox/providers/__init__.py +76 -0
- aip_agents/sandbox/providers/base.py +392 -0
- aip_agents/sandbox/providers/bedrock.py +28 -0
- aip_agents/sandbox/providers/e2b.py +230 -0
- aip_agents/sandbox/providers/opensandbox.py +163 -0
- aip_agents/sandbox/sandbox_runtime.py +967 -0
- aip_agents/sandbox/template_builder.py +240 -0
- aip_agents/sandbox/types.py +141 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/schema/__init__.py +106 -0
- aip_agents/schema/a2a.py +58 -0
- aip_agents/schema/agent.py +117 -0
- aip_agents/schema/artifacts.py +75 -0
- aip_agents/schema/attachments.py +425 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/langgraph.py +47 -0
- aip_agents/schema/model_id.py +103 -0
- aip_agents/schema/model_usage.py +50 -0
- aip_agents/schema/runtime_metadata.py +78 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/schema/tool_payloads.py +68 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/sentry.py +267 -0
- aip_agents/skills/__init__.py +30 -0
- aip_agents/skills/errors.py +110 -0
- aip_agents/skills/installer.py +479 -0
- aip_agents/skills/models.py +769 -0
- aip_agents/skills/stager.py +224 -0
- aip_agents/skills/validation.py +53 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/tools/__init__.py +65 -0
- aip_agents/tools/agent_schedule_tool.py +392 -0
- aip_agents/tools/browser_use/__init__.py +83 -0
- aip_agents/tools/browser_use/action_parser.py +104 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/minio_storage.py +203 -0
- aip_agents/tools/browser_use/schemas.py +121 -0
- aip_agents/tools/browser_use/session.py +13 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/steel_session_recording.py +321 -0
- aip_agents/tools/browser_use/streaming.py +263 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/types.py +37 -0
- aip_agents/tools/code_sandbox/__init__.py +48 -0
- aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
- aip_agents/tools/computer_use/__init__.py +75 -0
- aip_agents/tools/computer_use/computer_use_tool.py +910 -0
- aip_agents/tools/computer_use/schemas.py +78 -0
- aip_agents/tools/computer_use/streaming.py +198 -0
- aip_agents/tools/computer_use/types.py +32 -0
- aip_agents/tools/constants.py +177 -0
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/execute_ptc_code.py +582 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/tool.py +518 -0
- aip_agents/tools/gl_connector_tools.py +170 -0
- aip_agents/tools/memory_search/__init__.py +29 -0
- aip_agents/tools/memory_search/base.py +213 -0
- aip_agents/tools/memory_search/mem0.py +424 -0
- aip_agents/tools/memory_search/schema.py +81 -0
- aip_agents/tools/memory_search_tool.py +34 -0
- aip_agents/tools/streaming_common.py +577 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/types/__init__.py +73 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/utils/__init__.py +91 -0
- aip_agents/utils/a2a_connector.py +1758 -0
- aip_agents/utils/artifact_helpers.py +542 -0
- aip_agents/utils/artifacts/__init__.py +20 -0
- aip_agents/utils/artifacts/tracker.py +367 -0
- aip_agents/utils/attachment_helpers.py +178 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/file_prompt_utils.py +276 -0
- aip_agents/utils/file_watchers/__init__.py +33 -0
- aip_agents/utils/file_watchers/base.py +46 -0
- aip_agents/utils/file_watchers/e2b.py +128 -0
- aip_agents/utils/file_watchers/factory.py +39 -0
- aip_agents/utils/file_watchers/ls_diff.py +88 -0
- aip_agents/utils/file_watchers/sandbox.py +66 -0
- aip_agents/utils/file_watchers/watchdog.py +97 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/formatter_llm_client.py +258 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/converter.py +399 -0
- aip_agents/utils/langgraph/delegation_context.py +22 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
- aip_agents/utils/langgraph/tool_output_management.py +1047 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata_helper.py +363 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/pii/__init__.py +29 -0
- aip_agents/utils/pii/pii_handler.py +555 -0
- aip_agents/utils/pii/pii_helper.py +225 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/sse_chunk_transformer.py +836 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/token_usage_helper.py +531 -0
- aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
- aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
- aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
- aip_agents_binary-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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 ()
|