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,72 @@
|
|
|
1
|
+
"""Backend implementations for filesystem middleware.
|
|
2
|
+
|
|
3
|
+
This package provides backend implementations for filesystem storage,
|
|
4
|
+
including in-memory storage and protocol definitions.
|
|
5
|
+
|
|
6
|
+
Migration note for deepagents:
|
|
7
|
+
1. Replace protocol imports with deepagents.backends.protocol
|
|
8
|
+
2. Replace utils imports with deepagents.backends.utils
|
|
9
|
+
3. InMemoryBackend can stay or be replaced with StateBackend
|
|
10
|
+
|
|
11
|
+
Authors:
|
|
12
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from aip_agents.middleware.backends.in_memory import InMemoryBackend
|
|
18
|
+
from aip_agents.middleware.backends.local_disk import LocalDiskBackend
|
|
19
|
+
from aip_agents.middleware.backends.protocol import (
|
|
20
|
+
BackendProtocol,
|
|
21
|
+
EditResult,
|
|
22
|
+
FileInfo,
|
|
23
|
+
GrepMatch,
|
|
24
|
+
WriteResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from aip_agents.middleware.backends.sandbox import SandboxBackend
|
|
29
|
+
|
|
30
|
+
from aip_agents.middleware.backends.utils import (
|
|
31
|
+
ensure_absolute_path,
|
|
32
|
+
format_content_with_line_numbers,
|
|
33
|
+
format_grep_matches,
|
|
34
|
+
perform_string_replacement,
|
|
35
|
+
sanitize_tool_call_id,
|
|
36
|
+
truncate_if_too_long,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"InMemoryBackend",
|
|
41
|
+
"LocalDiskBackend",
|
|
42
|
+
"SandboxBackend",
|
|
43
|
+
"BackendProtocol",
|
|
44
|
+
"FileInfo",
|
|
45
|
+
"WriteResult",
|
|
46
|
+
"EditResult",
|
|
47
|
+
"GrepMatch",
|
|
48
|
+
"ensure_absolute_path",
|
|
49
|
+
"format_content_with_line_numbers",
|
|
50
|
+
"perform_string_replacement",
|
|
51
|
+
"format_grep_matches",
|
|
52
|
+
"truncate_if_too_long",
|
|
53
|
+
"sanitize_tool_call_id",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def __getattr__(name: str) -> Any:
|
|
58
|
+
if name == "SandboxBackend":
|
|
59
|
+
try:
|
|
60
|
+
from aip_agents.middleware.backends.sandbox import (
|
|
61
|
+
SandboxBackend as SandboxBackendType,
|
|
62
|
+
)
|
|
63
|
+
except ModuleNotFoundError as exc:
|
|
64
|
+
raise ModuleNotFoundError(
|
|
65
|
+
"SandboxBackend requires the E2B optional dependency. Install with: pip install 'aip-agents[local]'"
|
|
66
|
+
) from exc
|
|
67
|
+
return SandboxBackendType
|
|
68
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def __dir__() -> list[str]:
|
|
72
|
+
return sorted(__all__)
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"""In-memory filesystem backend implementation.
|
|
2
|
+
|
|
3
|
+
This module provides InMemoryBackend, a BackendProtocol implementation
|
|
4
|
+
that stores files in memory with thread-isolated storage.
|
|
5
|
+
|
|
6
|
+
Migration note for deepagents:
|
|
7
|
+
1. Import BackendProtocol and types from deepagents.backends.protocol
|
|
8
|
+
2. Import utility functions from deepagents.backends.utils (same API)
|
|
9
|
+
|
|
10
|
+
Authors:
|
|
11
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from base64 import b64decode, b64encode
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from aip_agents.middleware.backends.protocol import (
|
|
23
|
+
BackendProtocol,
|
|
24
|
+
EditResult,
|
|
25
|
+
FileInfo,
|
|
26
|
+
GrepMatch,
|
|
27
|
+
WriteResult,
|
|
28
|
+
)
|
|
29
|
+
from aip_agents.middleware.backends.utils import (
|
|
30
|
+
format_content_with_line_numbers,
|
|
31
|
+
perform_string_replacement,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = ["InMemoryBackend"]
|
|
35
|
+
|
|
36
|
+
_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S"
|
|
37
|
+
_BINARY_CONTENT_ENCODING = "base64"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class InMemoryBackend(BackendProtocol):
|
|
41
|
+
"""In-memory filesystem backend.
|
|
42
|
+
|
|
43
|
+
Explicitly implements BackendProtocol with thread-isolated storage.
|
|
44
|
+
Stores files in memory per thread_id, making it suitable for
|
|
45
|
+
agent conversations where each thread needs isolated file storage.
|
|
46
|
+
|
|
47
|
+
Thread Safety:
|
|
48
|
+
All operations are protected by threading.RLock to ensure
|
|
49
|
+
thread-safe access to the shared storage.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
_storage: Dictionary mapping thread_id to {path: FileData}.
|
|
53
|
+
_storage_lock: RLock protecting concurrent access to _storage.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> backend = InMemoryBackend()
|
|
57
|
+
>>> files = {"/test.txt": {"content": ["Hello"], "created_at": "2024-01-01", "modified_at": "2024-01-01"}}
|
|
58
|
+
>>> backend.set_files(files, thread_id="thread-1")
|
|
59
|
+
>>> retrieved = backend.get_files(thread_id="thread-1")
|
|
60
|
+
>>> "/test.txt" in retrieved
|
|
61
|
+
True
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
"""Initialize with empty storage."""
|
|
66
|
+
self._storage: dict[str, dict[str, Any]] = {}
|
|
67
|
+
self._storage_lock = threading.RLock()
|
|
68
|
+
|
|
69
|
+
def set_files(self, files: dict[str, Any], thread_id: str = "default") -> None:
|
|
70
|
+
"""Load files from state for a specific thread.
|
|
71
|
+
|
|
72
|
+
Called by FilesystemMiddleware.before_model to restore files
|
|
73
|
+
from checkpointed state into the backend.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
files: Dictionary mapping file paths to FileData.
|
|
77
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
78
|
+
|
|
79
|
+
Note:
|
|
80
|
+
Makes a copy of the files dict to prevent external modifications
|
|
81
|
+
affecting internal storage.
|
|
82
|
+
"""
|
|
83
|
+
with self._storage_lock:
|
|
84
|
+
self._storage[thread_id] = {
|
|
85
|
+
path: self._deserialize_file_data(file_data) for path, file_data in files.items()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def get_files(self, thread_id: str = "default") -> dict[str, Any]:
|
|
89
|
+
"""Get files for a specific thread.
|
|
90
|
+
|
|
91
|
+
Called by FilesystemMiddleware.after_model to save files
|
|
92
|
+
from backend into state for checkpointing.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Dictionary mapping file paths to FileData, or empty dict
|
|
99
|
+
if no files for this thread.
|
|
100
|
+
|
|
101
|
+
Note:
|
|
102
|
+
Returns a copy to prevent external modifications affecting
|
|
103
|
+
internal storage.
|
|
104
|
+
"""
|
|
105
|
+
with self._storage_lock:
|
|
106
|
+
return {
|
|
107
|
+
path: self._serialize_file_data(file_data)
|
|
108
|
+
for path, file_data in self._storage.get(thread_id, {}).items()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _serialize_file_data(file_data: Any) -> Any:
|
|
113
|
+
"""Return JSON-safe checkpoint data for one stored file entry."""
|
|
114
|
+
if not isinstance(file_data, dict):
|
|
115
|
+
return file_data # pragma: no cover
|
|
116
|
+
serialized = file_data.copy()
|
|
117
|
+
content = serialized.get("content")
|
|
118
|
+
if isinstance(content, bytes):
|
|
119
|
+
serialized["content"] = b64encode(content).decode("ascii")
|
|
120
|
+
serialized["encoding"] = _BINARY_CONTENT_ENCODING
|
|
121
|
+
return serialized
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _deserialize_file_data(file_data: Any) -> Any:
|
|
125
|
+
"""Restore one stored file entry from checkpoint data."""
|
|
126
|
+
if not isinstance(file_data, dict):
|
|
127
|
+
return file_data # pragma: no cover
|
|
128
|
+
restored = file_data.copy()
|
|
129
|
+
if restored.get("encoding") == _BINARY_CONTENT_ENCODING and isinstance(restored.get("content"), str):
|
|
130
|
+
restored["content"] = b64decode(restored["content"])
|
|
131
|
+
restored.pop("encoding", None)
|
|
132
|
+
restored["binary"] = True
|
|
133
|
+
return restored
|
|
134
|
+
|
|
135
|
+
def _get_thread_storage(self, thread_id: str = "default") -> dict[str, Any]:
|
|
136
|
+
"""Get or create storage for a thread.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
thread_id: Thread identifier.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Storage dictionary for the thread.
|
|
143
|
+
"""
|
|
144
|
+
with self._storage_lock:
|
|
145
|
+
if thread_id not in self._storage:
|
|
146
|
+
self._storage[thread_id] = {}
|
|
147
|
+
return self._storage[thread_id]
|
|
148
|
+
|
|
149
|
+
def _normalize_path(self, path: str) -> str:
|
|
150
|
+
"""Normalize a path to absolute form.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
path: Path to normalize.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Normalized absolute path.
|
|
157
|
+
"""
|
|
158
|
+
if not path.startswith("/"):
|
|
159
|
+
path = "/" + path
|
|
160
|
+
# Remove trailing slashes except for root
|
|
161
|
+
path = path.rstrip("/") or "/"
|
|
162
|
+
return path
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def _content_to_lines(content: Any) -> list[str]:
|
|
166
|
+
if isinstance(content, bytes):
|
|
167
|
+
raise ValueError("Binary content cannot be read as text") # pragma: no cover
|
|
168
|
+
if isinstance(content, str):
|
|
169
|
+
return content.split("\n") # pragma: no cover
|
|
170
|
+
return [str(line) for line in content]
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _is_binary_entry(file_data: Any) -> bool:
|
|
174
|
+
"""Return whether a stored file entry contains byte-backed content."""
|
|
175
|
+
if not isinstance(file_data, dict):
|
|
176
|
+
return False # pragma: no cover
|
|
177
|
+
return bool(file_data.get("binary")) or isinstance(file_data.get("content"), bytes)
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _content_to_bytes(content: Any) -> bytes:
|
|
181
|
+
if isinstance(content, bytes):
|
|
182
|
+
return content
|
|
183
|
+
if isinstance(content, str):
|
|
184
|
+
return content.encode("utf-8") # pragma: no cover
|
|
185
|
+
return "\n".join(str(line) for line in content).encode("utf-8")
|
|
186
|
+
|
|
187
|
+
def write(
|
|
188
|
+
self,
|
|
189
|
+
file_path: str,
|
|
190
|
+
content: str | list[str],
|
|
191
|
+
thread_id: str = "default",
|
|
192
|
+
) -> WriteResult:
|
|
193
|
+
"""Write content to a file.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
file_path: Path where to write the file.
|
|
197
|
+
content: Content to write (string or list of strings).
|
|
198
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
WriteResult indicating success.
|
|
202
|
+
"""
|
|
203
|
+
path = self._normalize_path(file_path)
|
|
204
|
+
storage = self._get_thread_storage(thread_id)
|
|
205
|
+
|
|
206
|
+
# Normalize content to list of strings
|
|
207
|
+
if isinstance(content, str):
|
|
208
|
+
lines = content.split("\n")
|
|
209
|
+
else:
|
|
210
|
+
lines = list(content)
|
|
211
|
+
|
|
212
|
+
now = time.strftime(_TIMESTAMP_FORMAT)
|
|
213
|
+
|
|
214
|
+
with self._storage_lock:
|
|
215
|
+
is_new = path not in storage
|
|
216
|
+
storage[path] = {
|
|
217
|
+
"content": lines,
|
|
218
|
+
"binary": False,
|
|
219
|
+
"created_at": storage.get(path, {}).get("created_at", now) if not is_new else now,
|
|
220
|
+
"modified_at": now,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return WriteResult(path=path, success=True)
|
|
224
|
+
|
|
225
|
+
def write_bytes(
|
|
226
|
+
self,
|
|
227
|
+
file_path: str,
|
|
228
|
+
content: bytes,
|
|
229
|
+
thread_id: str = "default",
|
|
230
|
+
) -> WriteResult:
|
|
231
|
+
"""Write raw bytes to a file."""
|
|
232
|
+
path = self._normalize_path(file_path)
|
|
233
|
+
storage = self._get_thread_storage(thread_id)
|
|
234
|
+
now = time.strftime(_TIMESTAMP_FORMAT)
|
|
235
|
+
|
|
236
|
+
with self._storage_lock:
|
|
237
|
+
is_new = path not in storage
|
|
238
|
+
storage[path] = {
|
|
239
|
+
"content": bytes(content),
|
|
240
|
+
"binary": True,
|
|
241
|
+
"created_at": storage.get(path, {}).get("created_at", now) if not is_new else now,
|
|
242
|
+
"modified_at": now,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return WriteResult(path=path, success=True)
|
|
246
|
+
|
|
247
|
+
def read(
|
|
248
|
+
self,
|
|
249
|
+
file_path: str,
|
|
250
|
+
line_offset: int = 0,
|
|
251
|
+
limit: int = 2000,
|
|
252
|
+
*,
|
|
253
|
+
offset: int | None = None,
|
|
254
|
+
thread_id: str = "default",
|
|
255
|
+
) -> str:
|
|
256
|
+
"""Read a file's contents with line numbers.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
file_path: Path to the file.
|
|
260
|
+
line_offset: Line number to start reading from (0-indexed).
|
|
261
|
+
limit: Maximum number of lines to read.
|
|
262
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
263
|
+
offset: Deprecated alias for ``line_offset``.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
File content formatted with line numbers.
|
|
267
|
+
|
|
268
|
+
Raises:
|
|
269
|
+
FileNotFoundError: If file does not exist.
|
|
270
|
+
"""
|
|
271
|
+
path = self._normalize_path(file_path)
|
|
272
|
+
storage = self._get_thread_storage(thread_id)
|
|
273
|
+
|
|
274
|
+
if offset is not None:
|
|
275
|
+
if line_offset != 0 and line_offset != offset:
|
|
276
|
+
raise ValueError("Conflicting values provided for line_offset and offset")
|
|
277
|
+
line_offset = offset
|
|
278
|
+
|
|
279
|
+
with self._storage_lock:
|
|
280
|
+
if path not in storage:
|
|
281
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
282
|
+
|
|
283
|
+
file_data = storage[path]
|
|
284
|
+
if self._is_binary_entry(file_data):
|
|
285
|
+
raise ValueError(f"File is binary and cannot be read as text: {file_path}")
|
|
286
|
+
lines = self._content_to_lines(file_data.get("content", []))
|
|
287
|
+
|
|
288
|
+
# Apply offset and limit
|
|
289
|
+
start = max(0, line_offset)
|
|
290
|
+
end = min(len(lines), start + limit)
|
|
291
|
+
selected_lines = lines[start:end]
|
|
292
|
+
|
|
293
|
+
content = "\n".join(selected_lines)
|
|
294
|
+
if not content:
|
|
295
|
+
return ""
|
|
296
|
+
return format_content_with_line_numbers(content, offset=start)
|
|
297
|
+
|
|
298
|
+
def read_bytes(self, file_path: str, thread_id: str = "default") -> bytes:
|
|
299
|
+
"""Read a file's contents as raw bytes.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
file_path: Path to the file.
|
|
303
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
File content as raw bytes (no formatting, no line numbers).
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
FileNotFoundError: If file does not exist.
|
|
310
|
+
IsADirectoryError: If path is a directory (not supported in memory backend).
|
|
311
|
+
"""
|
|
312
|
+
path = self._normalize_path(file_path)
|
|
313
|
+
storage = self._get_thread_storage(thread_id)
|
|
314
|
+
|
|
315
|
+
with self._storage_lock:
|
|
316
|
+
if path not in storage:
|
|
317
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
318
|
+
|
|
319
|
+
file_data = storage[path]
|
|
320
|
+
return self._content_to_bytes(file_data.get("content", []))
|
|
321
|
+
|
|
322
|
+
def read_chars(
|
|
323
|
+
self,
|
|
324
|
+
file_path: str,
|
|
325
|
+
char_offset: int = 0,
|
|
326
|
+
max_chars: int = 20000,
|
|
327
|
+
thread_id: str = "default",
|
|
328
|
+
) -> str:
|
|
329
|
+
"""Read a file's contents by character position.
|
|
330
|
+
|
|
331
|
+
Provides character-based pagination for reading large files
|
|
332
|
+
that may have very long single lines.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
file_path: Path to the file.
|
|
336
|
+
char_offset: Character position to start from (0-indexed).
|
|
337
|
+
max_chars: Maximum number of characters to read.
|
|
338
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
File content without line numbers (raw characters).
|
|
342
|
+
|
|
343
|
+
Raises:
|
|
344
|
+
FileNotFoundError: If file does not exist.
|
|
345
|
+
"""
|
|
346
|
+
path = self._normalize_path(file_path)
|
|
347
|
+
storage = self._get_thread_storage(thread_id)
|
|
348
|
+
|
|
349
|
+
with self._storage_lock:
|
|
350
|
+
if path not in storage:
|
|
351
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
352
|
+
|
|
353
|
+
file_data = storage[path]
|
|
354
|
+
if self._is_binary_entry(file_data):
|
|
355
|
+
raise ValueError(f"File is binary and cannot be read as text: {file_path}")
|
|
356
|
+
lines = self._content_to_lines(file_data.get("content", []))
|
|
357
|
+
full_content = "\n".join(lines)
|
|
358
|
+
|
|
359
|
+
# Apply char_offset and max_chars
|
|
360
|
+
start = max(0, char_offset)
|
|
361
|
+
if len(full_content) == 0:
|
|
362
|
+
return ""
|
|
363
|
+
if start >= len(full_content):
|
|
364
|
+
return "[End of file reached]"
|
|
365
|
+
end = min(len(full_content), start + max_chars)
|
|
366
|
+
selected_content = full_content[start:end]
|
|
367
|
+
|
|
368
|
+
return selected_content
|
|
369
|
+
|
|
370
|
+
def ls_info(self, path: str, thread_id: str = "default") -> list[FileInfo]:
|
|
371
|
+
"""List files in a directory.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
path: Directory path to list.
|
|
375
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
List of FileInfo objects.
|
|
379
|
+
|
|
380
|
+
Raises:
|
|
381
|
+
FileNotFoundError: If directory does not exist (treats root as always existing).
|
|
382
|
+
"""
|
|
383
|
+
path = self._normalize_path(path)
|
|
384
|
+
storage = self._get_thread_storage(thread_id)
|
|
385
|
+
|
|
386
|
+
with self._storage_lock:
|
|
387
|
+
if path == "/":
|
|
388
|
+
# List all files (we don't have directories, just files)
|
|
389
|
+
result = []
|
|
390
|
+
for file_path, file_data in storage.items():
|
|
391
|
+
size = len(self._content_to_bytes(file_data.get("content", [])))
|
|
392
|
+
result.append(FileInfo(path=file_path, type="file", size=size))
|
|
393
|
+
return result
|
|
394
|
+
else:
|
|
395
|
+
# For non-root, check if any files exist with this prefix
|
|
396
|
+
prefix = path if path.endswith("/") else path + "/"
|
|
397
|
+
matching_files = [fp for fp in storage.keys() if fp.startswith(prefix) or fp == path]
|
|
398
|
+
|
|
399
|
+
if not matching_files:
|
|
400
|
+
raise FileNotFoundError(f"Directory not found: {path}")
|
|
401
|
+
|
|
402
|
+
result = []
|
|
403
|
+
for file_path in matching_files:
|
|
404
|
+
file_data = storage[file_path]
|
|
405
|
+
size = len(self._content_to_bytes(file_data.get("content", [])))
|
|
406
|
+
result.append(FileInfo(path=file_path, type="file", size=size))
|
|
407
|
+
return result
|
|
408
|
+
|
|
409
|
+
def _get_files_to_search(self, storage: dict[str, Any], path: str | None) -> list[str]:
|
|
410
|
+
"""Get list of files to search based on path filter.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
storage: Storage dictionary for the thread.
|
|
414
|
+
path: Directory to search in, or None for all files.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
List of file paths to search.
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
FileNotFoundError: If path is specified but directory does not exist.
|
|
421
|
+
"""
|
|
422
|
+
if path is None or path == "/":
|
|
423
|
+
return list(storage.keys())
|
|
424
|
+
|
|
425
|
+
normalized_path = self._normalize_path(path)
|
|
426
|
+
prefix = normalized_path if normalized_path.endswith("/") else normalized_path + "/"
|
|
427
|
+
files_to_search = [fp for fp in storage.keys() if fp.startswith(prefix) or fp == normalized_path]
|
|
428
|
+
|
|
429
|
+
if normalized_path != "/" and not files_to_search:
|
|
430
|
+
raise FileNotFoundError(f"Path not found: {path}")
|
|
431
|
+
|
|
432
|
+
return files_to_search
|
|
433
|
+
|
|
434
|
+
def _compile_pattern(self, pattern: str) -> re.Pattern[str]:
|
|
435
|
+
"""Compile pattern as regex, falling back to literal if invalid.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
pattern: Pattern to compile.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
Compiled regex pattern.
|
|
442
|
+
"""
|
|
443
|
+
try:
|
|
444
|
+
return re.compile(pattern, re.IGNORECASE)
|
|
445
|
+
except re.error:
|
|
446
|
+
return re.compile(re.escape(pattern), re.IGNORECASE)
|
|
447
|
+
|
|
448
|
+
def grep(
|
|
449
|
+
self,
|
|
450
|
+
pattern: str,
|
|
451
|
+
path: str | None = None,
|
|
452
|
+
thread_id: str = "default",
|
|
453
|
+
) -> list[GrepMatch]:
|
|
454
|
+
"""Search for text pattern across files.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
pattern: Text pattern to search for (literal string, not regex).
|
|
458
|
+
path: Directory to search in. Defaults to None (search all).
|
|
459
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
List of GrepMatch objects.
|
|
463
|
+
|
|
464
|
+
Raises:
|
|
465
|
+
FileNotFoundError: If path is specified but directory does not exist.
|
|
466
|
+
"""
|
|
467
|
+
storage = self._get_thread_storage(thread_id)
|
|
468
|
+
matches = []
|
|
469
|
+
|
|
470
|
+
with self._storage_lock:
|
|
471
|
+
files_to_search = self._get_files_to_search(storage, path)
|
|
472
|
+
regex = self._compile_pattern(pattern)
|
|
473
|
+
|
|
474
|
+
for file_path in files_to_search:
|
|
475
|
+
file_data = storage.get(file_path, {})
|
|
476
|
+
if self._is_binary_entry(file_data):
|
|
477
|
+
continue
|
|
478
|
+
lines = self._content_to_lines(file_data.get("content", []))
|
|
479
|
+
|
|
480
|
+
for line_num, line in enumerate(lines, start=1):
|
|
481
|
+
if regex.search(line):
|
|
482
|
+
matches.append(
|
|
483
|
+
GrepMatch(
|
|
484
|
+
path=file_path,
|
|
485
|
+
line_number=line_num,
|
|
486
|
+
content=line,
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
return matches
|
|
491
|
+
|
|
492
|
+
# Alias grep_raw to grep for protocol compatibility
|
|
493
|
+
grep_raw = grep
|
|
494
|
+
|
|
495
|
+
def edit(
|
|
496
|
+
self,
|
|
497
|
+
file_path: str,
|
|
498
|
+
old_string: str,
|
|
499
|
+
new_string: str,
|
|
500
|
+
replace_all: bool = False,
|
|
501
|
+
thread_id: str = "default",
|
|
502
|
+
) -> EditResult:
|
|
503
|
+
"""Edit a file by replacing old_string with new_string.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
file_path: Path to the file to edit.
|
|
507
|
+
old_string: String to find and replace.
|
|
508
|
+
new_string: String to replace with.
|
|
509
|
+
replace_all: If True, replace all occurrences. If False,
|
|
510
|
+
old_string must appear exactly once. Defaults to False.
|
|
511
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
EditResult with number of replacements.
|
|
515
|
+
|
|
516
|
+
Notes:
|
|
517
|
+
Returns errors via EditResult.error to match deepagents-style tool flow.
|
|
518
|
+
"""
|
|
519
|
+
path = self._normalize_path(file_path)
|
|
520
|
+
storage = self._get_thread_storage(thread_id)
|
|
521
|
+
|
|
522
|
+
with self._storage_lock:
|
|
523
|
+
if path not in storage:
|
|
524
|
+
return EditResult(path=path, error=f"Error: File '{file_path}' not found", success=False)
|
|
525
|
+
|
|
526
|
+
file_data = storage[path]
|
|
527
|
+
if self._is_binary_entry(file_data):
|
|
528
|
+
return EditResult(
|
|
529
|
+
path=path, error=f"Error: '{file_path}' is binary and cannot be edited", success=False
|
|
530
|
+
)
|
|
531
|
+
lines = self._content_to_lines(file_data.get("content", []))
|
|
532
|
+
content = "\n".join(lines)
|
|
533
|
+
|
|
534
|
+
replacement = perform_string_replacement(content, old_string, new_string, replace_all)
|
|
535
|
+
if isinstance(replacement, str):
|
|
536
|
+
return EditResult(path=path, error=replacement, success=False)
|
|
537
|
+
|
|
538
|
+
new_content, occurrences = replacement
|
|
539
|
+
new_lines = new_content.split("\n")
|
|
540
|
+
|
|
541
|
+
# Update file
|
|
542
|
+
file_data["content"] = new_lines
|
|
543
|
+
file_data["binary"] = False
|
|
544
|
+
file_data["modified_at"] = time.strftime(_TIMESTAMP_FORMAT)
|
|
545
|
+
|
|
546
|
+
return EditResult(
|
|
547
|
+
path=path,
|
|
548
|
+
occurrences=occurrences,
|
|
549
|
+
success=True,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def glob_info(self, pattern: str, path: str = "/", thread_id: str = "default") -> list[FileInfo]:
|
|
553
|
+
"""Find files matching a glob pattern.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
pattern: Glob pattern to match (e.g., "**/*.py").
|
|
557
|
+
path: Base directory to search from. Defaults to "/".
|
|
558
|
+
thread_id: Thread identifier for isolation. Defaults to "default".
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
List of FileInfo objects for matching files.
|
|
562
|
+
"""
|
|
563
|
+
import fnmatch
|
|
564
|
+
|
|
565
|
+
storage = self._get_thread_storage(thread_id)
|
|
566
|
+
matches = []
|
|
567
|
+
|
|
568
|
+
# Convert glob pattern to something we can match
|
|
569
|
+
# Simple implementation: just check if filename matches
|
|
570
|
+
with self._storage_lock:
|
|
571
|
+
for file_path, file_data in storage.items():
|
|
572
|
+
# Check if file matches pattern
|
|
573
|
+
filename = file_path.split("/")[-1] if "/" in file_path else file_path
|
|
574
|
+
if fnmatch.fnmatch(filename, pattern) or fnmatch.fnmatch(file_path, pattern):
|
|
575
|
+
size = len(self._content_to_bytes(file_data.get("content", [])))
|
|
576
|
+
matches.append(FileInfo(path=file_path, type="file", size=size))
|
|
577
|
+
|
|
578
|
+
return matches
|