solace-agent-mesh 1.11.2__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.
- solace_agent_mesh/__init__.py +0 -0
- solace_agent_mesh/agent/__init__.py +0 -0
- solace_agent_mesh/agent/adk/__init__.py +0 -0
- solace_agent_mesh/agent/adk/adk_llm.txt +226 -0
- solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
- solace_agent_mesh/agent/adk/alembic/README +74 -0
- solace_agent_mesh/agent/adk/alembic/env.py +77 -0
- solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
- solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
- solace_agent_mesh/agent/adk/alembic.ini +112 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +52 -0
- solace_agent_mesh/agent/adk/artifacts/__init__.py +1 -0
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
- solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +545 -0
- solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +609 -0
- solace_agent_mesh/agent/adk/callbacks.py +2318 -0
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +406 -0
- solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +415 -0
- solace_agent_mesh/agent/adk/mcp_content_processor.py +666 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +1026 -0
- solace_agent_mesh/agent/adk/models/models_llm.txt +189 -0
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +132 -0
- solace_agent_mesh/agent/adk/runner.py +390 -0
- solace_agent_mesh/agent/adk/schema_migration.py +88 -0
- solace_agent_mesh/agent/adk/services.py +468 -0
- solace_agent_mesh/agent/adk/setup.py +1325 -0
- solace_agent_mesh/agent/adk/stream_parser.py +415 -0
- solace_agent_mesh/agent/adk/tool_wrapper.py +165 -0
- solace_agent_mesh/agent/agent_llm.txt +369 -0
- solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
- solace_agent_mesh/agent/protocol/__init__.py +0 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +2041 -0
- solace_agent_mesh/agent/protocol/protocol_llm.txt +81 -0
- solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
- solace_agent_mesh/agent/proxies/__init__.py +0 -0
- solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
- solace_agent_mesh/agent/proxies/a2a/app.py +56 -0
- solace_agent_mesh/agent/proxies/a2a/component.py +1585 -0
- solace_agent_mesh/agent/proxies/a2a/config.py +216 -0
- solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
- solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/base/app.py +100 -0
- solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
- solace_agent_mesh/agent/proxies/base/component.py +816 -0
- solace_agent_mesh/agent/proxies/base/config.py +85 -0
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +19 -0
- solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
- solace_agent_mesh/agent/sac/__init__.py +0 -0
- solace_agent_mesh/agent/sac/app.py +595 -0
- solace_agent_mesh/agent/sac/component.py +3668 -0
- solace_agent_mesh/agent/sac/patch_adk.py +103 -0
- solace_agent_mesh/agent/sac/sac_llm.txt +189 -0
- solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
- solace_agent_mesh/agent/sac/task_execution_context.py +415 -0
- solace_agent_mesh/agent/testing/__init__.py +3 -0
- solace_agent_mesh/agent/testing/debug_utils.py +135 -0
- solace_agent_mesh/agent/testing/testing_llm.txt +58 -0
- solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
- solace_agent_mesh/agent/tools/__init__.py +16 -0
- solace_agent_mesh/agent/tools/audio_tools.py +1740 -0
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +2500 -0
- solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +244 -0
- solace_agent_mesh/agent/tools/dynamic_tool.py +396 -0
- solace_agent_mesh/agent/tools/general_agent_tools.py +572 -0
- solace_agent_mesh/agent/tools/image_tools.py +1185 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +363 -0
- solace_agent_mesh/agent/tools/registry.py +38 -0
- solace_agent_mesh/agent/tools/test_tools.py +136 -0
- solace_agent_mesh/agent/tools/time_tools.py +126 -0
- solace_agent_mesh/agent/tools/tool_config_types.py +93 -0
- solace_agent_mesh/agent/tools/tool_definition.py +53 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +276 -0
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +275 -0
- solace_agent_mesh/agent/tools/web_tools.py +392 -0
- solace_agent_mesh/agent/utils/__init__.py +0 -0
- solace_agent_mesh/agent/utils/artifact_helpers.py +1353 -0
- solace_agent_mesh/agent/utils/config_parser.py +49 -0
- solace_agent_mesh/agent/utils/context_helpers.py +77 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +152 -0
- solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
- solace_agent_mesh/assets/docs/404.html +16 -0
- solace_agent_mesh/assets/docs/assets/css/styles.8162edfb.css +1 -0
- solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
- solace_agent_mesh/assets/docs/assets/images/sam-enterprise-credentials-b269f095349473118b2b33bdfcc40122.png +0 -0
- solace_agent_mesh/assets/docs/assets/js/032c2d61.f3d37824.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/0bcf40b7.c019ad46.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2131ec11.5c7a1f6e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js.LICENSE.txt +13 -0
- solace_agent_mesh/assets/docs/assets/js/2334.1cf50a20.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/240a0364.9ad94d1b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/341393d4.0fac2613.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3624.0eaa1fd0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.28b7c67b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.2ddc75c0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/41adc471.48b12a4e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/509e993c.a1fbf45a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.90a87880.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6063ff4c.ef84f702.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/66d4869e.b77431fc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6fdfefc7.99de744e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/722f809d.965da774.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/742f027b.46c07808.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/81a99df0.2484b8d9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js.LICENSE.txt +61 -0
- solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8731.6c1dbf0c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/945fb41e.6f4cdffd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9bb13469.b2333011.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ad71b5ed.af3ecfd1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c198a0dc.8f31f867.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de5f4c65.e8241890.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e04b235d.52cb25ed.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.b1068f9b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.4488e34c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.250993bf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js.LICENSE.txt +81 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.9e0813a2.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +154 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +99 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +90 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +107 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +166 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +101 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +219 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +92 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +29 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +110 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +345 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +83 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +84 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +25 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +85 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +60 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +144 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +191 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +128 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +34 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +267 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +142 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +116 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +164 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +140 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +57 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +72 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +102 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +37 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +247 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +184 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +75 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +85 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +41 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +78 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +25 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +78 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +160 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +142 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +100 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +52 -0
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
- solace_agent_mesh/assets/docs/img/logo.png +0 -0
- solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
- solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
- solace_agent_mesh/assets/docs/img/sam-enterprise-credentials.png +0 -0
- solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
- solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1765810064709.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -0
- solace_agent_mesh/assets/docs/search-doc-1765810064709.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -0
- solace_agent_mesh/assets/docs/sitemap.xml +1 -0
- solace_agent_mesh/cli/__init__.py +1 -0
- solace_agent_mesh/cli/commands/__init__.py +0 -0
- solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
- solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +729 -0
- solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +102 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +114 -0
- solace_agent_mesh/cli/commands/docs_cmd.py +60 -0
- solace_agent_mesh/cli/commands/eval_cmd.py +46 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +439 -0
- solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
- solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +238 -0
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +464 -0
- solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +119 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +215 -0
- solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +20 -0
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +137 -0
- solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +144 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +306 -0
- solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +283 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
- solace_agent_mesh/cli/commands/run_cmd.py +215 -0
- solace_agent_mesh/cli/main.py +52 -0
- solace_agent_mesh/cli/utils.py +262 -0
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-Dj3JtK42.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-ZKk9kEJ5.js +25 -0
- solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-BcUaNZ-Q.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-vjch4RYc.js +435 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-BNV4kZN0.js +535 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +15 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +16 -0
- solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
- solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
- solace_agent_mesh/common/__init__.py +1 -0
- solace_agent_mesh/common/a2a/__init__.py +241 -0
- solace_agent_mesh/common/a2a/a2a_llm.txt +175 -0
- solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
- solace_agent_mesh/common/a2a/artifact.py +368 -0
- solace_agent_mesh/common/a2a/events.py +213 -0
- solace_agent_mesh/common/a2a/message.py +375 -0
- solace_agent_mesh/common/a2a/protocol.py +689 -0
- solace_agent_mesh/common/a2a/task.py +127 -0
- solace_agent_mesh/common/a2a/translation.py +655 -0
- solace_agent_mesh/common/a2a/types.py +55 -0
- solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +445 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
- solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +48 -0
- solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
- solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +41 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +330 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +48 -0
- solace_agent_mesh/common/agent_registry.py +122 -0
- solace_agent_mesh/common/common_llm.txt +230 -0
- solace_agent_mesh/common/common_llm_detail.txt +2562 -0
- solace_agent_mesh/common/constants.py +6 -0
- solace_agent_mesh/common/data_parts.py +150 -0
- solace_agent_mesh/common/exceptions.py +49 -0
- solace_agent_mesh/common/middleware/__init__.py +12 -0
- solace_agent_mesh/common/middleware/config_resolver.py +132 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
- solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
- solace_agent_mesh/common/middleware/registry.py +127 -0
- solace_agent_mesh/common/oauth/__init__.py +17 -0
- solace_agent_mesh/common/oauth/oauth_client.py +408 -0
- solace_agent_mesh/common/oauth/utils.py +50 -0
- solace_agent_mesh/common/sac/__init__.py +0 -0
- solace_agent_mesh/common/sac/sac_llm.txt +71 -0
- solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
- solace_agent_mesh/common/sac/sam_component_base.py +730 -0
- solace_agent_mesh/common/sam_events/__init__.py +9 -0
- solace_agent_mesh/common/sam_events/event_service.py +208 -0
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
- solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
- solace_agent_mesh/common/services/__init__.py +4 -0
- solace_agent_mesh/common/services/employee_service.py +164 -0
- solace_agent_mesh/common/services/identity_service.py +134 -0
- solace_agent_mesh/common/services/providers/__init__.py +4 -0
- solace_agent_mesh/common/services/providers/local_file_identity_service.py +151 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +81 -0
- solace_agent_mesh/common/services/services_llm.txt +368 -0
- solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
- solace_agent_mesh/common/utils/__init__.py +7 -0
- solace_agent_mesh/common/utils/artifact_utils.py +31 -0
- solace_agent_mesh/common/utils/asyncio_macos_fix.py +88 -0
- solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
- solace_agent_mesh/common/utils/embeds/constants.py +56 -0
- solace_agent_mesh/common/utils/embeds/converter.py +447 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +220 -0
- solace_agent_mesh/common/utils/embeds/evaluators.py +395 -0
- solace_agent_mesh/common/utils/embeds/modifiers.py +793 -0
- solace_agent_mesh/common/utils/embeds/resolver.py +967 -0
- solace_agent_mesh/common/utils/embeds/types.py +23 -0
- solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
- solace_agent_mesh/common/utils/initializer.py +52 -0
- solace_agent_mesh/common/utils/log_formatters.py +64 -0
- solace_agent_mesh/common/utils/message_utils.py +80 -0
- solace_agent_mesh/common/utils/mime_helpers.py +172 -0
- solace_agent_mesh/common/utils/push_notification_auth.py +135 -0
- solace_agent_mesh/common/utils/pydantic_utils.py +159 -0
- solace_agent_mesh/common/utils/rbac_utils.py +69 -0
- solace_agent_mesh/common/utils/templates/__init__.py +8 -0
- solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
- solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
- solace_agent_mesh/common/utils/type_utils.py +28 -0
- solace_agent_mesh/common/utils/utils_llm.txt +335 -0
- solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
- solace_agent_mesh/config_portal/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/common.py +77 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/__init__.py +0 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +166 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
- solace_agent_mesh/config_portal/backend/server.py +644 -0
- solace_agent_mesh/config_portal/frontend/static/client/Solace_community_logo.png +0 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DiOiAjzL.js +103 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-Rk0n-9cK.js +140 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-mvZjNKiz.js +19 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-DzNKzXrc.js +68 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-ba77705e.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-B17tZKK7.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-V2BeTIUc.js +10 -0
- solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
- solace_agent_mesh/core_a2a/__init__.py +1 -0
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +90 -0
- solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
- solace_agent_mesh/core_a2a/service.py +307 -0
- solace_agent_mesh/evaluation/__init__.py +0 -0
- solace_agent_mesh/evaluation/evaluator.py +691 -0
- solace_agent_mesh/evaluation/message_organizer.py +553 -0
- solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
- solace_agent_mesh/evaluation/report/chart_section.html +141 -0
- solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
- solace_agent_mesh/evaluation/report/modal.html +59 -0
- solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
- solace_agent_mesh/evaluation/report/modal_script.js +296 -0
- solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
- solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
- solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
- solace_agent_mesh/evaluation/report/templates/header.html +340 -0
- solace_agent_mesh/evaluation/report_data_processor.py +970 -0
- solace_agent_mesh/evaluation/report_generator.py +607 -0
- solace_agent_mesh/evaluation/run.py +954 -0
- solace_agent_mesh/evaluation/shared/__init__.py +92 -0
- solace_agent_mesh/evaluation/shared/constants.py +47 -0
- solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
- solace_agent_mesh/evaluation/shared/helpers.py +35 -0
- solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
- solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
- solace_agent_mesh/evaluation/subscriber.py +776 -0
- solace_agent_mesh/evaluation/summary_builder.py +880 -0
- solace_agent_mesh/gateway/__init__.py +0 -0
- solace_agent_mesh/gateway/adapter/__init__.py +1 -0
- solace_agent_mesh/gateway/adapter/base.py +143 -0
- solace_agent_mesh/gateway/adapter/types.py +221 -0
- solace_agent_mesh/gateway/base/__init__.py +1 -0
- solace_agent_mesh/gateway/base/app.py +345 -0
- solace_agent_mesh/gateway/base/base_llm.txt +226 -0
- solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
- solace_agent_mesh/gateway/base/component.py +2030 -0
- solace_agent_mesh/gateway/base/task_context.py +75 -0
- solace_agent_mesh/gateway/gateway_llm.txt +369 -0
- solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
- solace_agent_mesh/gateway/generic/__init__.py +1 -0
- solace_agent_mesh/gateway/generic/app.py +50 -0
- solace_agent_mesh/gateway/generic/component.py +727 -0
- solace_agent_mesh/gateway/http_sse/__init__.py +0 -0
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +345 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +87 -0
- solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250910_d5b3f8f2e9a0_create_initial_database.py +58 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250911_b1c2d3e4f5g6_add_database_indexes.py +83 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +412 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +161 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +109 -0
- solace_agent_mesh/gateway/http_sse/app.py +351 -0
- solace_agent_mesh/gateway/http_sse/component.py +2360 -0
- solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +105 -0
- solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +109 -0
- solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +110 -0
- solace_agent_mesh/gateway/http_sse/dependencies.py +653 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +299 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
- solace_agent_mesh/gateway/http_sse/main.py +789 -0
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +46 -0
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +102 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +11 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +221 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +66 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/task.py +32 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +125 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +239 -0
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +34 -0
- solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
- solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
- solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +257 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +53 -0
- solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
- solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +39 -0
- solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
- solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +308 -0
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +268 -0
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +248 -0
- solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +74 -0
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1137 -0
- solace_agent_mesh/gateway/http_sse/routers/auth.py +311 -0
- solace_agent_mesh/gateway/http_sse/routers/config.py +371 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/__init__.py +10 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +450 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +15 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +133 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +33 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +18 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +123 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +33 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +168 -0
- solace_agent_mesh/gateway/http_sse/routers/people.py +38 -0
- solace_agent_mesh/gateway/http_sse/routers/projects.py +767 -0
- solace_agent_mesh/gateway/http_sse/routers/prompts.py +1415 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +312 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +634 -0
- solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +230 -0
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +1089 -0
- solace_agent_mesh/gateway/http_sse/routers/users.py +83 -0
- solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +1220 -0
- solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/services/agent_card_service.py +71 -0
- solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
- solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +273 -0
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +250 -0
- solace_agent_mesh/gateway/http_sse/services/people_service.py +78 -0
- solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
- solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +303 -0
- solace_agent_mesh/gateway/http_sse/services/session_service.py +702 -0
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +593 -0
- solace_agent_mesh/gateway/http_sse/services/task_service.py +119 -0
- solace_agent_mesh/gateway/http_sse/session_manager.py +219 -0
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +146 -0
- solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +252 -0
- solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
- solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
- solace_agent_mesh/gateway/http_sse/shared/enums.py +40 -0
- solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
- solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +217 -0
- solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
- solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
- solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +319 -0
- solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
- solace_agent_mesh/gateway/http_sse/shared/types.py +50 -0
- solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
- solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +88 -0
- solace_agent_mesh/gateway/http_sse/sse_manager.py +491 -0
- solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
- solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
- solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +72 -0
- solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
- solace_agent_mesh/llm.txt +228 -0
- solace_agent_mesh/llm_detail.txt +2835 -0
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/platform/__init__.py +18 -0
- solace_agent_mesh/services/platform/alembic/env.py +85 -0
- solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
- solace_agent_mesh/services/platform/alembic.ini +109 -0
- solace_agent_mesh/services/platform/api/__init__.py +3 -0
- solace_agent_mesh/services/platform/api/dependencies.py +147 -0
- solace_agent_mesh/services/platform/api/main.py +280 -0
- solace_agent_mesh/services/platform/api/middleware.py +51 -0
- solace_agent_mesh/services/platform/api/routers/__init__.py +24 -0
- solace_agent_mesh/services/platform/app.py +114 -0
- solace_agent_mesh/services/platform/component.py +235 -0
- solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
- solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
- solace_agent_mesh/templates/agent_template.yaml +53 -0
- solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
- solace_agent_mesh/templates/gateway_app_template.py +75 -0
- solace_agent_mesh/templates/gateway_component_template.py +484 -0
- solace_agent_mesh/templates/gateway_config_template.yaml +38 -0
- solace_agent_mesh/templates/logging_config_template.yaml +48 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +66 -0
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
- solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
- solace_agent_mesh/templates/plugin_custom_template.py +10 -0
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +60 -0
- solace_agent_mesh/templates/plugin_pyproject_template.toml +32 -0
- solace_agent_mesh/templates/plugin_readme_template.md +12 -0
- solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
- solace_agent_mesh/templates/plugin_tools_template.py +224 -0
- solace_agent_mesh/templates/shared_config.yaml +112 -0
- solace_agent_mesh/templates/templates_llm.txt +147 -0
- solace_agent_mesh/templates/webui.yaml +177 -0
- solace_agent_mesh-1.11.2.dist-info/METADATA +504 -0
- solace_agent_mesh-1.11.2.dist-info/RECORD +624 -0
- solace_agent_mesh-1.11.2.dist-info/WHEEL +4 -0
- solace_agent_mesh-1.11.2.dist-info/entry_points.txt +3 -0
- solace_agent_mesh-1.11.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,2500 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in ADK Tools for Artifact Management within the A2A Host.
|
|
3
|
+
These tools interact with the ADK ArtifactService via the ToolContext and
|
|
4
|
+
use state_delta for signaling artifact return requests to the host component.
|
|
5
|
+
Metadata handling is integrated via artifact_helpers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import fnmatch
|
|
13
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from google.adk.tools import ToolContext
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from google.adk.agents.invocation_context import InvocationContext
|
|
19
|
+
from google.genai import types as adk_types
|
|
20
|
+
from .tool_definition import BuiltinTool
|
|
21
|
+
from .registry import tool_registry
|
|
22
|
+
from ...agent.utils.artifact_helpers import (
|
|
23
|
+
save_artifact_with_metadata,
|
|
24
|
+
decode_and_get_bytes,
|
|
25
|
+
load_artifact_content_or_metadata,
|
|
26
|
+
is_filename_safe,
|
|
27
|
+
METADATA_SUFFIX,
|
|
28
|
+
DEFAULT_SCHEMA_MAX_KEYS,
|
|
29
|
+
)
|
|
30
|
+
from ...common.utils.embeds import (
|
|
31
|
+
evaluate_embed,
|
|
32
|
+
EMBED_REGEX,
|
|
33
|
+
EMBED_CHAIN_DELIMITER,
|
|
34
|
+
)
|
|
35
|
+
from ...common.utils.embeds.types import ResolutionMode
|
|
36
|
+
from ...agent.utils.context_helpers import get_original_session_id
|
|
37
|
+
from ...agent.adk.models.lite_llm import LiteLlm
|
|
38
|
+
from google.adk.models import LlmRequest
|
|
39
|
+
from google.adk.models.registry import LLMRegistry
|
|
40
|
+
from ...common.utils.mime_helpers import is_text_based_file
|
|
41
|
+
|
|
42
|
+
log = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
CATEGORY_NAME = "Artifact Management"
|
|
45
|
+
CATEGORY_DESCRIPTION = "List, read, create, update, and delete artifacts."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def _internal_create_artifact(
|
|
49
|
+
filename: str,
|
|
50
|
+
content: str,
|
|
51
|
+
mime_type: str,
|
|
52
|
+
tool_context: ToolContext = None,
|
|
53
|
+
description: Optional[str] = None,
|
|
54
|
+
metadata_json: Optional[str] = None,
|
|
55
|
+
schema_max_keys: Optional[int] = None,
|
|
56
|
+
) -> Dict[str, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Internal helper to create an artifact with its first chunk of content and metadata.
|
|
59
|
+
This function is not intended to be called directly by the LLM.
|
|
60
|
+
It is used by callbacks that process fenced artifact blocks.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
filename: The desired name for the artifact.
|
|
64
|
+
content: The first chunk of the artifact content, as a string.
|
|
65
|
+
If the mime_type suggests binary data, this string is expected
|
|
66
|
+
to be base64 encoded.
|
|
67
|
+
mime_type: The MIME type of the content.
|
|
68
|
+
tool_context: The ADK ToolContext, required for accessing services.
|
|
69
|
+
description (str, optional): A description for the artifact.
|
|
70
|
+
metadata_json (str, optional): A JSON string of additional metadata.
|
|
71
|
+
schema_max_keys (int, optional): Max keys for schema inference.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
A dictionary indicating the result, returned by save_artifact_with_metadata.
|
|
76
|
+
"""
|
|
77
|
+
if not tool_context:
|
|
78
|
+
return {
|
|
79
|
+
"status": "error",
|
|
80
|
+
"filename": filename,
|
|
81
|
+
"message": "ToolContext is missing, cannot save artifact.",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if not is_filename_safe(filename):
|
|
85
|
+
return {
|
|
86
|
+
"status": "error",
|
|
87
|
+
"filename": filename,
|
|
88
|
+
"message": "Filename is invalid or contains disallowed characters (e.g., '/', '..').",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
log_identifier = f"[BuiltinArtifactTool:_internal_create_artifact:{filename}]"
|
|
92
|
+
|
|
93
|
+
final_metadata = {}
|
|
94
|
+
if description:
|
|
95
|
+
final_metadata["description"] = description
|
|
96
|
+
if metadata_json:
|
|
97
|
+
try:
|
|
98
|
+
final_metadata.update(json.loads(metadata_json))
|
|
99
|
+
except (json.JSONDecodeError, TypeError):
|
|
100
|
+
log.warning(
|
|
101
|
+
"%s Invalid JSON in metadata_json attribute: %s",
|
|
102
|
+
log_identifier,
|
|
103
|
+
metadata_json,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
final_metadata["metadata_parsing_error"] = (
|
|
107
|
+
f"Invalid JSON provided: {metadata_json}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
log.debug("%s Processing request with metadata: %s", log_identifier, final_metadata)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
inv_context = tool_context._invocation_context
|
|
114
|
+
artifact_bytes, final_mime_type = decode_and_get_bytes(
|
|
115
|
+
content, mime_type, log_identifier
|
|
116
|
+
)
|
|
117
|
+
max_keys_to_use = (
|
|
118
|
+
schema_max_keys if schema_max_keys is not None else DEFAULT_SCHEMA_MAX_KEYS
|
|
119
|
+
)
|
|
120
|
+
if schema_max_keys is not None:
|
|
121
|
+
log.debug(
|
|
122
|
+
"%s Using schema_max_keys provided by LLM: %d",
|
|
123
|
+
log_identifier,
|
|
124
|
+
schema_max_keys,
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
log.debug(
|
|
128
|
+
"%s Using default schema_max_keys: %d",
|
|
129
|
+
log_identifier,
|
|
130
|
+
DEFAULT_SCHEMA_MAX_KEYS,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
artifact_service = inv_context.artifact_service
|
|
134
|
+
if not artifact_service:
|
|
135
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
136
|
+
session_last_update_time = inv_context.session.last_update_time
|
|
137
|
+
timestamp_for_artifact: datetime
|
|
138
|
+
if isinstance(session_last_update_time, datetime):
|
|
139
|
+
timestamp_for_artifact = session_last_update_time
|
|
140
|
+
elif isinstance(session_last_update_time, (int, float)):
|
|
141
|
+
log.debug(
|
|
142
|
+
"%s Converting numeric session.last_update_time (%s) to datetime.",
|
|
143
|
+
log_identifier,
|
|
144
|
+
session_last_update_time,
|
|
145
|
+
)
|
|
146
|
+
try:
|
|
147
|
+
timestamp_for_artifact = datetime.fromtimestamp(
|
|
148
|
+
session_last_update_time, timezone.utc
|
|
149
|
+
)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
log.warning(
|
|
152
|
+
"%s Failed to convert numeric timestamp %s to datetime: %s. Using current time.",
|
|
153
|
+
log_identifier,
|
|
154
|
+
session_last_update_time,
|
|
155
|
+
e,
|
|
156
|
+
)
|
|
157
|
+
timestamp_for_artifact = datetime.now(timezone.utc)
|
|
158
|
+
else:
|
|
159
|
+
if session_last_update_time is not None:
|
|
160
|
+
log.warning(
|
|
161
|
+
"%s Unexpected type for session.last_update_time: %s. Using current time.",
|
|
162
|
+
log_identifier,
|
|
163
|
+
type(session_last_update_time),
|
|
164
|
+
)
|
|
165
|
+
timestamp_for_artifact = datetime.now(timezone.utc)
|
|
166
|
+
result = await save_artifact_with_metadata(
|
|
167
|
+
artifact_service=artifact_service,
|
|
168
|
+
app_name=inv_context.app_name,
|
|
169
|
+
user_id=inv_context.user_id,
|
|
170
|
+
session_id=get_original_session_id(inv_context),
|
|
171
|
+
filename=filename,
|
|
172
|
+
content_bytes=artifact_bytes,
|
|
173
|
+
mime_type=final_mime_type,
|
|
174
|
+
metadata_dict=final_metadata,
|
|
175
|
+
timestamp=timestamp_for_artifact,
|
|
176
|
+
schema_max_keys=max_keys_to_use,
|
|
177
|
+
tool_context=tool_context,
|
|
178
|
+
)
|
|
179
|
+
log.info(
|
|
180
|
+
"%s Result from save_artifact_with_metadata: %s", log_identifier, result
|
|
181
|
+
)
|
|
182
|
+
return result
|
|
183
|
+
except Exception as e:
|
|
184
|
+
log.exception(
|
|
185
|
+
"%s Error creating artifact '%s': %s", log_identifier, filename, e
|
|
186
|
+
)
|
|
187
|
+
return {
|
|
188
|
+
"status": "error",
|
|
189
|
+
"filename": filename,
|
|
190
|
+
"message": f"Failed to create artifact: {e}",
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def list_artifacts(tool_context: ToolContext = None) -> Dict[str, Any]:
|
|
195
|
+
"""
|
|
196
|
+
Lists all available data artifact filenames and their versions for the current session.
|
|
197
|
+
Includes a summary of the latest version's metadata for each artifact.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
tool_context: The context provided by the ADK framework.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
A dictionary containing the list of artifacts with metadata summaries or an error.
|
|
204
|
+
"""
|
|
205
|
+
if not tool_context:
|
|
206
|
+
return {"status": "error", "message": "ToolContext is missing."}
|
|
207
|
+
log_identifier = "[BuiltinArtifactTool:list_artifacts]"
|
|
208
|
+
log.debug("%s Processing request.", log_identifier)
|
|
209
|
+
try:
|
|
210
|
+
artifact_service = tool_context._invocation_context.artifact_service
|
|
211
|
+
if not artifact_service:
|
|
212
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
213
|
+
app_name = tool_context._invocation_context.app_name
|
|
214
|
+
user_id = tool_context._invocation_context.user_id
|
|
215
|
+
session_id = get_original_session_id(tool_context._invocation_context)
|
|
216
|
+
list_keys_method = getattr(artifact_service, "list_artifact_keys")
|
|
217
|
+
all_keys = await list_keys_method(
|
|
218
|
+
app_name=app_name, user_id=user_id, session_id=session_id
|
|
219
|
+
)
|
|
220
|
+
response_files = []
|
|
221
|
+
processed_data_files = set()
|
|
222
|
+
for key in all_keys:
|
|
223
|
+
if key.endswith(METADATA_SUFFIX):
|
|
224
|
+
continue # Skip metadata files initially
|
|
225
|
+
|
|
226
|
+
if key in processed_data_files:
|
|
227
|
+
continue # Already processed this data file
|
|
228
|
+
|
|
229
|
+
filename = key
|
|
230
|
+
metadata_summary = None
|
|
231
|
+
versions = []
|
|
232
|
+
try:
|
|
233
|
+
versions = await artifact_service.list_versions(
|
|
234
|
+
app_name=app_name,
|
|
235
|
+
user_id=user_id,
|
|
236
|
+
session_id=session_id,
|
|
237
|
+
filename=filename,
|
|
238
|
+
)
|
|
239
|
+
if not versions:
|
|
240
|
+
log.warning(
|
|
241
|
+
"%s Found artifact key '%s' but no versions listed. Skipping.",
|
|
242
|
+
log_identifier,
|
|
243
|
+
filename,
|
|
244
|
+
)
|
|
245
|
+
continue
|
|
246
|
+
latest_version = max(versions)
|
|
247
|
+
metadata_filename = f"{filename}{METADATA_SUFFIX}"
|
|
248
|
+
if metadata_filename in all_keys:
|
|
249
|
+
try:
|
|
250
|
+
metadata_part = await artifact_service.load_artifact(
|
|
251
|
+
app_name=app_name,
|
|
252
|
+
user_id=user_id,
|
|
253
|
+
session_id=session_id,
|
|
254
|
+
filename=metadata_filename,
|
|
255
|
+
version=latest_version,
|
|
256
|
+
)
|
|
257
|
+
if metadata_part and metadata_part.inline_data:
|
|
258
|
+
try:
|
|
259
|
+
metadata_dict = json.loads(
|
|
260
|
+
metadata_part.inline_data.data.decode("utf-8")
|
|
261
|
+
)
|
|
262
|
+
schema = metadata_dict.get("schema", {})
|
|
263
|
+
metadata_summary = {
|
|
264
|
+
"description": metadata_dict.get("description"),
|
|
265
|
+
"source": metadata_dict.get("source"),
|
|
266
|
+
"type": metadata_dict.get("mime_type"),
|
|
267
|
+
"size": metadata_dict.get("size_bytes"),
|
|
268
|
+
"schema_type": schema.get(
|
|
269
|
+
"type", metadata_dict.get("mime_type")
|
|
270
|
+
),
|
|
271
|
+
"schema_inferred": schema.get("inferred"),
|
|
272
|
+
}
|
|
273
|
+
metadata_summary = {
|
|
274
|
+
k: v
|
|
275
|
+
for k, v in metadata_summary.items()
|
|
276
|
+
if v is not None
|
|
277
|
+
}
|
|
278
|
+
log.debug(
|
|
279
|
+
"%s Loaded metadata summary for '%s' v%d.",
|
|
280
|
+
log_identifier,
|
|
281
|
+
filename,
|
|
282
|
+
latest_version,
|
|
283
|
+
)
|
|
284
|
+
except json.JSONDecodeError as json_err:
|
|
285
|
+
log.warning(
|
|
286
|
+
"%s Failed to parse metadata JSON for '%s' v%d: %s",
|
|
287
|
+
log_identifier,
|
|
288
|
+
metadata_filename,
|
|
289
|
+
latest_version,
|
|
290
|
+
json_err,
|
|
291
|
+
)
|
|
292
|
+
metadata_summary = {"error": "Failed to parse metadata"}
|
|
293
|
+
except Exception as fmt_err:
|
|
294
|
+
log.warning(
|
|
295
|
+
"%s Failed to format metadata summary for '%s' v%d: %s",
|
|
296
|
+
log_identifier,
|
|
297
|
+
metadata_filename,
|
|
298
|
+
latest_version,
|
|
299
|
+
fmt_err,
|
|
300
|
+
)
|
|
301
|
+
metadata_summary = {
|
|
302
|
+
"error": "Failed to format metadata"
|
|
303
|
+
}
|
|
304
|
+
else:
|
|
305
|
+
log.warning(
|
|
306
|
+
"%s Metadata file '%s' v%d found but empty or unreadable.",
|
|
307
|
+
log_identifier,
|
|
308
|
+
metadata_filename,
|
|
309
|
+
latest_version,
|
|
310
|
+
)
|
|
311
|
+
metadata_summary = {
|
|
312
|
+
"error": "Metadata file empty or unreadable"
|
|
313
|
+
}
|
|
314
|
+
except Exception as load_err:
|
|
315
|
+
log.warning(
|
|
316
|
+
"%s Failed to load metadata file '%s' v%d: %s",
|
|
317
|
+
log_identifier,
|
|
318
|
+
metadata_filename,
|
|
319
|
+
latest_version,
|
|
320
|
+
load_err,
|
|
321
|
+
)
|
|
322
|
+
metadata_summary = {
|
|
323
|
+
"error": f"Failed to load metadata: {load_err}"
|
|
324
|
+
}
|
|
325
|
+
else:
|
|
326
|
+
log.debug(
|
|
327
|
+
"%s No companion metadata file found for '%s'.",
|
|
328
|
+
log_identifier,
|
|
329
|
+
filename,
|
|
330
|
+
)
|
|
331
|
+
metadata_summary = {"info": "No metadata file found"}
|
|
332
|
+
except Exception as version_err:
|
|
333
|
+
log.warning(
|
|
334
|
+
"%s Failed to list versions or process metadata for file '%s': %s. Skipping file.",
|
|
335
|
+
log_identifier,
|
|
336
|
+
filename,
|
|
337
|
+
version_err,
|
|
338
|
+
)
|
|
339
|
+
continue
|
|
340
|
+
response_files.append(
|
|
341
|
+
{
|
|
342
|
+
"filename": filename,
|
|
343
|
+
"versions": versions,
|
|
344
|
+
"metadata_summary": metadata_summary,
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
processed_data_files.add(filename)
|
|
348
|
+
log.info(
|
|
349
|
+
"%s Found %d data artifacts for session %s.",
|
|
350
|
+
log_identifier,
|
|
351
|
+
len(response_files),
|
|
352
|
+
session_id,
|
|
353
|
+
)
|
|
354
|
+
return {"status": "success", "artifacts": response_files}
|
|
355
|
+
except Exception as e:
|
|
356
|
+
log.exception("%s Error listing artifacts: %s", log_identifier, e)
|
|
357
|
+
return {"status": "error", "message": f"Failed to list artifacts: {e}"}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
async def load_artifact(
|
|
361
|
+
filename: str,
|
|
362
|
+
version: int,
|
|
363
|
+
load_metadata_only: bool = False,
|
|
364
|
+
max_content_length: Optional[int] = None,
|
|
365
|
+
include_line_numbers: bool = False,
|
|
366
|
+
tool_context: ToolContext = None,
|
|
367
|
+
) -> Dict[str, Any]:
|
|
368
|
+
"""
|
|
369
|
+
Loads the content or metadata of a specific artifact version.
|
|
370
|
+
Early-stage embeds in the filename argument are resolved.
|
|
371
|
+
|
|
372
|
+
If load_metadata_only is True, loads the full metadata dictionary.
|
|
373
|
+
Otherwise, loads text content (potentially truncated) or binary metadata summary.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
filename: The name of the artifact to load. May contain embeds.
|
|
377
|
+
version: The specific version number to load. Must be explicitly provided. Versions are 0-indexed.
|
|
378
|
+
load_metadata_only (bool): If True, load only the metadata JSON. Default False.
|
|
379
|
+
max_content_length (Optional[int]): Maximum character length for text content.
|
|
380
|
+
If None, uses app configuration. Range: 100-100,000.
|
|
381
|
+
include_line_numbers (bool): If True, prefix each line with its 1-based line number
|
|
382
|
+
followed by a TAB character for LLM viewing. Line numbers
|
|
383
|
+
are not stored in the artifact. Default False.
|
|
384
|
+
tool_context: The context provided by the ADK framework.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
A dictionary containing the artifact details and content/metadata or an error.
|
|
388
|
+
"""
|
|
389
|
+
if not tool_context:
|
|
390
|
+
return {
|
|
391
|
+
"status": "error",
|
|
392
|
+
"filename": filename,
|
|
393
|
+
"version": version,
|
|
394
|
+
"message": "ToolContext is missing.",
|
|
395
|
+
}
|
|
396
|
+
log_identifier = f"[BuiltinArtifactTool:load_artifact:{filename}:{version}]"
|
|
397
|
+
log.debug(
|
|
398
|
+
"%s Processing request (load_metadata_only=%s).",
|
|
399
|
+
log_identifier,
|
|
400
|
+
load_metadata_only,
|
|
401
|
+
)
|
|
402
|
+
if version is None:
|
|
403
|
+
version = "latest"
|
|
404
|
+
try:
|
|
405
|
+
artifact_service = tool_context._invocation_context.artifact_service
|
|
406
|
+
if not artifact_service:
|
|
407
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
408
|
+
app_name = tool_context._invocation_context.app_name
|
|
409
|
+
user_id = tool_context._invocation_context.user_id
|
|
410
|
+
session_id = get_original_session_id(tool_context._invocation_context)
|
|
411
|
+
agent = getattr(tool_context._invocation_context, "agent", None)
|
|
412
|
+
host_component = getattr(agent, "host_component", None) if agent else None
|
|
413
|
+
result = await load_artifact_content_or_metadata(
|
|
414
|
+
artifact_service=artifact_service,
|
|
415
|
+
app_name=app_name,
|
|
416
|
+
user_id=user_id,
|
|
417
|
+
session_id=session_id,
|
|
418
|
+
filename=filename,
|
|
419
|
+
version=version,
|
|
420
|
+
load_metadata_only=load_metadata_only,
|
|
421
|
+
max_content_length=max_content_length,
|
|
422
|
+
include_line_numbers=include_line_numbers,
|
|
423
|
+
component=host_component,
|
|
424
|
+
log_identifier_prefix="[BuiltinArtifactTool:load_artifact]",
|
|
425
|
+
)
|
|
426
|
+
return result
|
|
427
|
+
except FileNotFoundError as fnf_err:
|
|
428
|
+
log.warning(
|
|
429
|
+
"%s Artifact not found (reported by helper): %s", log_identifier, fnf_err
|
|
430
|
+
)
|
|
431
|
+
return {
|
|
432
|
+
"status": "error",
|
|
433
|
+
"filename": filename,
|
|
434
|
+
"version": version,
|
|
435
|
+
"message": str(fnf_err),
|
|
436
|
+
}
|
|
437
|
+
except ValueError as val_err:
|
|
438
|
+
log.warning(
|
|
439
|
+
"%s Value error during load (reported by helper): %s",
|
|
440
|
+
log_identifier,
|
|
441
|
+
val_err,
|
|
442
|
+
)
|
|
443
|
+
return {
|
|
444
|
+
"status": "error",
|
|
445
|
+
"filename": filename,
|
|
446
|
+
"version": version,
|
|
447
|
+
"message": str(val_err),
|
|
448
|
+
}
|
|
449
|
+
except Exception as e:
|
|
450
|
+
log.exception(
|
|
451
|
+
"%s Unexpected error in load_artifact tool: %s", log_identifier, e
|
|
452
|
+
)
|
|
453
|
+
return {
|
|
454
|
+
"status": "error",
|
|
455
|
+
"filename": filename,
|
|
456
|
+
"version": version,
|
|
457
|
+
"message": f"Unexpected error processing load request: {e}",
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
async def apply_embed_and_create_artifact(
|
|
462
|
+
output_filename: str,
|
|
463
|
+
embed_directive: str,
|
|
464
|
+
output_metadata: Optional[Dict[str, Any]] = None,
|
|
465
|
+
tool_context: ToolContext = None,
|
|
466
|
+
) -> Dict[str, Any]:
|
|
467
|
+
"""
|
|
468
|
+
Resolves an 'artifact_content' embed directive (including modifiers and formatting)
|
|
469
|
+
and saves the resulting content as a new artifact. The entire embed directive
|
|
470
|
+
must be provided as a string as the embed_directive argument.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
output_filename: The desired name for the new artifact.
|
|
474
|
+
embed_directive: The full '«artifact_content:...>>>...>>>format:...»' string.
|
|
475
|
+
output_metadata (dict, optional): Metadata for the new artifact.
|
|
476
|
+
tool_context: The context provided by the ADK framework.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
A dictionary indicating the result, including the new filename and version.
|
|
480
|
+
"""
|
|
481
|
+
if not tool_context:
|
|
482
|
+
return {"status": "error", "message": "ToolContext is missing."}
|
|
483
|
+
|
|
484
|
+
log_identifier = f"[BuiltinArtifactTool:apply_embed:{output_filename}]"
|
|
485
|
+
log.info(
|
|
486
|
+
"%s Processing request with directive: %s", log_identifier, embed_directive
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
match = EMBED_REGEX.fullmatch(embed_directive)
|
|
490
|
+
if not match:
|
|
491
|
+
return {
|
|
492
|
+
"status": "error",
|
|
493
|
+
"message": f"Invalid embed directive format: {embed_directive}",
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
embed_type = match.group(1)
|
|
497
|
+
expression = match.group(2)
|
|
498
|
+
format_spec = match.group(3)
|
|
499
|
+
|
|
500
|
+
if embed_type != "artifact_content":
|
|
501
|
+
return {
|
|
502
|
+
"status": "error",
|
|
503
|
+
"message": f"This tool only supports 'artifact_content' embeds, got '{embed_type}'.",
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
try:
|
|
507
|
+
inv_context = tool_context._invocation_context
|
|
508
|
+
artifact_service = inv_context.artifact_service
|
|
509
|
+
if not artifact_service:
|
|
510
|
+
raise ValueError("ArtifactService not available.")
|
|
511
|
+
|
|
512
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
513
|
+
if not host_component:
|
|
514
|
+
log.warning(
|
|
515
|
+
"%s Could not access host component config for limits. Proceeding without them.",
|
|
516
|
+
log_identifier,
|
|
517
|
+
)
|
|
518
|
+
embed_config = {}
|
|
519
|
+
else:
|
|
520
|
+
embed_config = {
|
|
521
|
+
"gateway_artifact_content_limit_bytes": host_component.get_config(
|
|
522
|
+
"gateway_artifact_content_limit_bytes", -1
|
|
523
|
+
),
|
|
524
|
+
"gateway_recursive_embed_depth": host_component.get_config(
|
|
525
|
+
"gateway_recursive_embed_depth", 3
|
|
526
|
+
),
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
gateway_context = {
|
|
530
|
+
"artifact_service": artifact_service,
|
|
531
|
+
"session_context": {
|
|
532
|
+
"app_name": inv_context.app_name,
|
|
533
|
+
"user_id": inv_context.user_id,
|
|
534
|
+
"session_id": get_original_session_id(inv_context),
|
|
535
|
+
},
|
|
536
|
+
}
|
|
537
|
+
except Exception as ctx_err:
|
|
538
|
+
log.error(
|
|
539
|
+
"%s Failed to prepare context/config for embed evaluation: %s",
|
|
540
|
+
log_identifier,
|
|
541
|
+
ctx_err,
|
|
542
|
+
)
|
|
543
|
+
return {
|
|
544
|
+
"status": "error",
|
|
545
|
+
"message": f"Internal error preparing context: {ctx_err}",
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
resolved_content_str, error_msg_from_eval, _ = await evaluate_embed(
|
|
549
|
+
embed_type=embed_type,
|
|
550
|
+
expression=expression,
|
|
551
|
+
format_spec=format_spec,
|
|
552
|
+
context=gateway_context,
|
|
553
|
+
log_identifier=log_identifier,
|
|
554
|
+
resolution_mode=ResolutionMode.TOOL_PARAMETER,
|
|
555
|
+
config=embed_config,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
if error_msg_from_eval or (
|
|
559
|
+
resolved_content_str and resolved_content_str.startswith("[Error:")
|
|
560
|
+
):
|
|
561
|
+
error_to_report = error_msg_from_eval or resolved_content_str
|
|
562
|
+
log.error("%s Embed resolution failed: %s", log_identifier, error_to_report)
|
|
563
|
+
return {
|
|
564
|
+
"status": "error",
|
|
565
|
+
"message": f"Embed resolution failed: {error_to_report}",
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
output_mime_type = "text/plain"
|
|
569
|
+
final_format = None
|
|
570
|
+
chain_parts = expression.split(EMBED_CHAIN_DELIMITER)
|
|
571
|
+
if len(chain_parts) > 1:
|
|
572
|
+
last_part = chain_parts[-1].strip()
|
|
573
|
+
format_match = re.match(r"format:(.*)", last_part, re.DOTALL)
|
|
574
|
+
if format_match:
|
|
575
|
+
final_format = format_match.group(1).strip().lower()
|
|
576
|
+
elif format_spec:
|
|
577
|
+
final_format = format_spec.strip().lower()
|
|
578
|
+
|
|
579
|
+
if final_format:
|
|
580
|
+
if final_format == "html":
|
|
581
|
+
output_mime_type = "text/html"
|
|
582
|
+
elif final_format == "json" or final_format == "json_pretty":
|
|
583
|
+
output_mime_type = "application/json"
|
|
584
|
+
elif final_format == "csv":
|
|
585
|
+
output_mime_type = "text/csv"
|
|
586
|
+
elif final_format == "datauri":
|
|
587
|
+
output_mime_type = "text/plain"
|
|
588
|
+
log.warning(
|
|
589
|
+
"%s Embed resolved to data URI; saving new artifact as text/plain.",
|
|
590
|
+
log_identifier,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
log.debug("%s Determined output MIME type as: %s", log_identifier, output_mime_type)
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
resolved_bytes = resolved_content_str.encode("utf-8")
|
|
597
|
+
inv_context = tool_context._invocation_context
|
|
598
|
+
artifact_service = inv_context.artifact_service
|
|
599
|
+
if not artifact_service:
|
|
600
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
601
|
+
|
|
602
|
+
save_result = await save_artifact_with_metadata(
|
|
603
|
+
artifact_service=artifact_service,
|
|
604
|
+
app_name=inv_context.app_name,
|
|
605
|
+
user_id=inv_context.user_id,
|
|
606
|
+
session_id=get_original_session_id(inv_context),
|
|
607
|
+
filename=output_filename,
|
|
608
|
+
content_bytes=resolved_bytes,
|
|
609
|
+
mime_type=output_mime_type,
|
|
610
|
+
metadata_dict=(
|
|
611
|
+
lambda base_meta, user_meta: (
|
|
612
|
+
base_meta.update(user_meta or {}),
|
|
613
|
+
base_meta,
|
|
614
|
+
)[1]
|
|
615
|
+
)({"source_directive": embed_directive}, output_metadata),
|
|
616
|
+
timestamp=inv_context.session.last_update_time
|
|
617
|
+
or datetime.now(timezone.utc),
|
|
618
|
+
schema_max_keys=(
|
|
619
|
+
host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
|
|
620
|
+
if host_component
|
|
621
|
+
else DEFAULT_SCHEMA_MAX_KEYS
|
|
622
|
+
),
|
|
623
|
+
tool_context=tool_context,
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
log.info(
|
|
627
|
+
"%s Successfully applied embed and saved new artifact '%s' (v%s).",
|
|
628
|
+
log_identifier,
|
|
629
|
+
output_filename,
|
|
630
|
+
save_result.get("data_version"),
|
|
631
|
+
)
|
|
632
|
+
return {
|
|
633
|
+
"status": "success",
|
|
634
|
+
"output_filename": output_filename,
|
|
635
|
+
"output_version": save_result.get("data_version"),
|
|
636
|
+
"output_mime_type": output_mime_type,
|
|
637
|
+
"message": f"Successfully created artifact '{output_filename}' v{save_result.get('data_version')} from embed directive.",
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
except Exception as save_err:
|
|
641
|
+
log.exception(
|
|
642
|
+
"%s Failed to save resolved content as artifact '%s': %s",
|
|
643
|
+
log_identifier,
|
|
644
|
+
output_filename,
|
|
645
|
+
save_err,
|
|
646
|
+
)
|
|
647
|
+
return {
|
|
648
|
+
"status": "error",
|
|
649
|
+
"message": f"Failed to save new artifact: {save_err}",
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
async def extract_content_from_artifact(
|
|
654
|
+
filename: str,
|
|
655
|
+
extraction_goal: str,
|
|
656
|
+
version: Optional[str] = "latest",
|
|
657
|
+
output_filename_base: Optional[str] = None,
|
|
658
|
+
tool_context: ToolContext = None,
|
|
659
|
+
) -> Dict[str, Any]:
|
|
660
|
+
"""
|
|
661
|
+
Loads an existing artifact, uses an internal LLM to process its content
|
|
662
|
+
based on an "extraction_goal," and manages the output by returning it
|
|
663
|
+
or saving it as a new artifact.
|
|
664
|
+
|
|
665
|
+
The tool's description for the LLM might dynamically update based on
|
|
666
|
+
the 'supported_binary_mime_types' configuration of the agent, indicating
|
|
667
|
+
which binary types it can attempt to process.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
filename (str): Name of the source artifact. May contain embeds.
|
|
671
|
+
extraction_goal (str): Natural language instruction for the LLM on what
|
|
672
|
+
to extract or how to transform the content.
|
|
673
|
+
May contain embeds.
|
|
674
|
+
version (Optional[Union[int, str]]): Version of the source artifact.
|
|
675
|
+
Can be an integer or "latest".
|
|
676
|
+
Defaults to "latest". May contain embeds.
|
|
677
|
+
output_filename_base (Optional[str]): Optional base name for the new
|
|
678
|
+
artifact if the extracted content
|
|
679
|
+
is saved. May contain embeds.
|
|
680
|
+
tool_context (ToolContext): Provided by the ADK framework.
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
Dict[str, Any]: A dictionary containing the status of the operation,
|
|
684
|
+
a message for the LLM, and potentially the extracted
|
|
685
|
+
data or details of a newly saved artifact.
|
|
686
|
+
Refer to the design document for specific response structures.
|
|
687
|
+
"""
|
|
688
|
+
log_identifier = f"[BuiltinArtifactTool:extract_content:{filename}:{version}]"
|
|
689
|
+
log.debug(
|
|
690
|
+
"%s Processing request. Goal: '%s', Output base: '%s'",
|
|
691
|
+
log_identifier,
|
|
692
|
+
extraction_goal,
|
|
693
|
+
output_filename_base,
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
if not tool_context:
|
|
697
|
+
return {
|
|
698
|
+
"status": "error_tool_context_missing",
|
|
699
|
+
"message_to_llm": "Tool execution failed: ToolContext is missing.",
|
|
700
|
+
"filename": filename,
|
|
701
|
+
"version_requested": str(version),
|
|
702
|
+
}
|
|
703
|
+
if not filename:
|
|
704
|
+
return {
|
|
705
|
+
"status": "error_missing_filename",
|
|
706
|
+
"message_to_llm": "Tool execution failed: 'filename' parameter is required.",
|
|
707
|
+
"version_requested": str(version),
|
|
708
|
+
}
|
|
709
|
+
if not extraction_goal:
|
|
710
|
+
return {
|
|
711
|
+
"status": "error_missing_extraction_goal",
|
|
712
|
+
"message_to_llm": "Tool execution failed: 'extraction_goal' parameter is required.",
|
|
713
|
+
"filename": filename,
|
|
714
|
+
"version_requested": str(version),
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
inv_context = tool_context._invocation_context
|
|
718
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
719
|
+
if not host_component:
|
|
720
|
+
log.error(
|
|
721
|
+
"%s Host component not found on agent. Cannot retrieve config.",
|
|
722
|
+
log_identifier,
|
|
723
|
+
)
|
|
724
|
+
return {
|
|
725
|
+
"status": "error_internal_configuration",
|
|
726
|
+
"message_to_llm": "Tool configuration error: Host component not accessible.",
|
|
727
|
+
"filename": filename,
|
|
728
|
+
"version_requested": str(version),
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
try:
|
|
732
|
+
save_threshold = host_component.get_config(
|
|
733
|
+
"tool_output_save_threshold_bytes", 2048
|
|
734
|
+
)
|
|
735
|
+
llm_max_bytes = host_component.get_config(
|
|
736
|
+
"tool_output_llm_return_max_bytes", 4096
|
|
737
|
+
)
|
|
738
|
+
extraction_config = host_component.get_config(
|
|
739
|
+
"extract_content_from_artifact_config", {}
|
|
740
|
+
)
|
|
741
|
+
supported_binary_mime_types = extraction_config.get(
|
|
742
|
+
"supported_binary_mime_types", []
|
|
743
|
+
)
|
|
744
|
+
model_config_for_extraction = extraction_config.get("model")
|
|
745
|
+
except Exception as e:
|
|
746
|
+
log.exception("%s Error retrieving tool configuration: %s", log_identifier, e)
|
|
747
|
+
return {
|
|
748
|
+
"status": "error_internal_configuration",
|
|
749
|
+
"message_to_llm": f"Tool configuration error: {e}",
|
|
750
|
+
"filename": filename,
|
|
751
|
+
"version_requested": str(version),
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
source_artifact_data = None
|
|
755
|
+
processed_version: Union[int, str]
|
|
756
|
+
|
|
757
|
+
if version is None or (
|
|
758
|
+
isinstance(version, str) and version.strip().lower() == "latest"
|
|
759
|
+
):
|
|
760
|
+
processed_version = "latest"
|
|
761
|
+
else:
|
|
762
|
+
try:
|
|
763
|
+
processed_version = int(version)
|
|
764
|
+
except ValueError:
|
|
765
|
+
log.warning(
|
|
766
|
+
"%s Invalid version string: '%s'. Must be an integer or 'latest'.",
|
|
767
|
+
log_identifier,
|
|
768
|
+
version,
|
|
769
|
+
)
|
|
770
|
+
return {
|
|
771
|
+
"status": "error_invalid_version_format",
|
|
772
|
+
"message_to_llm": f"Invalid version format '{version}'. Version must be an integer or 'latest'.",
|
|
773
|
+
"filename": filename,
|
|
774
|
+
"version_requested": str(version),
|
|
775
|
+
}
|
|
776
|
+
try:
|
|
777
|
+
log.debug(
|
|
778
|
+
"%s Loading source artifact '%s' version '%s' (processed as: %s)",
|
|
779
|
+
log_identifier,
|
|
780
|
+
filename,
|
|
781
|
+
version,
|
|
782
|
+
processed_version,
|
|
783
|
+
)
|
|
784
|
+
source_artifact_data = await load_artifact_content_or_metadata(
|
|
785
|
+
artifact_service=inv_context.artifact_service,
|
|
786
|
+
app_name=inv_context.app_name,
|
|
787
|
+
user_id=inv_context.user_id,
|
|
788
|
+
session_id=get_original_session_id(inv_context),
|
|
789
|
+
filename=filename,
|
|
790
|
+
version=processed_version,
|
|
791
|
+
return_raw_bytes=True,
|
|
792
|
+
log_identifier_prefix=log_identifier,
|
|
793
|
+
)
|
|
794
|
+
if source_artifact_data.get("status") != "success":
|
|
795
|
+
raise FileNotFoundError(
|
|
796
|
+
source_artifact_data.get("message", "Failed to load artifact")
|
|
797
|
+
)
|
|
798
|
+
log.info(
|
|
799
|
+
"%s Successfully loaded source artifact '%s' version %s (actual: v%s)",
|
|
800
|
+
log_identifier,
|
|
801
|
+
filename,
|
|
802
|
+
version,
|
|
803
|
+
source_artifact_data.get("version"),
|
|
804
|
+
)
|
|
805
|
+
except FileNotFoundError as e:
|
|
806
|
+
log.warning("%s Source artifact not found: %s", log_identifier, e)
|
|
807
|
+
return {
|
|
808
|
+
"status": "error_artifact_not_found",
|
|
809
|
+
"message_to_llm": f"Could not extract content. Source artifact '{filename}' (version {version}) was not found: {e}",
|
|
810
|
+
"filename": filename,
|
|
811
|
+
"version_requested": str(version),
|
|
812
|
+
}
|
|
813
|
+
except Exception as e:
|
|
814
|
+
log.exception("%s Error loading source artifact: %s", log_identifier, e)
|
|
815
|
+
return {
|
|
816
|
+
"status": "error_loading_artifact",
|
|
817
|
+
"message_to_llm": f"Error loading source artifact '{filename}': {e}",
|
|
818
|
+
"filename": filename,
|
|
819
|
+
"version_requested": str(version),
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
source_artifact_content_bytes = source_artifact_data.get("raw_bytes")
|
|
823
|
+
source_mime_type = source_artifact_data.get("mime_type", "application/octet-stream")
|
|
824
|
+
actual_source_version = source_artifact_data.get("version", "unknown")
|
|
825
|
+
|
|
826
|
+
chosen_llm = None
|
|
827
|
+
try:
|
|
828
|
+
if model_config_for_extraction:
|
|
829
|
+
if isinstance(model_config_for_extraction, str):
|
|
830
|
+
chosen_llm = LLMRegistry.new_llm(model_config_for_extraction)
|
|
831
|
+
log.info(
|
|
832
|
+
"%s Using tool-specific LLM (string): %s",
|
|
833
|
+
log_identifier,
|
|
834
|
+
model_config_for_extraction,
|
|
835
|
+
)
|
|
836
|
+
elif isinstance(model_config_for_extraction, dict):
|
|
837
|
+
chosen_llm = LiteLlm(**model_config_for_extraction)
|
|
838
|
+
log.info(
|
|
839
|
+
"%s Using tool-specific LLM (dict): %s",
|
|
840
|
+
log_identifier,
|
|
841
|
+
model_config_for_extraction.get("model"),
|
|
842
|
+
)
|
|
843
|
+
else:
|
|
844
|
+
log.warning(
|
|
845
|
+
"%s Invalid 'model' config for extraction tool. Falling back to agent default.",
|
|
846
|
+
log_identifier,
|
|
847
|
+
)
|
|
848
|
+
chosen_llm = inv_context.agent.canonical_model
|
|
849
|
+
else:
|
|
850
|
+
chosen_llm = inv_context.agent.canonical_model
|
|
851
|
+
log.info(
|
|
852
|
+
"%s Using agent's default LLM: %s", log_identifier, chosen_llm.model
|
|
853
|
+
)
|
|
854
|
+
except Exception as e:
|
|
855
|
+
log.exception("%s Error initializing LLM for extraction: %s", log_identifier, e)
|
|
856
|
+
return {
|
|
857
|
+
"status": "error_internal_llm_setup",
|
|
858
|
+
"message_to_llm": f"Failed to set up LLM for extraction: {e}",
|
|
859
|
+
"filename": filename,
|
|
860
|
+
"version_requested": str(version),
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
llm_parts = []
|
|
864
|
+
is_binary_supported = False
|
|
865
|
+
|
|
866
|
+
normalized_source_mime_type = source_mime_type.lower() if source_mime_type else ""
|
|
867
|
+
|
|
868
|
+
is_text_based = is_text_based_file(
|
|
869
|
+
mime_type=normalized_source_mime_type,
|
|
870
|
+
content_bytes=source_artifact_content_bytes,
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
if is_text_based:
|
|
874
|
+
try:
|
|
875
|
+
artifact_text_content = source_artifact_content_bytes.decode("utf-8")
|
|
876
|
+
llm_parts.append(
|
|
877
|
+
adk_types.Part(
|
|
878
|
+
text=f"Artifact Content (MIME type: {source_mime_type}):\n```\n{artifact_text_content}\n```"
|
|
879
|
+
)
|
|
880
|
+
)
|
|
881
|
+
log.debug("%s Prepared text content for LLM.", log_identifier)
|
|
882
|
+
except UnicodeDecodeError as e:
|
|
883
|
+
log.warning(
|
|
884
|
+
"%s Failed to decode text artifact as UTF-8: %s. Treating as opaque binary.",
|
|
885
|
+
log_identifier,
|
|
886
|
+
e,
|
|
887
|
+
)
|
|
888
|
+
llm_parts.append(
|
|
889
|
+
adk_types.Part(
|
|
890
|
+
text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}' and could not be decoded as text."
|
|
891
|
+
)
|
|
892
|
+
)
|
|
893
|
+
else: # Binary
|
|
894
|
+
for supported_pattern in supported_binary_mime_types:
|
|
895
|
+
if fnmatch.fnmatch(source_mime_type, supported_pattern):
|
|
896
|
+
is_binary_supported = True
|
|
897
|
+
break
|
|
898
|
+
if is_binary_supported:
|
|
899
|
+
llm_parts.append(
|
|
900
|
+
adk_types.Part(
|
|
901
|
+
inline_data=adk_types.Blob(
|
|
902
|
+
mime_type=source_mime_type, data=source_artifact_content_bytes
|
|
903
|
+
)
|
|
904
|
+
)
|
|
905
|
+
)
|
|
906
|
+
llm_parts.append(
|
|
907
|
+
adk_types.Part(
|
|
908
|
+
text=f"The above is the content of artifact '{filename}' (MIME type: {source_mime_type})."
|
|
909
|
+
)
|
|
910
|
+
)
|
|
911
|
+
log.debug(
|
|
912
|
+
"%s Prepared supported binary content (MIME: %s) for LLM.",
|
|
913
|
+
log_identifier,
|
|
914
|
+
source_mime_type,
|
|
915
|
+
)
|
|
916
|
+
else:
|
|
917
|
+
llm_parts.append(
|
|
918
|
+
adk_types.Part(
|
|
919
|
+
text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}'. Direct content processing is not supported by this tool's current configuration. Perform the extraction goal based on its filename and type if possible, or state that the content cannot be analyzed."
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
log.debug(
|
|
923
|
+
"%s Prepared message for unsupported binary content (MIME: %s) for LLM.",
|
|
924
|
+
log_identifier,
|
|
925
|
+
source_mime_type,
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
internal_llm_contents = [
|
|
929
|
+
adk_types.Content(
|
|
930
|
+
role="user", parts=[adk_types.Part(text=extraction_goal)] + llm_parts
|
|
931
|
+
)
|
|
932
|
+
]
|
|
933
|
+
internal_llm_request = LlmRequest(
|
|
934
|
+
model=chosen_llm.model,
|
|
935
|
+
contents=internal_llm_contents,
|
|
936
|
+
config=adk_types.GenerateContentConfig(
|
|
937
|
+
temperature=0.1,
|
|
938
|
+
),
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
extracted_content_str = ""
|
|
942
|
+
try:
|
|
943
|
+
log.info(
|
|
944
|
+
"%s Executing internal LLM call for extraction. Goal: %s",
|
|
945
|
+
log_identifier,
|
|
946
|
+
extraction_goal,
|
|
947
|
+
)
|
|
948
|
+
if hasattr(chosen_llm, "generate_content") and not hasattr(
|
|
949
|
+
chosen_llm, "generate_content_async"
|
|
950
|
+
):
|
|
951
|
+
llm_response = chosen_llm.generate_content(request=internal_llm_request)
|
|
952
|
+
if llm_response.parts:
|
|
953
|
+
extracted_content_str = llm_response.parts[0].text or ""
|
|
954
|
+
else:
|
|
955
|
+
extracted_content_str = ""
|
|
956
|
+
elif hasattr(chosen_llm, "generate_content_async"):
|
|
957
|
+
log.debug(
|
|
958
|
+
"%s Calling LLM's generate_content_async (non-streaming) for extraction.",
|
|
959
|
+
log_identifier,
|
|
960
|
+
)
|
|
961
|
+
try:
|
|
962
|
+
llm_response_obj = None
|
|
963
|
+
async for response_event in chosen_llm.generate_content_async(
|
|
964
|
+
internal_llm_request
|
|
965
|
+
):
|
|
966
|
+
llm_response_obj = response_event
|
|
967
|
+
break
|
|
968
|
+
if (
|
|
969
|
+
llm_response_obj
|
|
970
|
+
and hasattr(llm_response_obj, "text")
|
|
971
|
+
and llm_response_obj.text
|
|
972
|
+
):
|
|
973
|
+
extracted_content_str = llm_response_obj.text
|
|
974
|
+
elif (
|
|
975
|
+
llm_response_obj
|
|
976
|
+
and hasattr(llm_response_obj, "parts")
|
|
977
|
+
and llm_response_obj.parts
|
|
978
|
+
):
|
|
979
|
+
extracted_content_str = "".join(
|
|
980
|
+
[
|
|
981
|
+
part.text
|
|
982
|
+
for part in llm_response_obj.parts
|
|
983
|
+
if hasattr(part, "text") and part.text
|
|
984
|
+
]
|
|
985
|
+
)
|
|
986
|
+
elif (
|
|
987
|
+
llm_response_obj
|
|
988
|
+
and hasattr(llm_response_obj, "content")
|
|
989
|
+
and hasattr(llm_response_obj.content, "parts")
|
|
990
|
+
and llm_response_obj.content.parts
|
|
991
|
+
):
|
|
992
|
+
extracted_content_str = "".join(
|
|
993
|
+
[
|
|
994
|
+
part.text
|
|
995
|
+
for part in llm_response_obj.content.parts
|
|
996
|
+
if hasattr(part, "text") and part.text
|
|
997
|
+
]
|
|
998
|
+
)
|
|
999
|
+
else:
|
|
1000
|
+
extracted_content_str = ""
|
|
1001
|
+
log.warning(
|
|
1002
|
+
"%s LLM response object or its text/parts were not found or empty after non-streaming call.",
|
|
1003
|
+
log_identifier,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
except Exception as llm_async_err:
|
|
1007
|
+
log.exception(
|
|
1008
|
+
"%s Asynchronous LLM call for extraction failed: %s",
|
|
1009
|
+
log_identifier,
|
|
1010
|
+
llm_async_err,
|
|
1011
|
+
)
|
|
1012
|
+
extracted_content_str = (
|
|
1013
|
+
f"[ERROR: Asynchronous LLM call failed: {llm_async_err}]"
|
|
1014
|
+
)
|
|
1015
|
+
else:
|
|
1016
|
+
log.error(
|
|
1017
|
+
"%s LLM does not have a known generate_content or generate_content_async method. Extraction will be empty.",
|
|
1018
|
+
log_identifier,
|
|
1019
|
+
)
|
|
1020
|
+
extracted_content_str = "[ERROR: LLM method not found]"
|
|
1021
|
+
|
|
1022
|
+
log.info(
|
|
1023
|
+
"%s Internal LLM call completed. Extracted content length: %d chars",
|
|
1024
|
+
log_identifier,
|
|
1025
|
+
len(extracted_content_str),
|
|
1026
|
+
)
|
|
1027
|
+
if not extracted_content_str.strip():
|
|
1028
|
+
log.warning(
|
|
1029
|
+
"%s Internal LLM produced empty or whitespace-only content for extraction goal.",
|
|
1030
|
+
log_identifier,
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
except Exception as e:
|
|
1034
|
+
log.exception(
|
|
1035
|
+
"%s Internal LLM call for extraction failed: %s", log_identifier, e
|
|
1036
|
+
)
|
|
1037
|
+
return {
|
|
1038
|
+
"status": "error_extraction_failed",
|
|
1039
|
+
"message_to_llm": f"The LLM failed to process the artifact content for your goal '{extraction_goal}'. Error: {e}",
|
|
1040
|
+
"filename": filename,
|
|
1041
|
+
"version_requested": str(version),
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
extracted_content_bytes = extracted_content_str.encode("utf-8")
|
|
1045
|
+
extracted_content_size_bytes = len(extracted_content_bytes)
|
|
1046
|
+
output_mime_type = "text/plain"
|
|
1047
|
+
try:
|
|
1048
|
+
json.loads(extracted_content_str)
|
|
1049
|
+
output_mime_type = "application/json"
|
|
1050
|
+
log.debug(
|
|
1051
|
+
"%s Extracted content appears to be valid JSON. Setting output MIME to application/json.",
|
|
1052
|
+
log_identifier,
|
|
1053
|
+
)
|
|
1054
|
+
except json.JSONDecodeError:
|
|
1055
|
+
log.debug(
|
|
1056
|
+
"%s Extracted content is not JSON. Using output MIME text/plain.",
|
|
1057
|
+
log_identifier,
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
response_for_llm_str = extracted_content_str
|
|
1061
|
+
saved_extracted_artifact_details = None
|
|
1062
|
+
final_status = "success"
|
|
1063
|
+
message_to_llm_parts = [
|
|
1064
|
+
f"Successfully extracted content from '{filename}' (v{actual_source_version}) based on your goal: '{extraction_goal}'."
|
|
1065
|
+
]
|
|
1066
|
+
was_saved = False
|
|
1067
|
+
was_truncated = False
|
|
1068
|
+
|
|
1069
|
+
if extracted_content_size_bytes > save_threshold:
|
|
1070
|
+
log.info(
|
|
1071
|
+
"%s Extracted content size (%d bytes) exceeds save threshold (%d bytes). Saving as new artifact.",
|
|
1072
|
+
log_identifier,
|
|
1073
|
+
extracted_content_size_bytes,
|
|
1074
|
+
save_threshold,
|
|
1075
|
+
)
|
|
1076
|
+
saved_extracted_artifact_details = await _save_extracted_artifact(
|
|
1077
|
+
tool_context,
|
|
1078
|
+
host_component,
|
|
1079
|
+
extracted_content_bytes,
|
|
1080
|
+
filename,
|
|
1081
|
+
actual_source_version,
|
|
1082
|
+
extraction_goal,
|
|
1083
|
+
output_filename_base,
|
|
1084
|
+
output_mime_type,
|
|
1085
|
+
)
|
|
1086
|
+
if saved_extracted_artifact_details.get("status") == "success":
|
|
1087
|
+
was_saved = True
|
|
1088
|
+
message_to_llm_parts.append(
|
|
1089
|
+
f"The full extracted content was saved as artifact '{saved_extracted_artifact_details.get('data_filename')}' "
|
|
1090
|
+
f"(version {saved_extracted_artifact_details.get('data_version')}). "
|
|
1091
|
+
f"You can retrieve it using 'load_artifact' or perform further extractions on it using 'extract_content_from_artifact' "
|
|
1092
|
+
f"with this new filename and version."
|
|
1093
|
+
)
|
|
1094
|
+
else:
|
|
1095
|
+
message_to_llm_parts.append(
|
|
1096
|
+
f"Attempted to save the large extracted content, but failed: {saved_extracted_artifact_details.get('message')}"
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
if len(extracted_content_str.encode("utf-8")) > llm_max_bytes:
|
|
1100
|
+
was_truncated = True
|
|
1101
|
+
log.info(
|
|
1102
|
+
"%s Original extracted content (%d bytes) exceeds LLM return max bytes (%d bytes). Truncating for LLM response.",
|
|
1103
|
+
log_identifier,
|
|
1104
|
+
len(extracted_content_str.encode("utf-8")),
|
|
1105
|
+
llm_max_bytes,
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
if not was_saved:
|
|
1109
|
+
log.info(
|
|
1110
|
+
"%s Saving extracted content now because it needs truncation for LLM response and wasn't saved previously.",
|
|
1111
|
+
log_identifier,
|
|
1112
|
+
)
|
|
1113
|
+
saved_extracted_artifact_details = await _save_extracted_artifact(
|
|
1114
|
+
tool_context,
|
|
1115
|
+
host_component,
|
|
1116
|
+
extracted_content_bytes,
|
|
1117
|
+
filename,
|
|
1118
|
+
actual_source_version,
|
|
1119
|
+
extraction_goal,
|
|
1120
|
+
output_filename_base,
|
|
1121
|
+
output_mime_type,
|
|
1122
|
+
)
|
|
1123
|
+
if saved_extracted_artifact_details.get("status") == "success":
|
|
1124
|
+
was_saved = True
|
|
1125
|
+
message_to_llm_parts.append(
|
|
1126
|
+
f"The full extracted content (which is being truncated for this response) was saved as artifact "
|
|
1127
|
+
f"'{saved_extracted_artifact_details.get('data_filename')}' (version {saved_extracted_artifact_details.get('data_version')}). "
|
|
1128
|
+
f"You can retrieve the full content using 'load_artifact' or perform further extractions on it."
|
|
1129
|
+
)
|
|
1130
|
+
else:
|
|
1131
|
+
message_to_llm_parts.append(
|
|
1132
|
+
f"Attempted to save the extracted content before truncation, but failed: {saved_extracted_artifact_details.get('message')}"
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
truncation_suffix = "... [Content truncated]"
|
|
1136
|
+
adjusted_max_bytes = llm_max_bytes - len(truncation_suffix.encode("utf-8"))
|
|
1137
|
+
if adjusted_max_bytes < 0:
|
|
1138
|
+
adjusted_max_bytes = 0
|
|
1139
|
+
|
|
1140
|
+
temp_response_bytes = extracted_content_str.encode("utf-8")
|
|
1141
|
+
truncated_bytes = temp_response_bytes[:adjusted_max_bytes]
|
|
1142
|
+
response_for_llm_str = (
|
|
1143
|
+
truncated_bytes.decode("utf-8", "ignore") + truncation_suffix
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
message_to_llm_parts.append(
|
|
1147
|
+
"The extracted content provided in 'extracted_data_preview' has been truncated due to size limits. "
|
|
1148
|
+
"If saved, the full version is available in the specified artifact."
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
if was_saved and was_truncated:
|
|
1152
|
+
final_status = "success_full_content_saved_preview_returned"
|
|
1153
|
+
elif was_saved:
|
|
1154
|
+
final_status = "success_full_content_saved_and_returned"
|
|
1155
|
+
elif was_truncated:
|
|
1156
|
+
final_status = "success_content_returned_truncated_and_saved"
|
|
1157
|
+
else:
|
|
1158
|
+
final_status = "success_content_returned"
|
|
1159
|
+
|
|
1160
|
+
final_response_dict = {
|
|
1161
|
+
"status": final_status,
|
|
1162
|
+
"message_to_llm": " ".join(list(dict.fromkeys(message_to_llm_parts))),
|
|
1163
|
+
"source_filename": filename,
|
|
1164
|
+
"source_version_processed": actual_source_version,
|
|
1165
|
+
"extraction_goal_used": extraction_goal,
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
if was_truncated:
|
|
1169
|
+
final_response_dict["extracted_data_preview"] = response_for_llm_str
|
|
1170
|
+
else:
|
|
1171
|
+
final_response_dict["extracted_data"] = response_for_llm_str
|
|
1172
|
+
|
|
1173
|
+
if (
|
|
1174
|
+
saved_extracted_artifact_details
|
|
1175
|
+
and saved_extracted_artifact_details.get("status") == "success"
|
|
1176
|
+
):
|
|
1177
|
+
final_response_dict["saved_extracted_artifact_details"] = (
|
|
1178
|
+
saved_extracted_artifact_details
|
|
1179
|
+
)
|
|
1180
|
+
elif saved_extracted_artifact_details:
|
|
1181
|
+
final_response_dict["saved_extracted_artifact_attempt_details"] = (
|
|
1182
|
+
saved_extracted_artifact_details
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
log.info(
|
|
1186
|
+
"%s Tool execution finished. Final status: %s. Response preview: %s",
|
|
1187
|
+
log_identifier,
|
|
1188
|
+
final_status,
|
|
1189
|
+
final_response_dict,
|
|
1190
|
+
)
|
|
1191
|
+
return final_response_dict
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
async def append_to_artifact(
|
|
1195
|
+
filename: str,
|
|
1196
|
+
content_chunk: str,
|
|
1197
|
+
mime_type: str,
|
|
1198
|
+
tool_context: ToolContext = None,
|
|
1199
|
+
) -> Dict[str, Any]:
|
|
1200
|
+
"""
|
|
1201
|
+
Appends a chunk of content to an existing artifact. This operation will
|
|
1202
|
+
create a new version of the artifact. The content_chunk should be a string,
|
|
1203
|
+
potentially base64 encoded if it represents binary data (indicated by mime_type).
|
|
1204
|
+
The chunk size should be limited (e.g., max 3KB) by the LLM.
|
|
1205
|
+
|
|
1206
|
+
Args:
|
|
1207
|
+
filename: The name of the artifact to append to. May contain embeds.
|
|
1208
|
+
content_chunk: The chunk of content to append (max approx. 3KB).
|
|
1209
|
+
If mime_type suggests binary, this should be base64 encoded.
|
|
1210
|
+
May contain embeds.
|
|
1211
|
+
mime_type: The MIME type of the content_chunk. This helps determine if
|
|
1212
|
+
base64 decoding is needed for the chunk. The overall artifact's
|
|
1213
|
+
MIME type will be preserved from its latest version.
|
|
1214
|
+
May contain embeds.
|
|
1215
|
+
tool_context: The context provided by the ADK framework.
|
|
1216
|
+
|
|
1217
|
+
Returns:
|
|
1218
|
+
A dictionary indicating the result, including the new version of the artifact.
|
|
1219
|
+
"""
|
|
1220
|
+
if not tool_context:
|
|
1221
|
+
return {
|
|
1222
|
+
"status": "error",
|
|
1223
|
+
"filename": filename,
|
|
1224
|
+
"message": "ToolContext is missing, cannot append to artifact.",
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
log_identifier = f"[BuiltinArtifactTool:append_to_artifact:{filename}]"
|
|
1228
|
+
log.debug("%s Processing request to append chunk.", log_identifier)
|
|
1229
|
+
|
|
1230
|
+
try:
|
|
1231
|
+
inv_context = tool_context._invocation_context
|
|
1232
|
+
artifact_service = inv_context.artifact_service
|
|
1233
|
+
if not artifact_service:
|
|
1234
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1235
|
+
|
|
1236
|
+
app_name = inv_context.app_name
|
|
1237
|
+
user_id = inv_context.user_id
|
|
1238
|
+
session_id = get_original_session_id(inv_context)
|
|
1239
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
1240
|
+
|
|
1241
|
+
log.debug(
|
|
1242
|
+
"%s Loading latest version of artifact '%s' content to append to.",
|
|
1243
|
+
log_identifier,
|
|
1244
|
+
filename,
|
|
1245
|
+
)
|
|
1246
|
+
content_load_result = await load_artifact_content_or_metadata(
|
|
1247
|
+
artifact_service=artifact_service,
|
|
1248
|
+
app_name=app_name,
|
|
1249
|
+
user_id=user_id,
|
|
1250
|
+
session_id=session_id,
|
|
1251
|
+
filename=filename,
|
|
1252
|
+
version="latest",
|
|
1253
|
+
load_metadata_only=False,
|
|
1254
|
+
return_raw_bytes=True,
|
|
1255
|
+
component=host_component,
|
|
1256
|
+
log_identifier_prefix=f"{log_identifier}[LoadOriginalContent]",
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
if content_load_result.get("status") != "success":
|
|
1260
|
+
log.error(
|
|
1261
|
+
"%s Failed to load original artifact content '%s': %s",
|
|
1262
|
+
log_identifier,
|
|
1263
|
+
filename,
|
|
1264
|
+
content_load_result.get("message"),
|
|
1265
|
+
)
|
|
1266
|
+
return {
|
|
1267
|
+
"status": "error",
|
|
1268
|
+
"filename": filename,
|
|
1269
|
+
"message": f"Failed to load original artifact content to append to: {content_load_result.get('message')}",
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
original_artifact_bytes = content_load_result.get("raw_bytes", b"")
|
|
1273
|
+
original_mime_type = content_load_result.get(
|
|
1274
|
+
"mime_type", "application/octet-stream"
|
|
1275
|
+
)
|
|
1276
|
+
original_version_loaded = content_load_result.get("version", "unknown")
|
|
1277
|
+
log.info(
|
|
1278
|
+
"%s Loaded original artifact content '%s' v%s, type: %s, size: %d bytes.",
|
|
1279
|
+
log_identifier,
|
|
1280
|
+
filename,
|
|
1281
|
+
original_version_loaded,
|
|
1282
|
+
original_mime_type,
|
|
1283
|
+
len(original_artifact_bytes),
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
log.debug(
|
|
1287
|
+
"%s Loading latest version of artifact '%s' metadata.",
|
|
1288
|
+
log_identifier,
|
|
1289
|
+
filename,
|
|
1290
|
+
)
|
|
1291
|
+
metadata_load_result = await load_artifact_content_or_metadata(
|
|
1292
|
+
artifact_service=artifact_service,
|
|
1293
|
+
app_name=app_name,
|
|
1294
|
+
user_id=user_id,
|
|
1295
|
+
session_id=session_id,
|
|
1296
|
+
filename=filename,
|
|
1297
|
+
version="latest",
|
|
1298
|
+
load_metadata_only=True,
|
|
1299
|
+
component=host_component,
|
|
1300
|
+
log_identifier_prefix=f"{log_identifier}[LoadOriginalMetadata]",
|
|
1301
|
+
)
|
|
1302
|
+
original_metadata_dict = {}
|
|
1303
|
+
if metadata_load_result.get("status") == "success":
|
|
1304
|
+
original_metadata_dict = metadata_load_result.get("metadata", {})
|
|
1305
|
+
log.info(
|
|
1306
|
+
"%s Loaded original artifact metadata for '%s' v%s.",
|
|
1307
|
+
log_identifier,
|
|
1308
|
+
filename,
|
|
1309
|
+
metadata_load_result.get("version", "unknown"),
|
|
1310
|
+
)
|
|
1311
|
+
else:
|
|
1312
|
+
log.warning(
|
|
1313
|
+
"%s Failed to load original artifact metadata for '%s': %s. Proceeding with minimal metadata.",
|
|
1314
|
+
log_identifier,
|
|
1315
|
+
filename,
|
|
1316
|
+
metadata_load_result.get("message"),
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1319
|
+
chunk_bytes, _ = decode_and_get_bytes(
|
|
1320
|
+
content_chunk, mime_type, f"{log_identifier}[DecodeChunk]"
|
|
1321
|
+
)
|
|
1322
|
+
log.debug(
|
|
1323
|
+
"%s Decoded content_chunk (declared type: %s) to %d bytes.",
|
|
1324
|
+
log_identifier,
|
|
1325
|
+
mime_type,
|
|
1326
|
+
len(chunk_bytes),
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
combined_bytes = original_artifact_bytes + chunk_bytes
|
|
1330
|
+
log.debug(
|
|
1331
|
+
"%s Appended chunk. New total size: %d bytes.",
|
|
1332
|
+
log_identifier,
|
|
1333
|
+
len(combined_bytes),
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
new_metadata_for_save = {
|
|
1337
|
+
key: value
|
|
1338
|
+
for key, value in original_metadata_dict.items()
|
|
1339
|
+
if key
|
|
1340
|
+
not in [
|
|
1341
|
+
"filename",
|
|
1342
|
+
"mime_type",
|
|
1343
|
+
"size_bytes",
|
|
1344
|
+
"timestamp_utc",
|
|
1345
|
+
"schema",
|
|
1346
|
+
"version",
|
|
1347
|
+
]
|
|
1348
|
+
}
|
|
1349
|
+
new_metadata_for_save["appended_from_version"] = original_version_loaded
|
|
1350
|
+
new_metadata_for_save["appended_chunk_declared_mime_type"] = mime_type
|
|
1351
|
+
|
|
1352
|
+
schema_max_keys = (
|
|
1353
|
+
host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
|
|
1354
|
+
if host_component
|
|
1355
|
+
else DEFAULT_SCHEMA_MAX_KEYS
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
save_result = await save_artifact_with_metadata(
|
|
1359
|
+
artifact_service=artifact_service,
|
|
1360
|
+
app_name=app_name,
|
|
1361
|
+
user_id=user_id,
|
|
1362
|
+
session_id=session_id,
|
|
1363
|
+
filename=filename,
|
|
1364
|
+
content_bytes=combined_bytes,
|
|
1365
|
+
mime_type=original_mime_type,
|
|
1366
|
+
metadata_dict=new_metadata_for_save,
|
|
1367
|
+
timestamp=datetime.now(timezone.utc),
|
|
1368
|
+
schema_max_keys=schema_max_keys,
|
|
1369
|
+
tool_context=tool_context,
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
log.info(
|
|
1373
|
+
"%s Result from save_artifact_with_metadata after append: %s",
|
|
1374
|
+
log_identifier,
|
|
1375
|
+
save_result,
|
|
1376
|
+
)
|
|
1377
|
+
|
|
1378
|
+
if save_result.get("status") == "error":
|
|
1379
|
+
raise IOError(
|
|
1380
|
+
f"Failed to save appended artifact: {save_result.get('message', 'Unknown error')}"
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
return {
|
|
1384
|
+
"status": "success",
|
|
1385
|
+
"filename": filename,
|
|
1386
|
+
"new_version": save_result.get("data_version"),
|
|
1387
|
+
"total_size_bytes": len(combined_bytes),
|
|
1388
|
+
"message": f"Chunk appended to '{filename}'. New version is {save_result.get('data_version')} with total size {len(combined_bytes)} bytes.",
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
except FileNotFoundError as e:
|
|
1392
|
+
log.warning("%s Original artifact not found for append: %s", log_identifier, e)
|
|
1393
|
+
return {
|
|
1394
|
+
"status": "error",
|
|
1395
|
+
"filename": filename,
|
|
1396
|
+
"message": f"Original artifact '{filename}' not found: {e}",
|
|
1397
|
+
}
|
|
1398
|
+
except ValueError as e:
|
|
1399
|
+
log.warning("%s Value error during append: %s", log_identifier, e)
|
|
1400
|
+
return {"status": "error", "filename": filename, "message": str(e)}
|
|
1401
|
+
except IOError as e:
|
|
1402
|
+
log.warning("%s IO error during append: %s", log_identifier, e)
|
|
1403
|
+
return {"status": "error", "filename": filename, "message": str(e)}
|
|
1404
|
+
except Exception as e:
|
|
1405
|
+
log.exception(
|
|
1406
|
+
"%s Unexpected error appending to artifact '%s': %s",
|
|
1407
|
+
log_identifier,
|
|
1408
|
+
filename,
|
|
1409
|
+
e,
|
|
1410
|
+
)
|
|
1411
|
+
return {
|
|
1412
|
+
"status": "error",
|
|
1413
|
+
"filename": filename,
|
|
1414
|
+
"message": f"Failed to append to artifact: {e}",
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
async def _save_extracted_artifact(
|
|
1419
|
+
tool_context: ToolContext,
|
|
1420
|
+
host_component: Any,
|
|
1421
|
+
extracted_content_bytes: bytes,
|
|
1422
|
+
source_artifact_filename: str,
|
|
1423
|
+
source_artifact_version: Union[int, str],
|
|
1424
|
+
extraction_goal: str,
|
|
1425
|
+
output_filename_base: Optional[str],
|
|
1426
|
+
output_mime_type: str,
|
|
1427
|
+
) -> Dict[str, Any]:
|
|
1428
|
+
"""
|
|
1429
|
+
Saves the extracted content as a new artifact with comprehensive metadata.
|
|
1430
|
+
|
|
1431
|
+
Args:
|
|
1432
|
+
tool_context: The ADK ToolContext.
|
|
1433
|
+
host_component: The A2A_ADK_HostComponent instance for accessing config and services.
|
|
1434
|
+
extracted_content_bytes: The raw byte content of the extracted data.
|
|
1435
|
+
source_artifact_filename: The filename of the original artifact.
|
|
1436
|
+
source_artifact_version: The version of the original artifact.
|
|
1437
|
+
extraction_goal: The natural language goal used for extraction.
|
|
1438
|
+
output_filename_base: Optional base for the new artifact's filename.
|
|
1439
|
+
output_mime_type: The MIME type of the extracted content.
|
|
1440
|
+
|
|
1441
|
+
Returns:
|
|
1442
|
+
A dictionary containing details of the saved artifact, as returned by
|
|
1443
|
+
`save_artifact_with_metadata`.
|
|
1444
|
+
"""
|
|
1445
|
+
log_identifier = f"[BuiltinArtifactTool:_save_extracted_artifact]"
|
|
1446
|
+
log.debug("%s Saving extracted content...", log_identifier)
|
|
1447
|
+
|
|
1448
|
+
try:
|
|
1449
|
+
base_name = output_filename_base or f"{source_artifact_filename}_extracted"
|
|
1450
|
+
base_name_sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", base_name)
|
|
1451
|
+
base_name_sanitized = base_name_sanitized.strip("_")
|
|
1452
|
+
|
|
1453
|
+
suffix = uuid.uuid4().hex[:8]
|
|
1454
|
+
extension_map = {
|
|
1455
|
+
"text/plain": ".txt",
|
|
1456
|
+
"application/json": ".json",
|
|
1457
|
+
"text/csv": ".csv",
|
|
1458
|
+
"text/html": ".html",
|
|
1459
|
+
"image/png": ".png",
|
|
1460
|
+
"image/jpeg": ".jpg",
|
|
1461
|
+
"application/pdf": ".pdf",
|
|
1462
|
+
}
|
|
1463
|
+
ext = extension_map.get(output_mime_type.lower(), ".dat")
|
|
1464
|
+
filename = f"{base_name_sanitized}_{suffix}{ext}"
|
|
1465
|
+
log.debug("%s Generated output filename: %s", log_identifier, filename)
|
|
1466
|
+
|
|
1467
|
+
timestamp = datetime.now(timezone.utc)
|
|
1468
|
+
metadata_for_saving = {
|
|
1469
|
+
"description": f"Content extracted/transformed from artifact '{source_artifact_filename}' (version {source_artifact_version}) using goal: '{extraction_goal}'.",
|
|
1470
|
+
"source_artifact_filename": source_artifact_filename,
|
|
1471
|
+
"source_artifact_version": source_artifact_version,
|
|
1472
|
+
"extraction_goal_used": extraction_goal,
|
|
1473
|
+
}
|
|
1474
|
+
log.debug(
|
|
1475
|
+
"%s Prepared metadata for saving: %s", log_identifier, metadata_for_saving
|
|
1476
|
+
)
|
|
1477
|
+
|
|
1478
|
+
inv_context = tool_context._invocation_context
|
|
1479
|
+
artifact_service = inv_context.artifact_service
|
|
1480
|
+
if not artifact_service:
|
|
1481
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1482
|
+
|
|
1483
|
+
app_name = inv_context.app_name
|
|
1484
|
+
user_id = inv_context.user_id
|
|
1485
|
+
session_id = get_original_session_id(inv_context)
|
|
1486
|
+
schema_max_keys = host_component.get_config(
|
|
1487
|
+
"schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
log.debug(
|
|
1491
|
+
"%s Calling save_artifact_with_metadata for '%s' (app: %s, user: %s, session: %s, schema_keys: %d)",
|
|
1492
|
+
log_identifier,
|
|
1493
|
+
filename,
|
|
1494
|
+
app_name,
|
|
1495
|
+
user_id,
|
|
1496
|
+
session_id,
|
|
1497
|
+
schema_max_keys,
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
save_result = await save_artifact_with_metadata(
|
|
1501
|
+
artifact_service=artifact_service,
|
|
1502
|
+
app_name=app_name,
|
|
1503
|
+
user_id=user_id,
|
|
1504
|
+
session_id=session_id,
|
|
1505
|
+
filename=filename,
|
|
1506
|
+
content_bytes=extracted_content_bytes,
|
|
1507
|
+
mime_type=output_mime_type,
|
|
1508
|
+
metadata_dict=metadata_for_saving,
|
|
1509
|
+
timestamp=timestamp,
|
|
1510
|
+
schema_max_keys=schema_max_keys,
|
|
1511
|
+
tool_context=tool_context,
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
log.info(
|
|
1515
|
+
"%s Extracted content saved as artifact '%s' (version %s). Result: %s",
|
|
1516
|
+
log_identifier,
|
|
1517
|
+
save_result.get("data_filename", filename),
|
|
1518
|
+
save_result.get("data_version", "N/A"),
|
|
1519
|
+
save_result.get("status"),
|
|
1520
|
+
)
|
|
1521
|
+
return save_result
|
|
1522
|
+
|
|
1523
|
+
except Exception as e:
|
|
1524
|
+
log.exception(
|
|
1525
|
+
"%s Error in _save_extracted_artifact for source '%s': %s",
|
|
1526
|
+
log_identifier,
|
|
1527
|
+
source_artifact_filename,
|
|
1528
|
+
e,
|
|
1529
|
+
)
|
|
1530
|
+
return {
|
|
1531
|
+
"status": "error",
|
|
1532
|
+
"data_filename": filename if "filename" in locals() else "unknown_filename",
|
|
1533
|
+
"message": f"Failed to save extracted content as artifact: {e}",
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
async def _notify_artifact_save(
|
|
1538
|
+
filename: str,
|
|
1539
|
+
version: int,
|
|
1540
|
+
status: str,
|
|
1541
|
+
tool_context: ToolContext = None, # Keep tool_context for signature consistency
|
|
1542
|
+
) -> Dict[str, Any]:
|
|
1543
|
+
"""
|
|
1544
|
+
CRITICAL: _notify_artifact_save is automatically invoked by the system as a side-effect when you create artifacts. You should NEVER call this tool yourself. The system will call it for you and provide the results in your next turn. If you manually invoke it, you are making an error."
|
|
1545
|
+
"""
|
|
1546
|
+
return {
|
|
1547
|
+
"filename": filename,
|
|
1548
|
+
"version": version,
|
|
1549
|
+
"status": status,
|
|
1550
|
+
"message": "Artifact has been created and provided to the requester",
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
_notify_artifact_save_tool_def = BuiltinTool(
|
|
1555
|
+
name="_notify_artifact_save",
|
|
1556
|
+
implementation=_notify_artifact_save,
|
|
1557
|
+
description="CRITICAL: _notify_artifact_save is automatically invoked by the system as a side-effect when you create artifacts. You should NEVER call this tool yourself. The system will call it for you and provide the results in your next turn. If you manually invoke it, you are making an error.",
|
|
1558
|
+
category="internal",
|
|
1559
|
+
required_scopes=[], # No scopes needed for an internal notification tool
|
|
1560
|
+
parameters=adk_types.Schema(
|
|
1561
|
+
type=adk_types.Type.OBJECT,
|
|
1562
|
+
properties={
|
|
1563
|
+
"filename": adk_types.Schema(
|
|
1564
|
+
type=adk_types.Type.STRING,
|
|
1565
|
+
description="The name of the artifact that was saved.",
|
|
1566
|
+
),
|
|
1567
|
+
"version": adk_types.Schema(
|
|
1568
|
+
type=adk_types.Type.INTEGER,
|
|
1569
|
+
description="The version number of the saved artifact.",
|
|
1570
|
+
),
|
|
1571
|
+
"status": adk_types.Schema(
|
|
1572
|
+
type=adk_types.Type.STRING,
|
|
1573
|
+
description="The status of the save operation.",
|
|
1574
|
+
),
|
|
1575
|
+
},
|
|
1576
|
+
required=["filename", "version", "status"],
|
|
1577
|
+
),
|
|
1578
|
+
examples=[],
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
append_to_artifact_tool_def = BuiltinTool(
|
|
1582
|
+
name="append_to_artifact",
|
|
1583
|
+
implementation=append_to_artifact,
|
|
1584
|
+
description="Appends a chunk of content to an existing artifact. This operation will create a new version of the artifact. The content_chunk should be a string, potentially base64 encoded if it represents binary data (indicated by mime_type). The chunk size should be limited (e.g., max 3KB) by the LLM.",
|
|
1585
|
+
category="artifact_management",
|
|
1586
|
+
category_name=CATEGORY_NAME,
|
|
1587
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
1588
|
+
required_scopes=["tool:artifact:append"],
|
|
1589
|
+
parameters=adk_types.Schema(
|
|
1590
|
+
type=adk_types.Type.OBJECT,
|
|
1591
|
+
properties={
|
|
1592
|
+
"filename": adk_types.Schema(
|
|
1593
|
+
type=adk_types.Type.STRING,
|
|
1594
|
+
description="The name of the artifact to append to. May contain embeds.",
|
|
1595
|
+
),
|
|
1596
|
+
"content_chunk": adk_types.Schema(
|
|
1597
|
+
type=adk_types.Type.STRING,
|
|
1598
|
+
description="The chunk of content to append (max approx. 3KB). If mime_type suggests binary, this should be base64 encoded. May contain embeds.",
|
|
1599
|
+
),
|
|
1600
|
+
"mime_type": adk_types.Schema(
|
|
1601
|
+
type=adk_types.Type.STRING,
|
|
1602
|
+
description="The MIME type of the content_chunk. This helps determine if base64 decoding is needed for the chunk. The overall artifact's MIME type will be preserved from its latest version. May contain embeds.",
|
|
1603
|
+
),
|
|
1604
|
+
},
|
|
1605
|
+
required=["filename", "content_chunk", "mime_type"],
|
|
1606
|
+
),
|
|
1607
|
+
examples=[],
|
|
1608
|
+
)
|
|
1609
|
+
|
|
1610
|
+
list_artifacts_tool_def = BuiltinTool(
|
|
1611
|
+
name="list_artifacts",
|
|
1612
|
+
implementation=list_artifacts,
|
|
1613
|
+
description="Lists all available data artifact filenames and their versions for the current session. Includes a summary of the latest version's metadata for each artifact.",
|
|
1614
|
+
category="artifact_management",
|
|
1615
|
+
category_name=CATEGORY_NAME,
|
|
1616
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
1617
|
+
required_scopes=["tool:artifact:list"],
|
|
1618
|
+
parameters=adk_types.Schema(
|
|
1619
|
+
type=adk_types.Type.OBJECT,
|
|
1620
|
+
properties={},
|
|
1621
|
+
required=[],
|
|
1622
|
+
),
|
|
1623
|
+
examples=[],
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
load_artifact_tool_def = BuiltinTool(
|
|
1627
|
+
name="load_artifact",
|
|
1628
|
+
implementation=load_artifact,
|
|
1629
|
+
description="Loads the content or metadata of a specific artifact version. If load_metadata_only is True, loads the full metadata dictionary. Otherwise, loads text content (potentially truncated) or a summary for binary types. Line numbers can be optionally included for precise line range identification.",
|
|
1630
|
+
category="artifact_management",
|
|
1631
|
+
category_name=CATEGORY_NAME,
|
|
1632
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
1633
|
+
required_scopes=["tool:artifact:load"],
|
|
1634
|
+
parameters=adk_types.Schema(
|
|
1635
|
+
type=adk_types.Type.OBJECT,
|
|
1636
|
+
properties={
|
|
1637
|
+
"filename": adk_types.Schema(
|
|
1638
|
+
type=adk_types.Type.STRING,
|
|
1639
|
+
description="The name of the artifact to load. May contain embeds.",
|
|
1640
|
+
),
|
|
1641
|
+
"version": adk_types.Schema(
|
|
1642
|
+
type=adk_types.Type.INTEGER,
|
|
1643
|
+
description="The specific version number to load. Must be explicitly provided.",
|
|
1644
|
+
),
|
|
1645
|
+
"load_metadata_only": adk_types.Schema(
|
|
1646
|
+
type=adk_types.Type.BOOLEAN,
|
|
1647
|
+
description="If True, load only the metadata JSON. Default False.",
|
|
1648
|
+
nullable=True,
|
|
1649
|
+
),
|
|
1650
|
+
"max_content_length": adk_types.Schema(
|
|
1651
|
+
type=adk_types.Type.INTEGER,
|
|
1652
|
+
description="Optional. Maximum character length for text content. If None, uses app configuration. Range: 100-100,000.",
|
|
1653
|
+
nullable=True,
|
|
1654
|
+
),
|
|
1655
|
+
"include_line_numbers": adk_types.Schema(
|
|
1656
|
+
type=adk_types.Type.BOOLEAN,
|
|
1657
|
+
description="If True, prefix each line with its 1-based line number followed by a TAB character. Line numbers are for LLM viewing only and are not stored in the artifact. Default False.",
|
|
1658
|
+
nullable=True,
|
|
1659
|
+
),
|
|
1660
|
+
},
|
|
1661
|
+
required=["filename", "version"],
|
|
1662
|
+
),
|
|
1663
|
+
examples=[],
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
apply_embed_and_create_artifact_tool_def = BuiltinTool(
|
|
1667
|
+
name="apply_embed_and_create_artifact",
|
|
1668
|
+
implementation=apply_embed_and_create_artifact,
|
|
1669
|
+
description="Resolves an 'artifact_content' embed directive (including modifiers and formatting) and saves the resulting content as a new artifact. The entire embed directive must be provided as a string.",
|
|
1670
|
+
category="artifact_management",
|
|
1671
|
+
category_name=CATEGORY_NAME,
|
|
1672
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
1673
|
+
required_scopes=["tool:artifact:create", "tool:artifact:load"],
|
|
1674
|
+
parameters=adk_types.Schema(
|
|
1675
|
+
type=adk_types.Type.OBJECT,
|
|
1676
|
+
properties={
|
|
1677
|
+
"output_filename": adk_types.Schema(
|
|
1678
|
+
type=adk_types.Type.STRING,
|
|
1679
|
+
description="The desired name for the new artifact.",
|
|
1680
|
+
),
|
|
1681
|
+
"embed_directive": adk_types.Schema(
|
|
1682
|
+
type=adk_types.Type.STRING,
|
|
1683
|
+
description="The full '«artifact_content:...>>>...>>>format:...»' string.",
|
|
1684
|
+
),
|
|
1685
|
+
"output_metadata": adk_types.Schema(
|
|
1686
|
+
type=adk_types.Type.OBJECT,
|
|
1687
|
+
description="Optional metadata for the new artifact.",
|
|
1688
|
+
nullable=True,
|
|
1689
|
+
),
|
|
1690
|
+
},
|
|
1691
|
+
required=["output_filename", "embed_directive"],
|
|
1692
|
+
),
|
|
1693
|
+
raw_string_args=["embed_directive"],
|
|
1694
|
+
examples=[],
|
|
1695
|
+
)
|
|
1696
|
+
|
|
1697
|
+
extract_content_from_artifact_tool_def = BuiltinTool(
|
|
1698
|
+
name="extract_content_from_artifact",
|
|
1699
|
+
implementation=extract_content_from_artifact,
|
|
1700
|
+
description="Loads an existing artifact, uses an internal LLM to process its content based on an 'extraction_goal,' and manages the output by returning it or saving it as a new artifact.",
|
|
1701
|
+
category="artifact_management",
|
|
1702
|
+
category_name=CATEGORY_NAME,
|
|
1703
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
1704
|
+
required_scopes=["tool:artifact:load", "tool:artifact:create"],
|
|
1705
|
+
parameters=adk_types.Schema(
|
|
1706
|
+
type=adk_types.Type.OBJECT,
|
|
1707
|
+
properties={
|
|
1708
|
+
"filename": adk_types.Schema(
|
|
1709
|
+
type=adk_types.Type.STRING,
|
|
1710
|
+
description="Name of the source artifact. May contain embeds.",
|
|
1711
|
+
),
|
|
1712
|
+
"extraction_goal": adk_types.Schema(
|
|
1713
|
+
type=adk_types.Type.STRING,
|
|
1714
|
+
description="Natural language instruction for the LLM on what to extract or how to transform the content. May contain embeds.",
|
|
1715
|
+
),
|
|
1716
|
+
"version": adk_types.Schema(
|
|
1717
|
+
type=adk_types.Type.STRING,
|
|
1718
|
+
description="Version of the source artifact. Can be an integer or 'latest'. Defaults to 'latest'. May contain embeds.",
|
|
1719
|
+
nullable=True,
|
|
1720
|
+
),
|
|
1721
|
+
"output_filename_base": adk_types.Schema(
|
|
1722
|
+
type=adk_types.Type.STRING,
|
|
1723
|
+
description="Optional base name for the new artifact if the extracted content is saved. May contain embeds.",
|
|
1724
|
+
nullable=True,
|
|
1725
|
+
),
|
|
1726
|
+
},
|
|
1727
|
+
required=["filename", "extraction_goal"],
|
|
1728
|
+
),
|
|
1729
|
+
examples=[],
|
|
1730
|
+
)
|
|
1731
|
+
|
|
1732
|
+
tool_registry.register(_notify_artifact_save_tool_def)
|
|
1733
|
+
tool_registry.register(append_to_artifact_tool_def)
|
|
1734
|
+
tool_registry.register(list_artifacts_tool_def)
|
|
1735
|
+
tool_registry.register(load_artifact_tool_def)
|
|
1736
|
+
tool_registry.register(apply_embed_and_create_artifact_tool_def)
|
|
1737
|
+
tool_registry.register(extract_content_from_artifact_tool_def)
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
async def delete_artifact(
|
|
1741
|
+
filename: str,
|
|
1742
|
+
version: Optional[int] = None,
|
|
1743
|
+
tool_context: ToolContext = None,
|
|
1744
|
+
) -> Dict[str, Any]:
|
|
1745
|
+
"""
|
|
1746
|
+
Deletes a specific version of an artifact, or all versions if no version is specified.
|
|
1747
|
+
|
|
1748
|
+
Args:
|
|
1749
|
+
filename: The name of the artifact to delete.
|
|
1750
|
+
version: The specific version number to delete. If not provided, all versions will be deleted.
|
|
1751
|
+
tool_context: The context provided by the ADK framework.
|
|
1752
|
+
|
|
1753
|
+
Returns:
|
|
1754
|
+
A dictionary indicating the result of the deletion.
|
|
1755
|
+
"""
|
|
1756
|
+
if not tool_context:
|
|
1757
|
+
return {
|
|
1758
|
+
"status": "error",
|
|
1759
|
+
"filename": filename,
|
|
1760
|
+
"message": "ToolContext is missing, cannot delete artifact.",
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
log_identifier = (
|
|
1764
|
+
f"[BuiltinArtifactTool:delete_artifact:{filename}:{version or 'all'}]"
|
|
1765
|
+
)
|
|
1766
|
+
log.debug("%s Processing request.", log_identifier)
|
|
1767
|
+
|
|
1768
|
+
try:
|
|
1769
|
+
inv_context = tool_context._invocation_context
|
|
1770
|
+
artifact_service = inv_context.artifact_service
|
|
1771
|
+
if not artifact_service:
|
|
1772
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
1773
|
+
|
|
1774
|
+
app_name = inv_context.app_name
|
|
1775
|
+
user_id = inv_context.user_id
|
|
1776
|
+
session_id = get_original_session_id(inv_context)
|
|
1777
|
+
|
|
1778
|
+
if not hasattr(artifact_service, "delete_artifact"):
|
|
1779
|
+
raise NotImplementedError(
|
|
1780
|
+
"ArtifactService does not support deleting artifacts."
|
|
1781
|
+
)
|
|
1782
|
+
|
|
1783
|
+
if version is not None:
|
|
1784
|
+
log.warning(
|
|
1785
|
+
"%s Deleting a specific version (%s) is not supported by the current artifact service interface. "
|
|
1786
|
+
"All versions of the artifact will be deleted.",
|
|
1787
|
+
log_identifier,
|
|
1788
|
+
version,
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
await artifact_service.delete_artifact(
|
|
1792
|
+
app_name=app_name,
|
|
1793
|
+
user_id=user_id,
|
|
1794
|
+
session_id=session_id,
|
|
1795
|
+
filename=filename,
|
|
1796
|
+
)
|
|
1797
|
+
|
|
1798
|
+
log.info(
|
|
1799
|
+
"%s Successfully deleted artifact '%s' version '%s'.",
|
|
1800
|
+
log_identifier,
|
|
1801
|
+
filename,
|
|
1802
|
+
version or "all",
|
|
1803
|
+
)
|
|
1804
|
+
return {
|
|
1805
|
+
"status": "success",
|
|
1806
|
+
"filename": filename,
|
|
1807
|
+
"version": version or "all",
|
|
1808
|
+
"message": f"Artifact '{filename}' version '{version or 'all'}' deleted successfully.",
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
except FileNotFoundError as e:
|
|
1812
|
+
log.warning("%s Artifact not found for deletion: %s", log_identifier, e)
|
|
1813
|
+
return {
|
|
1814
|
+
"status": "error",
|
|
1815
|
+
"filename": filename,
|
|
1816
|
+
"message": f"Artifact '{filename}' not found.",
|
|
1817
|
+
}
|
|
1818
|
+
except Exception as e:
|
|
1819
|
+
log.exception(
|
|
1820
|
+
"%s Error deleting artifact '%s': %s", log_identifier, filename, e
|
|
1821
|
+
)
|
|
1822
|
+
return {
|
|
1823
|
+
"status": "error",
|
|
1824
|
+
"filename": filename,
|
|
1825
|
+
"message": f"Failed to delete artifact: {e}",
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
|
|
1829
|
+
delete_artifact_tool_def = BuiltinTool(
|
|
1830
|
+
name="delete_artifact",
|
|
1831
|
+
implementation=delete_artifact,
|
|
1832
|
+
description="Deletes a specific version of an artifact, or all versions if no version is specified.",
|
|
1833
|
+
category="artifact_management",
|
|
1834
|
+
category_name=CATEGORY_NAME,
|
|
1835
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
1836
|
+
required_scopes=["tool:artifact:delete"],
|
|
1837
|
+
parameters=adk_types.Schema(
|
|
1838
|
+
type=adk_types.Type.OBJECT,
|
|
1839
|
+
properties={
|
|
1840
|
+
"filename": adk_types.Schema(
|
|
1841
|
+
type=adk_types.Type.STRING,
|
|
1842
|
+
description="The name of the artifact to delete.",
|
|
1843
|
+
),
|
|
1844
|
+
"version": adk_types.Schema(
|
|
1845
|
+
type=adk_types.Type.INTEGER,
|
|
1846
|
+
description="The specific version number to delete. If not provided, all versions will be deleted.",
|
|
1847
|
+
nullable=True,
|
|
1848
|
+
),
|
|
1849
|
+
},
|
|
1850
|
+
required=["filename"],
|
|
1851
|
+
),
|
|
1852
|
+
examples=[],
|
|
1853
|
+
)
|
|
1854
|
+
|
|
1855
|
+
tool_registry.register(delete_artifact_tool_def)
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
def _perform_single_replacement(
|
|
1859
|
+
content: str,
|
|
1860
|
+
search_expr: str,
|
|
1861
|
+
replace_expr: str,
|
|
1862
|
+
is_regex: bool,
|
|
1863
|
+
regex_flags: str,
|
|
1864
|
+
log_identifier: str,
|
|
1865
|
+
strict_match_validation: bool = False,
|
|
1866
|
+
) -> Tuple[str, int, Optional[str]]:
|
|
1867
|
+
"""
|
|
1868
|
+
Performs a single search-and-replace operation.
|
|
1869
|
+
|
|
1870
|
+
Args:
|
|
1871
|
+
content: The text content to search/replace in
|
|
1872
|
+
search_expr: The search pattern (literal or regex)
|
|
1873
|
+
replace_expr: The replacement text
|
|
1874
|
+
is_regex: If True, search_expr is treated as regex
|
|
1875
|
+
regex_flags: Flags for regex behavior ('g', 'i', 'm', 's')
|
|
1876
|
+
log_identifier: Logging prefix
|
|
1877
|
+
strict_match_validation: If True, error on multiple matches without 'g' flag (for batch mode)
|
|
1878
|
+
|
|
1879
|
+
Returns:
|
|
1880
|
+
tuple: (new_content, match_count, error_message)
|
|
1881
|
+
error_message is None on success
|
|
1882
|
+
"""
|
|
1883
|
+
match_count = 0
|
|
1884
|
+
new_content = content
|
|
1885
|
+
|
|
1886
|
+
if is_regex:
|
|
1887
|
+
# Parse regex flags
|
|
1888
|
+
flags_value = 0
|
|
1889
|
+
global_replace = False
|
|
1890
|
+
|
|
1891
|
+
if regex_flags:
|
|
1892
|
+
for flag_char in regex_flags.lower():
|
|
1893
|
+
if flag_char == "g":
|
|
1894
|
+
global_replace = True
|
|
1895
|
+
elif flag_char == "i":
|
|
1896
|
+
flags_value |= re.IGNORECASE
|
|
1897
|
+
elif flag_char == "m":
|
|
1898
|
+
flags_value |= re.MULTILINE
|
|
1899
|
+
elif flag_char == "s":
|
|
1900
|
+
flags_value |= re.DOTALL
|
|
1901
|
+
else:
|
|
1902
|
+
log.warning(
|
|
1903
|
+
"%s Ignoring unrecognized regexp flag: '%s'",
|
|
1904
|
+
log_identifier,
|
|
1905
|
+
flag_char,
|
|
1906
|
+
)
|
|
1907
|
+
|
|
1908
|
+
# Convert JavaScript-style capture groups ($1, $2) to Python style (\1, \2)
|
|
1909
|
+
# Also handle escaped dollar signs ($$) -> literal $
|
|
1910
|
+
python_replace_expr = replace_expr
|
|
1911
|
+
# First, protect escaped dollars: $$ -> a placeholder
|
|
1912
|
+
python_replace_expr = python_replace_expr.replace("$$", "\x00DOLLAR\x00")
|
|
1913
|
+
# Convert capture groups: $1 -> \1
|
|
1914
|
+
python_replace_expr = re.sub(r"\$(\d+)", r"\\\1", python_replace_expr)
|
|
1915
|
+
# Restore escaped dollars: placeholder -> $
|
|
1916
|
+
python_replace_expr = python_replace_expr.replace("\x00DOLLAR\x00", "$")
|
|
1917
|
+
|
|
1918
|
+
try:
|
|
1919
|
+
# Compile the regex pattern
|
|
1920
|
+
pattern = re.compile(search_expr, flags_value)
|
|
1921
|
+
|
|
1922
|
+
# Count matches first
|
|
1923
|
+
match_count = len(pattern.findall(content))
|
|
1924
|
+
|
|
1925
|
+
if match_count == 0:
|
|
1926
|
+
return content, 0, f"No matches found"
|
|
1927
|
+
|
|
1928
|
+
# Check for multiple matches without global flag (only in strict mode for batch operations)
|
|
1929
|
+
if strict_match_validation and match_count > 1 and not global_replace:
|
|
1930
|
+
return (
|
|
1931
|
+
content,
|
|
1932
|
+
match_count,
|
|
1933
|
+
f"Multiple matches found ({match_count}) but global flag 'g' not set",
|
|
1934
|
+
)
|
|
1935
|
+
|
|
1936
|
+
# Perform replacement
|
|
1937
|
+
count_limit = 0 if global_replace else 1
|
|
1938
|
+
new_content = pattern.sub(python_replace_expr, content, count=count_limit)
|
|
1939
|
+
|
|
1940
|
+
return new_content, match_count, None
|
|
1941
|
+
|
|
1942
|
+
except re.error as regex_err:
|
|
1943
|
+
return content, 0, f"Invalid regular expression: {regex_err}"
|
|
1944
|
+
|
|
1945
|
+
else:
|
|
1946
|
+
# Literal string replacement
|
|
1947
|
+
match_count = content.count(search_expr)
|
|
1948
|
+
|
|
1949
|
+
if match_count == 0:
|
|
1950
|
+
return content, 0, f"No matches found"
|
|
1951
|
+
|
|
1952
|
+
# Replace all occurrences for literal mode
|
|
1953
|
+
new_content = content.replace(search_expr, replace_expr)
|
|
1954
|
+
return new_content, match_count, None
|
|
1955
|
+
|
|
1956
|
+
|
|
1957
|
+
async def artifact_search_and_replace_regex(
|
|
1958
|
+
filename: str,
|
|
1959
|
+
search_expression: Optional[str] = None,
|
|
1960
|
+
replace_expression: Optional[str] = None,
|
|
1961
|
+
is_regexp: bool = False,
|
|
1962
|
+
version: Optional[str] = "latest",
|
|
1963
|
+
regexp_flags: Optional[str] = "",
|
|
1964
|
+
new_filename: Optional[str] = None,
|
|
1965
|
+
new_description: Optional[str] = None,
|
|
1966
|
+
replacements: Optional[List[Dict[str, Any]]] = None,
|
|
1967
|
+
tool_context: ToolContext = None,
|
|
1968
|
+
) -> Dict[str, Any]:
|
|
1969
|
+
"""
|
|
1970
|
+
Performs search and replace on an artifact's text content using either
|
|
1971
|
+
literal string matching or regular expressions. Note that this is run once across the entire artifact.
|
|
1972
|
+
If multiple replacements are needed, then set the 'g' flag in regexp_flags.
|
|
1973
|
+
|
|
1974
|
+
Handling Multi-line Search and Replace:
|
|
1975
|
+
|
|
1976
|
+
When searching for or replacing text that spans multiple lines:
|
|
1977
|
+
|
|
1978
|
+
- In literal mode (is_regexp=false): Include actual newline characters directly in your search_expression
|
|
1979
|
+
and replace_expression parameters. Do NOT use escape sequences like \n - the tool will search for those
|
|
1980
|
+
literal characters. Multi-line parameter values are fully supported in the XML parameter format.
|
|
1981
|
+
|
|
1982
|
+
- In regex mode (is_regexp=true): Use the regex pattern \n to match newline characters in your pattern.
|
|
1983
|
+
|
|
1984
|
+
For multiple independent replacements:
|
|
1985
|
+
|
|
1986
|
+
Use the replacements array parameter to perform all replacements atomically in a single tool call, which is more efficient than multiple sequential calls.
|
|
1987
|
+
|
|
1988
|
+
Args:
|
|
1989
|
+
filename: The name of the artifact to search/replace in.
|
|
1990
|
+
search_expression: The pattern to search for (regex if is_regexp=true, literal otherwise).
|
|
1991
|
+
replace_expression: The replacement text. For regex mode, supports capture groups ($1, $2, etc.). Use $$ to insert a literal dollar sign
|
|
1992
|
+
is_regexp: If True, treat search_expression as a regular expression. If False, treat as literal string.
|
|
1993
|
+
version: The version of the artifact to operate on. Can be an integer version number as a string or 'latest'. Defaults to 'latest'.
|
|
1994
|
+
regexp_flags: Flags for regex behavior (only used when is_regexp=true).
|
|
1995
|
+
String of letters: 'g' (global/replace-all), 'i' (case-insensitive), 'm' (multiline), 's' (dotall).
|
|
1996
|
+
Defaults to empty string (no flags).
|
|
1997
|
+
new_filename: Optional. If provided, saves the result as a new artifact with this name.
|
|
1998
|
+
new_description: Optional. Description for the new/updated artifact.
|
|
1999
|
+
|
|
2000
|
+
Returns:
|
|
2001
|
+
A dictionary containing the result status, filename, version, match count, and any error messages.
|
|
2002
|
+
"""
|
|
2003
|
+
if not tool_context:
|
|
2004
|
+
return {
|
|
2005
|
+
"status": "error",
|
|
2006
|
+
"filename": filename,
|
|
2007
|
+
"message": "ToolContext is missing, cannot perform search and replace.",
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
log_identifier = (
|
|
2011
|
+
f"[BuiltinArtifactTool:artifact_search_and_replace_regex:{filename}:{version}]"
|
|
2012
|
+
)
|
|
2013
|
+
log.debug("%s Processing request.", log_identifier)
|
|
2014
|
+
|
|
2015
|
+
# Validate parameter combinations
|
|
2016
|
+
if replacements is not None and (
|
|
2017
|
+
search_expression is not None or replace_expression is not None
|
|
2018
|
+
):
|
|
2019
|
+
return {
|
|
2020
|
+
"status": "error",
|
|
2021
|
+
"filename": filename,
|
|
2022
|
+
"message": "Cannot provide both 'replacements' array and individual 'search_expression'/'replace_expression'. Use one or the other.",
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
if replacements is None and (
|
|
2026
|
+
search_expression is None or replace_expression is None
|
|
2027
|
+
):
|
|
2028
|
+
return {
|
|
2029
|
+
"status": "error",
|
|
2030
|
+
"filename": filename,
|
|
2031
|
+
"message": "Must provide either 'replacements' array or both 'search_expression' and 'replace_expression'.",
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
if replacements is not None:
|
|
2035
|
+
if not isinstance(replacements, list) or len(replacements) == 0:
|
|
2036
|
+
return {
|
|
2037
|
+
"status": "error",
|
|
2038
|
+
"filename": filename,
|
|
2039
|
+
"message": "replacements must be a non-empty array.",
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
# Validate each replacement entry
|
|
2043
|
+
for idx, repl in enumerate(replacements):
|
|
2044
|
+
if not isinstance(repl, dict):
|
|
2045
|
+
return {
|
|
2046
|
+
"status": "error",
|
|
2047
|
+
"filename": filename,
|
|
2048
|
+
"message": f"Replacement at index {idx} must be a dictionary.",
|
|
2049
|
+
}
|
|
2050
|
+
if "search" not in repl or "replace" not in repl or "is_regexp" not in repl:
|
|
2051
|
+
return {
|
|
2052
|
+
"status": "error",
|
|
2053
|
+
"filename": filename,
|
|
2054
|
+
"message": f"Replacement at index {idx} missing required fields: 'search', 'replace', 'is_regexp'.",
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
# Validate inputs for single replacement mode
|
|
2058
|
+
if replacements is None and not search_expression:
|
|
2059
|
+
return {
|
|
2060
|
+
"status": "error",
|
|
2061
|
+
"filename": filename,
|
|
2062
|
+
"message": "search_expression cannot be empty.",
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
# Determine output filename
|
|
2066
|
+
output_filename = new_filename if new_filename else filename
|
|
2067
|
+
|
|
2068
|
+
if new_filename and not is_filename_safe(new_filename):
|
|
2069
|
+
return {
|
|
2070
|
+
"status": "error",
|
|
2071
|
+
"filename": filename,
|
|
2072
|
+
"message": f"Invalid new_filename: '{new_filename}'. Filename must not contain path separators or traversal sequences.",
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
try:
|
|
2076
|
+
inv_context = tool_context._invocation_context
|
|
2077
|
+
artifact_service = inv_context.artifact_service
|
|
2078
|
+
if not artifact_service:
|
|
2079
|
+
raise ValueError("ArtifactService is not available in the context.")
|
|
2080
|
+
|
|
2081
|
+
app_name = inv_context.app_name
|
|
2082
|
+
user_id = inv_context.user_id
|
|
2083
|
+
session_id = get_original_session_id(inv_context)
|
|
2084
|
+
host_component = getattr(inv_context.agent, "host_component", None)
|
|
2085
|
+
|
|
2086
|
+
# Load the source artifact
|
|
2087
|
+
log.debug(
|
|
2088
|
+
"%s Loading artifact '%s' version '%s'.", log_identifier, filename, version
|
|
2089
|
+
)
|
|
2090
|
+
load_result = await load_artifact_content_or_metadata(
|
|
2091
|
+
artifact_service=artifact_service,
|
|
2092
|
+
app_name=app_name,
|
|
2093
|
+
user_id=user_id,
|
|
2094
|
+
session_id=session_id,
|
|
2095
|
+
filename=filename,
|
|
2096
|
+
version=version,
|
|
2097
|
+
return_raw_bytes=True,
|
|
2098
|
+
component=host_component,
|
|
2099
|
+
log_identifier_prefix=log_identifier,
|
|
2100
|
+
)
|
|
2101
|
+
|
|
2102
|
+
if load_result.get("status") != "success":
|
|
2103
|
+
return {
|
|
2104
|
+
"status": "error",
|
|
2105
|
+
"filename": filename,
|
|
2106
|
+
"version": version,
|
|
2107
|
+
"message": f"Failed to load artifact: {load_result.get('message', 'Unknown error')}",
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
source_bytes = load_result.get("raw_bytes")
|
|
2111
|
+
source_mime_type = load_result.get("mime_type", "application/octet-stream")
|
|
2112
|
+
actual_version = load_result.get("version", version)
|
|
2113
|
+
|
|
2114
|
+
# Verify it's a text-based artifact
|
|
2115
|
+
if not is_text_based_file(source_mime_type, source_bytes):
|
|
2116
|
+
return {
|
|
2117
|
+
"status": "error",
|
|
2118
|
+
"filename": filename,
|
|
2119
|
+
"version": actual_version,
|
|
2120
|
+
"message": f"Cannot perform search and replace on binary artifact of type '{source_mime_type}'. This tool only works with text-based content.",
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
# Decode the content
|
|
2124
|
+
try:
|
|
2125
|
+
original_content = source_bytes.decode("utf-8")
|
|
2126
|
+
except UnicodeDecodeError as decode_err:
|
|
2127
|
+
log.error(
|
|
2128
|
+
"%s Failed to decode artifact content as UTF-8: %s",
|
|
2129
|
+
log_identifier,
|
|
2130
|
+
decode_err,
|
|
2131
|
+
)
|
|
2132
|
+
return {
|
|
2133
|
+
"status": "error",
|
|
2134
|
+
"filename": filename,
|
|
2135
|
+
"version": actual_version,
|
|
2136
|
+
"message": f"Failed to decode artifact content as UTF-8: {decode_err}",
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
# Perform the search and replace
|
|
2140
|
+
if replacements:
|
|
2141
|
+
# Batch mode
|
|
2142
|
+
log.info(
|
|
2143
|
+
"%s Processing batch of %d replacements.",
|
|
2144
|
+
log_identifier,
|
|
2145
|
+
len(replacements),
|
|
2146
|
+
)
|
|
2147
|
+
|
|
2148
|
+
current_content = original_content
|
|
2149
|
+
replacement_results = []
|
|
2150
|
+
total_matches = 0
|
|
2151
|
+
|
|
2152
|
+
for idx, repl in enumerate(replacements):
|
|
2153
|
+
search_expr = repl["search"]
|
|
2154
|
+
replace_expr = repl["replace"]
|
|
2155
|
+
is_regex = repl["is_regexp"]
|
|
2156
|
+
regex_flags = repl.get("regexp_flags", "")
|
|
2157
|
+
|
|
2158
|
+
# Perform replacement on current state (with strict validation for batch mode)
|
|
2159
|
+
new_content, match_count, error_msg = _perform_single_replacement(
|
|
2160
|
+
current_content,
|
|
2161
|
+
search_expr,
|
|
2162
|
+
replace_expr,
|
|
2163
|
+
is_regex,
|
|
2164
|
+
regex_flags,
|
|
2165
|
+
log_identifier,
|
|
2166
|
+
strict_match_validation=True,
|
|
2167
|
+
)
|
|
2168
|
+
|
|
2169
|
+
if error_msg:
|
|
2170
|
+
# Rollback - return error with details
|
|
2171
|
+
log.warning(
|
|
2172
|
+
"%s Batch replacement failed at index %d: %s",
|
|
2173
|
+
log_identifier,
|
|
2174
|
+
idx,
|
|
2175
|
+
error_msg,
|
|
2176
|
+
)
|
|
2177
|
+
|
|
2178
|
+
# Mark all as skipped
|
|
2179
|
+
all_results = replacement_results + [
|
|
2180
|
+
{
|
|
2181
|
+
"search": repl["search"],
|
|
2182
|
+
"match_count": match_count,
|
|
2183
|
+
"status": "error",
|
|
2184
|
+
"error": error_msg,
|
|
2185
|
+
}
|
|
2186
|
+
]
|
|
2187
|
+
# Add remaining as skipped
|
|
2188
|
+
for i in range(idx + 1, len(replacements)):
|
|
2189
|
+
all_results.append(
|
|
2190
|
+
{
|
|
2191
|
+
"search": replacements[i]["search"],
|
|
2192
|
+
"match_count": 0,
|
|
2193
|
+
"status": "skipped",
|
|
2194
|
+
}
|
|
2195
|
+
)
|
|
2196
|
+
|
|
2197
|
+
return {
|
|
2198
|
+
"status": "error",
|
|
2199
|
+
"filename": filename,
|
|
2200
|
+
"version": actual_version,
|
|
2201
|
+
"message": f"Batch replacement failed: No changes applied due to error in replacement {idx + 1}",
|
|
2202
|
+
"replacement_results": all_results,
|
|
2203
|
+
"failed_replacement": {
|
|
2204
|
+
"index": idx,
|
|
2205
|
+
"search": search_expr,
|
|
2206
|
+
"error": error_msg,
|
|
2207
|
+
},
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
# Success - update state and continue
|
|
2211
|
+
current_content = new_content
|
|
2212
|
+
total_matches += match_count
|
|
2213
|
+
replacement_results.append(
|
|
2214
|
+
{
|
|
2215
|
+
"search": search_expr,
|
|
2216
|
+
"match_count": match_count,
|
|
2217
|
+
"status": "success",
|
|
2218
|
+
}
|
|
2219
|
+
)
|
|
2220
|
+
|
|
2221
|
+
log.debug(
|
|
2222
|
+
"%s Replacement %d/%d succeeded: %d matches",
|
|
2223
|
+
log_identifier,
|
|
2224
|
+
idx + 1,
|
|
2225
|
+
len(replacements),
|
|
2226
|
+
match_count,
|
|
2227
|
+
)
|
|
2228
|
+
|
|
2229
|
+
# All replacements succeeded
|
|
2230
|
+
final_content = current_content
|
|
2231
|
+
total_replacements = len(replacements)
|
|
2232
|
+
|
|
2233
|
+
log.info(
|
|
2234
|
+
"%s Batch replacement succeeded: %d operations, %d total matches",
|
|
2235
|
+
log_identifier,
|
|
2236
|
+
total_replacements,
|
|
2237
|
+
total_matches,
|
|
2238
|
+
)
|
|
2239
|
+
|
|
2240
|
+
else:
|
|
2241
|
+
# Single replacement mode (backward compatible)
|
|
2242
|
+
final_content, match_count, error_msg = _perform_single_replacement(
|
|
2243
|
+
original_content,
|
|
2244
|
+
search_expression,
|
|
2245
|
+
replace_expression,
|
|
2246
|
+
is_regexp,
|
|
2247
|
+
regexp_flags,
|
|
2248
|
+
log_identifier,
|
|
2249
|
+
)
|
|
2250
|
+
|
|
2251
|
+
if error_msg:
|
|
2252
|
+
# Check if it's a "no matches" error specifically
|
|
2253
|
+
if match_count == 0 and "No matches found" in error_msg:
|
|
2254
|
+
return {
|
|
2255
|
+
"status": "no_matches",
|
|
2256
|
+
"filename": filename,
|
|
2257
|
+
"version": actual_version,
|
|
2258
|
+
"match_count": 0,
|
|
2259
|
+
"message": f"No matches found for pattern '{search_expression}'. Artifact not modified.",
|
|
2260
|
+
}
|
|
2261
|
+
else:
|
|
2262
|
+
return {
|
|
2263
|
+
"status": "error",
|
|
2264
|
+
"filename": filename,
|
|
2265
|
+
"version": actual_version,
|
|
2266
|
+
"message": error_msg,
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
total_replacements = 1
|
|
2270
|
+
total_matches = match_count
|
|
2271
|
+
replacement_results = None
|
|
2272
|
+
|
|
2273
|
+
# Prepare metadata for the new/updated artifact
|
|
2274
|
+
if replacements:
|
|
2275
|
+
new_metadata = {
|
|
2276
|
+
"source": f"artifact_search_and_replace_regex (batch) from '{filename}' v{actual_version}",
|
|
2277
|
+
"total_replacements": total_replacements,
|
|
2278
|
+
"total_matches": total_matches,
|
|
2279
|
+
}
|
|
2280
|
+
else:
|
|
2281
|
+
new_metadata = {
|
|
2282
|
+
"source": f"artifact_search_and_replace_regex from '{filename}' v{actual_version}",
|
|
2283
|
+
"search_expression": search_expression,
|
|
2284
|
+
"replace_expression": replace_expression,
|
|
2285
|
+
"is_regexp": is_regexp,
|
|
2286
|
+
"match_count": match_count,
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
if regexp_flags and is_regexp:
|
|
2290
|
+
new_metadata["regexp_flags"] = regexp_flags
|
|
2291
|
+
|
|
2292
|
+
if new_description:
|
|
2293
|
+
new_metadata["description"] = new_description
|
|
2294
|
+
elif not new_filename:
|
|
2295
|
+
# If updating the same artifact, preserve original description if available
|
|
2296
|
+
try:
|
|
2297
|
+
metadata_load_result = await load_artifact_content_or_metadata(
|
|
2298
|
+
artifact_service=artifact_service,
|
|
2299
|
+
app_name=app_name,
|
|
2300
|
+
user_id=user_id,
|
|
2301
|
+
session_id=session_id,
|
|
2302
|
+
filename=filename,
|
|
2303
|
+
version=actual_version,
|
|
2304
|
+
load_metadata_only=True,
|
|
2305
|
+
component=host_component,
|
|
2306
|
+
log_identifier_prefix=log_identifier,
|
|
2307
|
+
)
|
|
2308
|
+
if metadata_load_result.get("status") == "success":
|
|
2309
|
+
original_metadata = metadata_load_result.get("metadata", {})
|
|
2310
|
+
if "description" in original_metadata:
|
|
2311
|
+
new_metadata["description"] = original_metadata["description"]
|
|
2312
|
+
except Exception as meta_err:
|
|
2313
|
+
log.warning(
|
|
2314
|
+
"%s Could not load original metadata to preserve description: %s",
|
|
2315
|
+
log_identifier,
|
|
2316
|
+
meta_err,
|
|
2317
|
+
)
|
|
2318
|
+
|
|
2319
|
+
# Save the result
|
|
2320
|
+
new_content_bytes = final_content.encode("utf-8")
|
|
2321
|
+
schema_max_keys = (
|
|
2322
|
+
host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
|
|
2323
|
+
if host_component
|
|
2324
|
+
else DEFAULT_SCHEMA_MAX_KEYS
|
|
2325
|
+
)
|
|
2326
|
+
|
|
2327
|
+
save_result = await save_artifact_with_metadata(
|
|
2328
|
+
artifact_service=artifact_service,
|
|
2329
|
+
app_name=app_name,
|
|
2330
|
+
user_id=user_id,
|
|
2331
|
+
session_id=session_id,
|
|
2332
|
+
filename=output_filename,
|
|
2333
|
+
content_bytes=new_content_bytes,
|
|
2334
|
+
mime_type=source_mime_type,
|
|
2335
|
+
metadata_dict=new_metadata,
|
|
2336
|
+
timestamp=datetime.now(timezone.utc),
|
|
2337
|
+
schema_max_keys=schema_max_keys,
|
|
2338
|
+
tool_context=tool_context,
|
|
2339
|
+
)
|
|
2340
|
+
|
|
2341
|
+
if save_result.get("status") not in ["success", "partial_success"]:
|
|
2342
|
+
log.error(
|
|
2343
|
+
"%s Failed to save modified artifact: %s",
|
|
2344
|
+
log_identifier,
|
|
2345
|
+
save_result.get("message"),
|
|
2346
|
+
)
|
|
2347
|
+
return {
|
|
2348
|
+
"status": "error",
|
|
2349
|
+
"filename": filename,
|
|
2350
|
+
"version": actual_version,
|
|
2351
|
+
"message": f"Search and replace succeeded, but failed to save result: {save_result.get('message')}",
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
result_version = save_result.get("data_version")
|
|
2355
|
+
log.info(
|
|
2356
|
+
"%s Successfully saved modified artifact '%s' as version %s.",
|
|
2357
|
+
log_identifier,
|
|
2358
|
+
output_filename,
|
|
2359
|
+
result_version,
|
|
2360
|
+
)
|
|
2361
|
+
|
|
2362
|
+
# Return appropriate response based on mode
|
|
2363
|
+
if replacements:
|
|
2364
|
+
return {
|
|
2365
|
+
"status": "success",
|
|
2366
|
+
"source_filename": filename,
|
|
2367
|
+
"source_version": actual_version,
|
|
2368
|
+
"output_filename": output_filename,
|
|
2369
|
+
"output_version": result_version,
|
|
2370
|
+
"total_replacements": total_replacements,
|
|
2371
|
+
"replacement_results": replacement_results,
|
|
2372
|
+
"total_matches": total_matches,
|
|
2373
|
+
"message": f"Batch replacement completed: {total_replacements} operations, {total_matches} total matches",
|
|
2374
|
+
}
|
|
2375
|
+
else:
|
|
2376
|
+
# Compute replacements_made for backward compatibility
|
|
2377
|
+
# For literal replacements, all matches are replaced
|
|
2378
|
+
# For regex without 'g' flag, only first match is replaced
|
|
2379
|
+
global_replace = "g" in (regexp_flags or "")
|
|
2380
|
+
replacements_made = (
|
|
2381
|
+
match_count if not is_regexp or global_replace else min(match_count, 1)
|
|
2382
|
+
)
|
|
2383
|
+
|
|
2384
|
+
return {
|
|
2385
|
+
"status": "success",
|
|
2386
|
+
"source_filename": filename,
|
|
2387
|
+
"source_version": actual_version,
|
|
2388
|
+
"output_filename": output_filename,
|
|
2389
|
+
"output_version": result_version,
|
|
2390
|
+
"match_count": match_count,
|
|
2391
|
+
"replacements_made": replacements_made,
|
|
2392
|
+
"message": f"Successfully performed {'regex' if is_regexp else 'literal'} search and replace. "
|
|
2393
|
+
f"Found {match_count} match(es), saved result as '{output_filename}' v{result_version}.",
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
except FileNotFoundError as fnf_err:
|
|
2397
|
+
log.warning("%s Artifact not found: %s", log_identifier, fnf_err)
|
|
2398
|
+
return {
|
|
2399
|
+
"status": "error",
|
|
2400
|
+
"filename": filename,
|
|
2401
|
+
"version": version,
|
|
2402
|
+
"message": f"Artifact not found: {fnf_err}",
|
|
2403
|
+
}
|
|
2404
|
+
except Exception as e:
|
|
2405
|
+
log.exception(
|
|
2406
|
+
"%s Unexpected error during search and replace: %s", log_identifier, e
|
|
2407
|
+
)
|
|
2408
|
+
return {
|
|
2409
|
+
"status": "error",
|
|
2410
|
+
"filename": filename,
|
|
2411
|
+
"version": version,
|
|
2412
|
+
"message": f"Unexpected error: {e}",
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
|
|
2416
|
+
artifact_search_and_replace_regex_tool_def = BuiltinTool(
|
|
2417
|
+
name="artifact_search_and_replace_regex",
|
|
2418
|
+
implementation=artifact_search_and_replace_regex,
|
|
2419
|
+
description="Performs search and replace on an artifact's text content using either literal string matching or regular expressions. Supports both single replacements and atomic batch replacements for efficiency.",
|
|
2420
|
+
category="artifact_management",
|
|
2421
|
+
category_name=CATEGORY_NAME,
|
|
2422
|
+
category_description=CATEGORY_DESCRIPTION,
|
|
2423
|
+
required_scopes=["tool:artifact:load", "tool:artifact:create"],
|
|
2424
|
+
parameters=adk_types.Schema(
|
|
2425
|
+
type=adk_types.Type.OBJECT,
|
|
2426
|
+
properties={
|
|
2427
|
+
"filename": adk_types.Schema(
|
|
2428
|
+
type=adk_types.Type.STRING,
|
|
2429
|
+
description="The name of the artifact to search/replace in.",
|
|
2430
|
+
),
|
|
2431
|
+
"search_expression": adk_types.Schema(
|
|
2432
|
+
type=adk_types.Type.STRING,
|
|
2433
|
+
description="The pattern to search for (single replacement mode). If is_regexp is true, this is treated as a regular expression. Otherwise, it's a literal string. Do not use if 'replacements' is provided.",
|
|
2434
|
+
nullable=True,
|
|
2435
|
+
),
|
|
2436
|
+
"replace_expression": adk_types.Schema(
|
|
2437
|
+
type=adk_types.Type.STRING,
|
|
2438
|
+
description="The replacement text (single replacement mode). For regex mode, supports capture group references using $1, $2, etc. Use $$ to insert a literal dollar sign. Do not use if 'replacements' is provided.",
|
|
2439
|
+
nullable=True,
|
|
2440
|
+
),
|
|
2441
|
+
"is_regexp": adk_types.Schema(
|
|
2442
|
+
type=adk_types.Type.BOOLEAN,
|
|
2443
|
+
description="If true, treat search_expression as a regular expression. If false, treat as literal string. Only used in single replacement mode.",
|
|
2444
|
+
nullable=True,
|
|
2445
|
+
),
|
|
2446
|
+
"version": adk_types.Schema(
|
|
2447
|
+
type=adk_types.Type.STRING,
|
|
2448
|
+
description="The version of the artifact to operate on. Can be an integer version number or 'latest'. Defaults to 'latest'.",
|
|
2449
|
+
nullable=True,
|
|
2450
|
+
),
|
|
2451
|
+
"regexp_flags": adk_types.Schema(
|
|
2452
|
+
type=adk_types.Type.STRING,
|
|
2453
|
+
description="Flags for regex behavior (only used when is_regexp=true in single mode). String of letters: 'g' (global/replace all), 'i' (case-insensitive), 'm' (multiline), 's' (dotall). Example: 'gim'. Defaults to empty string.",
|
|
2454
|
+
nullable=True,
|
|
2455
|
+
),
|
|
2456
|
+
"new_filename": adk_types.Schema(
|
|
2457
|
+
type=adk_types.Type.STRING,
|
|
2458
|
+
description="Optional. If provided, saves the result as a new artifact with this name instead of creating a new version of the original.",
|
|
2459
|
+
nullable=True,
|
|
2460
|
+
),
|
|
2461
|
+
"new_description": adk_types.Schema(
|
|
2462
|
+
type=adk_types.Type.STRING,
|
|
2463
|
+
description="Optional. Description for the new/updated artifact.",
|
|
2464
|
+
nullable=True,
|
|
2465
|
+
),
|
|
2466
|
+
"replacements": adk_types.Schema(
|
|
2467
|
+
type=adk_types.Type.ARRAY,
|
|
2468
|
+
items=adk_types.Schema(
|
|
2469
|
+
type=adk_types.Type.OBJECT,
|
|
2470
|
+
properties={
|
|
2471
|
+
"search": adk_types.Schema(
|
|
2472
|
+
type=adk_types.Type.STRING,
|
|
2473
|
+
description="The search pattern (literal string or regex).",
|
|
2474
|
+
),
|
|
2475
|
+
"replace": adk_types.Schema(
|
|
2476
|
+
type=adk_types.Type.STRING,
|
|
2477
|
+
description="The replacement text. For regex mode, supports $1, $2, etc. Use $$ for literal $.",
|
|
2478
|
+
),
|
|
2479
|
+
"is_regexp": adk_types.Schema(
|
|
2480
|
+
type=adk_types.Type.BOOLEAN,
|
|
2481
|
+
description="If true, 'search' is a regex pattern. If false, literal string.",
|
|
2482
|
+
),
|
|
2483
|
+
"regexp_flags": adk_types.Schema(
|
|
2484
|
+
type=adk_types.Type.STRING,
|
|
2485
|
+
description="Flags for regex: 'g' (global), 'i' (case-insensitive), 'm' (multiline), 's' (dotall). Default: ''.",
|
|
2486
|
+
nullable=True,
|
|
2487
|
+
),
|
|
2488
|
+
},
|
|
2489
|
+
required=["search", "replace", "is_regexp"],
|
|
2490
|
+
),
|
|
2491
|
+
description="Optional. Array of replacement operations to perform atomically. Each operation is processed sequentially on the cumulative result. If any operation fails, all changes are rolled back. Do not use with 'search_expression' or 'replace_expression'.",
|
|
2492
|
+
nullable=True,
|
|
2493
|
+
),
|
|
2494
|
+
},
|
|
2495
|
+
required=["filename"],
|
|
2496
|
+
),
|
|
2497
|
+
examples=[],
|
|
2498
|
+
)
|
|
2499
|
+
|
|
2500
|
+
tool_registry.register(artifact_search_and_replace_regex_tool_def)
|