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,690 @@
|
|
|
1
|
+
"""Filesystem middleware for agent file operations.
|
|
2
|
+
|
|
3
|
+
Provides file storage and management capabilities for agents, enabling them
|
|
4
|
+
to read, write, edit, and search files within isolated thread-specific storage.
|
|
5
|
+
|
|
6
|
+
TODO: When migrating to deepagents:
|
|
7
|
+
1. Import BackendProtocol from deepagents.backends.protocol
|
|
8
|
+
2. Keep InMemoryBackend or use deepagents.backends.StateBackend
|
|
9
|
+
3. Tools will import from deepagents middleware tools
|
|
10
|
+
|
|
11
|
+
Authors:
|
|
12
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from abc import abstractmethod
|
|
18
|
+
from functools import lru_cache
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from langchain_core.messages import ToolMessage
|
|
23
|
+
from langchain_core.tools import BaseTool
|
|
24
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
25
|
+
|
|
26
|
+
from aip_agents.common.callable_introspection import callable_accepts_keyword
|
|
27
|
+
from aip_agents.middleware.backends.in_memory import InMemoryBackend
|
|
28
|
+
from aip_agents.middleware.backends.local_disk import LocalDiskBackend
|
|
29
|
+
from aip_agents.middleware.backends.protocol import BackendProtocol
|
|
30
|
+
from aip_agents.middleware.backends.utils import sanitize_tool_call_id
|
|
31
|
+
from aip_agents.middleware.base import AgentMiddleware, ModelRequest
|
|
32
|
+
from aip_agents.middleware.tools.edit_file import EditFileTool
|
|
33
|
+
from aip_agents.middleware.tools.execute import ExecuteTool
|
|
34
|
+
from aip_agents.middleware.tools.grep_file import GrepTool
|
|
35
|
+
from aip_agents.middleware.tools.ls import LsTool
|
|
36
|
+
from aip_agents.middleware.tools.read_file import ReadFileTool
|
|
37
|
+
from aip_agents.middleware.tools.write_file import WriteFileTool
|
|
38
|
+
from aip_agents.sandbox.defaults import DEFAULT_SANDBOX_TEMPLATE
|
|
39
|
+
from aip_agents.utils.logger import get_logger
|
|
40
|
+
|
|
41
|
+
# Execution system prompt addition (shown when execute tool is available)
|
|
42
|
+
EXECUTION_SYSTEM_PROMPT = """## Execute Tool `execute`
|
|
43
|
+
|
|
44
|
+
You have access to an `execute` tool for running shell commands.
|
|
45
|
+
Use this tool to run commands, scripts, tests, builds, and other shell operations.
|
|
46
|
+
|
|
47
|
+
- execute: run a shell command (returns output and exit code)
|
|
48
|
+
|
|
49
|
+
⚠️ Always handle errors gracefully. Read stderr if execution fails."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _backend_supports_execution(backend: BackendProtocol) -> bool:
|
|
53
|
+
"""Check if backend supports command execution.
|
|
54
|
+
|
|
55
|
+
Handles both direct backends and CompositeBackend which may wrap
|
|
56
|
+
a sandbox backend as its default.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
backend: The backend to check.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True if the backend supports execution, False otherwise.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
composite_module = __import__(
|
|
66
|
+
"aip_agents.middleware.backends.composite",
|
|
67
|
+
fromlist=["CompositeBackend"],
|
|
68
|
+
)
|
|
69
|
+
composite_backend_cls = getattr(composite_module, "CompositeBackend")
|
|
70
|
+
|
|
71
|
+
if isinstance(backend, composite_backend_cls):
|
|
72
|
+
default = getattr(backend, "default", None)
|
|
73
|
+
if default is None:
|
|
74
|
+
return False
|
|
75
|
+
return _backend_supports_execution(default)
|
|
76
|
+
except ImportError:
|
|
77
|
+
# CompositeBackend is optional in this repository state.
|
|
78
|
+
# Keep compatibility with existing tests that use a class-name-based mock.
|
|
79
|
+
if getattr(backend, "__class__", None) and backend.__class__.__name__ == "CompositeBackend":
|
|
80
|
+
default = getattr(backend, "default", None)
|
|
81
|
+
if default is None:
|
|
82
|
+
return False
|
|
83
|
+
return _backend_supports_execution(default)
|
|
84
|
+
|
|
85
|
+
# For other backends, check if execute method exists
|
|
86
|
+
return hasattr(backend, "execute") and callable(getattr(backend, "execute"))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_execution_enabled(backend: BackendProtocol) -> bool:
|
|
90
|
+
"""Check if execution is enabled for this backend.
|
|
91
|
+
|
|
92
|
+
Even if a backend supports execution (has execute method),
|
|
93
|
+
it may be disabled via allow_execute flag.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
backend: The backend to check.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
True if execution is enabled, False otherwise.
|
|
100
|
+
"""
|
|
101
|
+
# Check if backend supports execution first
|
|
102
|
+
if not _backend_supports_execution(backend):
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
composite_module = __import__(
|
|
107
|
+
"aip_agents.middleware.backends.composite",
|
|
108
|
+
fromlist=["CompositeBackend"],
|
|
109
|
+
)
|
|
110
|
+
composite_backend_cls = getattr(composite_module, "CompositeBackend")
|
|
111
|
+
|
|
112
|
+
if isinstance(backend, composite_backend_cls):
|
|
113
|
+
default = getattr(backend, "default", None)
|
|
114
|
+
if default is None:
|
|
115
|
+
return False
|
|
116
|
+
return _is_execution_enabled(default)
|
|
117
|
+
except ImportError:
|
|
118
|
+
if getattr(backend, "__class__", None) and backend.__class__.__name__ == "CompositeBackend":
|
|
119
|
+
default = getattr(backend, "default", None)
|
|
120
|
+
if default is None:
|
|
121
|
+
return False
|
|
122
|
+
return _is_execution_enabled(default)
|
|
123
|
+
|
|
124
|
+
# Check if allow_execute is set to True (defaults to False for security)
|
|
125
|
+
return getattr(backend, "allow_execute", False)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@lru_cache(maxsize=128)
|
|
129
|
+
def _execute_accepts_timeout(backend_class: type) -> bool:
|
|
130
|
+
"""Check whether a backend class's `execute` accepts a `timeout` kwarg.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
backend_class: The backend class to inspect.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
True if the execute method accepts a timeout parameter, False otherwise.
|
|
137
|
+
"""
|
|
138
|
+
execute_fn = getattr(backend_class, "execute", None)
|
|
139
|
+
return callable_accepts_keyword(execute_fn, "timeout")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = [
|
|
143
|
+
"FilesystemConfig",
|
|
144
|
+
"InMemoryConfig",
|
|
145
|
+
"LocalDiskConfig",
|
|
146
|
+
"SandboxConfig",
|
|
147
|
+
"FilesystemMiddleware",
|
|
148
|
+
"FILESYSTEM_SYSTEM_PROMPT",
|
|
149
|
+
"EXECUTION_SYSTEM_PROMPT",
|
|
150
|
+
"_backend_supports_execution",
|
|
151
|
+
"_is_execution_enabled",
|
|
152
|
+
"_execute_accepts_timeout",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
logger = get_logger(__name__)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class FilesystemConfig(BaseModel):
|
|
159
|
+
"""Abstract base class for filesystem backend configurations.
|
|
160
|
+
|
|
161
|
+
Subclasses implement create_backend() to instantiate specific backends.
|
|
162
|
+
This allows users to configure filesystem storage with custom settings.
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
>>> config = InMemoryConfig()
|
|
166
|
+
>>> backend = config.create_backend()
|
|
167
|
+
>>> middleware = FilesystemMiddleware(backend=backend)
|
|
168
|
+
|
|
169
|
+
>>> config = LocalDiskConfig(base_directory="/tmp/agent_files")
|
|
170
|
+
>>> backend = config.create_backend()
|
|
171
|
+
>>> middleware = FilesystemMiddleware(backend=backend)
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def create_backend(self) -> BackendProtocol:
|
|
176
|
+
"""Create and return a backend instance based on this configuration.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
BackendProtocol: Configured backend instance ready for use.
|
|
180
|
+
"""
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class InMemoryConfig(FilesystemConfig):
|
|
185
|
+
"""Configuration for in-memory filesystem backend.
|
|
186
|
+
|
|
187
|
+
Stores all files in memory. Files are lost when the process exits.
|
|
188
|
+
Best for temporary storage and testing scenarios.
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
>>> config = InMemoryConfig()
|
|
192
|
+
>>> agent = LangGraphReactAgent(
|
|
193
|
+
... name="test_agent",
|
|
194
|
+
... instruction="Test",
|
|
195
|
+
... filesystem=config
|
|
196
|
+
... )
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def create_backend(self) -> BackendProtocol:
|
|
200
|
+
"""Create an in-memory backend.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
BackendProtocol: InMemoryBackend instance.
|
|
204
|
+
"""
|
|
205
|
+
return InMemoryBackend()
|
|
206
|
+
|
|
207
|
+
model_config = ConfigDict(frozen=True)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class LocalDiskConfig(FilesystemConfig):
|
|
211
|
+
"""Configuration for local disk filesystem backend.
|
|
212
|
+
|
|
213
|
+
Stores files on the local filesystem at the specified base path.
|
|
214
|
+
Files persist between process restarts.
|
|
215
|
+
Best for production use cases requiring file persistence.
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
base_directory: Root directory for file storage.
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
>>> import tempfile
|
|
223
|
+
>>> with tempfile.TemporaryDirectory() as tmpdir:
|
|
224
|
+
... config = LocalDiskConfig(base_directory=tmpdir)
|
|
225
|
+
... agent = LangGraphReactAgent(
|
|
226
|
+
... name="file_agent",
|
|
227
|
+
... instruction="Process files",
|
|
228
|
+
... filesystem=config
|
|
229
|
+
... )
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
base_directory: str | Path = Field(
|
|
233
|
+
...,
|
|
234
|
+
description="Root directory for file storage.",
|
|
235
|
+
)
|
|
236
|
+
allow_execute: bool = Field(
|
|
237
|
+
default=False,
|
|
238
|
+
description="Whether execute() is enabled for this local disk backend.",
|
|
239
|
+
)
|
|
240
|
+
env: dict[str, str] | None = Field(
|
|
241
|
+
default=None,
|
|
242
|
+
description="Optional environment variables overlay for execute() calls.",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def create_backend(self) -> BackendProtocol:
|
|
246
|
+
"""Create a local disk backend.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
BackendProtocol: LocalDiskBackend instance configured with base_path.
|
|
250
|
+
"""
|
|
251
|
+
return LocalDiskBackend(
|
|
252
|
+
base_directory=Path(self.base_directory),
|
|
253
|
+
allow_execute=self.allow_execute,
|
|
254
|
+
env=self.env,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class SandboxConfig(FilesystemConfig):
|
|
261
|
+
"""Configuration for sandbox filesystem backend.
|
|
262
|
+
|
|
263
|
+
Stores files and command execution inside E2B sandbox environments.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
template: str = Field(
|
|
267
|
+
default=DEFAULT_SANDBOX_TEMPLATE,
|
|
268
|
+
description="E2B template alias. Defaults to DEFAULT_SANDBOX_TEMPLATE.",
|
|
269
|
+
)
|
|
270
|
+
base_dir: str | None = Field(
|
|
271
|
+
default=None,
|
|
272
|
+
description=(
|
|
273
|
+
"Base workspace directory inside the sandbox. When None, resolved at "
|
|
274
|
+
"runtime to $HOME/workspace (falling back to /home/user/workspace) so "
|
|
275
|
+
"it is writable by the sandbox user."
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
allow_execute: bool = Field(
|
|
279
|
+
default=True,
|
|
280
|
+
description="Whether execute() is enabled for this sandbox backend.",
|
|
281
|
+
)
|
|
282
|
+
env: dict[str, str] | None = Field(
|
|
283
|
+
default=None,
|
|
284
|
+
description="Optional environment variables forwarded to sandbox execute() calls.",
|
|
285
|
+
)
|
|
286
|
+
timeout_seconds: int = Field(
|
|
287
|
+
default=300,
|
|
288
|
+
ge=1,
|
|
289
|
+
description="Default runtime timeout in seconds for sandbox commands.",
|
|
290
|
+
)
|
|
291
|
+
auto_build_template: bool = Field(
|
|
292
|
+
default=True,
|
|
293
|
+
description="Whether to auto-build missing template alias before fallback.",
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def create_backend(self) -> BackendProtocol:
|
|
297
|
+
"""Create a sandbox backend.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
BackendProtocol: SandboxBackend configured from this model.
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ImportError: If sandbox dependencies (e.g., e2b) are not installed.
|
|
304
|
+
"""
|
|
305
|
+
try:
|
|
306
|
+
from aip_agents.middleware.backends.sandbox import SandboxBackend
|
|
307
|
+
except ModuleNotFoundError as e:
|
|
308
|
+
missing_root = (e.name or "").split(".")[0]
|
|
309
|
+
if missing_root in {"e2b", "e2b_code_interpreter"}:
|
|
310
|
+
raise ImportError(
|
|
311
|
+
"SandboxBackend requires sandbox dependencies. Install with: pip install 'aip-agents[local]'"
|
|
312
|
+
) from e
|
|
313
|
+
raise
|
|
314
|
+
return SandboxBackend(
|
|
315
|
+
template=self.template,
|
|
316
|
+
base_dir=self.base_dir,
|
|
317
|
+
allow_execute=self.allow_execute,
|
|
318
|
+
env=self.env,
|
|
319
|
+
timeout_seconds=self.timeout_seconds,
|
|
320
|
+
auto_build_template=self.auto_build_template,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
model_config = ConfigDict(frozen=True)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
NUM_CHARS_PER_TOKEN = 4
|
|
327
|
+
TOOL_TOKEN_LIMIT_BEFORE_EVICT = 20000
|
|
328
|
+
PREVIEW_HEAD_SIZE = 500
|
|
329
|
+
PREVIEW_TAIL_SIZE = 500
|
|
330
|
+
AUTO_SAVE_DIR = "/tool_outputs"
|
|
331
|
+
|
|
332
|
+
TOOLS_EXCLUDED_FROM_EVICTION = (
|
|
333
|
+
"ls",
|
|
334
|
+
"grep",
|
|
335
|
+
"read_file",
|
|
336
|
+
"edit_file",
|
|
337
|
+
"write_file",
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
FILESYSTEM_SYSTEM_PROMPT = """## Filesystem Tools `read_file`, `grep`, `ls`, `write_file`, `edit_file`
|
|
342
|
+
|
|
343
|
+
You have access to a filesystem which you can interact with using these tools.
|
|
344
|
+
All file paths must start with a /.
|
|
345
|
+
|
|
346
|
+
- read_file: read a file from the filesystem
|
|
347
|
+
- grep: search for text within files
|
|
348
|
+
- ls: list files in a directory (requires absolute path)
|
|
349
|
+
- write_file: write to a file in the filesystem
|
|
350
|
+
- edit_file: perform exact string replacements in existing files
|
|
351
|
+
|
|
352
|
+
## Pagination Strategy
|
|
353
|
+
|
|
354
|
+
**CRITICAL: Choose ONE mode per file and never mix them in the same session.**
|
|
355
|
+
|
|
356
|
+
**Line-based** — for normal code/text files:
|
|
357
|
+
- `read_file(path, line_offset=0, limit=100)` → first 100 lines
|
|
358
|
+
- `read_file(path, line_offset=100, limit=100)` → next 100 lines
|
|
359
|
+
- Do NOT set `char_offset` or `max_chars`
|
|
360
|
+
|
|
361
|
+
**Character-based** — for large/long-line files (minified JS, JSON, CSV, etc.):
|
|
362
|
+
- `read_file(path, char_offset=0, max_chars=20000)` → first 20K chars
|
|
363
|
+
- `read_file(path, char_offset=20000, max_chars=20000)` → next 20K chars
|
|
364
|
+
- Do NOT set `line_offset` or `limit`
|
|
365
|
+
- Default chunk: 20000 characters; hard limit: 80000 characters per call
|
|
366
|
+
|
|
367
|
+
**How to decide:**
|
|
368
|
+
1. If you know the file has long lines (JSON, minified, CSV) → use char-based immediately
|
|
369
|
+
2. If a line-based read returns a truncation warning → copy the exact `char_offset` from that warning and continue with char-based
|
|
370
|
+
3. Once you pick a mode for a file, continue with that mode until done
|
|
371
|
+
|
|
372
|
+
## Auto-Save
|
|
373
|
+
|
|
374
|
+
Large tool outputs may be automatically saved to files. You will receive:
|
|
375
|
+
- The file path where content was saved
|
|
376
|
+
- A preview (head + tail) of the content
|
|
377
|
+
- Use `read_file` to access specific sections
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class FilesystemMiddleware(AgentMiddleware):
|
|
382
|
+
"""Middleware providing filesystem capabilities for agents.
|
|
383
|
+
|
|
384
|
+
Manages file storage and retrieval for agents using a pluggable backend.
|
|
385
|
+
Provides hooks for lifecycle management and system prompt enhancement
|
|
386
|
+
with file context information.
|
|
387
|
+
|
|
388
|
+
Thread Safety:
|
|
389
|
+
Uses the underlying backend's thread safety mechanisms.
|
|
390
|
+
Each thread_id has isolated storage.
|
|
391
|
+
|
|
392
|
+
Attributes:
|
|
393
|
+
tools: List of file operation tools (empty in PR 001, populated in 002-004).
|
|
394
|
+
system_prompt_additions: System prompt text explaining file access.
|
|
395
|
+
backend: Storage backend for file operations.
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
def __init__(
|
|
399
|
+
self,
|
|
400
|
+
backend: BackendProtocol | None = None,
|
|
401
|
+
tool_token_limit_before_evict: int | None = TOOL_TOKEN_LIMIT_BEFORE_EVICT,
|
|
402
|
+
max_execute_timeout: int = 300,
|
|
403
|
+
) -> None:
|
|
404
|
+
"""Initialize filesystem tools and tool-output eviction policy.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
backend: Filesystem backend implementation.
|
|
408
|
+
If ``None``, ``InMemoryBackend`` is used.
|
|
409
|
+
tool_token_limit_before_evict: Token threshold used to
|
|
410
|
+
decide when tool output should be evicted to
|
|
411
|
+
``/tool_outputs/<tool_call_id>.txt``. The middleware uses
|
|
412
|
+
``NUM_CHARS_PER_TOKEN`` as an approximation.
|
|
413
|
+
Set to ``None`` to disable eviction.
|
|
414
|
+
max_execute_timeout: Maximum timeout in seconds for execute commands.
|
|
415
|
+
Defaults to 300. Only used if backend supports execution.
|
|
416
|
+
|
|
417
|
+
Notes:
|
|
418
|
+
Eviction is skipped for tools listed in
|
|
419
|
+
``TOOLS_EXCLUDED_FROM_EVICTION``.
|
|
420
|
+
"""
|
|
421
|
+
self.backend = backend if backend is not None else InMemoryBackend()
|
|
422
|
+
self.tools: list[BaseTool] = [
|
|
423
|
+
ReadFileTool(backend=self.backend),
|
|
424
|
+
GrepTool(backend=self.backend),
|
|
425
|
+
LsTool(backend=self.backend),
|
|
426
|
+
WriteFileTool(backend=self.backend),
|
|
427
|
+
EditFileTool(backend=self.backend),
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
# Add execute tool if backend supports it AND execution is enabled
|
|
431
|
+
if _is_execution_enabled(self.backend):
|
|
432
|
+
self.tools.append(
|
|
433
|
+
ExecuteTool(
|
|
434
|
+
backend=self.backend,
|
|
435
|
+
max_execute_timeout=max_execute_timeout,
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT + "\n\n" + EXECUTION_SYSTEM_PROMPT
|
|
439
|
+
else:
|
|
440
|
+
self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT
|
|
441
|
+
|
|
442
|
+
self.tool_token_limit_before_evict = tool_token_limit_before_evict
|
|
443
|
+
|
|
444
|
+
def replace_backend(self, backend: BackendProtocol, max_execute_timeout: int = 300) -> None:
|
|
445
|
+
"""Replace the backend and rebuild all tools to point to it.
|
|
446
|
+
|
|
447
|
+
Called by the coordinator during sub-agent registration to inject a
|
|
448
|
+
shared backend. After this call, all tools will operate on the new
|
|
449
|
+
backend.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
backend: The new backend instance to use. Must implement BackendProtocol.
|
|
453
|
+
max_execute_timeout: Maximum timeout in seconds for execute commands.
|
|
454
|
+
Defaults to 300. Only used if backend supports execution.
|
|
455
|
+
"""
|
|
456
|
+
self.backend = backend
|
|
457
|
+
self.tools = [
|
|
458
|
+
ReadFileTool(backend=self.backend),
|
|
459
|
+
GrepTool(backend=self.backend),
|
|
460
|
+
LsTool(backend=self.backend),
|
|
461
|
+
WriteFileTool(backend=self.backend),
|
|
462
|
+
EditFileTool(backend=self.backend),
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
# Add execute tool if backend supports it AND execution is enabled
|
|
466
|
+
if _is_execution_enabled(self.backend):
|
|
467
|
+
self.tools.append(
|
|
468
|
+
ExecuteTool(
|
|
469
|
+
backend=self.backend,
|
|
470
|
+
max_execute_timeout=max_execute_timeout,
|
|
471
|
+
)
|
|
472
|
+
)
|
|
473
|
+
self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT + "\n\n" + EXECUTION_SYSTEM_PROMPT
|
|
474
|
+
else:
|
|
475
|
+
self.system_prompt_additions = FILESYSTEM_SYSTEM_PROMPT
|
|
476
|
+
|
|
477
|
+
def _is_tool_excluded_from_eviction(self, message: ToolMessage) -> bool:
|
|
478
|
+
"""Check if the tool is excluded from eviction."""
|
|
479
|
+
tool_name = None
|
|
480
|
+
tool_calls = getattr(message, "tool_calls", None)
|
|
481
|
+
if isinstance(tool_calls, dict):
|
|
482
|
+
tool_name = tool_calls.get("name")
|
|
483
|
+
|
|
484
|
+
if not tool_name:
|
|
485
|
+
tool_name = getattr(message, "name", None)
|
|
486
|
+
|
|
487
|
+
return isinstance(tool_name, str) and tool_name in TOOLS_EXCLUDED_FROM_EVICTION
|
|
488
|
+
|
|
489
|
+
def _exceeds_eviction_limit(self, content: str) -> bool:
|
|
490
|
+
"""Check if the content exceeds the eviction limit."""
|
|
491
|
+
if self.tool_token_limit_before_evict is None:
|
|
492
|
+
return False
|
|
493
|
+
return len(content) > NUM_CHARS_PER_TOKEN * self.tool_token_limit_before_evict
|
|
494
|
+
|
|
495
|
+
def _save_output(self, path: str, content: str, thread_id: str) -> None:
|
|
496
|
+
"""Save the output to the backend.
|
|
497
|
+
|
|
498
|
+
Auto-saved files are saved to the ``"default"`` thread storage because
|
|
499
|
+
file tools (ReadFileTool, GrepTool, etc.) do not currently pass
|
|
500
|
+
``thread_id`` to the backend. See issue #1158.
|
|
501
|
+
"""
|
|
502
|
+
if isinstance(self.backend, InMemoryBackend):
|
|
503
|
+
self.backend.write(path, content, thread_id="default")
|
|
504
|
+
return
|
|
505
|
+
self.backend.write(path, content)
|
|
506
|
+
|
|
507
|
+
def _format_auto_saved_preview(self, file_path: str, content: str) -> str:
|
|
508
|
+
"""Format the auto-saved preview."""
|
|
509
|
+
head = content[:PREVIEW_HEAD_SIZE]
|
|
510
|
+
tail = content[-PREVIEW_TAIL_SIZE:] if len(content) > PREVIEW_TAIL_SIZE else content
|
|
511
|
+
return (
|
|
512
|
+
f"Output saved to: {file_path}\n"
|
|
513
|
+
f"Preview (first {PREVIEW_HEAD_SIZE} chars): {head}...\n"
|
|
514
|
+
f"Preview (last {PREVIEW_TAIL_SIZE} chars): ...{tail}\n"
|
|
515
|
+
"Use read_file(path, char_offset=0, max_chars=20000) to read this file in chunks."
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
def _replace_tool_message_content(self, message: ToolMessage, content: str) -> ToolMessage:
|
|
519
|
+
"""Replace the content of a tool message and update tool_calls output.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
message: The original ToolMessage to update.
|
|
523
|
+
content: The new content to set in the message.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
A new ToolMessage instance with updated content and tool_calls.output.
|
|
527
|
+
"""
|
|
528
|
+
# Update content and tool_calls.output for consistency
|
|
529
|
+
update_dict: dict[str, Any] = {"content": content}
|
|
530
|
+
if hasattr(message, "tool_calls") and isinstance(message.tool_calls, dict):
|
|
531
|
+
updated_tool_calls = dict(message.tool_calls)
|
|
532
|
+
updated_tool_calls["output"] = content
|
|
533
|
+
update_dict["tool_calls"] = updated_tool_calls
|
|
534
|
+
return message.model_copy(update=update_dict)
|
|
535
|
+
|
|
536
|
+
def before_model(self, state: dict[str, Any]) -> dict[str, Any]:
|
|
537
|
+
"""Hook executed before model invocation.
|
|
538
|
+
|
|
539
|
+
Loads files from state into the backend storage for the current thread.
|
|
540
|
+
This restores the filesystem state from checkpointed data.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
state: Current agent state containing messages and other context.
|
|
544
|
+
Expected keys:
|
|
545
|
+
- "files": Dictionary mapping file paths to file data (optional).
|
|
546
|
+
- "configurable": Dict containing "thread_id" for isolation.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
Empty dict (no state updates needed from this hook).
|
|
550
|
+
"""
|
|
551
|
+
thread_id = state.get("configurable", {}).get("thread_id", "default")
|
|
552
|
+
|
|
553
|
+
if "files" in state and state["files"] is not None:
|
|
554
|
+
self.backend.set_files(state["files"], thread_id=thread_id)
|
|
555
|
+
|
|
556
|
+
return {}
|
|
557
|
+
|
|
558
|
+
def _evict_tool_messages(
|
|
559
|
+
self,
|
|
560
|
+
messages: list[Any],
|
|
561
|
+
thread_id: str,
|
|
562
|
+
) -> tuple[list[Any], bool]:
|
|
563
|
+
"""Evict large tool messages and return updated list.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
messages: List of messages to process.
|
|
567
|
+
thread_id: Thread ID for backend storage.
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
Tuple of (updated_messages, messages_changed).
|
|
571
|
+
"""
|
|
572
|
+
updated_messages = []
|
|
573
|
+
messages_changed = False
|
|
574
|
+
|
|
575
|
+
for message in messages:
|
|
576
|
+
if not isinstance(message, ToolMessage):
|
|
577
|
+
updated_messages.append(message)
|
|
578
|
+
continue
|
|
579
|
+
|
|
580
|
+
content = message.content if isinstance(message.content, str) else str(message.content)
|
|
581
|
+
if self._is_tool_excluded_from_eviction(message):
|
|
582
|
+
updated_messages.append(message)
|
|
583
|
+
continue
|
|
584
|
+
|
|
585
|
+
tool_name = getattr(message, "name", "unknown")
|
|
586
|
+
threshold = (
|
|
587
|
+
NUM_CHARS_PER_TOKEN * self.tool_token_limit_before_evict
|
|
588
|
+
if self.tool_token_limit_before_evict is not None
|
|
589
|
+
else None
|
|
590
|
+
)
|
|
591
|
+
logger.debug(
|
|
592
|
+
"Checking tool output for eviction: tool=%s size=%d threshold=%s",
|
|
593
|
+
tool_name,
|
|
594
|
+
len(content),
|
|
595
|
+
threshold,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
if not self._exceeds_eviction_limit(content):
|
|
599
|
+
updated_messages.append(message)
|
|
600
|
+
continue
|
|
601
|
+
|
|
602
|
+
raw_call_id = getattr(message, "tool_call_id", "") or "unknown"
|
|
603
|
+
safe_call_id = sanitize_tool_call_id(str(raw_call_id))
|
|
604
|
+
file_path = f"{AUTO_SAVE_DIR}/{safe_call_id}.txt"
|
|
605
|
+
|
|
606
|
+
self._save_output(file_path, content, thread_id)
|
|
607
|
+
preview_text = self._format_auto_saved_preview(file_path=file_path, content=content)
|
|
608
|
+
updated_messages.append(self._replace_tool_message_content(message=message, content=preview_text))
|
|
609
|
+
messages_changed = True
|
|
610
|
+
|
|
611
|
+
logger.info(
|
|
612
|
+
"Evicted tool output: tool=%s size=%d chars path=%s",
|
|
613
|
+
tool_name,
|
|
614
|
+
len(content),
|
|
615
|
+
file_path,
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
return updated_messages, messages_changed
|
|
619
|
+
|
|
620
|
+
def after_model(self, state: dict[str, Any]) -> dict[str, Any]:
|
|
621
|
+
"""Hook executed after model invocation.
|
|
622
|
+
|
|
623
|
+
Persists files to backend for checkpointing. Note: message eviction
|
|
624
|
+
is handled in modify_model_request hook before LLM invocation.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
state: Current agent state after model invocation.
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
Dict with "files" key if files exist, or empty dict if no updates needed.
|
|
631
|
+
"""
|
|
632
|
+
thread_id = state.get("configurable", {}).get("thread_id", "default")
|
|
633
|
+
|
|
634
|
+
updates: dict[str, Any] = {}
|
|
635
|
+
|
|
636
|
+
# Persist files to checkpoint only (message eviction happens in modify_model_request)
|
|
637
|
+
files = self.backend.get_files(thread_id=thread_id)
|
|
638
|
+
if files:
|
|
639
|
+
updates["files"] = files
|
|
640
|
+
|
|
641
|
+
return updates
|
|
642
|
+
|
|
643
|
+
def modify_model_request(self, request: ModelRequest, state: dict[str, Any]) -> ModelRequest:
|
|
644
|
+
"""Modify the model request before invocation."""
|
|
645
|
+
messages = request.get("messages", [])
|
|
646
|
+
thread_id = state.get("configurable", {}).get("thread_id", "default")
|
|
647
|
+
|
|
648
|
+
updated_messages, messages_changed = self._evict_tool_messages(messages, thread_id)
|
|
649
|
+
|
|
650
|
+
if messages_changed:
|
|
651
|
+
request["messages"] = updated_messages
|
|
652
|
+
|
|
653
|
+
return request
|
|
654
|
+
|
|
655
|
+
async def abefore_model(self, state: dict[str, Any]) -> dict[str, Any]:
|
|
656
|
+
"""Async version of before_model.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
state: Current agent state containing messages and other context.
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
Dict of state updates to merge into the agent state. Return empty dict
|
|
663
|
+
if no updates are needed.
|
|
664
|
+
"""
|
|
665
|
+
return self.before_model(state)
|
|
666
|
+
|
|
667
|
+
async def aafter_model(self, state: dict[str, Any]) -> dict[str, Any]:
|
|
668
|
+
"""Async version of after_model.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
state: Current agent state after model invocation.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
Dict of state updates to merge into the agent state. Return empty dict
|
|
675
|
+
if no updates are needed.
|
|
676
|
+
"""
|
|
677
|
+
thread_id = state.get("configurable", {}).get("thread_id", "default")
|
|
678
|
+
|
|
679
|
+
updates: dict[str, Any] = {}
|
|
680
|
+
|
|
681
|
+
aget_files = getattr(self.backend, "aget_files", None)
|
|
682
|
+
if callable(aget_files):
|
|
683
|
+
files = await aget_files(thread_id=thread_id)
|
|
684
|
+
else:
|
|
685
|
+
files = self.backend.get_files(thread_id=thread_id)
|
|
686
|
+
|
|
687
|
+
if files:
|
|
688
|
+
updates["files"] = files
|
|
689
|
+
|
|
690
|
+
return updates
|