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,2030 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Component class for Gateway implementations in the Solace AI Connector.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import asyncio
|
|
7
|
+
import base64
|
|
8
|
+
import queue
|
|
9
|
+
import re
|
|
10
|
+
import uuid
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any, Dict, Optional, List, Tuple, Union
|
|
13
|
+
|
|
14
|
+
from google.adk.artifacts import BaseArtifactService
|
|
15
|
+
|
|
16
|
+
from ...common.agent_registry import AgentRegistry
|
|
17
|
+
from ...common.sac.sam_component_base import SamComponentBase
|
|
18
|
+
from ...core_a2a.service import CoreA2AService
|
|
19
|
+
from ...agent.adk.services import initialize_artifact_service
|
|
20
|
+
from ...common.services.identity_service import (
|
|
21
|
+
BaseIdentityService,
|
|
22
|
+
create_identity_service,
|
|
23
|
+
)
|
|
24
|
+
from .task_context import TaskContextManager
|
|
25
|
+
from ...common.a2a.types import ContentPart
|
|
26
|
+
from ...common.utils.rbac_utils import validate_agent_access
|
|
27
|
+
from a2a.types import (
|
|
28
|
+
Message as A2AMessage,
|
|
29
|
+
AgentCard,
|
|
30
|
+
JSONRPCResponse,
|
|
31
|
+
Task,
|
|
32
|
+
TaskStatusUpdateEvent,
|
|
33
|
+
TaskArtifactUpdateEvent,
|
|
34
|
+
JSONRPCError,
|
|
35
|
+
TextPart,
|
|
36
|
+
DataPart,
|
|
37
|
+
FilePart,
|
|
38
|
+
FileWithBytes,
|
|
39
|
+
Artifact as A2AArtifact,
|
|
40
|
+
)
|
|
41
|
+
from ...common import a2a
|
|
42
|
+
from ...common.utils.embeds import (
|
|
43
|
+
resolve_embeds_in_string,
|
|
44
|
+
evaluate_embed,
|
|
45
|
+
LATE_EMBED_TYPES,
|
|
46
|
+
EARLY_EMBED_TYPES,
|
|
47
|
+
resolve_embeds_recursively_in_string,
|
|
48
|
+
)
|
|
49
|
+
from ...common.utils.embeds.types import ResolutionMode
|
|
50
|
+
from ...agent.utils.artifact_helpers import (
|
|
51
|
+
load_artifact_content_or_metadata,
|
|
52
|
+
format_artifact_uri,
|
|
53
|
+
)
|
|
54
|
+
from ...common.utils.mime_helpers import is_text_based_mime_type
|
|
55
|
+
from solace_ai_connector.common.message import (
|
|
56
|
+
Message as SolaceMessage,
|
|
57
|
+
)
|
|
58
|
+
from solace_ai_connector.common.event import Event, EventType
|
|
59
|
+
from abc import abstractmethod
|
|
60
|
+
|
|
61
|
+
from ...common.middleware.registry import MiddlewareRegistry
|
|
62
|
+
|
|
63
|
+
log = logging.getLogger(__name__)
|
|
64
|
+
|
|
65
|
+
info = {
|
|
66
|
+
"class_name": "BaseGatewayComponent",
|
|
67
|
+
"description": (
|
|
68
|
+
"Abstract base component for A2A gateways. Handles common service "
|
|
69
|
+
"initialization and provides a framework for platform-specific logic. "
|
|
70
|
+
"Configuration is typically derived from the parent BaseGatewayApp's app_config."
|
|
71
|
+
),
|
|
72
|
+
"config_parameters": [],
|
|
73
|
+
"input_schema": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"description": "Not typically used directly; component reacts to events from its input queue.",
|
|
76
|
+
},
|
|
77
|
+
"output_schema": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"description": "Not typically used directly; component sends data to external systems.",
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class BaseGatewayComponent(SamComponentBase):
|
|
85
|
+
"""
|
|
86
|
+
Abstract base class for Gateway components.
|
|
87
|
+
|
|
88
|
+
Initializes shared services and manages the core lifecycle for processing
|
|
89
|
+
A2A messages and interacting with an external communication platform.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
_RESOLVE_EMBEDS_IN_FINAL_RESPONSE = False
|
|
93
|
+
|
|
94
|
+
def get_config(self, key: str, default: Any = None) -> Any:
|
|
95
|
+
"""
|
|
96
|
+
Overrides the default get_config to first look inside the nested
|
|
97
|
+
'app_config' dictionary that BaseGatewayApp places in the component_config.
|
|
98
|
+
This is the primary way gateway components should access their configuration.
|
|
99
|
+
"""
|
|
100
|
+
if "app_config" in self.component_config:
|
|
101
|
+
value = self.component_config["app_config"].get(key)
|
|
102
|
+
if value is not None:
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
return super().get_config(key, default)
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
resolve_artifact_uris_in_gateway: bool = True,
|
|
110
|
+
supports_inline_artifact_resolution: bool = False,
|
|
111
|
+
filter_tool_data_parts: bool = True,
|
|
112
|
+
**kwargs: Any
|
|
113
|
+
):
|
|
114
|
+
"""
|
|
115
|
+
Initialize the BaseGatewayComponent.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
resolve_artifact_uris_in_gateway: If True, resolves artifact URIs before sending to external.
|
|
119
|
+
supports_inline_artifact_resolution: If True, SIGNAL_ARTIFACT_RETURN embeds are converted
|
|
120
|
+
to FileParts during embed resolution. If False (default), signals are passed through
|
|
121
|
+
for the gateway to handle manually. Use False for legacy gateways (e.g., Slack),
|
|
122
|
+
True for modern gateways that support inline artifact rendering (e.g., HTTP SSE).
|
|
123
|
+
filter_tool_data_parts: If True (default), filters out tool-related DataParts (tool_call,
|
|
124
|
+
tool_result, etc.) from final Task messages before sending to gateway. Use True for
|
|
125
|
+
gateways that don't want to display internal tool execution details (e.g., Slack),
|
|
126
|
+
False for gateways that display all parts (e.g., HTTP SSE Web UI).
|
|
127
|
+
**kwargs: Additional arguments passed to parent class.
|
|
128
|
+
"""
|
|
129
|
+
super().__init__(info, **kwargs)
|
|
130
|
+
self.resolve_artifact_uris_in_gateway = resolve_artifact_uris_in_gateway
|
|
131
|
+
self.supports_inline_artifact_resolution = supports_inline_artifact_resolution
|
|
132
|
+
self.filter_tool_data_parts = filter_tool_data_parts
|
|
133
|
+
log.info("%s Initializing Base Gateway Component...", self.log_identifier)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
# Note: self.namespace and self.max_message_size_bytes are initialized in SamComponentBase
|
|
137
|
+
self.gateway_id: str = self.get_config("gateway_id")
|
|
138
|
+
if not self.gateway_id:
|
|
139
|
+
raise ValueError("Gateway ID must be configured in the app_config.")
|
|
140
|
+
|
|
141
|
+
self.enable_embed_resolution: bool = self.get_config(
|
|
142
|
+
"enable_embed_resolution", True
|
|
143
|
+
)
|
|
144
|
+
self.gateway_max_artifact_resolve_size_bytes: int = self.get_config(
|
|
145
|
+
"gateway_max_artifact_resolve_size_bytes"
|
|
146
|
+
)
|
|
147
|
+
self.gateway_recursive_embed_depth: int = self.get_config(
|
|
148
|
+
"gateway_recursive_embed_depth"
|
|
149
|
+
)
|
|
150
|
+
self.artifact_handling_mode: str = self.get_config(
|
|
151
|
+
"artifact_handling_mode", "embed"
|
|
152
|
+
)
|
|
153
|
+
_ = self.get_config("artifact_service")
|
|
154
|
+
|
|
155
|
+
log.info(
|
|
156
|
+
"%s Retrieved common configs: Namespace=%s, GatewayID=%s",
|
|
157
|
+
self.log_identifier,
|
|
158
|
+
self.namespace,
|
|
159
|
+
self.gateway_id,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
log.error(
|
|
164
|
+
"%s Failed to retrieve essential configuration: %s",
|
|
165
|
+
self.log_identifier,
|
|
166
|
+
e,
|
|
167
|
+
)
|
|
168
|
+
raise ValueError(f"Configuration retrieval error: {e}") from e
|
|
169
|
+
|
|
170
|
+
self.agent_registry: AgentRegistry = AgentRegistry()
|
|
171
|
+
self.core_a2a_service: CoreA2AService = CoreA2AService(
|
|
172
|
+
agent_registry=self.agent_registry, namespace=self.namespace
|
|
173
|
+
)
|
|
174
|
+
self.shared_artifact_service: Optional[BaseArtifactService] = (
|
|
175
|
+
initialize_artifact_service(self)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self.task_context_manager: TaskContextManager = TaskContextManager()
|
|
179
|
+
self.internal_event_queue: queue.Queue = queue.Queue()
|
|
180
|
+
|
|
181
|
+
identity_service_config = self.get_config("identity_service")
|
|
182
|
+
self.identity_service: Optional[BaseIdentityService] = create_identity_service(
|
|
183
|
+
identity_service_config, self
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
self._config_resolver = MiddlewareRegistry.get_config_resolver()
|
|
187
|
+
log.info(
|
|
188
|
+
"%s Middleware system initialized (using default configuration resolver).",
|
|
189
|
+
self.log_identifier,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
log.info(
|
|
193
|
+
"%s Initialized Base Gateway Component.", self.log_identifier
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
async def authenticate_and_enrich_user(
|
|
197
|
+
self, external_event_data: Any
|
|
198
|
+
) -> Optional[Dict[str, Any]]:
|
|
199
|
+
"""
|
|
200
|
+
Orchestrates the full authentication and identity enrichment flow.
|
|
201
|
+
This method should be called by gateway handlers.
|
|
202
|
+
"""
|
|
203
|
+
log_id_prefix = f"{self.log_identifier}[AuthAndEnrich]"
|
|
204
|
+
|
|
205
|
+
auth_claims = await self._extract_initial_claims(external_event_data)
|
|
206
|
+
if not auth_claims:
|
|
207
|
+
log.warning(
|
|
208
|
+
"%s Initial claims extraction failed or returned no identity.",
|
|
209
|
+
log_id_prefix,
|
|
210
|
+
)
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
if self.identity_service:
|
|
214
|
+
enriched_profile = await self.identity_service.get_user_profile(auth_claims)
|
|
215
|
+
if enriched_profile:
|
|
216
|
+
final_profile = enriched_profile.copy()
|
|
217
|
+
final_profile.update(auth_claims)
|
|
218
|
+
log.info(
|
|
219
|
+
"%s Successfully merged auth claims and enriched profile for user: %s",
|
|
220
|
+
log_id_prefix,
|
|
221
|
+
auth_claims.get("id"),
|
|
222
|
+
)
|
|
223
|
+
return final_profile
|
|
224
|
+
else:
|
|
225
|
+
log.debug(
|
|
226
|
+
"%s IdentityService found no profile for user: %s. Using claims only.",
|
|
227
|
+
log_id_prefix,
|
|
228
|
+
auth_claims.get("id"),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return auth_claims
|
|
232
|
+
|
|
233
|
+
async def submit_a2a_task(
|
|
234
|
+
self,
|
|
235
|
+
target_agent_name: str,
|
|
236
|
+
a2a_parts: List[ContentPart],
|
|
237
|
+
external_request_context: Dict[str, Any],
|
|
238
|
+
user_identity: Any,
|
|
239
|
+
is_streaming: bool = True,
|
|
240
|
+
api_version: str = "v2",
|
|
241
|
+
task_id_override: str | None = None,
|
|
242
|
+
metadata: dict[str, Any] | None = None,
|
|
243
|
+
) -> str:
|
|
244
|
+
log_id_prefix = f"{self.log_identifier}[SubmitA2ATask]"
|
|
245
|
+
log.info(
|
|
246
|
+
"%s Submitting task for user_identity: %s",
|
|
247
|
+
log_id_prefix,
|
|
248
|
+
user_identity.get("id", user_identity),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if not isinstance(user_identity, dict) or not user_identity.get("id"):
|
|
252
|
+
log.error(
|
|
253
|
+
"%s Authentication failed or returned invalid profile. Denying task submission.",
|
|
254
|
+
log_id_prefix,
|
|
255
|
+
)
|
|
256
|
+
raise PermissionError("User not authenticated or identity is invalid.")
|
|
257
|
+
|
|
258
|
+
force_identity_str = self.get_config("force_user_identity")
|
|
259
|
+
if force_identity_str:
|
|
260
|
+
original_identity_id = user_identity.get("id")
|
|
261
|
+
user_identity = {"id": force_identity_str, "name": force_identity_str}
|
|
262
|
+
log.warning(
|
|
263
|
+
"%s DEVELOPMENT MODE: Forcing user_identity from '%s' to '%s'",
|
|
264
|
+
log_id_prefix,
|
|
265
|
+
original_identity_id,
|
|
266
|
+
force_identity_str,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
config_resolver = MiddlewareRegistry.get_config_resolver()
|
|
270
|
+
gateway_context = {"gateway_id": self.gateway_id}
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
user_config = await config_resolver.resolve_user_config(
|
|
274
|
+
user_identity, gateway_context, {}
|
|
275
|
+
)
|
|
276
|
+
log.debug(
|
|
277
|
+
"%s Resolved user configuration for user_identity '%s': %s",
|
|
278
|
+
log_id_prefix,
|
|
279
|
+
user_identity.get("id"),
|
|
280
|
+
{k: v for k, v in user_config.items() if not k.startswith("_")},
|
|
281
|
+
)
|
|
282
|
+
except Exception as config_err:
|
|
283
|
+
log.exception(
|
|
284
|
+
"%s Error resolving user configuration for '%s': %s. Proceeding with default configuration.",
|
|
285
|
+
log_id_prefix,
|
|
286
|
+
user_identity.get("id"),
|
|
287
|
+
config_err,
|
|
288
|
+
)
|
|
289
|
+
user_config = {}
|
|
290
|
+
|
|
291
|
+
user_config["user_profile"] = user_identity
|
|
292
|
+
|
|
293
|
+
# Validate user has permission to access this target agent
|
|
294
|
+
validate_agent_access(
|
|
295
|
+
user_config=user_config,
|
|
296
|
+
target_agent_name=target_agent_name,
|
|
297
|
+
validation_context={
|
|
298
|
+
"gateway_id": self.gateway_id,
|
|
299
|
+
"source": "gateway_request",
|
|
300
|
+
},
|
|
301
|
+
log_identifier=log_id_prefix,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
external_request_context["user_identity"] = user_identity
|
|
305
|
+
external_request_context["a2a_user_config"] = user_config
|
|
306
|
+
external_request_context["api_version"] = api_version
|
|
307
|
+
external_request_context["is_streaming"] = is_streaming
|
|
308
|
+
log.debug(
|
|
309
|
+
"%s Stored user_identity, configuration, api_version (%s), and is_streaming (%s) in external_request_context.",
|
|
310
|
+
log_id_prefix,
|
|
311
|
+
api_version,
|
|
312
|
+
is_streaming,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
now = datetime.now(timezone.utc)
|
|
316
|
+
timestamp_str = now.isoformat()
|
|
317
|
+
timestamp_header_part = TextPart(
|
|
318
|
+
text=f"Request received by gateway at: {timestamp_str}"
|
|
319
|
+
)
|
|
320
|
+
if not isinstance(a2a_parts, list):
|
|
321
|
+
a2a_parts = list(a2a_parts)
|
|
322
|
+
a2a_parts.insert(0, timestamp_header_part)
|
|
323
|
+
log.debug("%s Prepended timestamp to a2a_parts.", log_id_prefix)
|
|
324
|
+
|
|
325
|
+
a2a_session_id = external_request_context.get("a2a_session_id")
|
|
326
|
+
user_id_for_a2a = external_request_context.get(
|
|
327
|
+
"user_id_for_a2a", user_identity.get("id")
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
system_purpose = self.get_config("system_purpose", "")
|
|
331
|
+
response_format = self.get_config("response_format", "")
|
|
332
|
+
|
|
333
|
+
if not a2a_session_id:
|
|
334
|
+
a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
|
|
335
|
+
log.warning(
|
|
336
|
+
"%s 'a2a_session_id' not found in external_request_context, generated: %s",
|
|
337
|
+
self.log_identifier,
|
|
338
|
+
a2a_session_id,
|
|
339
|
+
)
|
|
340
|
+
external_request_context["a2a_session_id"] = a2a_session_id
|
|
341
|
+
|
|
342
|
+
a2a_metadata = {
|
|
343
|
+
"agent_name": target_agent_name,
|
|
344
|
+
"system_purpose": system_purpose,
|
|
345
|
+
"response_format": response_format,
|
|
346
|
+
}
|
|
347
|
+
invoked_artifacts = external_request_context.get("invoked_with_artifacts")
|
|
348
|
+
if invoked_artifacts:
|
|
349
|
+
a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
|
|
350
|
+
log.debug(
|
|
351
|
+
"%s Found %d artifact identifiers in external context to pass to agent.",
|
|
352
|
+
log_id_prefix,
|
|
353
|
+
len(invoked_artifacts),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
if metadata:
|
|
357
|
+
a2a_metadata.update(metadata)
|
|
358
|
+
|
|
359
|
+
# This correlation ID is used by the gateway to track the task
|
|
360
|
+
if task_id_override:
|
|
361
|
+
task_id = task_id_override
|
|
362
|
+
else:
|
|
363
|
+
task_id = f"gdk-task-{uuid.uuid4().hex}"
|
|
364
|
+
|
|
365
|
+
prepared_a2a_parts = await self._prepare_parts_for_publishing(
|
|
366
|
+
parts=a2a_parts,
|
|
367
|
+
user_id=user_id_for_a2a,
|
|
368
|
+
session_id=a2a_session_id,
|
|
369
|
+
target_agent_name=target_agent_name,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
a2a_message = a2a.create_user_message(
|
|
373
|
+
parts=prepared_a2a_parts,
|
|
374
|
+
metadata=a2a_metadata,
|
|
375
|
+
context_id=a2a_session_id,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
if is_streaming:
|
|
379
|
+
a2a_request = a2a.create_send_streaming_message_request(
|
|
380
|
+
message=a2a_message, task_id=task_id
|
|
381
|
+
)
|
|
382
|
+
else:
|
|
383
|
+
a2a_request = a2a.create_send_message_request(
|
|
384
|
+
message=a2a_message, task_id=task_id
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
payload = a2a_request.model_dump(by_alias=True, exclude_none=True)
|
|
388
|
+
target_topic = a2a.get_agent_request_topic(self.namespace, target_agent_name)
|
|
389
|
+
|
|
390
|
+
user_properties = {
|
|
391
|
+
"clientId": self.gateway_id,
|
|
392
|
+
"userId": user_id_for_a2a,
|
|
393
|
+
}
|
|
394
|
+
if user_config:
|
|
395
|
+
user_properties["a2aUserConfig"] = user_config
|
|
396
|
+
|
|
397
|
+
# Enterprise feature: Add signed user claims if trust manager available
|
|
398
|
+
if hasattr(self, "trust_manager") and self.trust_manager:
|
|
399
|
+
log.debug(
|
|
400
|
+
"%s Attempting to sign user claims for task %s",
|
|
401
|
+
log_id_prefix,
|
|
402
|
+
task_id,
|
|
403
|
+
)
|
|
404
|
+
try:
|
|
405
|
+
auth_token = self.trust_manager.sign_user_claims(
|
|
406
|
+
user_info=user_identity, task_id=task_id
|
|
407
|
+
)
|
|
408
|
+
user_properties["authToken"] = auth_token
|
|
409
|
+
log.debug(
|
|
410
|
+
"%s Successfully signed user claims for task %s",
|
|
411
|
+
log_id_prefix,
|
|
412
|
+
task_id,
|
|
413
|
+
)
|
|
414
|
+
except Exception as e:
|
|
415
|
+
log.error(
|
|
416
|
+
"%s Failed to sign user claims for task %s: %s",
|
|
417
|
+
log_id_prefix,
|
|
418
|
+
task_id,
|
|
419
|
+
e,
|
|
420
|
+
)
|
|
421
|
+
# Continue without token - enterprise feature is optional
|
|
422
|
+
else:
|
|
423
|
+
log.debug(
|
|
424
|
+
"%s Trust Manager not available, proceeding without authentication token",
|
|
425
|
+
log_id_prefix,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
user_properties["replyTo"] = a2a.get_gateway_response_topic(
|
|
429
|
+
self.namespace, self.gateway_id, task_id
|
|
430
|
+
)
|
|
431
|
+
if is_streaming:
|
|
432
|
+
user_properties["a2aStatusTopic"] = a2a.get_gateway_status_topic(
|
|
433
|
+
self.namespace, self.gateway_id, task_id
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
self.task_context_manager.store_context(task_id, external_request_context)
|
|
437
|
+
log.info("%s Stored external context for task_id: %s", log_id_prefix, task_id)
|
|
438
|
+
|
|
439
|
+
self.publish_a2a_message(
|
|
440
|
+
payload=payload, topic=target_topic, user_properties=user_properties
|
|
441
|
+
)
|
|
442
|
+
log.info(
|
|
443
|
+
"%s Submitted A2A task %s to agent %s. Streaming: %s",
|
|
444
|
+
log_id_prefix,
|
|
445
|
+
task_id,
|
|
446
|
+
target_agent_name,
|
|
447
|
+
is_streaming,
|
|
448
|
+
)
|
|
449
|
+
return task_id
|
|
450
|
+
|
|
451
|
+
def _handle_message(self, message: SolaceMessage, topic: str) -> None:
|
|
452
|
+
"""
|
|
453
|
+
Override to use queue-based pattern instead of direct async.
|
|
454
|
+
|
|
455
|
+
Gateway uses an internal queue for message processing to ensure
|
|
456
|
+
strict ordering and backpressure handling.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
message: The Solace message
|
|
460
|
+
topic: The topic the message was received on
|
|
461
|
+
"""
|
|
462
|
+
log.debug(
|
|
463
|
+
"%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
|
|
464
|
+
self.log_identifier,
|
|
465
|
+
topic,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
msg_data_for_processor = {
|
|
470
|
+
"topic": topic,
|
|
471
|
+
"payload": message.get_payload(),
|
|
472
|
+
"user_properties": message.get_user_properties(),
|
|
473
|
+
"_original_broker_message": message,
|
|
474
|
+
}
|
|
475
|
+
self.internal_event_queue.put_nowait(msg_data_for_processor)
|
|
476
|
+
except queue.Full:
|
|
477
|
+
log.error(
|
|
478
|
+
"%s Internal event queue full. Cannot bridge message.",
|
|
479
|
+
self.log_identifier,
|
|
480
|
+
)
|
|
481
|
+
raise
|
|
482
|
+
except Exception as e:
|
|
483
|
+
log.exception(
|
|
484
|
+
"%s Error bridging message to internal queue: %s",
|
|
485
|
+
self.log_identifier,
|
|
486
|
+
e,
|
|
487
|
+
)
|
|
488
|
+
raise
|
|
489
|
+
|
|
490
|
+
async def _handle_message_async(self, message, topic: str) -> None:
|
|
491
|
+
"""
|
|
492
|
+
Not used by gateway - we override _handle_message() instead.
|
|
493
|
+
|
|
494
|
+
This is here to satisfy the abstract method requirement, but the
|
|
495
|
+
gateway uses the queue-based pattern via _handle_message() override.
|
|
496
|
+
"""
|
|
497
|
+
raise NotImplementedError(
|
|
498
|
+
"Gateway uses queue-based message handling via _handle_message() override"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
async def _handle_resolved_signals(
|
|
502
|
+
self,
|
|
503
|
+
external_request_context: Dict,
|
|
504
|
+
signals: List[Tuple[None, str, Any]],
|
|
505
|
+
original_rpc_id: Optional[str],
|
|
506
|
+
is_finalizing_context: bool = False,
|
|
507
|
+
):
|
|
508
|
+
log_id_prefix = f"{self.log_identifier}[SignalHandler]"
|
|
509
|
+
if not signals:
|
|
510
|
+
return
|
|
511
|
+
|
|
512
|
+
for signal_tuple in signals:
|
|
513
|
+
if (
|
|
514
|
+
isinstance(signal_tuple, tuple)
|
|
515
|
+
and len(signal_tuple) == 3
|
|
516
|
+
and signal_tuple[0] is None
|
|
517
|
+
):
|
|
518
|
+
signal_type = signal_tuple[1]
|
|
519
|
+
signal_data = signal_tuple[2]
|
|
520
|
+
|
|
521
|
+
if signal_type == "SIGNAL_STATUS_UPDATE":
|
|
522
|
+
status_text = signal_data
|
|
523
|
+
log.info(
|
|
524
|
+
"%s Handling SIGNAL_STATUS_UPDATE: '%s'",
|
|
525
|
+
log_id_prefix,
|
|
526
|
+
status_text,
|
|
527
|
+
)
|
|
528
|
+
if is_finalizing_context:
|
|
529
|
+
log.debug(
|
|
530
|
+
"%s Suppressing SIGNAL_STATUS_UPDATE ('%s') during finalizing context.",
|
|
531
|
+
log_id_prefix,
|
|
532
|
+
status_text,
|
|
533
|
+
)
|
|
534
|
+
continue
|
|
535
|
+
try:
|
|
536
|
+
signal_a2a_message = a2a.create_agent_data_message(
|
|
537
|
+
data={
|
|
538
|
+
"type": "agent_progress_update",
|
|
539
|
+
"status_text": status_text,
|
|
540
|
+
},
|
|
541
|
+
part_metadata={"source": "gateway_signal"},
|
|
542
|
+
)
|
|
543
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
544
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
545
|
+
)
|
|
546
|
+
if not a2a_task_id_for_signal:
|
|
547
|
+
log.error(
|
|
548
|
+
"%s Cannot determine A2A task ID for signal event. Skipping.",
|
|
549
|
+
log_id_prefix,
|
|
550
|
+
)
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
signal_event = a2a.create_status_update(
|
|
554
|
+
task_id=a2a_task_id_for_signal,
|
|
555
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
556
|
+
message=signal_a2a_message,
|
|
557
|
+
is_final=False,
|
|
558
|
+
)
|
|
559
|
+
await self._send_update_to_external(
|
|
560
|
+
external_request_context=external_request_context,
|
|
561
|
+
event_data=signal_event,
|
|
562
|
+
is_final_chunk_of_update=True,
|
|
563
|
+
)
|
|
564
|
+
log.debug(
|
|
565
|
+
"%s Sent status signal as TaskStatusUpdateEvent.",
|
|
566
|
+
log_id_prefix,
|
|
567
|
+
)
|
|
568
|
+
except Exception as e:
|
|
569
|
+
log.exception(
|
|
570
|
+
"%s Error sending status signal: %s", log_id_prefix, e
|
|
571
|
+
)
|
|
572
|
+
elif signal_type == "SIGNAL_ARTIFACT_RETURN":
|
|
573
|
+
# Handle artifact return signal for legacy gateways
|
|
574
|
+
# During finalizing context (final Task), suppress this to avoid duplicates
|
|
575
|
+
# since the same signal might appear in both streaming and final responses
|
|
576
|
+
if is_finalizing_context:
|
|
577
|
+
log.debug(
|
|
578
|
+
"%s Suppressing SIGNAL_ARTIFACT_RETURN during finalizing context to avoid duplicate: %s",
|
|
579
|
+
log_id_prefix,
|
|
580
|
+
signal_data,
|
|
581
|
+
)
|
|
582
|
+
continue
|
|
583
|
+
|
|
584
|
+
log.info(
|
|
585
|
+
"%s Handling SIGNAL_ARTIFACT_RETURN for legacy gateway: %s",
|
|
586
|
+
log_id_prefix,
|
|
587
|
+
signal_data,
|
|
588
|
+
)
|
|
589
|
+
try:
|
|
590
|
+
filename = signal_data.get("filename")
|
|
591
|
+
version = signal_data.get("version")
|
|
592
|
+
|
|
593
|
+
if not filename:
|
|
594
|
+
log.error(
|
|
595
|
+
"%s SIGNAL_ARTIFACT_RETURN missing filename. Skipping.",
|
|
596
|
+
log_id_prefix,
|
|
597
|
+
)
|
|
598
|
+
continue
|
|
599
|
+
|
|
600
|
+
# Load artifact content (not just metadata) for legacy gateways
|
|
601
|
+
# Legacy gateways like Slack need the actual bytes to upload files
|
|
602
|
+
artifact_data = await load_artifact_content_or_metadata(
|
|
603
|
+
self.shared_artifact_service,
|
|
604
|
+
app_name=external_request_context.get(
|
|
605
|
+
"app_name_for_artifacts", self.gateway_id
|
|
606
|
+
),
|
|
607
|
+
user_id=external_request_context.get("user_id_for_artifacts"),
|
|
608
|
+
session_id=external_request_context.get("a2a_session_id"),
|
|
609
|
+
filename=filename,
|
|
610
|
+
version=version,
|
|
611
|
+
load_metadata_only=False, # Load full content for legacy gateways
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
if artifact_data.get("status") != "success":
|
|
615
|
+
log.error(
|
|
616
|
+
"%s Failed to load artifact content for %s v%s",
|
|
617
|
+
log_id_prefix,
|
|
618
|
+
filename,
|
|
619
|
+
version,
|
|
620
|
+
)
|
|
621
|
+
continue
|
|
622
|
+
|
|
623
|
+
# Get content and ensure it's bytes
|
|
624
|
+
content = artifact_data.get("content")
|
|
625
|
+
if not content:
|
|
626
|
+
log.error(
|
|
627
|
+
"%s No content found in artifact %s v%s",
|
|
628
|
+
log_id_prefix,
|
|
629
|
+
filename,
|
|
630
|
+
version,
|
|
631
|
+
)
|
|
632
|
+
continue
|
|
633
|
+
|
|
634
|
+
# Convert to bytes if it's a string (text-based artifacts)
|
|
635
|
+
if isinstance(content, str):
|
|
636
|
+
content_bytes = content.encode("utf-8")
|
|
637
|
+
elif isinstance(content, bytes):
|
|
638
|
+
content_bytes = content
|
|
639
|
+
else:
|
|
640
|
+
log.error(
|
|
641
|
+
"%s Artifact content is neither string nor bytes: %s",
|
|
642
|
+
log_id_prefix,
|
|
643
|
+
type(content),
|
|
644
|
+
)
|
|
645
|
+
continue
|
|
646
|
+
|
|
647
|
+
# Resolve any late embeds inside the artifact content before returning.
|
|
648
|
+
content_bytes = await self._resolve_embeds_in_artifact_content(
|
|
649
|
+
content_bytes=content_bytes,
|
|
650
|
+
mime_type=artifact_data.get("metadata", {}).get(
|
|
651
|
+
"mime_type"
|
|
652
|
+
),
|
|
653
|
+
filename=filename,
|
|
654
|
+
external_request_context=external_request_context,
|
|
655
|
+
log_id_prefix=log_id_prefix,
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# Create FilePart with bytes for legacy gateway to upload
|
|
659
|
+
file_part = a2a.create_file_part_from_bytes(
|
|
660
|
+
content_bytes=content_bytes,
|
|
661
|
+
name=filename,
|
|
662
|
+
mime_type=artifact_data.get("metadata", {}).get(
|
|
663
|
+
"mime_type"
|
|
664
|
+
),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
# Create artifact with the file part
|
|
668
|
+
# Import Part type for wrapping
|
|
669
|
+
from a2a.types import Artifact, Part
|
|
670
|
+
artifact = Artifact(
|
|
671
|
+
artifact_id=str(uuid.uuid4().hex),
|
|
672
|
+
parts=[Part(root=file_part)],
|
|
673
|
+
name=filename,
|
|
674
|
+
description=f"Artifact: {filename}",
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
# Send as TaskArtifactUpdateEvent
|
|
678
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
679
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
if not a2a_task_id_for_signal:
|
|
683
|
+
log.error(
|
|
684
|
+
"%s Cannot determine A2A task ID for artifact signal. Skipping.",
|
|
685
|
+
log_id_prefix,
|
|
686
|
+
)
|
|
687
|
+
continue
|
|
688
|
+
|
|
689
|
+
artifact_event = a2a.create_artifact_update(
|
|
690
|
+
task_id=a2a_task_id_for_signal,
|
|
691
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
692
|
+
artifact=artifact,
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
await self._send_update_to_external(
|
|
696
|
+
external_request_context=external_request_context,
|
|
697
|
+
event_data=artifact_event,
|
|
698
|
+
is_final_chunk_of_update=False,
|
|
699
|
+
)
|
|
700
|
+
log.info(
|
|
701
|
+
"%s Sent artifact signal as TaskArtifactUpdateEvent for %s",
|
|
702
|
+
log_id_prefix,
|
|
703
|
+
filename,
|
|
704
|
+
)
|
|
705
|
+
except Exception as e:
|
|
706
|
+
log.exception(
|
|
707
|
+
"%s Error sending artifact signal: %s", log_id_prefix, e
|
|
708
|
+
)
|
|
709
|
+
elif signal_type == "SIGNAL_ARTIFACT_CREATION_COMPLETE":
|
|
710
|
+
# Handle artifact creation completion for legacy gateways
|
|
711
|
+
# This is similar to SIGNAL_ARTIFACT_RETURN but for newly created artifacts
|
|
712
|
+
log.info(
|
|
713
|
+
"%s Handling SIGNAL_ARTIFACT_CREATION_COMPLETE for legacy gateway: %s",
|
|
714
|
+
log_id_prefix,
|
|
715
|
+
signal_data,
|
|
716
|
+
)
|
|
717
|
+
try:
|
|
718
|
+
filename = signal_data.get("filename")
|
|
719
|
+
version = signal_data.get("version")
|
|
720
|
+
|
|
721
|
+
if not filename:
|
|
722
|
+
log.error(
|
|
723
|
+
"%s SIGNAL_ARTIFACT_CREATION_COMPLETE missing filename. Skipping.",
|
|
724
|
+
log_id_prefix,
|
|
725
|
+
)
|
|
726
|
+
continue
|
|
727
|
+
|
|
728
|
+
# Load artifact content (not just metadata) for legacy gateways
|
|
729
|
+
# Legacy gateways like Slack need the actual bytes to upload files
|
|
730
|
+
artifact_data = await load_artifact_content_or_metadata(
|
|
731
|
+
self.shared_artifact_service,
|
|
732
|
+
app_name=external_request_context.get(
|
|
733
|
+
"app_name_for_artifacts", self.gateway_id
|
|
734
|
+
),
|
|
735
|
+
user_id=external_request_context.get("user_id_for_artifacts"),
|
|
736
|
+
session_id=external_request_context.get("a2a_session_id"),
|
|
737
|
+
filename=filename,
|
|
738
|
+
version=version,
|
|
739
|
+
load_metadata_only=False, # Load full content for legacy gateways
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
if artifact_data.get("status") != "success":
|
|
743
|
+
log.error(
|
|
744
|
+
"%s Failed to load artifact content for %s v%s",
|
|
745
|
+
log_id_prefix,
|
|
746
|
+
filename,
|
|
747
|
+
version,
|
|
748
|
+
)
|
|
749
|
+
continue
|
|
750
|
+
|
|
751
|
+
# Get content and ensure it's bytes
|
|
752
|
+
content = artifact_data.get("content")
|
|
753
|
+
if not content:
|
|
754
|
+
log.error(
|
|
755
|
+
"%s No content found in artifact %s v%s",
|
|
756
|
+
log_id_prefix,
|
|
757
|
+
filename,
|
|
758
|
+
version,
|
|
759
|
+
)
|
|
760
|
+
continue
|
|
761
|
+
|
|
762
|
+
# Convert to bytes if it's a string (text-based artifacts)
|
|
763
|
+
if isinstance(content, str):
|
|
764
|
+
content_bytes = content.encode("utf-8")
|
|
765
|
+
elif isinstance(content, bytes):
|
|
766
|
+
content_bytes = content
|
|
767
|
+
else:
|
|
768
|
+
log.error(
|
|
769
|
+
"%s Artifact content is neither string nor bytes: %s",
|
|
770
|
+
log_id_prefix,
|
|
771
|
+
type(content),
|
|
772
|
+
)
|
|
773
|
+
continue
|
|
774
|
+
|
|
775
|
+
# Create FilePart with bytes for legacy gateway to upload
|
|
776
|
+
file_part = a2a.create_file_part_from_bytes(
|
|
777
|
+
content_bytes=content_bytes,
|
|
778
|
+
name=filename,
|
|
779
|
+
mime_type=signal_data.get("mime_type") or artifact_data.get("metadata", {}).get("mime_type"),
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
# Create artifact with the file part
|
|
783
|
+
# Import Part type for wrapping
|
|
784
|
+
from a2a.types import Artifact, Part
|
|
785
|
+
artifact = Artifact(
|
|
786
|
+
artifact_id=str(uuid.uuid4().hex),
|
|
787
|
+
parts=[Part(root=file_part)],
|
|
788
|
+
name=filename,
|
|
789
|
+
description=f"Artifact: {filename}",
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
# Send as TaskArtifactUpdateEvent
|
|
793
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
794
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
if not a2a_task_id_for_signal:
|
|
798
|
+
log.error(
|
|
799
|
+
"%s Cannot determine A2A task ID for artifact creation signal. Skipping.",
|
|
800
|
+
log_id_prefix,
|
|
801
|
+
)
|
|
802
|
+
continue
|
|
803
|
+
|
|
804
|
+
artifact_event = a2a.create_artifact_update(
|
|
805
|
+
task_id=a2a_task_id_for_signal,
|
|
806
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
807
|
+
artifact=artifact,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
await self._send_update_to_external(
|
|
811
|
+
external_request_context=external_request_context,
|
|
812
|
+
event_data=artifact_event,
|
|
813
|
+
is_final_chunk_of_update=False,
|
|
814
|
+
)
|
|
815
|
+
log.info(
|
|
816
|
+
"%s Sent artifact creation completion as TaskArtifactUpdateEvent for %s",
|
|
817
|
+
log_id_prefix,
|
|
818
|
+
filename,
|
|
819
|
+
)
|
|
820
|
+
except Exception as e:
|
|
821
|
+
log.exception(
|
|
822
|
+
"%s Error sending artifact creation completion signal: %s", log_id_prefix, e
|
|
823
|
+
)
|
|
824
|
+
else:
|
|
825
|
+
log.warning(
|
|
826
|
+
"%s Received unhandled signal type during embed resolution: %s",
|
|
827
|
+
log_id_prefix,
|
|
828
|
+
signal_type,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
async def _resolve_embeds_in_artifact_content(
|
|
832
|
+
self,
|
|
833
|
+
content_bytes: bytes,
|
|
834
|
+
mime_type: Optional[str],
|
|
835
|
+
filename: str,
|
|
836
|
+
external_request_context: Dict[str, Any],
|
|
837
|
+
log_id_prefix: str,
|
|
838
|
+
) -> bytes:
|
|
839
|
+
"""
|
|
840
|
+
Checks if content is text-based and, if so, resolves late embeds within it.
|
|
841
|
+
Returns the (potentially modified) content as bytes.
|
|
842
|
+
"""
|
|
843
|
+
if is_text_based_mime_type(mime_type):
|
|
844
|
+
log.info(
|
|
845
|
+
"%s Artifact '%s' is text-based (%s). Resolving late embeds.",
|
|
846
|
+
log_id_prefix,
|
|
847
|
+
filename,
|
|
848
|
+
mime_type,
|
|
849
|
+
)
|
|
850
|
+
try:
|
|
851
|
+
decoded_content = content_bytes.decode("utf-8")
|
|
852
|
+
|
|
853
|
+
# Construct context and config for the resolver
|
|
854
|
+
embed_eval_context = {
|
|
855
|
+
"artifact_service": self.shared_artifact_service,
|
|
856
|
+
"session_context": {
|
|
857
|
+
"app_name": external_request_context.get(
|
|
858
|
+
"app_name_for_artifacts", self.gateway_id
|
|
859
|
+
),
|
|
860
|
+
"user_id": external_request_context.get(
|
|
861
|
+
"user_id_for_artifacts"
|
|
862
|
+
),
|
|
863
|
+
"session_id": external_request_context.get("a2a_session_id"),
|
|
864
|
+
},
|
|
865
|
+
}
|
|
866
|
+
embed_eval_config = {
|
|
867
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
868
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
resolved_string = await resolve_embeds_recursively_in_string(
|
|
872
|
+
text=decoded_content,
|
|
873
|
+
context=embed_eval_context,
|
|
874
|
+
resolver_func=evaluate_embed,
|
|
875
|
+
types_to_resolve=LATE_EMBED_TYPES,
|
|
876
|
+
resolution_mode=ResolutionMode.RECURSIVE_ARTIFACT_CONTENT,
|
|
877
|
+
log_identifier=f"{log_id_prefix}[RecursiveResolve]",
|
|
878
|
+
config=embed_eval_config,
|
|
879
|
+
max_depth=self.gateway_recursive_embed_depth,
|
|
880
|
+
)
|
|
881
|
+
resolved_bytes = resolved_string.encode("utf-8")
|
|
882
|
+
log.info(
|
|
883
|
+
"%s Successfully resolved embeds in '%s'. New size: %d bytes.",
|
|
884
|
+
log_id_prefix,
|
|
885
|
+
filename,
|
|
886
|
+
len(resolved_bytes),
|
|
887
|
+
)
|
|
888
|
+
return resolved_bytes
|
|
889
|
+
except Exception as resolve_err:
|
|
890
|
+
log.error(
|
|
891
|
+
"%s Failed to resolve embeds within artifact '%s': %s. Returning raw content.",
|
|
892
|
+
log_id_prefix,
|
|
893
|
+
filename,
|
|
894
|
+
resolve_err,
|
|
895
|
+
)
|
|
896
|
+
return content_bytes
|
|
897
|
+
|
|
898
|
+
async def _resolve_uri_in_file_part(
|
|
899
|
+
self, file_part: FilePart, external_request_context: Dict[str, Any]
|
|
900
|
+
):
|
|
901
|
+
"""
|
|
902
|
+
Checks if a FilePart has a resolvable URI and, if so,
|
|
903
|
+
resolves it and mutates the part in-place by calling the common utility.
|
|
904
|
+
After resolving the URI, it also resolves any late embeds within the content.
|
|
905
|
+
"""
|
|
906
|
+
await a2a.resolve_file_part_uri(
|
|
907
|
+
part=file_part,
|
|
908
|
+
artifact_service=self.shared_artifact_service,
|
|
909
|
+
log_identifier=self.log_identifier,
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
# After resolving the URI to get the content, resolve any late embeds inside it.
|
|
913
|
+
if file_part.file and isinstance(file_part.file, FileWithBytes):
|
|
914
|
+
# The content is a base64 encoded string in the `bytes` attribute.
|
|
915
|
+
# We need to decode it to raw bytes for processing.
|
|
916
|
+
try:
|
|
917
|
+
content_bytes = base64.b64decode(file_part.file.bytes)
|
|
918
|
+
except Exception as e:
|
|
919
|
+
log.error(
|
|
920
|
+
"%s Failed to base64 decode file content for embed resolution: %s",
|
|
921
|
+
f"{self.log_identifier}[UriResolve]",
|
|
922
|
+
e,
|
|
923
|
+
)
|
|
924
|
+
return
|
|
925
|
+
|
|
926
|
+
resolved_bytes = await self._resolve_embeds_in_artifact_content(
|
|
927
|
+
content_bytes=content_bytes,
|
|
928
|
+
mime_type=file_part.file.mime_type,
|
|
929
|
+
filename=file_part.file.name,
|
|
930
|
+
external_request_context=external_request_context,
|
|
931
|
+
log_id_prefix=f"{self.log_identifier}[UriResolve]",
|
|
932
|
+
)
|
|
933
|
+
# Re-encode the resolved content back to a base64 string for the FileWithBytes model.
|
|
934
|
+
file_part.file.bytes = base64.b64encode(resolved_bytes).decode("utf-8")
|
|
935
|
+
|
|
936
|
+
async def _resolve_uris_in_parts_list(
|
|
937
|
+
self, parts: List[ContentPart], external_request_context: Dict[str, Any]
|
|
938
|
+
):
|
|
939
|
+
"""Iterates over a list of part objects and resolves any FilePart URIs."""
|
|
940
|
+
if not parts:
|
|
941
|
+
return
|
|
942
|
+
for part in parts:
|
|
943
|
+
if isinstance(part, FilePart):
|
|
944
|
+
await self._resolve_uri_in_file_part(part, external_request_context)
|
|
945
|
+
|
|
946
|
+
async def _resolve_uris_in_payload(
|
|
947
|
+
self, parsed_event: Any, external_request_context: Dict[str, Any]
|
|
948
|
+
):
|
|
949
|
+
"""
|
|
950
|
+
Dispatcher that calls the appropriate targeted URI resolver based on the
|
|
951
|
+
Pydantic model type of the event.
|
|
952
|
+
"""
|
|
953
|
+
parts_to_resolve: List[ContentPart] = []
|
|
954
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent):
|
|
955
|
+
message = a2a.get_message_from_status_update(parsed_event)
|
|
956
|
+
if message:
|
|
957
|
+
parts_to_resolve.extend(a2a.get_parts_from_message(message))
|
|
958
|
+
elif isinstance(parsed_event, TaskArtifactUpdateEvent):
|
|
959
|
+
artifact = a2a.get_artifact_from_artifact_update(parsed_event)
|
|
960
|
+
if artifact:
|
|
961
|
+
parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
|
|
962
|
+
elif isinstance(parsed_event, Task):
|
|
963
|
+
if parsed_event.status and parsed_event.status.message:
|
|
964
|
+
parts_to_resolve.extend(
|
|
965
|
+
a2a.get_parts_from_message(parsed_event.status.message)
|
|
966
|
+
)
|
|
967
|
+
if parsed_event.artifacts:
|
|
968
|
+
for artifact in parsed_event.artifacts:
|
|
969
|
+
parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
|
|
970
|
+
|
|
971
|
+
if parts_to_resolve:
|
|
972
|
+
await self._resolve_uris_in_parts_list(
|
|
973
|
+
parts_to_resolve, external_request_context
|
|
974
|
+
)
|
|
975
|
+
else:
|
|
976
|
+
log.debug(
|
|
977
|
+
"%s Payload type '%s' did not yield any parts for URI resolution. Skipping.",
|
|
978
|
+
self.log_identifier,
|
|
979
|
+
type(parsed_event).__name__,
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
async def _handle_discovery_message(self, payload: Dict) -> bool:
|
|
983
|
+
"""Handles incoming agent discovery messages."""
|
|
984
|
+
try:
|
|
985
|
+
agent_card = AgentCard(**payload)
|
|
986
|
+
self.core_a2a_service.process_discovery_message(agent_card)
|
|
987
|
+
return True
|
|
988
|
+
except Exception as e:
|
|
989
|
+
log.error(
|
|
990
|
+
"%s Failed to process discovery message: %s. Payload: %s",
|
|
991
|
+
self.log_identifier,
|
|
992
|
+
e,
|
|
993
|
+
payload,
|
|
994
|
+
)
|
|
995
|
+
return False
|
|
996
|
+
|
|
997
|
+
async def _prepare_parts_for_publishing(
|
|
998
|
+
self,
|
|
999
|
+
parts: List[ContentPart],
|
|
1000
|
+
user_id: str,
|
|
1001
|
+
session_id: str,
|
|
1002
|
+
target_agent_name: str,
|
|
1003
|
+
) -> List[ContentPart]:
|
|
1004
|
+
"""
|
|
1005
|
+
Prepares message parts for publishing according to the configured artifact_handling_mode
|
|
1006
|
+
by calling the common utility function.
|
|
1007
|
+
"""
|
|
1008
|
+
processed_parts: List[ContentPart] = []
|
|
1009
|
+
for part in parts:
|
|
1010
|
+
if isinstance(part, FilePart):
|
|
1011
|
+
processed_part = await a2a.prepare_file_part_for_publishing(
|
|
1012
|
+
part=part,
|
|
1013
|
+
mode=self.artifact_handling_mode,
|
|
1014
|
+
artifact_service=self.shared_artifact_service,
|
|
1015
|
+
user_id=user_id,
|
|
1016
|
+
session_id=session_id,
|
|
1017
|
+
target_agent_name=target_agent_name,
|
|
1018
|
+
log_identifier=self.log_identifier,
|
|
1019
|
+
)
|
|
1020
|
+
if processed_part:
|
|
1021
|
+
processed_parts.append(processed_part)
|
|
1022
|
+
else:
|
|
1023
|
+
processed_parts.append(part)
|
|
1024
|
+
return processed_parts
|
|
1025
|
+
|
|
1026
|
+
def _should_include_data_part_in_final_output(self, part: Any) -> bool:
|
|
1027
|
+
"""
|
|
1028
|
+
Determines if a DataPart should be included in the final output sent to the gateway.
|
|
1029
|
+
|
|
1030
|
+
This filters out internal/tool-related DataParts that shouldn't be shown to end users.
|
|
1031
|
+
Gateways can override this method for custom filtering logic.
|
|
1032
|
+
|
|
1033
|
+
Args:
|
|
1034
|
+
part: The part to check (expected to be a DataPart)
|
|
1035
|
+
|
|
1036
|
+
Returns:
|
|
1037
|
+
True if the part should be included, False if it should be filtered out
|
|
1038
|
+
"""
|
|
1039
|
+
from a2a.types import DataPart
|
|
1040
|
+
|
|
1041
|
+
if not isinstance(part, DataPart):
|
|
1042
|
+
return True
|
|
1043
|
+
|
|
1044
|
+
# Check if this is a tool result by looking at metadata
|
|
1045
|
+
# Tool results have metadata.tool_name set
|
|
1046
|
+
if part.metadata and part.metadata.get("tool_name"):
|
|
1047
|
+
# This is a tool result - filter it out
|
|
1048
|
+
return False
|
|
1049
|
+
|
|
1050
|
+
# Get the type of the data part
|
|
1051
|
+
data_type = part.data.get("type") if part.data else None
|
|
1052
|
+
|
|
1053
|
+
# Filter out tool-related data parts that are internal
|
|
1054
|
+
tool_related_types = {
|
|
1055
|
+
"tool_call",
|
|
1056
|
+
"tool_result",
|
|
1057
|
+
"tool_error",
|
|
1058
|
+
"tool_execution",
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if data_type in tool_related_types:
|
|
1062
|
+
return False
|
|
1063
|
+
|
|
1064
|
+
# Handle artifact_creation_progress based on gateway capabilities
|
|
1065
|
+
if data_type == "artifact_creation_progress":
|
|
1066
|
+
# For modern gateways (HTTP SSE), keep these to display progress bubbles
|
|
1067
|
+
# For legacy gateways (Slack), filter them out as they'll be converted to FileParts
|
|
1068
|
+
if self.supports_inline_artifact_resolution:
|
|
1069
|
+
return True # Keep for HTTP SSE
|
|
1070
|
+
else:
|
|
1071
|
+
return False # Filter for Slack (will be converted to FileParts instead)
|
|
1072
|
+
|
|
1073
|
+
# Keep user-facing data parts like general progress updates
|
|
1074
|
+
user_facing_types = {
|
|
1075
|
+
"agent_progress_update",
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if data_type in user_facing_types:
|
|
1079
|
+
return True
|
|
1080
|
+
|
|
1081
|
+
# Default: include unknown types (to avoid hiding potentially useful info)
|
|
1082
|
+
return True
|
|
1083
|
+
|
|
1084
|
+
async def _resolve_embeds_and_handle_signals(
|
|
1085
|
+
self,
|
|
1086
|
+
event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
|
|
1087
|
+
external_request_context: Dict[str, Any],
|
|
1088
|
+
a2a_task_id: str,
|
|
1089
|
+
original_rpc_id: Optional[str],
|
|
1090
|
+
is_finalizing_context: bool = False,
|
|
1091
|
+
) -> bool:
|
|
1092
|
+
if not self.enable_embed_resolution:
|
|
1093
|
+
return False
|
|
1094
|
+
|
|
1095
|
+
log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
|
|
1096
|
+
content_modified = False
|
|
1097
|
+
|
|
1098
|
+
embed_eval_context = {
|
|
1099
|
+
"artifact_service": self.shared_artifact_service,
|
|
1100
|
+
"session_context": {
|
|
1101
|
+
"app_name": external_request_context.get(
|
|
1102
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1103
|
+
),
|
|
1104
|
+
"user_id": external_request_context.get("user_id_for_artifacts"),
|
|
1105
|
+
"session_id": external_request_context.get("a2a_session_id"),
|
|
1106
|
+
},
|
|
1107
|
+
}
|
|
1108
|
+
embed_eval_config = {
|
|
1109
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1110
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
|
|
1114
|
+
if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
|
|
1115
|
+
if event_with_parts.status and event_with_parts.status.message:
|
|
1116
|
+
parts_owner = event_with_parts.status.message
|
|
1117
|
+
elif isinstance(event_with_parts, TaskArtifactUpdateEvent):
|
|
1118
|
+
if event_with_parts.artifact:
|
|
1119
|
+
parts_owner = event_with_parts.artifact
|
|
1120
|
+
|
|
1121
|
+
if not (parts_owner and parts_owner.parts):
|
|
1122
|
+
return False
|
|
1123
|
+
|
|
1124
|
+
is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
|
|
1125
|
+
stream_buffer_key = f"{a2a_task_id}_stream_buffer"
|
|
1126
|
+
current_buffer = ""
|
|
1127
|
+
if is_streaming_status_update:
|
|
1128
|
+
current_buffer = (
|
|
1129
|
+
self.task_context_manager.get_context(stream_buffer_key) or ""
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
original_parts: List[ContentPart] = (
|
|
1133
|
+
a2a.get_parts_from_message(parts_owner)
|
|
1134
|
+
if isinstance(parts_owner, A2AMessage)
|
|
1135
|
+
else a2a.get_parts_from_artifact(parts_owner)
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
new_parts: List[ContentPart] = []
|
|
1139
|
+
other_signals = []
|
|
1140
|
+
|
|
1141
|
+
for part in original_parts:
|
|
1142
|
+
if isinstance(part, TextPart) and part.text:
|
|
1143
|
+
text_to_resolve = current_buffer + part.text
|
|
1144
|
+
current_buffer = "" # Buffer is now being processed
|
|
1145
|
+
|
|
1146
|
+
(
|
|
1147
|
+
resolved_text,
|
|
1148
|
+
processed_idx,
|
|
1149
|
+
signals_with_placeholders,
|
|
1150
|
+
) = await resolve_embeds_in_string(
|
|
1151
|
+
text=text_to_resolve,
|
|
1152
|
+
context=embed_eval_context,
|
|
1153
|
+
resolver_func=evaluate_embed,
|
|
1154
|
+
types_to_resolve=LATE_EMBED_TYPES.union({"status_update"}),
|
|
1155
|
+
resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
|
|
1156
|
+
log_identifier=log_id_prefix,
|
|
1157
|
+
config=embed_eval_config,
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
if not signals_with_placeholders:
|
|
1161
|
+
new_parts.append(a2a.create_text_part(text=resolved_text))
|
|
1162
|
+
else:
|
|
1163
|
+
placeholder_map = {p: s for _, s, p in signals_with_placeholders}
|
|
1164
|
+
split_pattern = (
|
|
1165
|
+
f"({'|'.join(re.escape(p) for p in placeholder_map.keys())})"
|
|
1166
|
+
)
|
|
1167
|
+
text_fragments = re.split(split_pattern, resolved_text)
|
|
1168
|
+
|
|
1169
|
+
for i, fragment in enumerate(text_fragments):
|
|
1170
|
+
if not fragment:
|
|
1171
|
+
continue
|
|
1172
|
+
if fragment in placeholder_map:
|
|
1173
|
+
signal_tuple = placeholder_map[fragment]
|
|
1174
|
+
signal_type, signal_data = signal_tuple[1], signal_tuple[2]
|
|
1175
|
+
if signal_type == "SIGNAL_ARTIFACT_RETURN":
|
|
1176
|
+
# Only convert to FilePart if gateway supports inline artifact resolution
|
|
1177
|
+
if self.supports_inline_artifact_resolution:
|
|
1178
|
+
try:
|
|
1179
|
+
filename, version = (
|
|
1180
|
+
signal_data["filename"],
|
|
1181
|
+
signal_data["version"],
|
|
1182
|
+
)
|
|
1183
|
+
artifact_data = (
|
|
1184
|
+
await load_artifact_content_or_metadata(
|
|
1185
|
+
self.shared_artifact_service,
|
|
1186
|
+
**embed_eval_context["session_context"],
|
|
1187
|
+
filename=filename,
|
|
1188
|
+
version=version,
|
|
1189
|
+
load_metadata_only=True,
|
|
1190
|
+
)
|
|
1191
|
+
)
|
|
1192
|
+
if artifact_data.get("status") == "success":
|
|
1193
|
+
uri = format_artifact_uri(
|
|
1194
|
+
**embed_eval_context["session_context"],
|
|
1195
|
+
filename=filename,
|
|
1196
|
+
version=artifact_data.get("version"),
|
|
1197
|
+
)
|
|
1198
|
+
new_parts.append(
|
|
1199
|
+
a2a.create_file_part_from_uri(
|
|
1200
|
+
uri,
|
|
1201
|
+
filename,
|
|
1202
|
+
artifact_data.get("metadata", {}).get(
|
|
1203
|
+
"mime_type"
|
|
1204
|
+
),
|
|
1205
|
+
)
|
|
1206
|
+
)
|
|
1207
|
+
else:
|
|
1208
|
+
new_parts.append(
|
|
1209
|
+
a2a.create_text_part(
|
|
1210
|
+
f"[Error: Artifact '{filename}' v{version} not found.]"
|
|
1211
|
+
)
|
|
1212
|
+
)
|
|
1213
|
+
except Exception as e:
|
|
1214
|
+
log.exception(
|
|
1215
|
+
"%s Error handling SIGNAL_ARTIFACT_RETURN: %s",
|
|
1216
|
+
log_id_prefix,
|
|
1217
|
+
e,
|
|
1218
|
+
)
|
|
1219
|
+
new_parts.append(
|
|
1220
|
+
a2a.create_text_part(
|
|
1221
|
+
f"[Error: Could not retrieve artifact '{signal_data.get('filename')}'.]"
|
|
1222
|
+
)
|
|
1223
|
+
)
|
|
1224
|
+
else:
|
|
1225
|
+
# Legacy gateway mode: pass signal through for gateway to handle
|
|
1226
|
+
other_signals.append(signal_tuple)
|
|
1227
|
+
elif signal_type == "SIGNAL_INLINE_BINARY_CONTENT":
|
|
1228
|
+
signal_data["content_bytes"] = signal_data.get("bytes")
|
|
1229
|
+
del signal_data["bytes"]
|
|
1230
|
+
new_parts.append(
|
|
1231
|
+
a2a.create_file_part_from_bytes(**signal_data)
|
|
1232
|
+
)
|
|
1233
|
+
else:
|
|
1234
|
+
other_signals.append(signal_tuple)
|
|
1235
|
+
else:
|
|
1236
|
+
# Check if the non-placeholder fragment is just whitespace
|
|
1237
|
+
# and is between two placeholders. If so, drop it.
|
|
1238
|
+
is_just_whitespace = not fragment.strip()
|
|
1239
|
+
prev_fragment_was_placeholder = (
|
|
1240
|
+
i > 0 and text_fragments[i - 1] in placeholder_map
|
|
1241
|
+
)
|
|
1242
|
+
next_fragment_is_placeholder = (
|
|
1243
|
+
i < len(text_fragments) - 1
|
|
1244
|
+
and text_fragments[i + 1] in placeholder_map
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
if (
|
|
1248
|
+
is_just_whitespace
|
|
1249
|
+
and prev_fragment_was_placeholder
|
|
1250
|
+
and next_fragment_is_placeholder
|
|
1251
|
+
):
|
|
1252
|
+
log.debug(
|
|
1253
|
+
"%s Dropping whitespace fragment between two file signals.",
|
|
1254
|
+
log_id_prefix,
|
|
1255
|
+
)
|
|
1256
|
+
continue
|
|
1257
|
+
|
|
1258
|
+
new_parts.append(a2a.create_text_part(text=fragment))
|
|
1259
|
+
|
|
1260
|
+
if is_streaming_status_update:
|
|
1261
|
+
current_buffer = text_to_resolve[processed_idx:]
|
|
1262
|
+
|
|
1263
|
+
elif isinstance(part, FilePart) and part.file:
|
|
1264
|
+
# Handle recursive embeds in text-based FileParts
|
|
1265
|
+
new_parts.append(part) # Placeholder for now
|
|
1266
|
+
elif isinstance(part, DataPart):
|
|
1267
|
+
# Handle special DataPart types
|
|
1268
|
+
data_type = part.data.get("type") if part.data else None
|
|
1269
|
+
|
|
1270
|
+
if data_type == "template_block":
|
|
1271
|
+
# Resolve template block and replace with resolved text
|
|
1272
|
+
try:
|
|
1273
|
+
from ...common.utils.templates import resolve_template_blocks_in_string
|
|
1274
|
+
|
|
1275
|
+
# Reconstruct the template block syntax
|
|
1276
|
+
data_artifact = part.data.get("data_artifact", "")
|
|
1277
|
+
jsonpath = part.data.get("jsonpath")
|
|
1278
|
+
limit = part.data.get("limit")
|
|
1279
|
+
template_content = part.data.get("template_content", "")
|
|
1280
|
+
|
|
1281
|
+
# Build params string
|
|
1282
|
+
params_parts = [f'data="{data_artifact}"']
|
|
1283
|
+
if jsonpath:
|
|
1284
|
+
params_parts.append(f'jsonpath="{jsonpath}"')
|
|
1285
|
+
if limit is not None:
|
|
1286
|
+
params_parts.append(f'limit="{limit}"')
|
|
1287
|
+
params_str = " ".join(params_parts)
|
|
1288
|
+
|
|
1289
|
+
# Reconstruct full template block
|
|
1290
|
+
template_block = f"«««template: {params_str}\n{template_content}\n»»»"
|
|
1291
|
+
|
|
1292
|
+
log.debug(
|
|
1293
|
+
"%s Resolving template block inline: data=%s",
|
|
1294
|
+
log_id_prefix,
|
|
1295
|
+
data_artifact,
|
|
1296
|
+
)
|
|
1297
|
+
|
|
1298
|
+
# Resolve the template
|
|
1299
|
+
resolved_text = await resolve_template_blocks_in_string(
|
|
1300
|
+
text=template_block,
|
|
1301
|
+
artifact_service=self.shared_artifact_service,
|
|
1302
|
+
session_context={
|
|
1303
|
+
"app_name": external_request_context.get(
|
|
1304
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1305
|
+
),
|
|
1306
|
+
"user_id": external_request_context.get("user_id_for_artifacts"),
|
|
1307
|
+
"session_id": external_request_context.get("a2a_session_id"),
|
|
1308
|
+
},
|
|
1309
|
+
log_identifier=f"{log_id_prefix}[TemplateResolve]",
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
log.info(
|
|
1313
|
+
"%s Template resolved successfully. Output length: %d",
|
|
1314
|
+
log_id_prefix,
|
|
1315
|
+
len(resolved_text),
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
# Replace the DataPart with a TextPart containing the resolved content
|
|
1319
|
+
new_parts.append(a2a.create_text_part(text=resolved_text))
|
|
1320
|
+
|
|
1321
|
+
except Exception as e:
|
|
1322
|
+
log.error(
|
|
1323
|
+
"%s Failed to resolve template block: %s",
|
|
1324
|
+
log_id_prefix,
|
|
1325
|
+
e,
|
|
1326
|
+
exc_info=True,
|
|
1327
|
+
)
|
|
1328
|
+
# Send error message as TextPart
|
|
1329
|
+
error_text = f"[Template rendering error: {str(e)}]"
|
|
1330
|
+
new_parts.append(a2a.create_text_part(text=error_text))
|
|
1331
|
+
|
|
1332
|
+
elif (
|
|
1333
|
+
data_type == "artifact_creation_progress"
|
|
1334
|
+
and not self.supports_inline_artifact_resolution
|
|
1335
|
+
):
|
|
1336
|
+
# Legacy gateway mode: convert completed artifact creation to FilePart
|
|
1337
|
+
status = part.data.get("status")
|
|
1338
|
+
if status == "completed" and not is_finalizing_context:
|
|
1339
|
+
# Extract artifact info from the DataPart
|
|
1340
|
+
filename = part.data.get("filename")
|
|
1341
|
+
version = part.data.get("version")
|
|
1342
|
+
mime_type = part.data.get("mime_type")
|
|
1343
|
+
|
|
1344
|
+
if filename and version is not None:
|
|
1345
|
+
log.info(
|
|
1346
|
+
"%s Converting artifact creation completion to FilePart for legacy gateway: %s v%s",
|
|
1347
|
+
log_id_prefix,
|
|
1348
|
+
filename,
|
|
1349
|
+
version,
|
|
1350
|
+
)
|
|
1351
|
+
# This will be sent as an artifact signal, so don't add to new_parts
|
|
1352
|
+
# Instead, add to other_signals for processing
|
|
1353
|
+
signal_tuple = (
|
|
1354
|
+
None,
|
|
1355
|
+
"SIGNAL_ARTIFACT_CREATION_COMPLETE",
|
|
1356
|
+
{
|
|
1357
|
+
"filename": filename,
|
|
1358
|
+
"version": version,
|
|
1359
|
+
"mime_type": mime_type,
|
|
1360
|
+
},
|
|
1361
|
+
)
|
|
1362
|
+
other_signals.append(signal_tuple)
|
|
1363
|
+
else:
|
|
1364
|
+
# Missing required info, keep the DataPart as-is
|
|
1365
|
+
new_parts.append(part)
|
|
1366
|
+
elif status == "completed" and is_finalizing_context:
|
|
1367
|
+
# Suppress during finalizing to avoid duplicates
|
|
1368
|
+
log.debug(
|
|
1369
|
+
"%s Suppressing artifact creation completion during finalizing context for %s",
|
|
1370
|
+
log_id_prefix,
|
|
1371
|
+
part.data.get("filename"),
|
|
1372
|
+
)
|
|
1373
|
+
continue
|
|
1374
|
+
else:
|
|
1375
|
+
# Keep in-progress or failed status DataParts
|
|
1376
|
+
new_parts.append(part)
|
|
1377
|
+
else:
|
|
1378
|
+
# Not an artifact creation DataPart, or modern gateway - keep as-is
|
|
1379
|
+
new_parts.append(part)
|
|
1380
|
+
else:
|
|
1381
|
+
new_parts.append(part)
|
|
1382
|
+
|
|
1383
|
+
if other_signals:
|
|
1384
|
+
await self._handle_resolved_signals(
|
|
1385
|
+
external_request_context,
|
|
1386
|
+
other_signals,
|
|
1387
|
+
original_rpc_id,
|
|
1388
|
+
is_finalizing_context,
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
if new_parts != original_parts:
|
|
1392
|
+
content_modified = True
|
|
1393
|
+
if isinstance(parts_owner, A2AMessage):
|
|
1394
|
+
if isinstance(event_with_parts, TaskStatusUpdateEvent):
|
|
1395
|
+
event_with_parts.status.message = a2a.update_message_parts(
|
|
1396
|
+
parts_owner, new_parts
|
|
1397
|
+
)
|
|
1398
|
+
elif isinstance(event_with_parts, Task):
|
|
1399
|
+
event_with_parts.status.message = a2a.update_message_parts(
|
|
1400
|
+
parts_owner, new_parts
|
|
1401
|
+
)
|
|
1402
|
+
elif isinstance(parts_owner, A2AArtifact):
|
|
1403
|
+
event_with_parts.artifact = a2a.update_artifact_parts(
|
|
1404
|
+
parts_owner, new_parts
|
|
1405
|
+
)
|
|
1406
|
+
|
|
1407
|
+
if is_streaming_status_update:
|
|
1408
|
+
self.task_context_manager.store_context(stream_buffer_key, current_buffer)
|
|
1409
|
+
|
|
1410
|
+
return content_modified or bool(other_signals)
|
|
1411
|
+
|
|
1412
|
+
async def _process_parsed_a2a_event(
|
|
1413
|
+
self,
|
|
1414
|
+
parsed_event: Union[
|
|
1415
|
+
Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError
|
|
1416
|
+
],
|
|
1417
|
+
external_request_context: Dict[str, Any],
|
|
1418
|
+
a2a_task_id: str,
|
|
1419
|
+
original_rpc_id: Optional[str],
|
|
1420
|
+
) -> None:
|
|
1421
|
+
"""
|
|
1422
|
+
Processes a parsed A2A event: resolves embeds, handles signals,
|
|
1423
|
+
sends to external, and manages context.
|
|
1424
|
+
"""
|
|
1425
|
+
log_id_prefix = f"{self.log_identifier}[ProcessParsed:{a2a_task_id}]"
|
|
1426
|
+
is_truly_final_event_for_context_cleanup = False
|
|
1427
|
+
is_finalizing_context_for_embeds = False
|
|
1428
|
+
|
|
1429
|
+
if isinstance(parsed_event, JSONRPCError):
|
|
1430
|
+
log.warning(
|
|
1431
|
+
"%s Handling JSONRPCError for task %s.", log_id_prefix, a2a_task_id
|
|
1432
|
+
)
|
|
1433
|
+
await self._send_error_to_external(external_request_context, parsed_event)
|
|
1434
|
+
is_truly_final_event_for_context_cleanup = True
|
|
1435
|
+
else:
|
|
1436
|
+
content_was_modified_or_signals_handled = False
|
|
1437
|
+
|
|
1438
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent) and parsed_event.final:
|
|
1439
|
+
is_finalizing_context_for_embeds = True
|
|
1440
|
+
elif isinstance(parsed_event, Task):
|
|
1441
|
+
is_finalizing_context_for_embeds = True
|
|
1442
|
+
|
|
1443
|
+
if not isinstance(parsed_event, JSONRPCError):
|
|
1444
|
+
content_was_modified_or_signals_handled = (
|
|
1445
|
+
await self._resolve_embeds_and_handle_signals(
|
|
1446
|
+
parsed_event,
|
|
1447
|
+
external_request_context,
|
|
1448
|
+
a2a_task_id,
|
|
1449
|
+
original_rpc_id,
|
|
1450
|
+
is_finalizing_context=is_finalizing_context_for_embeds,
|
|
1451
|
+
)
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
if self.resolve_artifact_uris_in_gateway:
|
|
1455
|
+
log.debug(
|
|
1456
|
+
"%s Resolving artifact URIs before sending to external...",
|
|
1457
|
+
log_id_prefix,
|
|
1458
|
+
)
|
|
1459
|
+
await self._resolve_uris_in_payload(
|
|
1460
|
+
parsed_event, external_request_context
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
send_this_event_to_external = True
|
|
1464
|
+
is_final_chunk_of_status_update = False
|
|
1465
|
+
|
|
1466
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent):
|
|
1467
|
+
is_final_chunk_of_status_update = parsed_event.final
|
|
1468
|
+
if (
|
|
1469
|
+
not (
|
|
1470
|
+
parsed_event.status
|
|
1471
|
+
and parsed_event.status.message
|
|
1472
|
+
and parsed_event.status.message.parts
|
|
1473
|
+
)
|
|
1474
|
+
and not parsed_event.metadata
|
|
1475
|
+
and not is_final_chunk_of_status_update
|
|
1476
|
+
and not content_was_modified_or_signals_handled
|
|
1477
|
+
):
|
|
1478
|
+
send_this_event_to_external = False
|
|
1479
|
+
log.debug(
|
|
1480
|
+
"%s Suppressing empty intermediate status update.",
|
|
1481
|
+
log_id_prefix,
|
|
1482
|
+
)
|
|
1483
|
+
elif isinstance(parsed_event, TaskArtifactUpdateEvent):
|
|
1484
|
+
if (
|
|
1485
|
+
not (parsed_event.artifact and parsed_event.artifact.parts)
|
|
1486
|
+
and not content_was_modified_or_signals_handled
|
|
1487
|
+
):
|
|
1488
|
+
send_this_event_to_external = False
|
|
1489
|
+
log.debug("%s Suppressing empty artifact update.", log_id_prefix)
|
|
1490
|
+
elif isinstance(parsed_event, Task):
|
|
1491
|
+
is_truly_final_event_for_context_cleanup = True
|
|
1492
|
+
|
|
1493
|
+
if (
|
|
1494
|
+
self._RESOLVE_EMBEDS_IN_FINAL_RESPONSE
|
|
1495
|
+
and parsed_event.status
|
|
1496
|
+
and parsed_event.status.message
|
|
1497
|
+
and parsed_event.status.message.parts
|
|
1498
|
+
):
|
|
1499
|
+
log.debug(
|
|
1500
|
+
"%s Resolving embeds in final task response...", log_id_prefix
|
|
1501
|
+
)
|
|
1502
|
+
message = parsed_event.status.message
|
|
1503
|
+
combined_text = a2a.get_text_from_message(message)
|
|
1504
|
+
data_parts = a2a.get_data_parts_from_message(message)
|
|
1505
|
+
file_parts = a2a.get_file_parts_from_message(message)
|
|
1506
|
+
non_text_parts = data_parts + file_parts
|
|
1507
|
+
|
|
1508
|
+
if combined_text:
|
|
1509
|
+
embed_eval_context = {
|
|
1510
|
+
"artifact_service": self.shared_artifact_service,
|
|
1511
|
+
"session_context": {
|
|
1512
|
+
"app_name": external_request_context.get(
|
|
1513
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1514
|
+
),
|
|
1515
|
+
"user_id": external_request_context.get(
|
|
1516
|
+
"user_id_for_artifacts"
|
|
1517
|
+
),
|
|
1518
|
+
"session_id": external_request_context.get(
|
|
1519
|
+
"a2a_session_id"
|
|
1520
|
+
),
|
|
1521
|
+
},
|
|
1522
|
+
}
|
|
1523
|
+
embed_eval_config = {
|
|
1524
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1525
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1526
|
+
}
|
|
1527
|
+
all_embed_types = EARLY_EMBED_TYPES.union(LATE_EMBED_TYPES)
|
|
1528
|
+
resolved_text, _, signals = await resolve_embeds_in_string(
|
|
1529
|
+
text=combined_text,
|
|
1530
|
+
context=embed_eval_context,
|
|
1531
|
+
resolver_func=evaluate_embed,
|
|
1532
|
+
types_to_resolve=all_embed_types,
|
|
1533
|
+
resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
|
|
1534
|
+
log_identifier=log_id_prefix,
|
|
1535
|
+
config=embed_eval_config,
|
|
1536
|
+
)
|
|
1537
|
+
if signals:
|
|
1538
|
+
log.debug(
|
|
1539
|
+
"%s Handling %d signals found during final response embed resolution.",
|
|
1540
|
+
log_id_prefix,
|
|
1541
|
+
len(signals),
|
|
1542
|
+
)
|
|
1543
|
+
await self._handle_resolved_signals(
|
|
1544
|
+
external_request_context,
|
|
1545
|
+
signals,
|
|
1546
|
+
original_rpc_id,
|
|
1547
|
+
is_finalizing_context=True,
|
|
1548
|
+
)
|
|
1549
|
+
|
|
1550
|
+
new_parts = (
|
|
1551
|
+
[a2a.create_text_part(text=resolved_text)]
|
|
1552
|
+
if resolved_text
|
|
1553
|
+
else []
|
|
1554
|
+
)
|
|
1555
|
+
new_parts.extend(non_text_parts)
|
|
1556
|
+
parsed_event.status.message = a2a.update_message_parts(
|
|
1557
|
+
message=parsed_event.status.message,
|
|
1558
|
+
new_parts=new_parts,
|
|
1559
|
+
)
|
|
1560
|
+
log.info(
|
|
1561
|
+
"%s Final response text updated with resolved embeds.",
|
|
1562
|
+
log_id_prefix,
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1565
|
+
final_buffer_key = f"{a2a_task_id}_stream_buffer"
|
|
1566
|
+
remaining_buffer = self.task_context_manager.get_context(
|
|
1567
|
+
final_buffer_key
|
|
1568
|
+
)
|
|
1569
|
+
if remaining_buffer:
|
|
1570
|
+
log.info(
|
|
1571
|
+
"%s Flushing remaining buffer for task %s before final response.",
|
|
1572
|
+
log_id_prefix,
|
|
1573
|
+
a2a_task_id,
|
|
1574
|
+
)
|
|
1575
|
+
embed_eval_context = {
|
|
1576
|
+
"artifact_service": self.shared_artifact_service,
|
|
1577
|
+
"session_context": {
|
|
1578
|
+
"app_name": external_request_context.get(
|
|
1579
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1580
|
+
),
|
|
1581
|
+
"user_id": external_request_context.get(
|
|
1582
|
+
"user_id_for_artifacts"
|
|
1583
|
+
),
|
|
1584
|
+
"session_id": external_request_context.get(
|
|
1585
|
+
"a2a_session_id"
|
|
1586
|
+
),
|
|
1587
|
+
},
|
|
1588
|
+
}
|
|
1589
|
+
embed_eval_config = {
|
|
1590
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1591
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1592
|
+
}
|
|
1593
|
+
resolved_remaining_text, _, signals = (
|
|
1594
|
+
await resolve_embeds_in_string(
|
|
1595
|
+
text=remaining_buffer,
|
|
1596
|
+
context=embed_eval_context,
|
|
1597
|
+
resolver_func=evaluate_embed,
|
|
1598
|
+
types_to_resolve=LATE_EMBED_TYPES.copy(),
|
|
1599
|
+
resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
|
|
1600
|
+
log_identifier=log_id_prefix,
|
|
1601
|
+
config=embed_eval_config,
|
|
1602
|
+
)
|
|
1603
|
+
)
|
|
1604
|
+
await self._handle_resolved_signals(
|
|
1605
|
+
external_request_context,
|
|
1606
|
+
signals,
|
|
1607
|
+
original_rpc_id,
|
|
1608
|
+
is_finalizing_context=True,
|
|
1609
|
+
)
|
|
1610
|
+
if resolved_remaining_text:
|
|
1611
|
+
flush_message = a2a.create_agent_text_message(
|
|
1612
|
+
text=resolved_remaining_text
|
|
1613
|
+
)
|
|
1614
|
+
flush_event = a2a.create_status_update(
|
|
1615
|
+
task_id=a2a_task_id,
|
|
1616
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
1617
|
+
message=flush_message,
|
|
1618
|
+
is_final=False,
|
|
1619
|
+
)
|
|
1620
|
+
await self._send_update_to_external(
|
|
1621
|
+
external_request_context, flush_event, True
|
|
1622
|
+
)
|
|
1623
|
+
self.task_context_manager.remove_context(final_buffer_key)
|
|
1624
|
+
|
|
1625
|
+
if send_this_event_to_external:
|
|
1626
|
+
if isinstance(parsed_event, Task):
|
|
1627
|
+
# Filter DataParts from final Task if gateway has filtering enabled
|
|
1628
|
+
# This prevents tool results and other internal data from appearing in user-facing output
|
|
1629
|
+
if (
|
|
1630
|
+
self.filter_tool_data_parts
|
|
1631
|
+
and parsed_event.status
|
|
1632
|
+
and parsed_event.status.message
|
|
1633
|
+
and parsed_event.status.message.parts
|
|
1634
|
+
):
|
|
1635
|
+
original_parts = a2a.get_parts_from_message(
|
|
1636
|
+
parsed_event.status.message
|
|
1637
|
+
)
|
|
1638
|
+
filtered_parts = [
|
|
1639
|
+
part
|
|
1640
|
+
for part in original_parts
|
|
1641
|
+
if self._should_include_data_part_in_final_output(part)
|
|
1642
|
+
]
|
|
1643
|
+
if len(filtered_parts) != len(original_parts):
|
|
1644
|
+
log.debug(
|
|
1645
|
+
"%s Filtered %d DataParts from final Task message",
|
|
1646
|
+
log_id_prefix,
|
|
1647
|
+
len(original_parts) - len(filtered_parts),
|
|
1648
|
+
)
|
|
1649
|
+
parsed_event.status.message = a2a.update_message_parts(
|
|
1650
|
+
parsed_event.status.message, filtered_parts
|
|
1651
|
+
)
|
|
1652
|
+
|
|
1653
|
+
await self._send_final_response_to_external(
|
|
1654
|
+
external_request_context, parsed_event
|
|
1655
|
+
)
|
|
1656
|
+
elif isinstance(
|
|
1657
|
+
parsed_event, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
|
|
1658
|
+
):
|
|
1659
|
+
final_chunk_flag = (
|
|
1660
|
+
is_final_chunk_of_status_update
|
|
1661
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent)
|
|
1662
|
+
else False
|
|
1663
|
+
)
|
|
1664
|
+
await self._send_update_to_external(
|
|
1665
|
+
external_request_context, parsed_event, final_chunk_flag
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
if is_truly_final_event_for_context_cleanup:
|
|
1669
|
+
log.info(
|
|
1670
|
+
"%s Truly final event processed for task %s. Removing context.",
|
|
1671
|
+
log_id_prefix,
|
|
1672
|
+
a2a_task_id,
|
|
1673
|
+
)
|
|
1674
|
+
self.task_context_manager.remove_context(a2a_task_id)
|
|
1675
|
+
self.task_context_manager.remove_context(f"{a2a_task_id}_stream_buffer")
|
|
1676
|
+
|
|
1677
|
+
async def _handle_agent_event(
|
|
1678
|
+
self, topic: str, payload: Dict, task_id_from_topic: str
|
|
1679
|
+
) -> bool:
|
|
1680
|
+
"""
|
|
1681
|
+
Handles messages received on gateway response and status topics.
|
|
1682
|
+
Parses the payload, retrieves context using task_id_from_topic, and dispatches for processing.
|
|
1683
|
+
"""
|
|
1684
|
+
try:
|
|
1685
|
+
rpc_response = JSONRPCResponse.model_validate(payload)
|
|
1686
|
+
except Exception as e:
|
|
1687
|
+
log.error(
|
|
1688
|
+
"%s Failed to parse payload as JSONRPCResponse for topic %s (Task ID from topic: %s): %s. Payload: %s",
|
|
1689
|
+
self.log_identifier,
|
|
1690
|
+
topic,
|
|
1691
|
+
task_id_from_topic,
|
|
1692
|
+
e,
|
|
1693
|
+
payload,
|
|
1694
|
+
)
|
|
1695
|
+
return False
|
|
1696
|
+
|
|
1697
|
+
original_rpc_id = str(a2a.get_response_id(rpc_response))
|
|
1698
|
+
|
|
1699
|
+
external_request_context = self.task_context_manager.get_context(
|
|
1700
|
+
task_id_from_topic
|
|
1701
|
+
)
|
|
1702
|
+
if not external_request_context:
|
|
1703
|
+
log.warning(
|
|
1704
|
+
"%s No external context found for A2A Task ID: %s (from topic). Ignoring message. Topic: %s, RPC ID: %s",
|
|
1705
|
+
self.log_identifier,
|
|
1706
|
+
task_id_from_topic,
|
|
1707
|
+
topic,
|
|
1708
|
+
original_rpc_id,
|
|
1709
|
+
)
|
|
1710
|
+
return True
|
|
1711
|
+
|
|
1712
|
+
external_request_context["a2a_task_id_for_event"] = task_id_from_topic
|
|
1713
|
+
external_request_context["original_rpc_id"] = original_rpc_id
|
|
1714
|
+
|
|
1715
|
+
parsed_event_obj: Union[
|
|
1716
|
+
Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError, None
|
|
1717
|
+
] = None
|
|
1718
|
+
error = a2a.get_response_error(rpc_response)
|
|
1719
|
+
if error:
|
|
1720
|
+
parsed_event_obj = error
|
|
1721
|
+
else:
|
|
1722
|
+
result = a2a.get_response_result(rpc_response)
|
|
1723
|
+
if result:
|
|
1724
|
+
# The result is already a parsed Pydantic model.
|
|
1725
|
+
parsed_event_obj = result
|
|
1726
|
+
|
|
1727
|
+
# Validate task ID match
|
|
1728
|
+
actual_task_id = None
|
|
1729
|
+
if isinstance(parsed_event_obj, Task):
|
|
1730
|
+
actual_task_id = parsed_event_obj.id
|
|
1731
|
+
elif isinstance(
|
|
1732
|
+
parsed_event_obj, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
|
|
1733
|
+
):
|
|
1734
|
+
actual_task_id = parsed_event_obj.task_id
|
|
1735
|
+
|
|
1736
|
+
if (
|
|
1737
|
+
task_id_from_topic
|
|
1738
|
+
and actual_task_id
|
|
1739
|
+
and actual_task_id != task_id_from_topic
|
|
1740
|
+
):
|
|
1741
|
+
log.error(
|
|
1742
|
+
"%s Task ID mismatch! Expected: %s, Got from payload: %s.",
|
|
1743
|
+
self.log_identifier,
|
|
1744
|
+
task_id_from_topic,
|
|
1745
|
+
actual_task_id,
|
|
1746
|
+
)
|
|
1747
|
+
parsed_event_obj = None
|
|
1748
|
+
|
|
1749
|
+
if not parsed_event_obj:
|
|
1750
|
+
log.error(
|
|
1751
|
+
"%s Failed to parse or validate A2A event from RPC result for task %s. Result: %s",
|
|
1752
|
+
self.log_identifier,
|
|
1753
|
+
task_id_from_topic,
|
|
1754
|
+
a2a.get_response_result(rpc_response) or "N/A",
|
|
1755
|
+
)
|
|
1756
|
+
generic_error = JSONRPCError(
|
|
1757
|
+
code=-32000, message="Invalid event structure received from agent."
|
|
1758
|
+
)
|
|
1759
|
+
await self._send_error_to_external(external_request_context, generic_error)
|
|
1760
|
+
self.task_context_manager.remove_context(task_id_from_topic)
|
|
1761
|
+
self.task_context_manager.remove_context(
|
|
1762
|
+
f"{task_id_from_topic}_stream_buffer"
|
|
1763
|
+
)
|
|
1764
|
+
return False
|
|
1765
|
+
|
|
1766
|
+
try:
|
|
1767
|
+
await self._process_parsed_a2a_event(
|
|
1768
|
+
parsed_event_obj,
|
|
1769
|
+
external_request_context,
|
|
1770
|
+
task_id_from_topic,
|
|
1771
|
+
original_rpc_id,
|
|
1772
|
+
)
|
|
1773
|
+
return True
|
|
1774
|
+
except Exception as e:
|
|
1775
|
+
log.exception(
|
|
1776
|
+
"%s Error in _process_parsed_a2a_event for task %s: %s",
|
|
1777
|
+
self.log_identifier,
|
|
1778
|
+
task_id_from_topic,
|
|
1779
|
+
e,
|
|
1780
|
+
)
|
|
1781
|
+
error_obj = JSONRPCError(
|
|
1782
|
+
code=-32000, message=f"Gateway processing error: {e}"
|
|
1783
|
+
)
|
|
1784
|
+
await self._send_error_to_external(external_request_context, error_obj)
|
|
1785
|
+
self.task_context_manager.remove_context(task_id_from_topic)
|
|
1786
|
+
self.task_context_manager.remove_context(
|
|
1787
|
+
f"{task_id_from_topic}_stream_buffer"
|
|
1788
|
+
)
|
|
1789
|
+
return False
|
|
1790
|
+
|
|
1791
|
+
async def _async_setup_and_run(self) -> None:
|
|
1792
|
+
"""Main async logic for the gateway component."""
|
|
1793
|
+
# Call base class to initialize Trust Manager
|
|
1794
|
+
await super()._async_setup_and_run()
|
|
1795
|
+
|
|
1796
|
+
log.info(
|
|
1797
|
+
"%s Starting _start_listener() to initiate external platform connection.",
|
|
1798
|
+
self.log_identifier,
|
|
1799
|
+
)
|
|
1800
|
+
self._start_listener()
|
|
1801
|
+
|
|
1802
|
+
await self._message_processor_loop()
|
|
1803
|
+
|
|
1804
|
+
def _pre_async_cleanup(self) -> None:
|
|
1805
|
+
"""Pre-cleanup actions for the gateway component."""
|
|
1806
|
+
# Cleanup Trust Manager if present (ENTERPRISE FEATURE)
|
|
1807
|
+
if self.trust_manager:
|
|
1808
|
+
try:
|
|
1809
|
+
log.info("%s Cleaning up Trust Manager...", self.log_identifier)
|
|
1810
|
+
self.trust_manager.cleanup(self.cancel_timer)
|
|
1811
|
+
log.info("%s Trust Manager cleanup complete", self.log_identifier)
|
|
1812
|
+
except Exception as e:
|
|
1813
|
+
log.error(
|
|
1814
|
+
"%s Error during Trust Manager cleanup: %s", self.log_identifier, e
|
|
1815
|
+
)
|
|
1816
|
+
|
|
1817
|
+
log.info("%s Calling _stop_listener()...", self.log_identifier)
|
|
1818
|
+
self._stop_listener()
|
|
1819
|
+
|
|
1820
|
+
if self.internal_event_queue:
|
|
1821
|
+
log.info(
|
|
1822
|
+
"%s Signaling _message_processor_loop to stop by putting sentinel on queue...",
|
|
1823
|
+
self.log_identifier,
|
|
1824
|
+
)
|
|
1825
|
+
# This unblocks the `self.internal_event_queue.get()` call in the loop
|
|
1826
|
+
self.internal_event_queue.put(None)
|
|
1827
|
+
|
|
1828
|
+
async def _message_processor_loop(self):
|
|
1829
|
+
log.debug("%s Starting message processor loop as an asyncio task...", self.log_identifier)
|
|
1830
|
+
loop = self.get_async_loop()
|
|
1831
|
+
|
|
1832
|
+
while not self.stop_signal.is_set():
|
|
1833
|
+
original_broker_message: Optional[SolaceMessage] = None
|
|
1834
|
+
item = None
|
|
1835
|
+
processed_successfully = False
|
|
1836
|
+
topic = None
|
|
1837
|
+
|
|
1838
|
+
try:
|
|
1839
|
+
item = await loop.run_in_executor(None, self.internal_event_queue.get)
|
|
1840
|
+
|
|
1841
|
+
if item is None:
|
|
1842
|
+
log.info(
|
|
1843
|
+
"%s Received shutdown sentinel. Exiting message processor loop.",
|
|
1844
|
+
self.log_identifier,
|
|
1845
|
+
)
|
|
1846
|
+
break
|
|
1847
|
+
|
|
1848
|
+
topic = item.get("topic")
|
|
1849
|
+
payload = item.get("payload")
|
|
1850
|
+
original_broker_message = item.get("_original_broker_message")
|
|
1851
|
+
|
|
1852
|
+
if not topic or payload is None or not original_broker_message:
|
|
1853
|
+
log.warning(
|
|
1854
|
+
"%s Invalid item received from internal queue: %s",
|
|
1855
|
+
self.log_identifier,
|
|
1856
|
+
item,
|
|
1857
|
+
)
|
|
1858
|
+
processed_successfully = False
|
|
1859
|
+
continue
|
|
1860
|
+
|
|
1861
|
+
if a2a.topic_matches_subscription(
|
|
1862
|
+
topic, a2a.get_discovery_topic(self.namespace)
|
|
1863
|
+
):
|
|
1864
|
+
processed_successfully = await self._handle_discovery_message(
|
|
1865
|
+
payload
|
|
1866
|
+
)
|
|
1867
|
+
elif (
|
|
1868
|
+
hasattr(self, "trust_manager")
|
|
1869
|
+
and self.trust_manager
|
|
1870
|
+
and self.trust_manager.is_trust_card_topic(topic)
|
|
1871
|
+
):
|
|
1872
|
+
await self.trust_manager.handle_trust_card_message(payload, topic)
|
|
1873
|
+
processed_successfully = True
|
|
1874
|
+
elif a2a.topic_matches_subscription(
|
|
1875
|
+
topic,
|
|
1876
|
+
a2a.get_gateway_response_subscription_topic(
|
|
1877
|
+
self.namespace, self.gateway_id
|
|
1878
|
+
),
|
|
1879
|
+
) or a2a.topic_matches_subscription(
|
|
1880
|
+
topic,
|
|
1881
|
+
a2a.get_gateway_status_subscription_topic(
|
|
1882
|
+
self.namespace, self.gateway_id
|
|
1883
|
+
),
|
|
1884
|
+
):
|
|
1885
|
+
task_id_from_topic: Optional[str] = None
|
|
1886
|
+
response_sub = a2a.get_gateway_response_subscription_topic(
|
|
1887
|
+
self.namespace, self.gateway_id
|
|
1888
|
+
)
|
|
1889
|
+
status_sub = a2a.get_gateway_status_subscription_topic(
|
|
1890
|
+
self.namespace, self.gateway_id
|
|
1891
|
+
)
|
|
1892
|
+
|
|
1893
|
+
if a2a.topic_matches_subscription(topic, response_sub):
|
|
1894
|
+
task_id_from_topic = a2a.extract_task_id_from_topic(
|
|
1895
|
+
topic, response_sub, self.log_identifier
|
|
1896
|
+
)
|
|
1897
|
+
elif a2a.topic_matches_subscription(topic, status_sub):
|
|
1898
|
+
task_id_from_topic = a2a.extract_task_id_from_topic(
|
|
1899
|
+
topic, status_sub, self.log_identifier
|
|
1900
|
+
)
|
|
1901
|
+
|
|
1902
|
+
if task_id_from_topic:
|
|
1903
|
+
processed_successfully = await self._handle_agent_event(
|
|
1904
|
+
topic, payload, task_id_from_topic
|
|
1905
|
+
)
|
|
1906
|
+
else:
|
|
1907
|
+
log.error(
|
|
1908
|
+
"%s Could not extract task_id from topic %s for _handle_agent_event. Ignoring.",
|
|
1909
|
+
self.log_identifier,
|
|
1910
|
+
topic,
|
|
1911
|
+
)
|
|
1912
|
+
processed_successfully = False
|
|
1913
|
+
else:
|
|
1914
|
+
log.warning(
|
|
1915
|
+
"%s Received message on unhandled topic: %s. Acknowledging.",
|
|
1916
|
+
self.log_identifier,
|
|
1917
|
+
topic,
|
|
1918
|
+
)
|
|
1919
|
+
processed_successfully = True
|
|
1920
|
+
|
|
1921
|
+
except queue.Empty:
|
|
1922
|
+
continue
|
|
1923
|
+
except asyncio.CancelledError:
|
|
1924
|
+
log.info("%s Message processor loop cancelled.", self.log_identifier)
|
|
1925
|
+
break
|
|
1926
|
+
except Exception as e:
|
|
1927
|
+
log.exception(
|
|
1928
|
+
"%s Unhandled error in message processor loop: %s",
|
|
1929
|
+
self.log_identifier,
|
|
1930
|
+
e,
|
|
1931
|
+
)
|
|
1932
|
+
processed_successfully = False
|
|
1933
|
+
await asyncio.sleep(1)
|
|
1934
|
+
finally:
|
|
1935
|
+
if original_broker_message:
|
|
1936
|
+
if processed_successfully:
|
|
1937
|
+
original_broker_message.call_acknowledgements()
|
|
1938
|
+
else:
|
|
1939
|
+
original_broker_message.call_negative_acknowledgements()
|
|
1940
|
+
log.warning(
|
|
1941
|
+
"%s NACKed SolaceMessage for topic: %s",
|
|
1942
|
+
self.log_identifier,
|
|
1943
|
+
topic or "unknown",
|
|
1944
|
+
)
|
|
1945
|
+
|
|
1946
|
+
if item and item is not None:
|
|
1947
|
+
self.internal_event_queue.task_done()
|
|
1948
|
+
|
|
1949
|
+
log.info("%s Message processor loop finished.", self.log_identifier)
|
|
1950
|
+
|
|
1951
|
+
@abstractmethod
|
|
1952
|
+
async def _extract_initial_claims(
|
|
1953
|
+
self, external_event_data: Any
|
|
1954
|
+
) -> Optional[Dict[str, Any]]:
|
|
1955
|
+
"""
|
|
1956
|
+
Extracts the primary identity claims from a platform-specific event.
|
|
1957
|
+
This method MUST be implemented by derived gateway components.
|
|
1958
|
+
|
|
1959
|
+
Args:
|
|
1960
|
+
external_event_data: Raw event data from the external platform
|
|
1961
|
+
(e.g., FastAPIRequest, Slack event dictionary).
|
|
1962
|
+
|
|
1963
|
+
Returns:
|
|
1964
|
+
A dictionary of initial claims, which MUST include an 'id' key.
|
|
1965
|
+
Example: {"id": "user@example.com", "source": "slack_api"}
|
|
1966
|
+
Return None if authentication fails.
|
|
1967
|
+
"""
|
|
1968
|
+
pass
|
|
1969
|
+
|
|
1970
|
+
@abstractmethod
|
|
1971
|
+
async def _translate_external_input(
|
|
1972
|
+
self, external_event: Any
|
|
1973
|
+
) -> Tuple[str, List[ContentPart], Dict[str, Any]]:
|
|
1974
|
+
"""
|
|
1975
|
+
Translates raw platform-specific event data into A2A task parameters.
|
|
1976
|
+
|
|
1977
|
+
Args:
|
|
1978
|
+
external_event: Raw event data from the external platform
|
|
1979
|
+
(e.g., FastAPIRequest, Slack event dictionary).
|
|
1980
|
+
|
|
1981
|
+
Returns:
|
|
1982
|
+
A tuple containing:
|
|
1983
|
+
- target_agent_name (str): The name of the A2A agent to target.
|
|
1984
|
+
- a2a_parts (List[ContentPart]): A list of A2A Part objects.
|
|
1985
|
+
- external_request_context (Dict[str, Any]): Context for TaskContextManager.
|
|
1986
|
+
"""
|
|
1987
|
+
pass
|
|
1988
|
+
|
|
1989
|
+
@abstractmethod
|
|
1990
|
+
def _start_listener(self) -> None:
|
|
1991
|
+
pass
|
|
1992
|
+
|
|
1993
|
+
@abstractmethod
|
|
1994
|
+
def _stop_listener(self) -> None:
|
|
1995
|
+
pass
|
|
1996
|
+
|
|
1997
|
+
@abstractmethod
|
|
1998
|
+
async def _send_update_to_external(
|
|
1999
|
+
self,
|
|
2000
|
+
external_request_context: Dict[str, Any],
|
|
2001
|
+
event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
|
|
2002
|
+
is_final_chunk_of_update: bool,
|
|
2003
|
+
) -> None:
|
|
2004
|
+
pass
|
|
2005
|
+
|
|
2006
|
+
@abstractmethod
|
|
2007
|
+
async def _send_final_response_to_external(
|
|
2008
|
+
self, external_request_context: Dict[str, Any], task_data: Task
|
|
2009
|
+
) -> None:
|
|
2010
|
+
pass
|
|
2011
|
+
|
|
2012
|
+
@abstractmethod
|
|
2013
|
+
async def _send_error_to_external(
|
|
2014
|
+
self, external_request_context: Dict[str, Any], error_data: JSONRPCError
|
|
2015
|
+
) -> None:
|
|
2016
|
+
pass
|
|
2017
|
+
|
|
2018
|
+
def _get_component_id(self) -> str:
|
|
2019
|
+
"""Returns the gateway ID as the component identifier."""
|
|
2020
|
+
return self.gateway_id
|
|
2021
|
+
|
|
2022
|
+
def _get_component_type(self) -> str:
|
|
2023
|
+
"""Returns 'gateway' as the component type."""
|
|
2024
|
+
return "gateway"
|
|
2025
|
+
|
|
2026
|
+
def invoke(self, message, data):
|
|
2027
|
+
if isinstance(message, SolaceMessage):
|
|
2028
|
+
message.call_acknowledgements()
|
|
2029
|
+
log.warning("%s Invoke method called unexpectedly.", self.log_identifier)
|
|
2030
|
+
return None
|