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,338 @@
|
|
|
1
|
+
"""Skill-attributed resource discovery and loading tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import posixpath
|
|
8
|
+
import re
|
|
9
|
+
from typing import Annotated, Any, Final, TypedDict
|
|
10
|
+
|
|
11
|
+
from langchain_core.tools import BaseTool
|
|
12
|
+
from pydantic import BaseModel, Field, SkipValidation
|
|
13
|
+
|
|
14
|
+
from aip_agents.middleware.schema import MiddlewareToolConfig
|
|
15
|
+
from aip_agents.skills.errors import SkillValidationError
|
|
16
|
+
from aip_agents.skills.validation import validate_skill_name
|
|
17
|
+
|
|
18
|
+
MAX_SKILL_RESOURCE_CHARS: Final[int] = 20_000
|
|
19
|
+
_TOKEN_RE = re.compile(r"[a-z0-9]+")
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"MAX_SKILL_RESOURCE_CHARS",
|
|
23
|
+
"SkillResourceInput",
|
|
24
|
+
"SkillResourceResult",
|
|
25
|
+
"SkillResourceSummary",
|
|
26
|
+
"SkillResourceTool",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SkillResourceInput(BaseModel):
|
|
31
|
+
"""Input schema for skill_resource."""
|
|
32
|
+
|
|
33
|
+
name: str = Field(description="Skill name that owns the resource")
|
|
34
|
+
path: str | None = Field(default=None, description="Resource backend path or skill-relative path")
|
|
35
|
+
query: str | None = Field(
|
|
36
|
+
default=None, description="Optional keyword query used to pick a resource from the manifest"
|
|
37
|
+
)
|
|
38
|
+
max_chars: int | None = Field(default=None, description="Maximum characters to return for text resources")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SkillResourceSummary(TypedDict, total=False):
|
|
42
|
+
"""Manifest-backed resource metadata."""
|
|
43
|
+
|
|
44
|
+
backend_path: str
|
|
45
|
+
relative_path: str
|
|
46
|
+
resource_type: str
|
|
47
|
+
size_bytes: int
|
|
48
|
+
is_binary: bool
|
|
49
|
+
summary: str | None
|
|
50
|
+
score: int
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SkillResourceResult(TypedDict, total=False):
|
|
54
|
+
"""Structured output returned by `skill_resource`."""
|
|
55
|
+
|
|
56
|
+
name: str
|
|
57
|
+
backend_root: str | None
|
|
58
|
+
resource_index_path: str | None
|
|
59
|
+
resource_path: str | None
|
|
60
|
+
relative_path: str | None
|
|
61
|
+
resource_type: str | None
|
|
62
|
+
size_bytes: int | None
|
|
63
|
+
is_binary: bool | None
|
|
64
|
+
summary: str | None
|
|
65
|
+
content: str
|
|
66
|
+
content_hash: str
|
|
67
|
+
budget_truncated: bool
|
|
68
|
+
warnings: list[str]
|
|
69
|
+
matches: list[SkillResourceSummary]
|
|
70
|
+
error: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SkillResourceTool(BaseTool):
|
|
74
|
+
"""Tool for skill-attributed support-resource discovery and loading."""
|
|
75
|
+
|
|
76
|
+
name: str = "skill_resource"
|
|
77
|
+
description: str = (
|
|
78
|
+
"Discover or load a staged skill support resource through the skill manifest. "
|
|
79
|
+
"Use this for references, examples, scripts, and assets after skill_load so resource use is skill-attributed."
|
|
80
|
+
)
|
|
81
|
+
args_schema: type[BaseModel] = SkillResourceInput # type: ignore[assignment]
|
|
82
|
+
tool_config_schema: type[BaseModel] = MiddlewareToolConfig
|
|
83
|
+
backend: Annotated[Any, SkipValidation()]
|
|
84
|
+
destination_root: str = "/skills"
|
|
85
|
+
skill_inventory: Annotated[Any, SkipValidation()]
|
|
86
|
+
|
|
87
|
+
def _run(
|
|
88
|
+
self,
|
|
89
|
+
name: str,
|
|
90
|
+
path: str | None = None,
|
|
91
|
+
query: str | None = None,
|
|
92
|
+
max_chars: int | None = None,
|
|
93
|
+
) -> SkillResourceResult:
|
|
94
|
+
requested_name = name.strip()
|
|
95
|
+
try:
|
|
96
|
+
validate_skill_name(requested_name)
|
|
97
|
+
except SkillValidationError as exc:
|
|
98
|
+
return self._error_result(requested_name, f"Invalid skill name: {exc}")
|
|
99
|
+
|
|
100
|
+
skill_names = {skill.name for skill in self.skill_inventory() or []}
|
|
101
|
+
if requested_name not in skill_names:
|
|
102
|
+
return self._error_result(requested_name, f"Skill not found: {requested_name}")
|
|
103
|
+
|
|
104
|
+
backend_root = posixpath.join(self.destination_root, requested_name)
|
|
105
|
+
resource_index_path = posixpath.join(backend_root, ".aip-skill-index.json")
|
|
106
|
+
resources_result = self._load_manifest(requested_name, backend_root, resource_index_path)
|
|
107
|
+
if "error" in resources_result:
|
|
108
|
+
return resources_result
|
|
109
|
+
|
|
110
|
+
resources = resources_result["matches"]
|
|
111
|
+
selected = self._select_resource(resources, backend_root=backend_root, path=path, query=query)
|
|
112
|
+
if selected is None:
|
|
113
|
+
if path:
|
|
114
|
+
return self._error_result(requested_name, f"Resource not found in skill manifest: {path}")
|
|
115
|
+
return {
|
|
116
|
+
"name": requested_name,
|
|
117
|
+
"backend_root": backend_root,
|
|
118
|
+
"resource_index_path": resource_index_path,
|
|
119
|
+
"resource_path": None,
|
|
120
|
+
"relative_path": None,
|
|
121
|
+
"resource_type": None,
|
|
122
|
+
"size_bytes": None,
|
|
123
|
+
"is_binary": None,
|
|
124
|
+
"summary": None,
|
|
125
|
+
"content": "",
|
|
126
|
+
"content_hash": "",
|
|
127
|
+
"budget_truncated": False,
|
|
128
|
+
"warnings": ["No resource selected; inspect matches or provide path/query."],
|
|
129
|
+
"matches": self._rank_resources(resources, query),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return self._load_resource(requested_name, backend_root, resource_index_path, selected, max_chars=max_chars)
|
|
133
|
+
|
|
134
|
+
def _load_manifest(self, name: str, backend_root: str, resource_index_path: str) -> SkillResourceResult:
|
|
135
|
+
try:
|
|
136
|
+
manifest = json.loads(self.backend.read_bytes(resource_index_path).decode("utf-8"))
|
|
137
|
+
except Exception as exc: # pragma: no cover
|
|
138
|
+
return self._error_result(
|
|
139
|
+
name, f"Unable to read resource manifest at {resource_index_path}: {exc}"
|
|
140
|
+
) # pragma: no cover
|
|
141
|
+
|
|
142
|
+
resources = manifest.get("resources", [])
|
|
143
|
+
if not isinstance(resources, list):
|
|
144
|
+
return self._error_result(
|
|
145
|
+
name, f"Invalid resource manifest at {resource_index_path}: resources must be a list"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
summaries: list[SkillResourceSummary] = []
|
|
149
|
+
for item in resources:
|
|
150
|
+
if not isinstance(item, dict):
|
|
151
|
+
continue
|
|
152
|
+
relative_path = self._normalize_manifest_relative_path(str(item.get("relative_path", "")))
|
|
153
|
+
backend_path = self._normalize_manifest_backend_path(
|
|
154
|
+
str(item.get("backend_path", "")), backend_root=backend_root, relative_path=relative_path
|
|
155
|
+
)
|
|
156
|
+
size_bytes = self._coerce_size_bytes(item.get("size_bytes", 0))
|
|
157
|
+
if backend_path is None or relative_path is None:
|
|
158
|
+
continue
|
|
159
|
+
summaries.append(
|
|
160
|
+
{
|
|
161
|
+
"backend_path": backend_path,
|
|
162
|
+
"relative_path": relative_path,
|
|
163
|
+
"resource_type": str(item.get("resource_type", "asset")),
|
|
164
|
+
"size_bytes": size_bytes,
|
|
165
|
+
"is_binary": bool(item.get("is_binary", False)),
|
|
166
|
+
"summary": item.get("summary") if item.get("summary") is None else str(item.get("summary")),
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
return {
|
|
170
|
+
"name": name,
|
|
171
|
+
"backend_root": backend_root,
|
|
172
|
+
"resource_index_path": resource_index_path,
|
|
173
|
+
"matches": summaries,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
def _select_resource(
|
|
177
|
+
self,
|
|
178
|
+
resources: list[SkillResourceSummary],
|
|
179
|
+
*,
|
|
180
|
+
backend_root: str,
|
|
181
|
+
path: str | None,
|
|
182
|
+
query: str | None,
|
|
183
|
+
) -> SkillResourceSummary | None:
|
|
184
|
+
normalized_path = self._normalize_requested_path(path, backend_root=backend_root) if path else None
|
|
185
|
+
if normalized_path:
|
|
186
|
+
for resource in resources:
|
|
187
|
+
if normalized_path in {resource["relative_path"], resource["backend_path"]}:
|
|
188
|
+
return resource
|
|
189
|
+
return None
|
|
190
|
+
ranked = self._rank_resources(resources, query)
|
|
191
|
+
if query and ranked and ranked[0].get("score", 0) > 0:
|
|
192
|
+
return ranked[0]
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
def _load_resource(
|
|
196
|
+
self,
|
|
197
|
+
name: str,
|
|
198
|
+
backend_root: str,
|
|
199
|
+
resource_index_path: str,
|
|
200
|
+
resource: SkillResourceSummary,
|
|
201
|
+
*,
|
|
202
|
+
max_chars: int | None,
|
|
203
|
+
) -> SkillResourceResult:
|
|
204
|
+
resource_path = resource["backend_path"]
|
|
205
|
+
try:
|
|
206
|
+
raw_content = self.backend.read_bytes(resource_path)
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
return self._resource_error_result(
|
|
209
|
+
name, backend_root, resource_index_path, resource, f"Unable to read resource at {resource_path}: {exc}"
|
|
210
|
+
)
|
|
211
|
+
content_hash = f"sha256:{hashlib.sha256(raw_content).hexdigest()}"
|
|
212
|
+
warnings: list[str] = []
|
|
213
|
+
content = ""
|
|
214
|
+
budget_truncated = False
|
|
215
|
+
if resource["is_binary"]:
|
|
216
|
+
warnings.append("Binary resource metadata returned without raw content.")
|
|
217
|
+
else:
|
|
218
|
+
decoded = raw_content.decode("utf-8", errors="replace")
|
|
219
|
+
limit = max(1, min(max_chars or MAX_SKILL_RESOURCE_CHARS, MAX_SKILL_RESOURCE_CHARS))
|
|
220
|
+
budget_truncated = len(decoded) > limit
|
|
221
|
+
content = decoded[:limit] if budget_truncated else decoded
|
|
222
|
+
if budget_truncated:
|
|
223
|
+
warnings.append(f"Resource content truncated to {limit} characters.")
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"name": name,
|
|
227
|
+
"backend_root": backend_root,
|
|
228
|
+
"resource_index_path": resource_index_path,
|
|
229
|
+
"resource_path": resource_path,
|
|
230
|
+
"relative_path": resource["relative_path"],
|
|
231
|
+
"resource_type": resource["resource_type"],
|
|
232
|
+
"size_bytes": resource["size_bytes"],
|
|
233
|
+
"is_binary": resource["is_binary"],
|
|
234
|
+
"summary": resource.get("summary"),
|
|
235
|
+
"content": content,
|
|
236
|
+
"content_hash": content_hash,
|
|
237
|
+
"budget_truncated": budget_truncated,
|
|
238
|
+
"warnings": warnings,
|
|
239
|
+
"matches": [resource],
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
def _rank_resources(self, resources: list[SkillResourceSummary], query: str | None) -> list[SkillResourceSummary]:
|
|
243
|
+
if not query:
|
|
244
|
+
return sorted(resources, key=lambda item: item["relative_path"])
|
|
245
|
+
query_tokens = set(_TOKEN_RE.findall(query.lower()))
|
|
246
|
+
ranked: list[SkillResourceSummary] = []
|
|
247
|
+
for resource in resources:
|
|
248
|
+
haystack = " ".join(
|
|
249
|
+
[resource["relative_path"], resource["resource_type"], str(resource.get("summary") or "")]
|
|
250
|
+
).lower()
|
|
251
|
+
score = sum(1 for token in query_tokens if token in haystack)
|
|
252
|
+
ranked.append({**resource, "score": score})
|
|
253
|
+
return sorted(ranked, key=lambda item: (-int(item.get("score", 0)), item["relative_path"]))
|
|
254
|
+
|
|
255
|
+
def _normalize_requested_path(self, path: str | None, *, backend_root: str) -> str | None:
|
|
256
|
+
if path is None:
|
|
257
|
+
return None # pragma: no cover
|
|
258
|
+
normalized = posixpath.normpath(path.strip().replace("\\", "/"))
|
|
259
|
+
if normalized in {"", "."}:
|
|
260
|
+
return None
|
|
261
|
+
if normalized.startswith(f"{backend_root}/"):
|
|
262
|
+
return normalized
|
|
263
|
+
if normalized.startswith("/"):
|
|
264
|
+
return normalized # pragma: no cover
|
|
265
|
+
return normalized.lstrip("/")
|
|
266
|
+
|
|
267
|
+
def _normalize_manifest_relative_path(self, relative_path: str) -> str | None:
|
|
268
|
+
normalized = relative_path.strip().replace("\\", "/")
|
|
269
|
+
if not normalized or normalized.startswith("/") or "\x00" in normalized:
|
|
270
|
+
return None
|
|
271
|
+
if any(part == ".." for part in normalized.split("/")):
|
|
272
|
+
return None
|
|
273
|
+
normalized = posixpath.normpath(normalized)
|
|
274
|
+
if normalized in {"", "."}:
|
|
275
|
+
return None # pragma: no cover
|
|
276
|
+
return normalized
|
|
277
|
+
|
|
278
|
+
def _normalize_manifest_backend_path(
|
|
279
|
+
self, backend_path: str, *, backend_root: str, relative_path: str | None
|
|
280
|
+
) -> str | None:
|
|
281
|
+
if relative_path is None:
|
|
282
|
+
return None
|
|
283
|
+
normalized = posixpath.normpath(backend_path.strip().replace("\\", "/"))
|
|
284
|
+
expected = posixpath.normpath(posixpath.join(backend_root, relative_path))
|
|
285
|
+
if normalized != expected or not normalized.startswith(f"{backend_root}/"):
|
|
286
|
+
return None
|
|
287
|
+
return normalized
|
|
288
|
+
|
|
289
|
+
def _coerce_size_bytes(self, value: Any) -> int:
|
|
290
|
+
try:
|
|
291
|
+
return max(0, int(value))
|
|
292
|
+
except (TypeError, ValueError):
|
|
293
|
+
return 0
|
|
294
|
+
|
|
295
|
+
def _resource_error_result(
|
|
296
|
+
self,
|
|
297
|
+
name: str,
|
|
298
|
+
backend_root: str,
|
|
299
|
+
resource_index_path: str,
|
|
300
|
+
resource: SkillResourceSummary,
|
|
301
|
+
error: str,
|
|
302
|
+
) -> SkillResourceResult:
|
|
303
|
+
return {
|
|
304
|
+
"name": name,
|
|
305
|
+
"backend_root": backend_root,
|
|
306
|
+
"resource_index_path": resource_index_path,
|
|
307
|
+
"resource_path": resource["backend_path"],
|
|
308
|
+
"relative_path": resource["relative_path"],
|
|
309
|
+
"resource_type": resource["resource_type"],
|
|
310
|
+
"size_bytes": resource["size_bytes"],
|
|
311
|
+
"is_binary": resource["is_binary"],
|
|
312
|
+
"summary": resource.get("summary"),
|
|
313
|
+
"content": "",
|
|
314
|
+
"content_hash": "",
|
|
315
|
+
"budget_truncated": False,
|
|
316
|
+
"warnings": [error],
|
|
317
|
+
"matches": [resource],
|
|
318
|
+
"error": error,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
def _error_result(self, name: str, error: str) -> SkillResourceResult:
|
|
322
|
+
return {
|
|
323
|
+
"name": name,
|
|
324
|
+
"backend_root": None,
|
|
325
|
+
"resource_index_path": None,
|
|
326
|
+
"resource_path": None,
|
|
327
|
+
"relative_path": None,
|
|
328
|
+
"resource_type": None,
|
|
329
|
+
"size_bytes": None,
|
|
330
|
+
"is_binary": None,
|
|
331
|
+
"summary": None,
|
|
332
|
+
"content": "",
|
|
333
|
+
"content_hash": "",
|
|
334
|
+
"budget_truncated": False,
|
|
335
|
+
"warnings": [error],
|
|
336
|
+
"matches": [],
|
|
337
|
+
"error": error,
|
|
338
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Write file tool for agents.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
|
|
6
|
+
TODO: When migrating to deepagents, replace this entire file with:
|
|
7
|
+
from deepagents.middleware.tools.write_file import WriteFileTool, WriteFileInput
|
|
8
|
+
|
|
9
|
+
Note: deepagents WriteFileTool uses ToolRuntime pattern instead of BaseTool.
|
|
10
|
+
We'll need to adapt our middleware to use deepagents' tool registration.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Annotated, Any
|
|
14
|
+
|
|
15
|
+
from langchain_core.tools import BaseTool
|
|
16
|
+
from pydantic import BaseModel, Field, SkipValidation
|
|
17
|
+
|
|
18
|
+
from aip_agents.middleware.schema import MiddlewareToolConfig
|
|
19
|
+
|
|
20
|
+
WRITE_FILE_TOOL_DESCRIPTION = """Writes to a new file in the filesystem.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
- The write_file tool will create the a new file.
|
|
24
|
+
- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WriteFileInput(BaseModel):
|
|
29
|
+
"""Input schema for ``write_file``.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
path (str): Absolute destination path for the new file.
|
|
33
|
+
content (str): UTF-8 text content to write.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
path: str = Field(description="Absolute path to the file to write. Must be absolute, not relative.")
|
|
37
|
+
content: str = Field(description="Content to write to the file.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WriteFileTool(BaseTool):
|
|
41
|
+
"""Create a new file in the configured filesystem backend.
|
|
42
|
+
|
|
43
|
+
This tool enforces create-only semantics: if the target path already exists,
|
|
44
|
+
it returns an error instead of overwriting the file.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
name (str): Tool identifier exposed to the model.
|
|
48
|
+
description (str): Human-readable usage guidance for the model.
|
|
49
|
+
args_schema (type[BaseModel]): Pydantic schema for tool arguments.
|
|
50
|
+
backend (Any): Filesystem backend implementing ``read`` and ``write``.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
name: str = "write_file"
|
|
54
|
+
description: str = WRITE_FILE_TOOL_DESCRIPTION
|
|
55
|
+
args_schema: type[BaseModel] = WriteFileInput
|
|
56
|
+
tool_config_schema: type[BaseModel] = MiddlewareToolConfig
|
|
57
|
+
backend: Annotated[Any, SkipValidation()] # BackendProtocol - skip validation for Protocol type
|
|
58
|
+
|
|
59
|
+
def _file_exists(self, path: str) -> bool:
|
|
60
|
+
"""Check whether a filesystem entry already exists at ``path``.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
path (str): Absolute path to inspect.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
bool: ``True`` when a file or directory exists at ``path``;
|
|
67
|
+
``False`` when the path does not exist.
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
self.backend.read(path, line_offset=0, limit=1)
|
|
71
|
+
return True
|
|
72
|
+
except FileNotFoundError:
|
|
73
|
+
return False
|
|
74
|
+
except IsADirectoryError:
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
def _run(self, path: str, content: str) -> str:
|
|
78
|
+
"""Create a new file with the provided content.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
path (str): Absolute destination path for the new file.
|
|
82
|
+
content (str): Full file content to persist.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
str: Success or error status message. Returns
|
|
86
|
+
``"Successfully wrote {n} characters to {path}"`` on success,
|
|
87
|
+
or ``"Error: ..."`` when creation fails.
|
|
88
|
+
|
|
89
|
+
Notes:
|
|
90
|
+
This method catches runtime exceptions and converts them to
|
|
91
|
+
user-facing error messages; it does not raise exceptions.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
>>> tool._run(path="/reports/summary.md", content="# Summary")
|
|
95
|
+
'Successfully wrote 9 characters to /reports/summary.md'
|
|
96
|
+
>>> tool._run(path="/reports/summary.md", content="# New")
|
|
97
|
+
'Error: File already exists: /reports/summary.md'
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
if self._file_exists(path):
|
|
101
|
+
return f"Error: File already exists: {path}"
|
|
102
|
+
|
|
103
|
+
if hasattr(self.backend, "ensure_parent_dir"):
|
|
104
|
+
self.backend.ensure_parent_dir(path)
|
|
105
|
+
|
|
106
|
+
result = self.backend.write(path, content)
|
|
107
|
+
if result.success:
|
|
108
|
+
return f"Successfully wrote {len(content)} characters to {path}"
|
|
109
|
+
else:
|
|
110
|
+
return f"Error: {result.error or 'Unknown error'}"
|
|
111
|
+
except PermissionError:
|
|
112
|
+
return f"Error: Permission denied: {path}"
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return f"Error: {str(e)}"
|