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,169 @@
|
|
|
1
|
+
"""Safe compaction metadata contracts for observability.
|
|
2
|
+
|
|
3
|
+
Provides the CompactionMetadata dataclass and helpers to build
|
|
4
|
+
safe A2A/SSE event metadata for compaction lifecycle events.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
_COMPACTION_ACTIVITY_ID = "context_compaction"
|
|
17
|
+
_AGENT_THINKING_STEP_KIND = "agent_thinking_step"
|
|
18
|
+
|
|
19
|
+
_STAGE_MESSAGES = {
|
|
20
|
+
"started": "Compacting context...",
|
|
21
|
+
"completed": "Context compacted",
|
|
22
|
+
"skipped": "Compaction skipped",
|
|
23
|
+
"failed": "Context compaction failed",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class CompactionMetadata:
|
|
29
|
+
"""Safe observable metadata for a single compaction cycle.
|
|
30
|
+
|
|
31
|
+
All string fields are safe for logging, traces, and stream events.
|
|
32
|
+
Raw payloads, secrets, signed URLs, and base64 blobs are excluded
|
|
33
|
+
by construction.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
summary_id: Opaque identifier for the compaction summary.
|
|
37
|
+
tokens_before: Estimated prompt tokens before compaction.
|
|
38
|
+
tokens_after: Estimated prompt tokens after compaction.
|
|
39
|
+
offloaded_ref_count: Number of payload references offloaded.
|
|
40
|
+
preservation_topic: Optional topic preserved during compaction.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
summary_id: str | None
|
|
44
|
+
tokens_before: int | None
|
|
45
|
+
tokens_after: int | None
|
|
46
|
+
offloaded_ref_count: int
|
|
47
|
+
preservation_topic: str | None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_compaction_event_metadata(
|
|
51
|
+
*,
|
|
52
|
+
stage: str,
|
|
53
|
+
trigger_reason: str | None = None,
|
|
54
|
+
summary_status: str | None = None,
|
|
55
|
+
tokens_before: int | None = None,
|
|
56
|
+
tokens_after: int | None = None,
|
|
57
|
+
offloaded_ref_count: int = 0,
|
|
58
|
+
summary_id: str | None = None,
|
|
59
|
+
preservation_topic: str | None = None,
|
|
60
|
+
) -> dict[str, Any]:
|
|
61
|
+
"""Build safe A2A/SSE metadata dict for a compaction lifecycle event.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
stage: Compaction lifecycle stage (started, completed, skipped, failed).
|
|
65
|
+
trigger_reason: Why compaction was triggered.
|
|
66
|
+
summary_status: Status of the summary generation.
|
|
67
|
+
tokens_before: Estimated tokens before compaction.
|
|
68
|
+
tokens_after: Estimated tokens after compaction.
|
|
69
|
+
offloaded_ref_count: Number of offloaded payload references.
|
|
70
|
+
summary_id: Opaque summary identifier.
|
|
71
|
+
preservation_topic: Optional topic preserved during compaction.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Safe metadata dict for A2A/SSE status_update events.
|
|
75
|
+
Guarantees no raw payloads, secrets, signed URLs, or base64 blobs.
|
|
76
|
+
"""
|
|
77
|
+
metadata: dict[str, Any] = {
|
|
78
|
+
"kind": _AGENT_THINKING_STEP_KIND,
|
|
79
|
+
"step_type": "context_compaction",
|
|
80
|
+
"stage": stage,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if summary_status:
|
|
84
|
+
metadata["summary_status"] = summary_status
|
|
85
|
+
if tokens_before is not None:
|
|
86
|
+
metadata["tokens_before"] = tokens_before
|
|
87
|
+
if tokens_after is not None:
|
|
88
|
+
metadata["tokens_after"] = tokens_after
|
|
89
|
+
if tokens_before is not None and tokens_after is not None:
|
|
90
|
+
metadata["estimated_token_savings"] = max(0, tokens_before - tokens_after)
|
|
91
|
+
if offloaded_ref_count:
|
|
92
|
+
metadata["offloaded_ref_count"] = offloaded_ref_count
|
|
93
|
+
if summary_id:
|
|
94
|
+
metadata["summary_id"] = summary_id
|
|
95
|
+
if trigger_reason:
|
|
96
|
+
metadata["trigger_reason"] = trigger_reason
|
|
97
|
+
if preservation_topic:
|
|
98
|
+
metadata["preservation_topic"] = preservation_topic
|
|
99
|
+
|
|
100
|
+
metadata["thinking_and_activity_info"] = _build_compaction_activity_info(metadata)
|
|
101
|
+
return metadata
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _build_compaction_activity_info(metadata: dict[str, Any]) -> dict[str, str]:
|
|
105
|
+
"""Build the existing glchat sidebar activity payload shape."""
|
|
106
|
+
payload: dict[str, Any] = {
|
|
107
|
+
"type": "context_compaction",
|
|
108
|
+
"message": safe_compaction_content(str(metadata.get("stage", ""))),
|
|
109
|
+
"stage": metadata.get("stage", ""),
|
|
110
|
+
}
|
|
111
|
+
for key in (
|
|
112
|
+
"summary_status",
|
|
113
|
+
"trigger_reason",
|
|
114
|
+
"tokens_before",
|
|
115
|
+
"tokens_after",
|
|
116
|
+
"estimated_token_savings",
|
|
117
|
+
"offloaded_ref_count",
|
|
118
|
+
"summary_id",
|
|
119
|
+
"preservation_topic",
|
|
120
|
+
):
|
|
121
|
+
if key in metadata:
|
|
122
|
+
payload[key] = metadata[key]
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"data_type": "activity",
|
|
126
|
+
"id": _COMPACTION_ACTIVITY_ID,
|
|
127
|
+
"data_value": json.dumps(payload, separators=(",", ":")),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def safe_compaction_content(stage: str) -> str:
|
|
132
|
+
"""Return a safe, short content string for a compaction stream event.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
stage: Compaction lifecycle stage.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Short non-sensitive status text safe for stream event content.
|
|
139
|
+
"""
|
|
140
|
+
return _STAGE_MESSAGES.get(stage, "Context compaction")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def build_compaction_metadata(
|
|
144
|
+
state: Any,
|
|
145
|
+
*,
|
|
146
|
+
tokens_before: int | None = None,
|
|
147
|
+
tokens_after: int | None = None,
|
|
148
|
+
preservation_topic: str | None = None,
|
|
149
|
+
summary_id_for_state: str | None = None,
|
|
150
|
+
) -> CompactionMetadata:
|
|
151
|
+
"""Build a CompactionMetadata from a CompactionState and extra params.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
state: CompactionState from the context preparation result.
|
|
155
|
+
tokens_before: Estimated prompt tokens before compaction (overrides state).
|
|
156
|
+
tokens_after: Estimated prompt tokens after compaction (overrides state).
|
|
157
|
+
preservation_topic: Optional topic preserved during compaction.
|
|
158
|
+
summary_id_for_state: Optional summary ID from result.summary.summary_id.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
CompactionMetadata with safe fields only.
|
|
162
|
+
"""
|
|
163
|
+
return CompactionMetadata(
|
|
164
|
+
summary_id=summary_id_for_state or getattr(state, "summary_id", None),
|
|
165
|
+
tokens_before=tokens_before,
|
|
166
|
+
tokens_after=tokens_after,
|
|
167
|
+
offloaded_ref_count=getattr(state, "offloaded_ref_count", 0),
|
|
168
|
+
preservation_topic=preservation_topic,
|
|
169
|
+
)
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""Helpers for lossless payload offloading and safe context pruning.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import uuid
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # pragma: no cover - only for type checking
|
|
19
|
+
from aip_agents.middleware.backends.protocol import BackendProtocol
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _estimate_tokens(text: str, chars_per_token: int = 4) -> int:
|
|
23
|
+
# deferred to #809: Use shared aip_agents.context.utils.estimate_tokens
|
|
24
|
+
if not text:
|
|
25
|
+
return 0
|
|
26
|
+
return max(1, (len(text) + chars_per_token - 1) // chars_per_token)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
SourceKind = Literal["tool_input", "tool_result", "subagent_output", "attachment", "artifact"]
|
|
30
|
+
|
|
31
|
+
_SIGNED_URL_PATTERN = re.compile(r"(?i)(x-amz-signature|sig|signature)=")
|
|
32
|
+
_SIGNED_QUERY_KV_PATTERN = re.compile(r"(?i)\b(x-amz-signature|sig|signature)\s*=\s*[^&\s;]+")
|
|
33
|
+
_SECRET_KV_PATTERN = re.compile(r"(?i)(api[_-]?key|token|password|secret)\s*[:=]\s*[^\s]+")
|
|
34
|
+
_LONG_BASE64_PATTERN = re.compile(r"(?=[^+/=]*[+/=])[A-Za-z0-9+/=]{120,}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class OffloadedPayloadRef:
|
|
39
|
+
"""Reference metadata for an offloaded payload.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
ref_id: Stable identifier returned by the offload backend.
|
|
43
|
+
source_kind: Payload origin used for audit and downstream handling.
|
|
44
|
+
backend_uri: Backend-specific location for retrieving the full payload.
|
|
45
|
+
display_name: Safe human-readable name for the payload, if available.
|
|
46
|
+
mime_type: Payload MIME type, if known.
|
|
47
|
+
size_bytes: Original payload size in bytes, if known.
|
|
48
|
+
token_estimate: Estimated token cost of the original payload, if known.
|
|
49
|
+
checksum: Integrity checksum for the original payload, if available.
|
|
50
|
+
preview: Redacted inline preview used in the transformed message.
|
|
51
|
+
redaction_applied: Whether the preview was changed by secret redaction.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
ref_id: str
|
|
55
|
+
source_kind: SourceKind
|
|
56
|
+
backend_uri: str
|
|
57
|
+
display_name: str | None
|
|
58
|
+
mime_type: str | None
|
|
59
|
+
size_bytes: int | None
|
|
60
|
+
token_estimate: int | None
|
|
61
|
+
checksum: str | None
|
|
62
|
+
preview: str
|
|
63
|
+
redaction_applied: bool
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def offload_oversized_payloads(
|
|
67
|
+
messages: Sequence[dict[str, Any]],
|
|
68
|
+
*,
|
|
69
|
+
backend: BackendProtocol | None = None,
|
|
70
|
+
max_inline_chars: int = 4_000,
|
|
71
|
+
preview_chars: int = 400,
|
|
72
|
+
) -> tuple[list[dict[str, Any]], list[OffloadedPayloadRef]]:
|
|
73
|
+
"""Offload oversized or sensitive payload content from messages.
|
|
74
|
+
|
|
75
|
+
Message order and tool-call IDs are preserved.
|
|
76
|
+
|
|
77
|
+
Usage::
|
|
78
|
+
|
|
79
|
+
from aip_agents.context.offload import offload_oversized_payloads
|
|
80
|
+
|
|
81
|
+
messages = [
|
|
82
|
+
{"role": "tool", "name": "search", "content": "x" * 5000},
|
|
83
|
+
]
|
|
84
|
+
transformed, refs = offload_oversized_payloads(
|
|
85
|
+
messages, backend=backend, max_inline_chars=4000
|
|
86
|
+
)
|
|
87
|
+
# transformed[0]["content"] contains a preview + ref link
|
|
88
|
+
# refs[0] has the OffloadedPayloadRef metadata
|
|
89
|
+
"""
|
|
90
|
+
transformed: list[dict[str, Any]] = []
|
|
91
|
+
refs: list[OffloadedPayloadRef] = []
|
|
92
|
+
|
|
93
|
+
for message in messages:
|
|
94
|
+
msg = copy.deepcopy(message)
|
|
95
|
+
content = _extract_text_content(msg)
|
|
96
|
+
|
|
97
|
+
content_ref = _offload_content_field(
|
|
98
|
+
msg,
|
|
99
|
+
content,
|
|
100
|
+
backend=backend,
|
|
101
|
+
max_inline_chars=max_inline_chars,
|
|
102
|
+
preview_chars=preview_chars,
|
|
103
|
+
)
|
|
104
|
+
if content_ref is not None:
|
|
105
|
+
refs.append(content_ref)
|
|
106
|
+
|
|
107
|
+
refs.extend(
|
|
108
|
+
_offload_tool_call_arguments(
|
|
109
|
+
msg,
|
|
110
|
+
backend=backend,
|
|
111
|
+
max_inline_chars=max_inline_chars,
|
|
112
|
+
preview_chars=preview_chars,
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
transformed.append(msg)
|
|
116
|
+
|
|
117
|
+
return transformed, refs
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _offload_content_field(
|
|
121
|
+
message: dict[str, Any],
|
|
122
|
+
content: str,
|
|
123
|
+
*,
|
|
124
|
+
backend: BackendProtocol | None,
|
|
125
|
+
max_inline_chars: int,
|
|
126
|
+
preview_chars: int,
|
|
127
|
+
) -> OffloadedPayloadRef | None:
|
|
128
|
+
if not _should_offload_content(content, max_inline_chars=max_inline_chars):
|
|
129
|
+
return None
|
|
130
|
+
if backend is None:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
ref = offload_payload(
|
|
134
|
+
content,
|
|
135
|
+
source_kind=_infer_source_kind(message),
|
|
136
|
+
display_name=_infer_display_name(message),
|
|
137
|
+
backend=backend,
|
|
138
|
+
preview_chars=preview_chars,
|
|
139
|
+
)
|
|
140
|
+
if ref is None:
|
|
141
|
+
return None
|
|
142
|
+
message["content"] = render_offloaded_reference_message(ref)
|
|
143
|
+
message["offloaded_ref_id"] = ref.ref_id
|
|
144
|
+
return ref
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _offload_tool_call_arguments(
|
|
148
|
+
message: dict[str, Any],
|
|
149
|
+
*,
|
|
150
|
+
backend: BackendProtocol | None,
|
|
151
|
+
max_inline_chars: int,
|
|
152
|
+
preview_chars: int,
|
|
153
|
+
) -> list[OffloadedPayloadRef]:
|
|
154
|
+
tool_calls = message.get("tool_calls")
|
|
155
|
+
if not isinstance(tool_calls, list):
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
refs: list[OffloadedPayloadRef] = []
|
|
159
|
+
for call in tool_calls:
|
|
160
|
+
if not isinstance(call, dict):
|
|
161
|
+
continue
|
|
162
|
+
arguments = _tool_call_arguments(call)
|
|
163
|
+
if arguments is None or not _should_offload_content(arguments, max_inline_chars=max_inline_chars):
|
|
164
|
+
continue
|
|
165
|
+
if backend is None:
|
|
166
|
+
continue
|
|
167
|
+
ref = offload_payload(
|
|
168
|
+
arguments,
|
|
169
|
+
source_kind="tool_input",
|
|
170
|
+
display_name=_tool_call_name(call) or _infer_display_name(message),
|
|
171
|
+
backend=backend,
|
|
172
|
+
preview_chars=preview_chars,
|
|
173
|
+
)
|
|
174
|
+
if ref is None:
|
|
175
|
+
continue
|
|
176
|
+
_replace_tool_call_arguments(
|
|
177
|
+
call, json.dumps({"context_payload_ref": ref.ref_id, "source": "tool_input", "preview": ref.preview})
|
|
178
|
+
)
|
|
179
|
+
refs.append(ref)
|
|
180
|
+
|
|
181
|
+
if refs:
|
|
182
|
+
message["offloaded_tool_call_ref_ids"] = [ref.ref_id for ref in refs]
|
|
183
|
+
return refs
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _tool_call_arguments(call: dict[str, Any]) -> str | None:
|
|
187
|
+
function = call.get("function")
|
|
188
|
+
if isinstance(function, dict) and isinstance(function.get("arguments"), str):
|
|
189
|
+
return function["arguments"]
|
|
190
|
+
args = call.get("args")
|
|
191
|
+
return args if isinstance(args, str) else None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _replace_tool_call_arguments(call: dict[str, Any], replacement: str) -> None:
|
|
195
|
+
function = call.get("function")
|
|
196
|
+
if isinstance(function, dict) and isinstance(function.get("arguments"), str):
|
|
197
|
+
function["arguments"] = replacement
|
|
198
|
+
return
|
|
199
|
+
if isinstance(call.get("args"), str):
|
|
200
|
+
call["args"] = replacement
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _tool_call_name(call: dict[str, Any]) -> str | None:
|
|
204
|
+
if isinstance(call.get("name"), str):
|
|
205
|
+
return call["name"]
|
|
206
|
+
function = call.get("function")
|
|
207
|
+
if isinstance(function, dict) and isinstance(function.get("name"), str):
|
|
208
|
+
return function["name"]
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def offload_payload(
|
|
213
|
+
payload_text: str,
|
|
214
|
+
*,
|
|
215
|
+
source_kind: SourceKind,
|
|
216
|
+
display_name: str | None,
|
|
217
|
+
backend: BackendProtocol | None = None,
|
|
218
|
+
preview_chars: int = 400,
|
|
219
|
+
mime_type: str | None = "text/plain",
|
|
220
|
+
) -> OffloadedPayloadRef | None:
|
|
221
|
+
"""Create an offloaded payload reference and optional backend write.
|
|
222
|
+
|
|
223
|
+
Returns None when no backend is provided or the backend write fails.
|
|
224
|
+
"""
|
|
225
|
+
ref_id = f"ctx_ref_{uuid.uuid4().hex[:12]}"
|
|
226
|
+
checksum = hashlib.sha256(payload_text.encode("utf-8")).hexdigest()
|
|
227
|
+
size_bytes = len(payload_text.encode("utf-8"))
|
|
228
|
+
preview, redaction_applied = _safe_preview(payload_text, max_chars=preview_chars)
|
|
229
|
+
|
|
230
|
+
if backend is None:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
backend_path = f"/context-offloads/{ref_id}.txt"
|
|
234
|
+
try:
|
|
235
|
+
write_result = backend.write(backend_path, payload_text)
|
|
236
|
+
except Exception:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
if not getattr(write_result, "success", False):
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
token_estimate = _estimate_tokens(payload_text)
|
|
243
|
+
return OffloadedPayloadRef(
|
|
244
|
+
ref_id=ref_id,
|
|
245
|
+
source_kind=source_kind,
|
|
246
|
+
backend_uri=backend_path,
|
|
247
|
+
display_name=display_name,
|
|
248
|
+
mime_type=mime_type,
|
|
249
|
+
size_bytes=size_bytes,
|
|
250
|
+
token_estimate=token_estimate,
|
|
251
|
+
checksum=checksum,
|
|
252
|
+
preview=preview,
|
|
253
|
+
redaction_applied=redaction_applied,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def render_offloaded_reference_message(ref: OffloadedPayloadRef) -> str:
|
|
258
|
+
"""Render model-visible replacement content for an offloaded payload."""
|
|
259
|
+
return (
|
|
260
|
+
"[Context payload offloaded]\n"
|
|
261
|
+
f"source: {ref.source_kind}\n"
|
|
262
|
+
f"ref: {ref.ref_id}\n"
|
|
263
|
+
f"name: {ref.display_name or 'unknown'}\n"
|
|
264
|
+
f"size: {ref.size_bytes or 0} bytes\n"
|
|
265
|
+
"preview:\n"
|
|
266
|
+
f"{ref.preview}\n"
|
|
267
|
+
"Use the runtime/backend ref to retrieve details if needed."
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def prune_duplicate_tool_outputs(
|
|
272
|
+
messages: Sequence[dict[str, Any]],
|
|
273
|
+
*,
|
|
274
|
+
protected_tail_start_index: int,
|
|
275
|
+
) -> list[dict[str, Any]]:
|
|
276
|
+
"""Prune duplicate older tool outputs while preserving provider-valid pairs.
|
|
277
|
+
|
|
278
|
+
Usage::
|
|
279
|
+
|
|
280
|
+
from aip_agents.context.offload import prune_duplicate_tool_outputs
|
|
281
|
+
|
|
282
|
+
# Only tool results at or after protected_tail_start_index are kept;
|
|
283
|
+
# older duplicates are replaced with back-reference placeholders.
|
|
284
|
+
pruned = prune_duplicate_tool_outputs(messages, protected_tail_start_index=10)
|
|
285
|
+
"""
|
|
286
|
+
transformed = [copy.deepcopy(message) for message in messages]
|
|
287
|
+
latest_by_fingerprint: dict[str, int] = {}
|
|
288
|
+
|
|
289
|
+
for index in range(len(transformed) - 1, -1, -1):
|
|
290
|
+
_replace_duplicate_tool_output(
|
|
291
|
+
transformed,
|
|
292
|
+
latest_by_fingerprint,
|
|
293
|
+
index,
|
|
294
|
+
protected_tail_start_index=protected_tail_start_index,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return transformed
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _replace_duplicate_tool_output(
|
|
301
|
+
messages: list[dict[str, Any]],
|
|
302
|
+
latest_by_fingerprint: dict[str, int],
|
|
303
|
+
index: int,
|
|
304
|
+
*,
|
|
305
|
+
protected_tail_start_index: int,
|
|
306
|
+
) -> None:
|
|
307
|
+
message = messages[index]
|
|
308
|
+
if not _is_tool_result_message(message):
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
fingerprint = _tool_output_fingerprint(message)
|
|
312
|
+
if index >= protected_tail_start_index:
|
|
313
|
+
latest_by_fingerprint.setdefault(fingerprint, index)
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
newest_index = latest_by_fingerprint.get(fingerprint)
|
|
317
|
+
if newest_index is None:
|
|
318
|
+
latest_by_fingerprint[fingerprint] = index
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
_replace_with_tool_output_back_reference(message, newest_index, messages[newest_index].get("offloaded_ref_id"))
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _replace_with_tool_output_back_reference(message: dict[str, Any], newest_index: int, newest_ref: Any) -> None:
|
|
325
|
+
content = message.get("content")
|
|
326
|
+
if not isinstance(content, str):
|
|
327
|
+
return
|
|
328
|
+
message["content"] = (
|
|
329
|
+
"[Tool output back-reference]\n"
|
|
330
|
+
f"original_index: {newest_index}\n"
|
|
331
|
+
f"ref: {newest_ref or 'none'}\n"
|
|
332
|
+
"Use the newest protected-tail tool output for full details."
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def repair_tool_result_pairing(messages: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
337
|
+
"""Repair missing tool-result IDs when a single prior tool call is unambiguous.
|
|
338
|
+
|
|
339
|
+
Usage::
|
|
340
|
+
|
|
341
|
+
from aip_agents.context.offload import repair_tool_result_pairing
|
|
342
|
+
|
|
343
|
+
# After pruning or offloading, tool-call IDs may be missing from
|
|
344
|
+
# tool-result messages. This restores them when one unambiguous call
|
|
345
|
+
# precedes each result.
|
|
346
|
+
repaired = repair_tool_result_pairing(messages)
|
|
347
|
+
"""
|
|
348
|
+
repaired = [copy.deepcopy(message) for message in messages]
|
|
349
|
+
recent_tool_call_ids: list[str] = []
|
|
350
|
+
|
|
351
|
+
for message in repaired:
|
|
352
|
+
recent_tool_call_ids = _collect_tool_call_ids(message, recent_tool_call_ids)
|
|
353
|
+
recent_tool_call_ids = _repair_tool_result_id(message, recent_tool_call_ids)
|
|
354
|
+
|
|
355
|
+
return repaired
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _collect_tool_call_ids(message: dict[str, Any], current: list[str]) -> list[str]:
|
|
359
|
+
tool_calls = message.get("tool_calls")
|
|
360
|
+
if not isinstance(tool_calls, list):
|
|
361
|
+
return current
|
|
362
|
+
fresh: list[str] = []
|
|
363
|
+
for call in tool_calls:
|
|
364
|
+
if isinstance(call, dict):
|
|
365
|
+
call_id = call.get("id")
|
|
366
|
+
if isinstance(call_id, str) and call_id:
|
|
367
|
+
fresh.append(call_id)
|
|
368
|
+
return fresh
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _repair_tool_result_id(message: dict[str, Any], recent_tool_call_ids: list[str]) -> list[str]:
|
|
372
|
+
if not _is_tool_result_message(message):
|
|
373
|
+
return recent_tool_call_ids
|
|
374
|
+
tool_call_id = message.get("tool_call_id")
|
|
375
|
+
if isinstance(tool_call_id, str) and tool_call_id:
|
|
376
|
+
return []
|
|
377
|
+
if len(recent_tool_call_ids) == 1:
|
|
378
|
+
message["tool_call_id"] = recent_tool_call_ids[0]
|
|
379
|
+
return []
|
|
380
|
+
return recent_tool_call_ids
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _extract_text_content(message: dict[str, Any]) -> str:
|
|
384
|
+
content = message.get("content")
|
|
385
|
+
if isinstance(content, str):
|
|
386
|
+
return content
|
|
387
|
+
return str(content or "")
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _should_offload_content(content: str, *, max_inline_chars: int) -> bool:
|
|
391
|
+
if len(content) > max_inline_chars:
|
|
392
|
+
return True
|
|
393
|
+
if _SECRET_KV_PATTERN.search(content):
|
|
394
|
+
return True
|
|
395
|
+
if _SIGNED_URL_PATTERN.search(content):
|
|
396
|
+
return True
|
|
397
|
+
if _LONG_BASE64_PATTERN.search(content):
|
|
398
|
+
return True
|
|
399
|
+
return False
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _infer_source_kind(message: dict[str, Any]) -> SourceKind:
|
|
403
|
+
explicit = message.get("source_kind")
|
|
404
|
+
if explicit in {"tool_input", "tool_result", "subagent_output", "attachment", "artifact"}:
|
|
405
|
+
return explicit
|
|
406
|
+
|
|
407
|
+
role = str(message.get("role", "")).lower()
|
|
408
|
+
if role in {"tool", "tool_result"}:
|
|
409
|
+
return "tool_result"
|
|
410
|
+
if role in {"user", "human"}:
|
|
411
|
+
return "tool_input"
|
|
412
|
+
return "artifact"
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _infer_display_name(message: dict[str, Any]) -> str | None:
|
|
416
|
+
for key in ("name", "tool_name", "title"):
|
|
417
|
+
value = message.get(key)
|
|
418
|
+
if isinstance(value, str) and value.strip():
|
|
419
|
+
return value.strip()
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _safe_preview(
|
|
424
|
+
content: str, *, max_chars: int
|
|
425
|
+
) -> tuple[str, bool]: # NOSONAR - intentionally redacts sensitive data
|
|
426
|
+
# deferred to #809: Use shared aip_agents.context.utils.safe_preview
|
|
427
|
+
redacted = _SIGNED_QUERY_KV_PATTERN.sub(lambda m: f"{m.group(1)}=[REDACTED]", content)
|
|
428
|
+
redacted = _SECRET_KV_PATTERN.sub(lambda m: f"{m.group(1)}=[REDACTED]", redacted)
|
|
429
|
+
redacted = _LONG_BASE64_PATTERN.sub("[BASE64_REDACTED]", redacted)
|
|
430
|
+
redacted = re.sub(r"(?i)(https?://[^\s?]+)\?[^\s]+", r"\1?[REDACTED_QUERY]", redacted)
|
|
431
|
+
redaction_applied = redacted != content
|
|
432
|
+
|
|
433
|
+
clipped = redacted[:max_chars]
|
|
434
|
+
if len(redacted) > max_chars:
|
|
435
|
+
clipped += "..."
|
|
436
|
+
|
|
437
|
+
return clipped, redaction_applied
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _is_tool_result_message(message: dict[str, Any]) -> bool:
|
|
441
|
+
role = str(message.get("role", "")).lower()
|
|
442
|
+
return role in {"tool", "tool_result"} or "tool_call_id" in message
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _tool_output_fingerprint(message: dict[str, Any]) -> str:
|
|
446
|
+
tool_name = str(message.get("name") or message.get("tool_name") or "")
|
|
447
|
+
content = _extract_text_content(message)
|
|
448
|
+
normalized = f"{tool_name}\n{content}".encode()
|
|
449
|
+
return hashlib.sha256(normalized).hexdigest()
|