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,2041 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains event handling logic for the A2A_ADK_HostComponent.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import fnmatch
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Dict
|
|
10
|
+
|
|
11
|
+
from a2a.types import (
|
|
12
|
+
A2ARequest,
|
|
13
|
+
AgentCapabilities,
|
|
14
|
+
AgentCard,
|
|
15
|
+
AgentExtension,
|
|
16
|
+
DataPart,
|
|
17
|
+
JSONRPCResponse,
|
|
18
|
+
Task,
|
|
19
|
+
TaskArtifactUpdateEvent,
|
|
20
|
+
TaskState,
|
|
21
|
+
TaskStatusUpdateEvent,
|
|
22
|
+
TextPart,
|
|
23
|
+
)
|
|
24
|
+
from google.adk.agents import RunConfig
|
|
25
|
+
from google.adk.agents.run_config import StreamingMode
|
|
26
|
+
from solace_ai_connector.common.event import Event, EventType
|
|
27
|
+
from solace_ai_connector.common.message import Message as SolaceMessage
|
|
28
|
+
from sqlalchemy.exc import OperationalError
|
|
29
|
+
|
|
30
|
+
from ...agent.adk.callbacks import _publish_data_part_status_update
|
|
31
|
+
from ...agent.adk.runner import TaskCancelledError, run_adk_async_task_thread_wrapper
|
|
32
|
+
from ...agent.utils.artifact_helpers import generate_artifact_metadata_summary
|
|
33
|
+
from ...common import a2a
|
|
34
|
+
from ...common.utils.embeds.constants import (
|
|
35
|
+
EMBED_DELIMITER_OPEN,
|
|
36
|
+
EMBED_DELIMITER_CLOSE,
|
|
37
|
+
)
|
|
38
|
+
from ...common.a2a import (
|
|
39
|
+
get_agent_request_topic,
|
|
40
|
+
get_agent_response_subscription_topic,
|
|
41
|
+
get_agent_status_subscription_topic,
|
|
42
|
+
get_client_response_topic,
|
|
43
|
+
get_discovery_topic,
|
|
44
|
+
get_sam_events_subscription_topic,
|
|
45
|
+
get_text_from_message,
|
|
46
|
+
topic_matches_subscription,
|
|
47
|
+
translate_a2a_to_adk_content,
|
|
48
|
+
)
|
|
49
|
+
from ...common.a2a.types import ToolsExtensionParams
|
|
50
|
+
from ...common.data_parts import ToolResultData
|
|
51
|
+
from ..sac.task_execution_context import TaskExecutionContext
|
|
52
|
+
|
|
53
|
+
if TYPE_CHECKING:
|
|
54
|
+
from ..sac.component import SamAgentComponent
|
|
55
|
+
|
|
56
|
+
log = logging.getLogger(__name__)
|
|
57
|
+
trace_logger = logging.getLogger("sam_trace")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _forward_jsonrpc_response(
|
|
61
|
+
component: "SamAgentComponent",
|
|
62
|
+
original_jsonrpc_request_id: str,
|
|
63
|
+
result_data: Any,
|
|
64
|
+
target_topic: str,
|
|
65
|
+
main_logical_task_id: str,
|
|
66
|
+
peer_agent_name: str,
|
|
67
|
+
message: SolaceMessage,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Utility method to forward a JSONRPCResponse with the given result data.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
component: The SamAgentComponent instance
|
|
74
|
+
original_jsonrpc_request_id: The original JSONRPC request ID
|
|
75
|
+
result_data: The data to include in the response result
|
|
76
|
+
target_topic: The topic to publish to
|
|
77
|
+
main_logical_task_id: The main logical task ID for logging
|
|
78
|
+
peer_agent_name: The peer agent name for logging
|
|
79
|
+
message: The original message to acknowledge
|
|
80
|
+
"""
|
|
81
|
+
forwarded_rpc_response = JSONRPCResponse(
|
|
82
|
+
id=original_jsonrpc_request_id,
|
|
83
|
+
result=result_data,
|
|
84
|
+
)
|
|
85
|
+
payload_to_publish = forwarded_rpc_response.model_dump(
|
|
86
|
+
by_alias=True, exclude_none=True
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
component.publish_a2a_message(
|
|
91
|
+
payload_to_publish,
|
|
92
|
+
target_topic,
|
|
93
|
+
)
|
|
94
|
+
log.debug(
|
|
95
|
+
"%s Forwarded DataPart signal for main task %s (from peer %s) to %s.",
|
|
96
|
+
component.log_identifier,
|
|
97
|
+
main_logical_task_id,
|
|
98
|
+
peer_agent_name,
|
|
99
|
+
target_topic,
|
|
100
|
+
)
|
|
101
|
+
except Exception as pub_err:
|
|
102
|
+
log.exception(
|
|
103
|
+
"%s Failed to publish forwarded status signal for main task %s: %s",
|
|
104
|
+
component.log_identifier,
|
|
105
|
+
main_logical_task_id,
|
|
106
|
+
pub_err,
|
|
107
|
+
)
|
|
108
|
+
message.call_acknowledgements()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _register_peer_artifacts_in_parent_context(
|
|
112
|
+
parent_task_context: "TaskExecutionContext",
|
|
113
|
+
peer_task_object: Task,
|
|
114
|
+
log_identifier: str,
|
|
115
|
+
):
|
|
116
|
+
"""
|
|
117
|
+
Registers artifacts produced by a peer agent in the parent agent's
|
|
118
|
+
task execution context, allowing them to be "bubbled up".
|
|
119
|
+
"""
|
|
120
|
+
if not parent_task_context:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if peer_task_object.metadata and "produced_artifacts" in peer_task_object.metadata:
|
|
124
|
+
peer_artifacts = peer_task_object.metadata.get("produced_artifacts", [])
|
|
125
|
+
if not peer_artifacts:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
log.debug(
|
|
129
|
+
"%s Registering %d artifacts from peer response into parent task context.",
|
|
130
|
+
log_identifier,
|
|
131
|
+
len(peer_artifacts),
|
|
132
|
+
)
|
|
133
|
+
for artifact_ref in peer_artifacts:
|
|
134
|
+
filename = artifact_ref.get("filename")
|
|
135
|
+
version = artifact_ref.get("version")
|
|
136
|
+
if filename and version is not None:
|
|
137
|
+
parent_task_context.register_produced_artifact(
|
|
138
|
+
filename=filename,
|
|
139
|
+
version=version,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def process_event(component, event: Event):
|
|
144
|
+
"""
|
|
145
|
+
Processes incoming events (Messages, Timers, etc.). Routes to specific handlers.
|
|
146
|
+
Args:
|
|
147
|
+
component: The A2A_ADK_HostComponent instance.
|
|
148
|
+
event: The event object received from the SAC framework.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
if event.event_type == EventType.MESSAGE:
|
|
152
|
+
message = event.data
|
|
153
|
+
topic = message.get_topic()
|
|
154
|
+
if not topic:
|
|
155
|
+
log.warning(
|
|
156
|
+
"%s Received message without topic. Ignoring.",
|
|
157
|
+
component.log_identifier,
|
|
158
|
+
)
|
|
159
|
+
return
|
|
160
|
+
namespace = component.get_config("namespace")
|
|
161
|
+
agent_name = component.get_config("agent_name")
|
|
162
|
+
agent_request_topic = get_agent_request_topic(namespace, agent_name)
|
|
163
|
+
discovery_topic = get_discovery_topic(namespace)
|
|
164
|
+
agent_response_sub_prefix = (
|
|
165
|
+
get_agent_response_subscription_topic(namespace, agent_name)[:-2] + "/"
|
|
166
|
+
)
|
|
167
|
+
agent_status_sub_prefix = (
|
|
168
|
+
get_agent_status_subscription_topic(namespace, agent_name)[:-2] + "/"
|
|
169
|
+
)
|
|
170
|
+
sam_events_topic = get_sam_events_subscription_topic(namespace, "session")
|
|
171
|
+
if topic == agent_request_topic:
|
|
172
|
+
await handle_a2a_request(component, message)
|
|
173
|
+
elif topic == discovery_topic:
|
|
174
|
+
payload = message.get_payload()
|
|
175
|
+
if isinstance(payload, dict) and payload.get("name") != agent_name:
|
|
176
|
+
handle_agent_card_message(component, message)
|
|
177
|
+
else:
|
|
178
|
+
message.call_acknowledgements()
|
|
179
|
+
elif topic_matches_subscription(topic, sam_events_topic):
|
|
180
|
+
handle_sam_event(component, message, topic)
|
|
181
|
+
elif topic.startswith(agent_response_sub_prefix) or topic.startswith(
|
|
182
|
+
agent_status_sub_prefix
|
|
183
|
+
):
|
|
184
|
+
await handle_a2a_response(component, message)
|
|
185
|
+
elif hasattr(component, "trust_manager") and component.trust_manager:
|
|
186
|
+
# Check if this is a trust card message (enterprise feature)
|
|
187
|
+
try:
|
|
188
|
+
if component.trust_manager.is_trust_card_topic(topic):
|
|
189
|
+
await component.trust_manager.handle_trust_card_message(
|
|
190
|
+
message, topic
|
|
191
|
+
)
|
|
192
|
+
message.call_acknowledgements()
|
|
193
|
+
return
|
|
194
|
+
except Exception as e:
|
|
195
|
+
log.error(
|
|
196
|
+
"%s Error handling trust card message: %s",
|
|
197
|
+
component.log_identifier,
|
|
198
|
+
e,
|
|
199
|
+
)
|
|
200
|
+
message.call_acknowledgements()
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
log.warning(
|
|
204
|
+
"%s Received message on unhandled topic: %s",
|
|
205
|
+
component.log_identifier,
|
|
206
|
+
topic,
|
|
207
|
+
)
|
|
208
|
+
message.call_acknowledgements()
|
|
209
|
+
else:
|
|
210
|
+
log.warning(
|
|
211
|
+
"%s Received message on unhandled topic: %s",
|
|
212
|
+
component.log_identifier,
|
|
213
|
+
topic,
|
|
214
|
+
)
|
|
215
|
+
message.call_acknowledgements()
|
|
216
|
+
elif event.event_type == EventType.TIMER:
|
|
217
|
+
timer_data = event.data
|
|
218
|
+
log.debug(
|
|
219
|
+
"%s Received timer event: %s", component.log_identifier, timer_data
|
|
220
|
+
)
|
|
221
|
+
if timer_data.get("timer_id") == component._card_publish_timer_id:
|
|
222
|
+
publish_agent_card(component)
|
|
223
|
+
else:
|
|
224
|
+
# Handle other timer events including health check timer
|
|
225
|
+
component.handle_timer_event(timer_data)
|
|
226
|
+
elif event.event_type == EventType.CACHE_EXPIRY:
|
|
227
|
+
# Delegate cache expiry handling to the component itself.
|
|
228
|
+
await component.handle_cache_expiry_event(event.data)
|
|
229
|
+
else:
|
|
230
|
+
log.warning(
|
|
231
|
+
"%s Received unknown event type: %s",
|
|
232
|
+
component.log_identifier,
|
|
233
|
+
event.event_type,
|
|
234
|
+
)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
log.exception(
|
|
237
|
+
"%s Unhandled error in process_event: %s", component.log_identifier, e
|
|
238
|
+
)
|
|
239
|
+
if event.event_type == EventType.MESSAGE:
|
|
240
|
+
try:
|
|
241
|
+
event.data.call_negative_acknowledgements()
|
|
242
|
+
log.warning(
|
|
243
|
+
"%s NACKed message due to error in process_event.",
|
|
244
|
+
component.log_identifier,
|
|
245
|
+
)
|
|
246
|
+
except Exception as nack_e:
|
|
247
|
+
log.error(
|
|
248
|
+
"%s Failed to NACK message after error in process_event: %s",
|
|
249
|
+
component.log_identifier,
|
|
250
|
+
nack_e,
|
|
251
|
+
)
|
|
252
|
+
component.handle_error(e, event)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
async def _publish_peer_tool_result_notification(
|
|
256
|
+
component: "SamAgentComponent",
|
|
257
|
+
correlation_data: dict[str, Any],
|
|
258
|
+
payload_to_queue: Any,
|
|
259
|
+
log_identifier: str,
|
|
260
|
+
):
|
|
261
|
+
"""Publishes a ToolResultData status update for a completed peer tool call."""
|
|
262
|
+
peer_tool_name = correlation_data.get("peer_tool_name")
|
|
263
|
+
function_call_id = correlation_data.get("adk_function_call_id")
|
|
264
|
+
original_task_context_data = correlation_data.get("original_task_context")
|
|
265
|
+
|
|
266
|
+
if not (peer_tool_name and function_call_id and original_task_context_data):
|
|
267
|
+
log.warning(
|
|
268
|
+
"%s Missing data in correlation_data. Cannot publish peer tool result notification.",
|
|
269
|
+
log_identifier,
|
|
270
|
+
)
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
log.info(
|
|
274
|
+
"%s Publishing tool_result notification for completed peer task '%s'.",
|
|
275
|
+
log_identifier,
|
|
276
|
+
peer_tool_name,
|
|
277
|
+
)
|
|
278
|
+
try:
|
|
279
|
+
tool_result_notification = ToolResultData(
|
|
280
|
+
tool_name=peer_tool_name,
|
|
281
|
+
result_data=payload_to_queue,
|
|
282
|
+
function_call_id=function_call_id,
|
|
283
|
+
)
|
|
284
|
+
await _publish_data_part_status_update(
|
|
285
|
+
host_component=component,
|
|
286
|
+
a2a_context=original_task_context_data,
|
|
287
|
+
data_part_model=tool_result_notification,
|
|
288
|
+
)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
log.error(
|
|
291
|
+
"%s Failed to publish peer tool result notification for '%s': %s",
|
|
292
|
+
log_identifier,
|
|
293
|
+
peer_tool_name,
|
|
294
|
+
e,
|
|
295
|
+
exc_info=True,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
async def handle_a2a_request(component, message: SolaceMessage):
|
|
300
|
+
"""
|
|
301
|
+
Handles an incoming A2A request message.
|
|
302
|
+
Starts the ADK runner for SendTask/SendTaskStreaming requests.
|
|
303
|
+
Handles CancelTask requests directly.
|
|
304
|
+
Stores the original SolaceMessage in context for the ADK runner to ACK/NACK.
|
|
305
|
+
"""
|
|
306
|
+
log.info(
|
|
307
|
+
"%s Received new A2A request on topic: %s",
|
|
308
|
+
component.log_identifier,
|
|
309
|
+
message.get_topic(),
|
|
310
|
+
)
|
|
311
|
+
try:
|
|
312
|
+
payload_dict = message.get_payload()
|
|
313
|
+
if not isinstance(payload_dict, dict):
|
|
314
|
+
raise ValueError("Payload is not a dictionary.")
|
|
315
|
+
|
|
316
|
+
a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
|
|
317
|
+
jsonrpc_request_id = a2a.get_request_id(a2a_request)
|
|
318
|
+
|
|
319
|
+
# Extract properties from message user properties
|
|
320
|
+
client_id = message.get_user_properties().get("clientId", "default_client")
|
|
321
|
+
status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
|
|
322
|
+
reply_topic_from_peer = message.get_user_properties().get("replyTo")
|
|
323
|
+
namespace = component.get_config("namespace")
|
|
324
|
+
a2a_user_config = message.get_user_properties().get("a2aUserConfig", {})
|
|
325
|
+
if not isinstance(a2a_user_config, dict):
|
|
326
|
+
log.warning("a2aUserConfig is not a dict, using empty dict instead")
|
|
327
|
+
a2a_user_config = {}
|
|
328
|
+
|
|
329
|
+
# The concept of logical_task_id changes. For Cancel, it's in params.id.
|
|
330
|
+
# For Send, we will generate it.
|
|
331
|
+
logical_task_id = None
|
|
332
|
+
method = a2a.get_request_method(a2a_request)
|
|
333
|
+
|
|
334
|
+
# Enterprise feature: Verify user authentication if trust manager enabled
|
|
335
|
+
verified_user_identity = None
|
|
336
|
+
if hasattr(component, "trust_manager") and component.trust_manager:
|
|
337
|
+
# Determine task_id for verification
|
|
338
|
+
if method == "tasks/cancel":
|
|
339
|
+
verification_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
|
|
340
|
+
elif method in ["message/send", "message/stream"]:
|
|
341
|
+
verification_task_id = str(a2a.get_request_id(a2a_request))
|
|
342
|
+
else:
|
|
343
|
+
verification_task_id = None
|
|
344
|
+
|
|
345
|
+
if verification_task_id:
|
|
346
|
+
try:
|
|
347
|
+
# Enterprise handles all verification logic
|
|
348
|
+
verified_user_identity = (
|
|
349
|
+
component.trust_manager.verify_request_authentication(
|
|
350
|
+
message=message,
|
|
351
|
+
task_id=verification_task_id,
|
|
352
|
+
namespace=namespace,
|
|
353
|
+
jsonrpc_request_id=jsonrpc_request_id,
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if verified_user_identity:
|
|
358
|
+
log.info(
|
|
359
|
+
"%s Successfully authenticated user '%s' for task %s",
|
|
360
|
+
component.log_identifier,
|
|
361
|
+
verified_user_identity.get("user_id"),
|
|
362
|
+
verification_task_id,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
except Exception as e:
|
|
366
|
+
# Authentication failed - enterprise provides error details
|
|
367
|
+
log.error(
|
|
368
|
+
"%s Authentication failed for task %s: %s",
|
|
369
|
+
component.log_identifier,
|
|
370
|
+
verification_task_id,
|
|
371
|
+
e,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Build error response using enterprise exception data if available
|
|
375
|
+
error_data = {
|
|
376
|
+
"reason": "authentication_failed",
|
|
377
|
+
"task_id": verification_task_id,
|
|
378
|
+
}
|
|
379
|
+
if hasattr(e, "create_error_response_data"):
|
|
380
|
+
error_data = e.create_error_response_data()
|
|
381
|
+
|
|
382
|
+
error_response = a2a.create_invalid_request_error_response(
|
|
383
|
+
message="Authentication failed",
|
|
384
|
+
request_id=jsonrpc_request_id,
|
|
385
|
+
data=error_data,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Determine reply topic
|
|
389
|
+
reply_topic = message.get_user_properties().get("replyTo")
|
|
390
|
+
if not reply_topic:
|
|
391
|
+
client_id = message.get_user_properties().get(
|
|
392
|
+
"clientId", "default_client"
|
|
393
|
+
)
|
|
394
|
+
reply_topic = a2a.get_client_response_topic(
|
|
395
|
+
namespace, client_id
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
component.publish_a2a_message(
|
|
399
|
+
payload=error_response.model_dump(exclude_none=True),
|
|
400
|
+
topic=reply_topic,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
message.call_acknowledgements()
|
|
405
|
+
log.debug(
|
|
406
|
+
"%s ACKed message with failed authentication",
|
|
407
|
+
component.log_identifier,
|
|
408
|
+
)
|
|
409
|
+
except Exception as ack_e:
|
|
410
|
+
log.error(
|
|
411
|
+
"%s Failed to ACK message after authentication failure: %s",
|
|
412
|
+
component.log_identifier,
|
|
413
|
+
ack_e,
|
|
414
|
+
)
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
if method == "tasks/cancel":
|
|
418
|
+
logical_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
|
|
419
|
+
log.info(
|
|
420
|
+
"%s Received CancelTaskRequest for Task ID: %s.",
|
|
421
|
+
component.log_identifier,
|
|
422
|
+
logical_task_id,
|
|
423
|
+
)
|
|
424
|
+
task_context = None
|
|
425
|
+
with component.active_tasks_lock:
|
|
426
|
+
task_context = component.active_tasks.get(logical_task_id)
|
|
427
|
+
|
|
428
|
+
if task_context:
|
|
429
|
+
task_context.cancel()
|
|
430
|
+
log.info(
|
|
431
|
+
"%s Sent cancellation signal to ADK task %s.",
|
|
432
|
+
component.log_identifier,
|
|
433
|
+
logical_task_id,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
peer_sub_tasks = task_context.active_peer_sub_tasks.copy()
|
|
437
|
+
if peer_sub_tasks:
|
|
438
|
+
for sub_task_id, sub_task_info in peer_sub_tasks.items():
|
|
439
|
+
target_peer_agent_name = sub_task_info.get("peer_agent_name")
|
|
440
|
+
peer_task_id_to_cancel = sub_task_info.get("peer_task_id")
|
|
441
|
+
|
|
442
|
+
if not peer_task_id_to_cancel:
|
|
443
|
+
log.warning(
|
|
444
|
+
"%s Cannot cancel peer sub-task %s for main task %s because the peer's taskId is not yet known.",
|
|
445
|
+
component.log_identifier,
|
|
446
|
+
sub_task_id,
|
|
447
|
+
logical_task_id,
|
|
448
|
+
)
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
if peer_task_id_to_cancel and target_peer_agent_name:
|
|
452
|
+
log.info(
|
|
453
|
+
"%s Attempting to cancel peer sub-task %s (Peer Task ID: %s) for agent %s (main task %s).",
|
|
454
|
+
component.log_identifier,
|
|
455
|
+
sub_task_id,
|
|
456
|
+
peer_task_id_to_cancel,
|
|
457
|
+
target_peer_agent_name,
|
|
458
|
+
logical_task_id,
|
|
459
|
+
)
|
|
460
|
+
try:
|
|
461
|
+
peer_cancel_request = a2a.create_cancel_task_request(
|
|
462
|
+
task_id=peer_task_id_to_cancel
|
|
463
|
+
)
|
|
464
|
+
peer_cancel_user_props = {
|
|
465
|
+
"clientId": component.agent_name
|
|
466
|
+
}
|
|
467
|
+
component.publish_a2a_message(
|
|
468
|
+
payload=peer_cancel_request.model_dump(
|
|
469
|
+
exclude_none=True
|
|
470
|
+
),
|
|
471
|
+
topic=component._get_agent_request_topic(
|
|
472
|
+
target_peer_agent_name
|
|
473
|
+
),
|
|
474
|
+
user_properties=peer_cancel_user_props,
|
|
475
|
+
)
|
|
476
|
+
log.info(
|
|
477
|
+
"%s Sent CancelTaskRequest to peer %s for its task %s.",
|
|
478
|
+
component.log_identifier,
|
|
479
|
+
target_peer_agent_name,
|
|
480
|
+
peer_task_id_to_cancel,
|
|
481
|
+
)
|
|
482
|
+
except Exception as e_peer_cancel:
|
|
483
|
+
log.error(
|
|
484
|
+
"%s Failed to send CancelTaskRequest to peer %s for task %s: %s",
|
|
485
|
+
component.log_identifier,
|
|
486
|
+
target_peer_agent_name,
|
|
487
|
+
peer_task_id_to_cancel,
|
|
488
|
+
e_peer_cancel,
|
|
489
|
+
)
|
|
490
|
+
else:
|
|
491
|
+
log.warning(
|
|
492
|
+
"%s Peer info for main task %s incomplete, cannot cancel peer task. Info: %s",
|
|
493
|
+
component.log_identifier,
|
|
494
|
+
logical_task_id,
|
|
495
|
+
sub_task_info,
|
|
496
|
+
)
|
|
497
|
+
else:
|
|
498
|
+
# No peer sub-tasks - check if task is paused and needs immediate finalization
|
|
499
|
+
if task_context.get_is_paused():
|
|
500
|
+
log.info(
|
|
501
|
+
"%s Task %s is paused with no peer sub-tasks. Scheduling immediate finalization.",
|
|
502
|
+
component.log_identifier,
|
|
503
|
+
logical_task_id,
|
|
504
|
+
)
|
|
505
|
+
loop = component.get_async_loop()
|
|
506
|
+
if loop and loop.is_running():
|
|
507
|
+
task_context.set_paused(False)
|
|
508
|
+
|
|
509
|
+
asyncio.run_coroutine_threadsafe(
|
|
510
|
+
component.finalize_task_with_cleanup(
|
|
511
|
+
task_context.a2a_context,
|
|
512
|
+
is_paused=False,
|
|
513
|
+
exception=TaskCancelledError(
|
|
514
|
+
f"Task {logical_task_id} cancelled while paused."
|
|
515
|
+
)
|
|
516
|
+
),
|
|
517
|
+
loop,
|
|
518
|
+
)
|
|
519
|
+
else:
|
|
520
|
+
log.error(
|
|
521
|
+
"%s Cannot finalize cancelled paused task %s - event loop not available.",
|
|
522
|
+
component.log_identifier,
|
|
523
|
+
logical_task_id,
|
|
524
|
+
)
|
|
525
|
+
else:
|
|
526
|
+
log.info(
|
|
527
|
+
"%s No active task found for cancellation (ID: %s) or task already completed. Ignoring signal.",
|
|
528
|
+
component.log_identifier,
|
|
529
|
+
logical_task_id,
|
|
530
|
+
)
|
|
531
|
+
try:
|
|
532
|
+
message.call_acknowledgements()
|
|
533
|
+
log.debug(
|
|
534
|
+
"%s ACKed CancelTaskRequest for Task ID: %s.",
|
|
535
|
+
component.log_identifier,
|
|
536
|
+
logical_task_id,
|
|
537
|
+
)
|
|
538
|
+
except Exception as ack_e:
|
|
539
|
+
log.error(
|
|
540
|
+
"%s Failed to ACK CancelTaskRequest for Task ID %s: %s",
|
|
541
|
+
component.log_identifier,
|
|
542
|
+
logical_task_id,
|
|
543
|
+
ack_e,
|
|
544
|
+
)
|
|
545
|
+
return None
|
|
546
|
+
elif method in ["message/send", "message/stream"]:
|
|
547
|
+
a2a_message = a2a.get_message_from_send_request(a2a_request)
|
|
548
|
+
if not a2a_message:
|
|
549
|
+
raise ValueError("Could not extract message from SendMessageRequest")
|
|
550
|
+
|
|
551
|
+
# The gateway/client is the source of truth for the task ID.
|
|
552
|
+
# The agent adopts the ID from the JSON-RPC request envelope.
|
|
553
|
+
logical_task_id = str(a2a.get_request_id(a2a_request))
|
|
554
|
+
|
|
555
|
+
try:
|
|
556
|
+
from solace_agent_mesh_enterprise.auth.input_required import (
|
|
557
|
+
a2a_auth_message_handler,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
message_handled = await a2a_auth_message_handler(
|
|
562
|
+
component, a2a_message, logical_task_id
|
|
563
|
+
)
|
|
564
|
+
if message_handled:
|
|
565
|
+
message.call_acknowledgements()
|
|
566
|
+
log.debug(
|
|
567
|
+
"%s ACKed message handled by input-required auth handler.",
|
|
568
|
+
component.log_identifier,
|
|
569
|
+
)
|
|
570
|
+
return None
|
|
571
|
+
except Exception as auth_import_err:
|
|
572
|
+
log.error(
|
|
573
|
+
"%s Error in input-required auth handler: %s",
|
|
574
|
+
component.log_identifier,
|
|
575
|
+
auth_import_err,
|
|
576
|
+
)
|
|
577
|
+
message.call_acknowledgements()
|
|
578
|
+
return None
|
|
579
|
+
|
|
580
|
+
except ImportError:
|
|
581
|
+
pass
|
|
582
|
+
|
|
583
|
+
# The session id is now contextId on the message
|
|
584
|
+
original_session_id = a2a_message.context_id
|
|
585
|
+
message_id = a2a_message.message_id
|
|
586
|
+
task_metadata = a2a_message.metadata or {}
|
|
587
|
+
system_purpose = task_metadata.get("system_purpose")
|
|
588
|
+
response_format = task_metadata.get("response_format")
|
|
589
|
+
session_behavior_from_meta = task_metadata.get("sessionBehavior")
|
|
590
|
+
if session_behavior_from_meta:
|
|
591
|
+
session_behavior = str(session_behavior_from_meta).upper()
|
|
592
|
+
if session_behavior not in ["PERSISTENT", "RUN_BASED"]:
|
|
593
|
+
log.warning(
|
|
594
|
+
"%s Invalid 'sessionBehavior' in task metadata: '%s'. Using component default: '%s'.",
|
|
595
|
+
component.log_identifier,
|
|
596
|
+
session_behavior,
|
|
597
|
+
component.default_session_behavior,
|
|
598
|
+
)
|
|
599
|
+
session_behavior = component.default_session_behavior
|
|
600
|
+
else:
|
|
601
|
+
log.info(
|
|
602
|
+
"%s Using 'sessionBehavior' from task metadata: '%s'.",
|
|
603
|
+
component.log_identifier,
|
|
604
|
+
session_behavior,
|
|
605
|
+
)
|
|
606
|
+
else:
|
|
607
|
+
session_behavior = component.default_session_behavior
|
|
608
|
+
log.debug(
|
|
609
|
+
"%s No 'sessionBehavior' in task metadata. Using component default: '%s'.",
|
|
610
|
+
component.log_identifier,
|
|
611
|
+
session_behavior,
|
|
612
|
+
)
|
|
613
|
+
user_id = message.get_user_properties().get("userId", "default_user")
|
|
614
|
+
agent_name = component.get_config("agent_name")
|
|
615
|
+
is_streaming_request = method == "message/stream"
|
|
616
|
+
host_supports_streaming = component.get_config("supports_streaming", False)
|
|
617
|
+
if is_streaming_request and not host_supports_streaming:
|
|
618
|
+
raise ValueError(
|
|
619
|
+
"Host does not support streaming (tasks/sendSubscribe) requests."
|
|
620
|
+
)
|
|
621
|
+
effective_session_id = original_session_id
|
|
622
|
+
is_run_based_session = False
|
|
623
|
+
temporary_run_session_id_for_cleanup = None
|
|
624
|
+
|
|
625
|
+
session_id_from_data = None
|
|
626
|
+
if a2a_message and a2a_message.parts:
|
|
627
|
+
for part in a2a_message.parts:
|
|
628
|
+
if isinstance(part, DataPart) and "session_id" in part.data:
|
|
629
|
+
session_id_from_data = part.data["session_id"]
|
|
630
|
+
log.info(
|
|
631
|
+
f"Extracted session_id '{session_id_from_data}' from DataPart."
|
|
632
|
+
)
|
|
633
|
+
break
|
|
634
|
+
|
|
635
|
+
if session_id_from_data:
|
|
636
|
+
original_session_id = session_id_from_data
|
|
637
|
+
|
|
638
|
+
if session_behavior == "RUN_BASED":
|
|
639
|
+
is_run_based_session = True
|
|
640
|
+
effective_session_id = f"{original_session_id}:{logical_task_id}:run"
|
|
641
|
+
temporary_run_session_id_for_cleanup = effective_session_id
|
|
642
|
+
log.info(
|
|
643
|
+
"%s Session behavior is RUN_BASED. OriginalID='%s', EffectiveID for this run='%s', TaskID='%s'.",
|
|
644
|
+
component.log_identifier,
|
|
645
|
+
original_session_id,
|
|
646
|
+
effective_session_id,
|
|
647
|
+
logical_task_id,
|
|
648
|
+
)
|
|
649
|
+
else:
|
|
650
|
+
is_run_based_session = False
|
|
651
|
+
effective_session_id = original_session_id
|
|
652
|
+
temporary_run_session_id_for_cleanup = None
|
|
653
|
+
log.info(
|
|
654
|
+
"%s Session behavior is PERSISTENT. EffectiveID='%s' for TaskID='%s'.",
|
|
655
|
+
component.log_identifier,
|
|
656
|
+
effective_session_id,
|
|
657
|
+
logical_task_id,
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
adk_session_for_run = await component.session_service.get_session(
|
|
661
|
+
app_name=agent_name, user_id=user_id, session_id=effective_session_id
|
|
662
|
+
)
|
|
663
|
+
if adk_session_for_run is None:
|
|
664
|
+
adk_session_for_run = await component.session_service.create_session(
|
|
665
|
+
app_name=agent_name,
|
|
666
|
+
user_id=user_id,
|
|
667
|
+
session_id=effective_session_id,
|
|
668
|
+
)
|
|
669
|
+
log.info(
|
|
670
|
+
"%s Created new ADK session '%s' for task '%s'.",
|
|
671
|
+
component.log_identifier,
|
|
672
|
+
effective_session_id,
|
|
673
|
+
logical_task_id,
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
else:
|
|
677
|
+
log.info(
|
|
678
|
+
"%s Reusing existing ADK session '%s' for task '%s'.",
|
|
679
|
+
component.log_identifier,
|
|
680
|
+
effective_session_id,
|
|
681
|
+
logical_task_id,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if is_run_based_session:
|
|
685
|
+
try:
|
|
686
|
+
original_adk_session_data = (
|
|
687
|
+
await component.session_service.get_session(
|
|
688
|
+
app_name=agent_name,
|
|
689
|
+
user_id=user_id,
|
|
690
|
+
session_id=original_session_id,
|
|
691
|
+
)
|
|
692
|
+
)
|
|
693
|
+
if original_adk_session_data and hasattr(
|
|
694
|
+
original_adk_session_data, "history"
|
|
695
|
+
):
|
|
696
|
+
original_history_events = original_adk_session_data.history
|
|
697
|
+
if original_history_events:
|
|
698
|
+
log.debug(
|
|
699
|
+
"%s Copying %d events from original session '%s' to run-based session '%s'.",
|
|
700
|
+
component.log_identifier,
|
|
701
|
+
len(original_history_events),
|
|
702
|
+
original_session_id,
|
|
703
|
+
effective_session_id,
|
|
704
|
+
)
|
|
705
|
+
run_based_adk_session_for_copy = (
|
|
706
|
+
await component.session_service.create_session(
|
|
707
|
+
app_name=agent_name,
|
|
708
|
+
user_id=user_id,
|
|
709
|
+
session_id=effective_session_id,
|
|
710
|
+
)
|
|
711
|
+
)
|
|
712
|
+
for event_to_copy in original_history_events:
|
|
713
|
+
await component.session_service.append_event(
|
|
714
|
+
session=run_based_adk_session_for_copy,
|
|
715
|
+
event=event_to_copy,
|
|
716
|
+
)
|
|
717
|
+
else:
|
|
718
|
+
log.debug(
|
|
719
|
+
"%s No history to copy from original session '%s' for run-based task '%s'.",
|
|
720
|
+
component.log_identifier,
|
|
721
|
+
original_session_id,
|
|
722
|
+
logical_task_id,
|
|
723
|
+
)
|
|
724
|
+
else:
|
|
725
|
+
log.debug(
|
|
726
|
+
"%s Original session '%s' not found or has no history, cannot copy for run-based task '%s'.",
|
|
727
|
+
component.log_identifier,
|
|
728
|
+
original_session_id,
|
|
729
|
+
logical_task_id,
|
|
730
|
+
)
|
|
731
|
+
except Exception as e_copy:
|
|
732
|
+
log.error(
|
|
733
|
+
"%s Error copying history for run-based session '%s' (task '%s'): %s. Proceeding with empty session.",
|
|
734
|
+
component.log_identifier,
|
|
735
|
+
effective_session_id,
|
|
736
|
+
logical_task_id,
|
|
737
|
+
e_copy,
|
|
738
|
+
)
|
|
739
|
+
a2a_context = {
|
|
740
|
+
"jsonrpc_request_id": jsonrpc_request_id,
|
|
741
|
+
"logical_task_id": logical_task_id,
|
|
742
|
+
"contextId": original_session_id,
|
|
743
|
+
"messageId": message_id,
|
|
744
|
+
"session_id": original_session_id, # Keep for now for compatibility
|
|
745
|
+
"user_id": user_id,
|
|
746
|
+
"client_id": client_id,
|
|
747
|
+
"is_streaming": is_streaming_request,
|
|
748
|
+
"statusTopic": status_topic_from_peer,
|
|
749
|
+
"replyToTopic": reply_topic_from_peer,
|
|
750
|
+
"a2a_user_config": a2a_user_config,
|
|
751
|
+
"effective_session_id": effective_session_id,
|
|
752
|
+
"is_run_based_session": is_run_based_session,
|
|
753
|
+
"temporary_run_session_id_for_cleanup": temporary_run_session_id_for_cleanup,
|
|
754
|
+
"agent_name_for_session": (
|
|
755
|
+
agent_name if is_run_based_session else None
|
|
756
|
+
),
|
|
757
|
+
"user_id_for_session": user_id if is_run_based_session else None,
|
|
758
|
+
"system_purpose": system_purpose,
|
|
759
|
+
"response_format": response_format,
|
|
760
|
+
"host_agent_name": agent_name,
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
# Store verified user identity claims in a2a_context (not the raw token)
|
|
764
|
+
if verified_user_identity:
|
|
765
|
+
a2a_context["verified_user_identity"] = verified_user_identity
|
|
766
|
+
log.debug(
|
|
767
|
+
"%s Stored verified user identity in a2a_context for task %s",
|
|
768
|
+
component.log_identifier,
|
|
769
|
+
logical_task_id,
|
|
770
|
+
)
|
|
771
|
+
if trace_logger.isEnabledFor(logging.DEBUG):
|
|
772
|
+
trace_logger.debug(
|
|
773
|
+
"%s A2A Context (shared service model): %s",
|
|
774
|
+
component.log_identifier,
|
|
775
|
+
a2a_context,
|
|
776
|
+
)
|
|
777
|
+
else:
|
|
778
|
+
log.debug(
|
|
779
|
+
"%s A2A Context prepared for task %s",
|
|
780
|
+
component.log_identifier,
|
|
781
|
+
a2a_context.get("logical_task_id", "unknown"),
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
# Create and store the execution context for this task
|
|
785
|
+
task_context = TaskExecutionContext(
|
|
786
|
+
task_id=logical_task_id, a2a_context=a2a_context
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# Store the original Solace message in TaskExecutionContext instead of a2a_context
|
|
790
|
+
# This avoids serialization issues when a2a_context is stored in ADK session state
|
|
791
|
+
task_context.set_original_solace_message(message)
|
|
792
|
+
|
|
793
|
+
# Store auth token for peer delegation using generic security storage
|
|
794
|
+
if hasattr(component, "trust_manager") and component.trust_manager:
|
|
795
|
+
auth_token = message.get_user_properties().get("authToken")
|
|
796
|
+
if auth_token:
|
|
797
|
+
task_context.set_security_data("auth_token", auth_token)
|
|
798
|
+
log.debug(
|
|
799
|
+
"%s Stored authentication token in TaskExecutionContext security storage for task %s",
|
|
800
|
+
component.log_identifier,
|
|
801
|
+
logical_task_id,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
with component.active_tasks_lock:
|
|
805
|
+
component.active_tasks[logical_task_id] = task_context
|
|
806
|
+
log.info(
|
|
807
|
+
"%s Created and stored new TaskExecutionContext for task %s.",
|
|
808
|
+
component.log_identifier,
|
|
809
|
+
logical_task_id,
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
a2a_message_for_adk = a2a_message
|
|
813
|
+
invoked_artifacts = (
|
|
814
|
+
a2a_message_for_adk.metadata.get("invoked_with_artifacts", [])
|
|
815
|
+
if a2a_message_for_adk.metadata
|
|
816
|
+
else []
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
if invoked_artifacts:
|
|
820
|
+
log.info(
|
|
821
|
+
"%s Task %s invoked with %d artifact(s). Preparing context from metadata.",
|
|
822
|
+
component.log_identifier,
|
|
823
|
+
logical_task_id,
|
|
824
|
+
len(invoked_artifacts),
|
|
825
|
+
)
|
|
826
|
+
header_text = (
|
|
827
|
+
"The user has provided the following artifacts as context for your task. "
|
|
828
|
+
"Use the information contained within their metadata to complete your objective."
|
|
829
|
+
)
|
|
830
|
+
artifact_summary = await generate_artifact_metadata_summary(
|
|
831
|
+
component=component,
|
|
832
|
+
artifact_identifiers=invoked_artifacts,
|
|
833
|
+
user_id=user_id,
|
|
834
|
+
session_id=effective_session_id,
|
|
835
|
+
app_name=agent_name,
|
|
836
|
+
header_text=header_text,
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
task_description = get_text_from_message(a2a_message_for_adk)
|
|
840
|
+
final_prompt = f"{task_description}\n\n{artifact_summary}"
|
|
841
|
+
|
|
842
|
+
a2a_message_for_adk = a2a.update_message_parts(
|
|
843
|
+
message=a2a_message_for_adk,
|
|
844
|
+
new_parts=[a2a.create_text_part(text=final_prompt)],
|
|
845
|
+
)
|
|
846
|
+
log.debug(
|
|
847
|
+
"%s Generated new prompt for task %s with artifact context.",
|
|
848
|
+
component.log_identifier,
|
|
849
|
+
logical_task_id,
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
adk_content = await translate_a2a_to_adk_content(
|
|
853
|
+
a2a_message=a2a_message_for_adk,
|
|
854
|
+
component=component,
|
|
855
|
+
user_id=user_id,
|
|
856
|
+
session_id=effective_session_id,
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
adk_session = await component.session_service.get_session(
|
|
860
|
+
app_name=agent_name, user_id=user_id, session_id=effective_session_id
|
|
861
|
+
)
|
|
862
|
+
if adk_session is None:
|
|
863
|
+
log.info(
|
|
864
|
+
"%s ADK session '%s' not found in component.session_service, creating new one.",
|
|
865
|
+
component.log_identifier,
|
|
866
|
+
effective_session_id,
|
|
867
|
+
)
|
|
868
|
+
adk_session = await component.session_service.create_session(
|
|
869
|
+
app_name=agent_name,
|
|
870
|
+
user_id=user_id,
|
|
871
|
+
session_id=effective_session_id,
|
|
872
|
+
)
|
|
873
|
+
else:
|
|
874
|
+
log.info(
|
|
875
|
+
"%s Reusing existing ADK session '%s' from component.session_service.",
|
|
876
|
+
component.log_identifier,
|
|
877
|
+
effective_session_id,
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
# Always use SSE streaming mode for the ADK runner.
|
|
881
|
+
# This ensures that real-time callbacks (e.g., for fenced artifact
|
|
882
|
+
# progress) can function correctly for all task types. The component's
|
|
883
|
+
# internal logic uses the 'is_run_based_session' flag to differentiate
|
|
884
|
+
# between aggregating a final response and streaming partial updates.
|
|
885
|
+
streaming_mode = StreamingMode.SSE
|
|
886
|
+
|
|
887
|
+
max_llm_calls_per_task = component.get_config("max_llm_calls_per_task", 20)
|
|
888
|
+
log.debug(
|
|
889
|
+
"%s Using max_llm_calls_per_task: %s",
|
|
890
|
+
component.log_identifier,
|
|
891
|
+
max_llm_calls_per_task,
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
run_config = RunConfig(
|
|
895
|
+
streaming_mode=streaming_mode, max_llm_calls=max_llm_calls_per_task
|
|
896
|
+
)
|
|
897
|
+
log.info(
|
|
898
|
+
"%s Setting ADK RunConfig streaming_mode to: %s, max_llm_calls to: %s",
|
|
899
|
+
component.log_identifier,
|
|
900
|
+
streaming_mode,
|
|
901
|
+
max_llm_calls_per_task,
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
log.info(
|
|
905
|
+
"%s Starting ADK runner task for request %s (Task ID: %s)",
|
|
906
|
+
component.log_identifier,
|
|
907
|
+
jsonrpc_request_id,
|
|
908
|
+
logical_task_id,
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
await run_adk_async_task_thread_wrapper(
|
|
912
|
+
component,
|
|
913
|
+
adk_session,
|
|
914
|
+
adk_content,
|
|
915
|
+
run_config,
|
|
916
|
+
a2a_context,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
log.info(
|
|
920
|
+
"%s ADK task execution awaited for Task ID %s.",
|
|
921
|
+
component.log_identifier,
|
|
922
|
+
logical_task_id,
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
else:
|
|
926
|
+
log.warning(
|
|
927
|
+
"%s Received unhandled A2A request type: %s. Acknowledging.",
|
|
928
|
+
component.log_identifier,
|
|
929
|
+
method,
|
|
930
|
+
)
|
|
931
|
+
try:
|
|
932
|
+
message.call_acknowledgements()
|
|
933
|
+
except Exception as ack_e:
|
|
934
|
+
log.error(
|
|
935
|
+
"%s Failed to ACK unhandled request type %s: %s",
|
|
936
|
+
component.log_identifier,
|
|
937
|
+
method,
|
|
938
|
+
ack_e,
|
|
939
|
+
)
|
|
940
|
+
return None
|
|
941
|
+
|
|
942
|
+
except (json.JSONDecodeError, ValueError, TypeError) as e:
|
|
943
|
+
log.error(
|
|
944
|
+
"%s Failed to parse, validate, or start ADK task for A2A request: %s",
|
|
945
|
+
component.log_identifier,
|
|
946
|
+
e,
|
|
947
|
+
)
|
|
948
|
+
error_data = {"taskId": logical_task_id} if logical_task_id else None
|
|
949
|
+
error_response = a2a.create_internal_error_response(
|
|
950
|
+
message=str(e), request_id=jsonrpc_request_id, data=error_data
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
target_topic = reply_topic_from_peer or (
|
|
954
|
+
get_client_response_topic(namespace, client_id) if client_id else None
|
|
955
|
+
)
|
|
956
|
+
if target_topic:
|
|
957
|
+
component.publish_a2a_message(
|
|
958
|
+
error_response.model_dump(exclude_none=True),
|
|
959
|
+
target_topic,
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
try:
|
|
963
|
+
message.call_negative_acknowledgements()
|
|
964
|
+
log.warning(
|
|
965
|
+
"%s NACKed original A2A request due to parsing/validation/start error.",
|
|
966
|
+
component.log_identifier,
|
|
967
|
+
)
|
|
968
|
+
except Exception as nack_e:
|
|
969
|
+
log.error(
|
|
970
|
+
"%s Failed to NACK message after pre-start error: %s",
|
|
971
|
+
component.log_identifier,
|
|
972
|
+
nack_e,
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
component.handle_error(e, Event(EventType.MESSAGE, message))
|
|
976
|
+
return None
|
|
977
|
+
|
|
978
|
+
except OperationalError as e:
|
|
979
|
+
log.error(
|
|
980
|
+
"%s Database error while processing A2A request: %s",
|
|
981
|
+
component.log_identifier,
|
|
982
|
+
e,
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
# Check if it's a schema error
|
|
986
|
+
error_msg = str(e).lower()
|
|
987
|
+
if "no such column" in error_msg or "no such table" in error_msg:
|
|
988
|
+
user_message = (
|
|
989
|
+
"Database schema update required. "
|
|
990
|
+
"Please contact your administrator to run database migrations."
|
|
991
|
+
)
|
|
992
|
+
else:
|
|
993
|
+
user_message = (
|
|
994
|
+
"Database error occurred. Please try again or contact support."
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
error_response = a2a.create_internal_error_response(
|
|
998
|
+
message=user_message,
|
|
999
|
+
request_id=jsonrpc_request_id,
|
|
1000
|
+
data={"taskId": logical_task_id} if logical_task_id else None,
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
target_topic = reply_topic_from_peer or (
|
|
1004
|
+
get_client_response_topic(namespace, client_id) if client_id else None
|
|
1005
|
+
)
|
|
1006
|
+
if target_topic:
|
|
1007
|
+
component.publish_a2a_message(
|
|
1008
|
+
error_response.model_dump(exclude_none=True),
|
|
1009
|
+
target_topic,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
try:
|
|
1013
|
+
message.call_negative_acknowledgements()
|
|
1014
|
+
log.warning(
|
|
1015
|
+
"%s NACKed A2A request due to database error.",
|
|
1016
|
+
component.log_identifier,
|
|
1017
|
+
)
|
|
1018
|
+
except Exception as nack_e:
|
|
1019
|
+
log.error(
|
|
1020
|
+
"%s Failed to NACK message after database error: %s",
|
|
1021
|
+
component.log_identifier,
|
|
1022
|
+
nack_e,
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
component.handle_error(e, Event(EventType.MESSAGE, message))
|
|
1026
|
+
return None
|
|
1027
|
+
|
|
1028
|
+
except Exception as e:
|
|
1029
|
+
log.exception(
|
|
1030
|
+
"%s Unexpected error handling A2A request: %s", component.log_identifier, e
|
|
1031
|
+
)
|
|
1032
|
+
error_response = a2a.create_internal_error_response(
|
|
1033
|
+
message=f"Unexpected server error: {e}",
|
|
1034
|
+
request_id=jsonrpc_request_id,
|
|
1035
|
+
data={"taskId": logical_task_id},
|
|
1036
|
+
)
|
|
1037
|
+
target_topic = reply_topic_from_peer or (
|
|
1038
|
+
get_client_response_topic(namespace, client_id) if client_id else None
|
|
1039
|
+
)
|
|
1040
|
+
if target_topic:
|
|
1041
|
+
component.publish_a2a_message(
|
|
1042
|
+
error_response.model_dump(exclude_none=True),
|
|
1043
|
+
target_topic,
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
try:
|
|
1047
|
+
message.call_negative_acknowledgements()
|
|
1048
|
+
log.warning(
|
|
1049
|
+
"%s NACKed original A2A request due to unexpected error.",
|
|
1050
|
+
component.log_identifier,
|
|
1051
|
+
)
|
|
1052
|
+
except Exception as nack_e:
|
|
1053
|
+
log.error(
|
|
1054
|
+
"%s Failed to NACK message after unexpected error: %s",
|
|
1055
|
+
component.log_identifier,
|
|
1056
|
+
nack_e,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
component.handle_error(e, Event(EventType.MESSAGE, message))
|
|
1060
|
+
return None
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def handle_agent_card_message(component, message: SolaceMessage):
|
|
1064
|
+
"""Handles incoming Agent Card messages."""
|
|
1065
|
+
try:
|
|
1066
|
+
payload = message.get_payload()
|
|
1067
|
+
if not isinstance(payload, dict):
|
|
1068
|
+
log.warning(
|
|
1069
|
+
"%s Received agent card with non-dict payload. Ignoring.",
|
|
1070
|
+
component.log_identifier,
|
|
1071
|
+
)
|
|
1072
|
+
message.call_acknowledgements()
|
|
1073
|
+
return
|
|
1074
|
+
|
|
1075
|
+
agent_card = AgentCard(**payload)
|
|
1076
|
+
agent_name = agent_card.name
|
|
1077
|
+
self_agent_name = component.get_config("agent_name")
|
|
1078
|
+
|
|
1079
|
+
if agent_name == self_agent_name:
|
|
1080
|
+
message.call_acknowledgements()
|
|
1081
|
+
return
|
|
1082
|
+
|
|
1083
|
+
agent_discovery = component.get_config("agent_discovery", {})
|
|
1084
|
+
if agent_discovery.get("enabled", False) is False:
|
|
1085
|
+
message.call_acknowledgements()
|
|
1086
|
+
return
|
|
1087
|
+
|
|
1088
|
+
inter_agent_config = component.get_config("inter_agent_communication", {})
|
|
1089
|
+
allow_list = inter_agent_config.get("allow_list", ["*"])
|
|
1090
|
+
deny_list = inter_agent_config.get("deny_list", [])
|
|
1091
|
+
is_allowed = False
|
|
1092
|
+
for pattern in allow_list:
|
|
1093
|
+
if fnmatch.fnmatch(agent_name, pattern):
|
|
1094
|
+
is_allowed = True
|
|
1095
|
+
break
|
|
1096
|
+
|
|
1097
|
+
if is_allowed:
|
|
1098
|
+
for pattern in deny_list:
|
|
1099
|
+
if fnmatch.fnmatch(agent_name, pattern):
|
|
1100
|
+
is_allowed = False
|
|
1101
|
+
break
|
|
1102
|
+
|
|
1103
|
+
if is_allowed:
|
|
1104
|
+
|
|
1105
|
+
# Also store in peer_agents for backward compatibility
|
|
1106
|
+
component.peer_agents[agent_name] = agent_card
|
|
1107
|
+
|
|
1108
|
+
# Store the agent card in the registry for health tracking
|
|
1109
|
+
is_new = component.agent_registry.add_or_update_agent(agent_card)
|
|
1110
|
+
|
|
1111
|
+
if is_new:
|
|
1112
|
+
log.info(
|
|
1113
|
+
"%s Registered new agent '%s' in registry.",
|
|
1114
|
+
component.log_identifier,
|
|
1115
|
+
agent_name,
|
|
1116
|
+
)
|
|
1117
|
+
else:
|
|
1118
|
+
log.debug(
|
|
1119
|
+
"%s Updated existing agent '%s' in registry.",
|
|
1120
|
+
component.log_identifier,
|
|
1121
|
+
agent_name,
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
message.call_acknowledgements()
|
|
1125
|
+
|
|
1126
|
+
except Exception as e:
|
|
1127
|
+
log.exception(
|
|
1128
|
+
"%s Error processing agent card message: %s", component.log_identifier, e
|
|
1129
|
+
)
|
|
1130
|
+
message.call_acknowledgements()
|
|
1131
|
+
component.handle_error(e, Event(EventType.MESSAGE, message))
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
async def handle_a2a_response(component, message: SolaceMessage):
|
|
1135
|
+
"""Handles incoming responses/status updates from peer agents."""
|
|
1136
|
+
sub_task_id = None
|
|
1137
|
+
payload_to_queue = None
|
|
1138
|
+
is_final_response = False
|
|
1139
|
+
|
|
1140
|
+
try:
|
|
1141
|
+
topic = message.get_topic()
|
|
1142
|
+
agent_response_sub = a2a.get_agent_response_subscription_topic(
|
|
1143
|
+
component.namespace, component.agent_name
|
|
1144
|
+
)
|
|
1145
|
+
agent_status_sub = a2a.get_agent_status_subscription_topic(
|
|
1146
|
+
component.namespace, component.agent_name
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
if a2a.topic_matches_subscription(topic, agent_response_sub):
|
|
1150
|
+
sub_task_id = a2a.extract_task_id_from_topic(
|
|
1151
|
+
topic, agent_response_sub, component.log_identifier
|
|
1152
|
+
)
|
|
1153
|
+
elif a2a.topic_matches_subscription(topic, agent_status_sub):
|
|
1154
|
+
sub_task_id = a2a.extract_task_id_from_topic(
|
|
1155
|
+
topic, agent_status_sub, component.log_identifier
|
|
1156
|
+
)
|
|
1157
|
+
else:
|
|
1158
|
+
sub_task_id = None
|
|
1159
|
+
|
|
1160
|
+
if not sub_task_id:
|
|
1161
|
+
log.error(
|
|
1162
|
+
"%s Could not extract sub-task ID from topic: %s",
|
|
1163
|
+
component.log_identifier,
|
|
1164
|
+
topic,
|
|
1165
|
+
)
|
|
1166
|
+
message.call_negative_acknowledgements()
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
log.debug("%s Extracted sub-task ID: %s", component.log_identifier, sub_task_id)
|
|
1170
|
+
|
|
1171
|
+
payload_dict = message.get_payload()
|
|
1172
|
+
if not isinstance(payload_dict, dict):
|
|
1173
|
+
log.error(
|
|
1174
|
+
"%s Received non-dict payload for sub-task %s. Payload: %s",
|
|
1175
|
+
component.log_identifier,
|
|
1176
|
+
sub_task_id,
|
|
1177
|
+
payload_dict,
|
|
1178
|
+
)
|
|
1179
|
+
payload_to_queue = {
|
|
1180
|
+
"error": "Received invalid payload format from peer.",
|
|
1181
|
+
"code": "PEER_PAYLOAD_ERROR",
|
|
1182
|
+
}
|
|
1183
|
+
is_final_response = True
|
|
1184
|
+
else:
|
|
1185
|
+
try:
|
|
1186
|
+
a2a_response = JSONRPCResponse.model_validate(payload_dict)
|
|
1187
|
+
|
|
1188
|
+
result = a2a.get_response_result(a2a_response)
|
|
1189
|
+
if result:
|
|
1190
|
+
payload_data = result
|
|
1191
|
+
|
|
1192
|
+
# Store the peer's task ID if we see it for the first time
|
|
1193
|
+
peer_task_id = getattr(payload_data, "task_id", None)
|
|
1194
|
+
if peer_task_id:
|
|
1195
|
+
correlation_data = (
|
|
1196
|
+
await component._get_correlation_data_for_sub_task(
|
|
1197
|
+
sub_task_id
|
|
1198
|
+
)
|
|
1199
|
+
)
|
|
1200
|
+
if correlation_data and "peer_task_id" not in correlation_data:
|
|
1201
|
+
log.info(
|
|
1202
|
+
"%s Received first response for sub-task %s. Storing peer taskId: %s",
|
|
1203
|
+
component.log_identifier,
|
|
1204
|
+
sub_task_id,
|
|
1205
|
+
peer_task_id,
|
|
1206
|
+
)
|
|
1207
|
+
main_logical_task_id = correlation_data.get(
|
|
1208
|
+
"logical_task_id"
|
|
1209
|
+
)
|
|
1210
|
+
with component.active_tasks_lock:
|
|
1211
|
+
task_context = component.active_tasks.get(
|
|
1212
|
+
main_logical_task_id
|
|
1213
|
+
)
|
|
1214
|
+
if task_context:
|
|
1215
|
+
with task_context.lock:
|
|
1216
|
+
if (
|
|
1217
|
+
sub_task_id
|
|
1218
|
+
in task_context.active_peer_sub_tasks
|
|
1219
|
+
):
|
|
1220
|
+
task_context.active_peer_sub_tasks[
|
|
1221
|
+
sub_task_id
|
|
1222
|
+
]["peer_task_id"] = peer_task_id
|
|
1223
|
+
|
|
1224
|
+
parsed_successfully = False
|
|
1225
|
+
is_final_response = False
|
|
1226
|
+
payload_to_queue = None
|
|
1227
|
+
|
|
1228
|
+
if isinstance(payload_data, TaskStatusUpdateEvent):
|
|
1229
|
+
try:
|
|
1230
|
+
status_event = payload_data
|
|
1231
|
+
|
|
1232
|
+
data_parts = a2a.get_data_parts_from_status_update(
|
|
1233
|
+
status_event
|
|
1234
|
+
)
|
|
1235
|
+
if data_parts:
|
|
1236
|
+
|
|
1237
|
+
peer_agent_name = (
|
|
1238
|
+
status_event.metadata.get(
|
|
1239
|
+
"agent_name", "UnknownPeer"
|
|
1240
|
+
)
|
|
1241
|
+
if status_event.metadata
|
|
1242
|
+
else "UnknownPeer"
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
correlation_data = (
|
|
1246
|
+
await component._get_correlation_data_for_sub_task(
|
|
1247
|
+
sub_task_id
|
|
1248
|
+
)
|
|
1249
|
+
)
|
|
1250
|
+
if not correlation_data:
|
|
1251
|
+
log.warning(
|
|
1252
|
+
"%s Correlation data not found for sub-task %s. Cannot forward status signal.",
|
|
1253
|
+
component.log_identifier,
|
|
1254
|
+
sub_task_id,
|
|
1255
|
+
)
|
|
1256
|
+
message.call_acknowledgements()
|
|
1257
|
+
return
|
|
1258
|
+
|
|
1259
|
+
original_task_context = correlation_data.get(
|
|
1260
|
+
"original_task_context"
|
|
1261
|
+
)
|
|
1262
|
+
if not original_task_context:
|
|
1263
|
+
log.warning(
|
|
1264
|
+
"%s original_task_context not found in correlation data for sub-task %s. Cannot forward status signal.",
|
|
1265
|
+
component.log_identifier,
|
|
1266
|
+
sub_task_id,
|
|
1267
|
+
)
|
|
1268
|
+
message.call_acknowledgements()
|
|
1269
|
+
return
|
|
1270
|
+
|
|
1271
|
+
main_logical_task_id = original_task_context.get(
|
|
1272
|
+
"logical_task_id"
|
|
1273
|
+
)
|
|
1274
|
+
original_jsonrpc_request_id = original_task_context.get(
|
|
1275
|
+
"jsonrpc_request_id"
|
|
1276
|
+
)
|
|
1277
|
+
main_context_id = original_task_context.get("contextId")
|
|
1278
|
+
|
|
1279
|
+
target_topic_for_forward = original_task_context.get(
|
|
1280
|
+
"statusTopic"
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
if (
|
|
1284
|
+
not main_logical_task_id
|
|
1285
|
+
or not original_jsonrpc_request_id
|
|
1286
|
+
or not target_topic_for_forward
|
|
1287
|
+
):
|
|
1288
|
+
log.error(
|
|
1289
|
+
"%s Missing critical info (main_task_id, original_rpc_id, or target_status_topic) in context for sub-task %s. Cannot forward. Context: %s",
|
|
1290
|
+
component.log_identifier,
|
|
1291
|
+
sub_task_id,
|
|
1292
|
+
original_task_context,
|
|
1293
|
+
)
|
|
1294
|
+
message.call_acknowledgements()
|
|
1295
|
+
return
|
|
1296
|
+
|
|
1297
|
+
event_metadata = {
|
|
1298
|
+
"agent_name": component.agent_name,
|
|
1299
|
+
"forwarded_from_peer": peer_agent_name,
|
|
1300
|
+
"original_peer_event_taskId": status_event.task_id,
|
|
1301
|
+
"original_peer_event_timestamp": (
|
|
1302
|
+
status_event.status.timestamp
|
|
1303
|
+
if status_event.status
|
|
1304
|
+
and status_event.status.timestamp
|
|
1305
|
+
else None
|
|
1306
|
+
),
|
|
1307
|
+
"function_call_id": correlation_data.get(
|
|
1308
|
+
"adk_function_call_id", None
|
|
1309
|
+
),
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
if (
|
|
1313
|
+
status_event.status.state
|
|
1314
|
+
== TaskState.input_required
|
|
1315
|
+
):
|
|
1316
|
+
log.debug(
|
|
1317
|
+
"%s Received input-required status for sub-task %s. Requesting user input. Forwarding to target.",
|
|
1318
|
+
component.log_identifier,
|
|
1319
|
+
sub_task_id,
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
if (
|
|
1323
|
+
status_event.metadata
|
|
1324
|
+
and "task_call_stack" in status_event.metadata
|
|
1325
|
+
and isinstance(
|
|
1326
|
+
status_event.metadata["task_call_stack"],
|
|
1327
|
+
list,
|
|
1328
|
+
)
|
|
1329
|
+
):
|
|
1330
|
+
task_call_stack = status_event.metadata[
|
|
1331
|
+
"task_call_stack"
|
|
1332
|
+
].copy()
|
|
1333
|
+
task_call_stack.insert(0, sub_task_id)
|
|
1334
|
+
event_metadata["task_call_stack"] = (
|
|
1335
|
+
task_call_stack
|
|
1336
|
+
)
|
|
1337
|
+
else:
|
|
1338
|
+
event_metadata["task_call_stack"] = [
|
|
1339
|
+
sub_task_id
|
|
1340
|
+
]
|
|
1341
|
+
|
|
1342
|
+
status_event.metadata = event_metadata
|
|
1343
|
+
status_event.task_id = main_logical_task_id
|
|
1344
|
+
|
|
1345
|
+
_forward_jsonrpc_response(
|
|
1346
|
+
component=component,
|
|
1347
|
+
original_jsonrpc_request_id=original_jsonrpc_request_id,
|
|
1348
|
+
result_data=status_event,
|
|
1349
|
+
target_topic=target_topic_for_forward,
|
|
1350
|
+
main_logical_task_id=main_logical_task_id,
|
|
1351
|
+
peer_agent_name=peer_agent_name,
|
|
1352
|
+
message=message,
|
|
1353
|
+
)
|
|
1354
|
+
return
|
|
1355
|
+
|
|
1356
|
+
# Filter out artifact creation progress from peer agents.
|
|
1357
|
+
# These are implementation details that should not leak across
|
|
1358
|
+
# agent boundaries. Artifacts are properly bubbled up in the
|
|
1359
|
+
# final Task response metadata.
|
|
1360
|
+
filtered_data_parts = []
|
|
1361
|
+
for data_part in data_parts:
|
|
1362
|
+
if isinstance(data_part.data, dict) and data_part.data.get("type") == "artifact_creation_progress":
|
|
1363
|
+
log.debug(
|
|
1364
|
+
"%s Filtered out artifact_creation_progress DataPart from peer sub-task %s. Not forwarding to user.",
|
|
1365
|
+
component.log_identifier,
|
|
1366
|
+
sub_task_id,
|
|
1367
|
+
)
|
|
1368
|
+
continue
|
|
1369
|
+
filtered_data_parts.append(data_part)
|
|
1370
|
+
|
|
1371
|
+
# Only forward if there are non-filtered data parts
|
|
1372
|
+
if filtered_data_parts:
|
|
1373
|
+
for data_part in filtered_data_parts:
|
|
1374
|
+
log.info(
|
|
1375
|
+
"%s Received DataPart signal from peer for sub-task %s. Forwarding...",
|
|
1376
|
+
component.log_identifier,
|
|
1377
|
+
sub_task_id,
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
forwarded_message = a2a.create_agent_parts_message(
|
|
1381
|
+
parts=[data_part],
|
|
1382
|
+
metadata=event_metadata,
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
forwarded_event = a2a.create_status_update(
|
|
1386
|
+
task_id=main_logical_task_id,
|
|
1387
|
+
context_id=main_context_id,
|
|
1388
|
+
message=forwarded_message,
|
|
1389
|
+
is_final=False,
|
|
1390
|
+
)
|
|
1391
|
+
if (
|
|
1392
|
+
status_event.status
|
|
1393
|
+
and status_event.status.timestamp
|
|
1394
|
+
):
|
|
1395
|
+
forwarded_event.status.timestamp = (
|
|
1396
|
+
status_event.status.timestamp
|
|
1397
|
+
)
|
|
1398
|
+
_forward_jsonrpc_response(
|
|
1399
|
+
component=component,
|
|
1400
|
+
original_jsonrpc_request_id=original_jsonrpc_request_id,
|
|
1401
|
+
result_data=forwarded_event,
|
|
1402
|
+
target_topic=target_topic_for_forward,
|
|
1403
|
+
main_logical_task_id=main_logical_task_id,
|
|
1404
|
+
peer_agent_name=peer_agent_name,
|
|
1405
|
+
message=message,
|
|
1406
|
+
)
|
|
1407
|
+
return
|
|
1408
|
+
else:
|
|
1409
|
+
log.debug(
|
|
1410
|
+
"%s All DataParts from peer sub-task %s were filtered. Not forwarding.",
|
|
1411
|
+
component.log_identifier,
|
|
1412
|
+
sub_task_id,
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1415
|
+
payload_to_queue = status_event.model_dump(
|
|
1416
|
+
by_alias=True, exclude_none=True
|
|
1417
|
+
)
|
|
1418
|
+
if status_event.final:
|
|
1419
|
+
log.debug(
|
|
1420
|
+
"%s Parsed TaskStatusUpdateEvent(final=True) from peer for sub-task %s. This is an intermediate update for PeerAgentTool.",
|
|
1421
|
+
component.log_identifier,
|
|
1422
|
+
sub_task_id,
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
if status_event.status and status_event.status.message:
|
|
1426
|
+
response_parts_data = []
|
|
1427
|
+
unwrapped_parts = a2a.get_parts_from_message(
|
|
1428
|
+
status_event.status.message
|
|
1429
|
+
)
|
|
1430
|
+
for part in unwrapped_parts:
|
|
1431
|
+
if isinstance(part, TextPart):
|
|
1432
|
+
response_parts_data.append(str(part.text))
|
|
1433
|
+
elif isinstance(part, DataPart):
|
|
1434
|
+
try:
|
|
1435
|
+
response_parts_data.append(
|
|
1436
|
+
json.dumps(part.data)
|
|
1437
|
+
)
|
|
1438
|
+
except TypeError:
|
|
1439
|
+
response_parts_data.append(
|
|
1440
|
+
str(part.data)
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
payload_to_queue = {
|
|
1444
|
+
"result": "\n".join(response_parts_data)
|
|
1445
|
+
}
|
|
1446
|
+
log.debug(
|
|
1447
|
+
"%s Extracted content for TaskStatusUpdateEvent(final=True) for sub-task %s: %s",
|
|
1448
|
+
component.log_identifier,
|
|
1449
|
+
sub_task_id,
|
|
1450
|
+
payload_to_queue,
|
|
1451
|
+
)
|
|
1452
|
+
else:
|
|
1453
|
+
log.debug(
|
|
1454
|
+
"%s TaskStatusUpdateEvent(final=True) for sub-task %s has no message parts to extract. Sending event object.",
|
|
1455
|
+
component.log_identifier,
|
|
1456
|
+
sub_task_id,
|
|
1457
|
+
)
|
|
1458
|
+
else:
|
|
1459
|
+
log.debug(
|
|
1460
|
+
"%s Parsed TaskStatusUpdateEvent(final=False) from peer for sub-task %s. This is an intermediate update.",
|
|
1461
|
+
component.log_identifier,
|
|
1462
|
+
sub_task_id,
|
|
1463
|
+
)
|
|
1464
|
+
parsed_successfully = True
|
|
1465
|
+
except Exception as e:
|
|
1466
|
+
log.warning(
|
|
1467
|
+
"%s Failed to process payload as TaskStatusUpdateEvent for sub-task %s. Payload: %s. Error: %s",
|
|
1468
|
+
component.log_identifier,
|
|
1469
|
+
sub_task_id,
|
|
1470
|
+
payload_data,
|
|
1471
|
+
e,
|
|
1472
|
+
)
|
|
1473
|
+
payload_to_queue = None
|
|
1474
|
+
|
|
1475
|
+
elif isinstance(payload_data, TaskArtifactUpdateEvent):
|
|
1476
|
+
try:
|
|
1477
|
+
artifact_event = payload_data
|
|
1478
|
+
payload_to_queue = artifact_event.model_dump(
|
|
1479
|
+
by_alias=True, exclude_none=True
|
|
1480
|
+
)
|
|
1481
|
+
is_final_response = False
|
|
1482
|
+
log.debug(
|
|
1483
|
+
"%s Parsed TaskArtifactUpdateEvent from peer for sub-task %s. This is an intermediate update.",
|
|
1484
|
+
component.log_identifier,
|
|
1485
|
+
sub_task_id,
|
|
1486
|
+
)
|
|
1487
|
+
parsed_successfully = True
|
|
1488
|
+
except Exception as e:
|
|
1489
|
+
log.warning(
|
|
1490
|
+
"%s Failed to parse payload as TaskArtifactUpdateEvent for sub-task %s. Payload: %s. Error: %s",
|
|
1491
|
+
component.log_identifier,
|
|
1492
|
+
sub_task_id,
|
|
1493
|
+
payload_data,
|
|
1494
|
+
e,
|
|
1495
|
+
)
|
|
1496
|
+
payload_to_queue = None
|
|
1497
|
+
|
|
1498
|
+
elif isinstance(payload_data, Task):
|
|
1499
|
+
try:
|
|
1500
|
+
final_task = payload_data
|
|
1501
|
+
payload_to_queue = final_task.model_dump(
|
|
1502
|
+
by_alias=True, exclude_none=True
|
|
1503
|
+
)
|
|
1504
|
+
is_final_response = True
|
|
1505
|
+
log.debug(
|
|
1506
|
+
"%s Parsed final Task object from peer for sub-task %s.",
|
|
1507
|
+
component.log_identifier,
|
|
1508
|
+
sub_task_id,
|
|
1509
|
+
)
|
|
1510
|
+
parsed_successfully = True
|
|
1511
|
+
except Exception as task_parse_error:
|
|
1512
|
+
log.error(
|
|
1513
|
+
"%s Failed to parse peer response for sub-task %s as Task. Payload: %s. Error: %s",
|
|
1514
|
+
component.log_identifier,
|
|
1515
|
+
sub_task_id,
|
|
1516
|
+
payload_data,
|
|
1517
|
+
task_parse_error,
|
|
1518
|
+
)
|
|
1519
|
+
if not a2a.get_response_error(a2a_response):
|
|
1520
|
+
error = a2a.create_internal_error(
|
|
1521
|
+
message=f"Failed to parse response from peer agent for sub-task {sub_task_id}",
|
|
1522
|
+
data={
|
|
1523
|
+
"original_payload": payload_data.model_dump(
|
|
1524
|
+
by_alias=True, exclude_none=True
|
|
1525
|
+
),
|
|
1526
|
+
"error": str(task_parse_error),
|
|
1527
|
+
},
|
|
1528
|
+
)
|
|
1529
|
+
a2a_response = a2a.create_error_response(
|
|
1530
|
+
error, a2a.get_response_id(a2a_response)
|
|
1531
|
+
)
|
|
1532
|
+
payload_to_queue = None
|
|
1533
|
+
is_final_response = True
|
|
1534
|
+
|
|
1535
|
+
if (
|
|
1536
|
+
not parsed_successfully
|
|
1537
|
+
and not a2a.get_response_error(a2a_response)
|
|
1538
|
+
and payload_to_queue is None
|
|
1539
|
+
):
|
|
1540
|
+
log.error(
|
|
1541
|
+
"%s Unhandled payload structure from peer for sub-task %s: %s.",
|
|
1542
|
+
component.log_identifier,
|
|
1543
|
+
sub_task_id,
|
|
1544
|
+
payload_data,
|
|
1545
|
+
)
|
|
1546
|
+
error = a2a.create_internal_error(
|
|
1547
|
+
message=f"Unknown response structure from peer agent for sub-task {sub_task_id}",
|
|
1548
|
+
data={
|
|
1549
|
+
"original_payload": payload_data.model_dump(
|
|
1550
|
+
by_alias=True, exclude_none=True
|
|
1551
|
+
)
|
|
1552
|
+
},
|
|
1553
|
+
)
|
|
1554
|
+
a2a_response = a2a.create_error_response(
|
|
1555
|
+
error, a2a.get_response_id(a2a_response)
|
|
1556
|
+
)
|
|
1557
|
+
is_final_response = True
|
|
1558
|
+
|
|
1559
|
+
elif error := a2a.get_response_error(a2a_response):
|
|
1560
|
+
log.warning(
|
|
1561
|
+
"%s Received error response from peer for sub-task %s: %s",
|
|
1562
|
+
component.log_identifier,
|
|
1563
|
+
sub_task_id,
|
|
1564
|
+
error,
|
|
1565
|
+
)
|
|
1566
|
+
payload_to_queue = {
|
|
1567
|
+
"error": error.message,
|
|
1568
|
+
"code": error.code,
|
|
1569
|
+
"data": error.data,
|
|
1570
|
+
}
|
|
1571
|
+
is_final_response = True
|
|
1572
|
+
else:
|
|
1573
|
+
log.warning(
|
|
1574
|
+
"%s Received JSONRPCResponse with no result or error for sub-task %s.",
|
|
1575
|
+
component.log_identifier,
|
|
1576
|
+
sub_task_id,
|
|
1577
|
+
)
|
|
1578
|
+
payload_to_queue = {"result": "Peer responded with empty message."}
|
|
1579
|
+
is_final_response = True
|
|
1580
|
+
|
|
1581
|
+
except Exception as parse_error:
|
|
1582
|
+
log.error(
|
|
1583
|
+
"%s Failed to parse A2A response payload for sub-task %s: %s",
|
|
1584
|
+
component.log_identifier,
|
|
1585
|
+
sub_task_id,
|
|
1586
|
+
parse_error,
|
|
1587
|
+
)
|
|
1588
|
+
payload_to_queue = {
|
|
1589
|
+
"error": f"Failed to parse response from peer: {parse_error}",
|
|
1590
|
+
"code": "PEER_PARSE_ERROR",
|
|
1591
|
+
}
|
|
1592
|
+
# Print out the stack trace for debugging
|
|
1593
|
+
log.exception(
|
|
1594
|
+
"%s Exception stack trace: %s",
|
|
1595
|
+
component.log_identifier,
|
|
1596
|
+
parse_error,
|
|
1597
|
+
)
|
|
1598
|
+
|
|
1599
|
+
if not is_final_response:
|
|
1600
|
+
# This is an intermediate status update for monitoring.
|
|
1601
|
+
# Log it, acknowledge it, but do not aggregate its content.
|
|
1602
|
+
log.debug(
|
|
1603
|
+
"%s Received and ignored intermediate status update from peer for sub-task %s.",
|
|
1604
|
+
component.log_identifier,
|
|
1605
|
+
sub_task_id,
|
|
1606
|
+
)
|
|
1607
|
+
# Reset the timeout since we received a status update
|
|
1608
|
+
await component.reset_peer_timeout(sub_task_id)
|
|
1609
|
+
message.call_acknowledgements()
|
|
1610
|
+
return
|
|
1611
|
+
|
|
1612
|
+
correlation_data = await component._claim_peer_sub_task_completion(sub_task_id)
|
|
1613
|
+
if not correlation_data:
|
|
1614
|
+
# The helper method logs the reason (timeout, already claimed, etc.)
|
|
1615
|
+
message.call_acknowledgements()
|
|
1616
|
+
return
|
|
1617
|
+
|
|
1618
|
+
async def _handle_final_peer_response():
|
|
1619
|
+
"""
|
|
1620
|
+
Handles a final peer response by updating the completion counter and,
|
|
1621
|
+
if all peer tasks are complete, calling the re-trigger logic.
|
|
1622
|
+
"""
|
|
1623
|
+
logical_task_id = correlation_data.get("logical_task_id")
|
|
1624
|
+
invocation_id = correlation_data.get("invocation_id")
|
|
1625
|
+
|
|
1626
|
+
if not logical_task_id or not invocation_id:
|
|
1627
|
+
log.error(
|
|
1628
|
+
"%s 'logical_task_id' or 'invocation_id' not found in correlation data for sub-task %s. Cannot proceed.",
|
|
1629
|
+
component.log_identifier,
|
|
1630
|
+
sub_task_id,
|
|
1631
|
+
)
|
|
1632
|
+
return
|
|
1633
|
+
|
|
1634
|
+
log_retrigger = (
|
|
1635
|
+
f"{component.log_identifier}[RetriggerManager:{logical_task_id}]"
|
|
1636
|
+
)
|
|
1637
|
+
|
|
1638
|
+
with component.active_tasks_lock:
|
|
1639
|
+
task_context = component.active_tasks.get(logical_task_id)
|
|
1640
|
+
|
|
1641
|
+
if not task_context:
|
|
1642
|
+
log.error(
|
|
1643
|
+
"%s TaskExecutionContext not found for task %s. Cannot process final peer response.",
|
|
1644
|
+
log_retrigger,
|
|
1645
|
+
logical_task_id,
|
|
1646
|
+
)
|
|
1647
|
+
return
|
|
1648
|
+
|
|
1649
|
+
final_text = ""
|
|
1650
|
+
artifact_summary = ""
|
|
1651
|
+
if isinstance(payload_to_queue, dict):
|
|
1652
|
+
if "result" in payload_to_queue:
|
|
1653
|
+
final_text = payload_to_queue["result"]
|
|
1654
|
+
elif "error" in payload_to_queue:
|
|
1655
|
+
final_text = (
|
|
1656
|
+
f"Peer agent returned an error: {payload_to_queue['error']}"
|
|
1657
|
+
)
|
|
1658
|
+
elif "status" in payload_to_queue: # It's a Task object
|
|
1659
|
+
try:
|
|
1660
|
+
task_obj = Task(**payload_to_queue)
|
|
1661
|
+
if task_obj.status and task_obj.status.message:
|
|
1662
|
+
final_text = get_text_from_message(task_obj.status.message)
|
|
1663
|
+
|
|
1664
|
+
if (
|
|
1665
|
+
task_obj.metadata
|
|
1666
|
+
and "produced_artifacts" in task_obj.metadata
|
|
1667
|
+
):
|
|
1668
|
+
produced_artifacts = task_obj.metadata.get(
|
|
1669
|
+
"produced_artifacts", []
|
|
1670
|
+
)
|
|
1671
|
+
if produced_artifacts:
|
|
1672
|
+
peer_agent_name = task_obj.metadata.get(
|
|
1673
|
+
"agent_name", "A peer agent"
|
|
1674
|
+
)
|
|
1675
|
+
original_task_context = correlation_data.get(
|
|
1676
|
+
"original_task_context", {}
|
|
1677
|
+
)
|
|
1678
|
+
user_id = original_task_context.get("user_id")
|
|
1679
|
+
session_id = original_task_context.get("session_id")
|
|
1680
|
+
|
|
1681
|
+
header_text = f"Peer agent `{peer_agent_name}` created {len(produced_artifacts)} artifact(s):"
|
|
1682
|
+
|
|
1683
|
+
if user_id and session_id:
|
|
1684
|
+
artifact_summary = (
|
|
1685
|
+
await generate_artifact_metadata_summary(
|
|
1686
|
+
component=component,
|
|
1687
|
+
artifact_identifiers=produced_artifacts,
|
|
1688
|
+
user_id=user_id,
|
|
1689
|
+
session_id=session_id,
|
|
1690
|
+
app_name=peer_agent_name,
|
|
1691
|
+
header_text=header_text,
|
|
1692
|
+
)
|
|
1693
|
+
)
|
|
1694
|
+
|
|
1695
|
+
# Add guidance about artifact_return responsibility
|
|
1696
|
+
artifact_return_guidance = (
|
|
1697
|
+
f"\n\n**Note:** If any of these artifacts fulfill the user's request, "
|
|
1698
|
+
f"you should return them directly to the user using the "
|
|
1699
|
+
f"{EMBED_DELIMITER_OPEN}artifact_return:filename:version{EMBED_DELIMITER_CLOSE} embed. "
|
|
1700
|
+
f"This is more convenient for the user than just describing the artifacts. "
|
|
1701
|
+
f"Replace 'filename' and 'version' with the actual values from the artifact metadata above."
|
|
1702
|
+
)
|
|
1703
|
+
artifact_summary += artifact_return_guidance
|
|
1704
|
+
else:
|
|
1705
|
+
log.warning(
|
|
1706
|
+
"%s Could not generate artifact summary: missing user_id or session_id in correlation data.",
|
|
1707
|
+
log_retrigger,
|
|
1708
|
+
)
|
|
1709
|
+
artifact_summary = ""
|
|
1710
|
+
# Bubble up the peer's artifacts to the parent context
|
|
1711
|
+
_register_peer_artifacts_in_parent_context(
|
|
1712
|
+
task_context, task_obj, log_retrigger
|
|
1713
|
+
)
|
|
1714
|
+
|
|
1715
|
+
except Exception:
|
|
1716
|
+
final_text = json.dumps(payload_to_queue)
|
|
1717
|
+
else:
|
|
1718
|
+
final_text = json.dumps(payload_to_queue)
|
|
1719
|
+
elif isinstance(payload_to_queue, str):
|
|
1720
|
+
final_text = payload_to_queue
|
|
1721
|
+
else:
|
|
1722
|
+
final_text = str(payload_to_queue)
|
|
1723
|
+
|
|
1724
|
+
full_response_text = final_text
|
|
1725
|
+
if artifact_summary:
|
|
1726
|
+
full_response_text = f"{artifact_summary}\n---\n\nPeer Agent Response:\n\n{full_response_text}"
|
|
1727
|
+
|
|
1728
|
+
await _publish_peer_tool_result_notification(
|
|
1729
|
+
component=component,
|
|
1730
|
+
correlation_data=correlation_data,
|
|
1731
|
+
payload_to_queue=payload_to_queue,
|
|
1732
|
+
log_identifier=log_retrigger,
|
|
1733
|
+
)
|
|
1734
|
+
|
|
1735
|
+
current_result = {
|
|
1736
|
+
"adk_function_call_id": correlation_data.get("adk_function_call_id"),
|
|
1737
|
+
"peer_tool_name": correlation_data.get("peer_tool_name"),
|
|
1738
|
+
"payload": {"result": full_response_text},
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
all_sub_tasks_completed = task_context.record_parallel_result(
|
|
1742
|
+
current_result, invocation_id
|
|
1743
|
+
)
|
|
1744
|
+
log.info(
|
|
1745
|
+
"%s Updated parallel counter for task %s: %s",
|
|
1746
|
+
log_retrigger,
|
|
1747
|
+
logical_task_id,
|
|
1748
|
+
task_context.parallel_tool_calls.get(invocation_id),
|
|
1749
|
+
)
|
|
1750
|
+
|
|
1751
|
+
if not all_sub_tasks_completed:
|
|
1752
|
+
log.info(
|
|
1753
|
+
"%s Waiting for more peer responses for task %s.",
|
|
1754
|
+
log_retrigger,
|
|
1755
|
+
logical_task_id,
|
|
1756
|
+
)
|
|
1757
|
+
return
|
|
1758
|
+
|
|
1759
|
+
log.info(
|
|
1760
|
+
"%s All peer responses received for task %s. Retriggering agent.",
|
|
1761
|
+
log_retrigger,
|
|
1762
|
+
logical_task_id,
|
|
1763
|
+
)
|
|
1764
|
+
results_to_inject = task_context.parallel_tool_calls.get(
|
|
1765
|
+
invocation_id, {}
|
|
1766
|
+
).get("results", [])
|
|
1767
|
+
|
|
1768
|
+
await component._retrigger_agent_with_peer_responses(
|
|
1769
|
+
results_to_inject, correlation_data, task_context
|
|
1770
|
+
)
|
|
1771
|
+
|
|
1772
|
+
loop = component.get_async_loop()
|
|
1773
|
+
if loop and loop.is_running():
|
|
1774
|
+
asyncio.run_coroutine_threadsafe(_handle_final_peer_response(), loop)
|
|
1775
|
+
else:
|
|
1776
|
+
log.error(
|
|
1777
|
+
"%s Async loop not available. Cannot handle final peer response for sub-task %s.",
|
|
1778
|
+
component.log_identifier,
|
|
1779
|
+
sub_task_id,
|
|
1780
|
+
)
|
|
1781
|
+
|
|
1782
|
+
message.call_acknowledgements()
|
|
1783
|
+
log.info(
|
|
1784
|
+
"%s Acknowledged final peer response message for sub-task %s.",
|
|
1785
|
+
component.log_identifier,
|
|
1786
|
+
sub_task_id,
|
|
1787
|
+
)
|
|
1788
|
+
|
|
1789
|
+
except Exception as e:
|
|
1790
|
+
log.exception(
|
|
1791
|
+
"%s Unexpected error handling A2A response for sub-task %s: %s",
|
|
1792
|
+
component.log_identifier,
|
|
1793
|
+
sub_task_id,
|
|
1794
|
+
e,
|
|
1795
|
+
)
|
|
1796
|
+
try:
|
|
1797
|
+
message.call_negative_acknowledgements()
|
|
1798
|
+
log.warning(
|
|
1799
|
+
"%s NACKed peer response message for sub-task %s due to unexpected error.",
|
|
1800
|
+
component.log_identifier,
|
|
1801
|
+
sub_task_id,
|
|
1802
|
+
)
|
|
1803
|
+
except Exception as nack_e:
|
|
1804
|
+
log.error(
|
|
1805
|
+
"%s Failed to NACK peer response message for sub-task %s after error: %s",
|
|
1806
|
+
component.log_identifier,
|
|
1807
|
+
sub_task_id,
|
|
1808
|
+
nack_e,
|
|
1809
|
+
)
|
|
1810
|
+
component.handle_error(e, Event(EventType.MESSAGE, message))
|
|
1811
|
+
|
|
1812
|
+
|
|
1813
|
+
def publish_agent_card(component):
|
|
1814
|
+
"""Publishes the agent's card to the discovery topic."""
|
|
1815
|
+
try:
|
|
1816
|
+
card_config = component.get_config("agent_card", {})
|
|
1817
|
+
agent_name = component.get_config("agent_name")
|
|
1818
|
+
display_name = component.get_config("display_name")
|
|
1819
|
+
namespace = component.get_config("namespace")
|
|
1820
|
+
supports_streaming = component.get_config("supports_streaming", False)
|
|
1821
|
+
peer_agents = component.peer_agents
|
|
1822
|
+
|
|
1823
|
+
agent_request_topic = get_agent_request_topic(namespace, agent_name)
|
|
1824
|
+
dynamic_url = f"solace:{agent_request_topic}"
|
|
1825
|
+
|
|
1826
|
+
# Define unique URIs for our custom extensions.
|
|
1827
|
+
DEPLOYMENT_EXTENSION_URI = "https://solace.com/a2a/extensions/sam/deployment"
|
|
1828
|
+
PEER_TOPOLOGY_EXTENSION_URI = (
|
|
1829
|
+
"https://solace.com/a2a/extensions/peer-agent-topology"
|
|
1830
|
+
)
|
|
1831
|
+
DISPLAY_NAME_EXTENSION_URI = "https://solace.com/a2a/extensions/display-name"
|
|
1832
|
+
TOOLS_EXTENSION_URI = "https://solace.com/a2a/extensions/sam/tools"
|
|
1833
|
+
|
|
1834
|
+
extensions_list = []
|
|
1835
|
+
|
|
1836
|
+
# Create the extension object for deployment tracking.
|
|
1837
|
+
deployment_config = component.get_config("deployment", {})
|
|
1838
|
+
deployment_id = deployment_config.get("id")
|
|
1839
|
+
|
|
1840
|
+
if deployment_id:
|
|
1841
|
+
deployment_extension = AgentExtension(
|
|
1842
|
+
uri=DEPLOYMENT_EXTENSION_URI,
|
|
1843
|
+
description="SAM deployment tracking for rolling updates",
|
|
1844
|
+
required=False,
|
|
1845
|
+
params={"id": deployment_id}
|
|
1846
|
+
)
|
|
1847
|
+
extensions_list.append(deployment_extension)
|
|
1848
|
+
log.debug(
|
|
1849
|
+
"%s Added deployment extension with ID: %s",
|
|
1850
|
+
component.log_identifier,
|
|
1851
|
+
deployment_id
|
|
1852
|
+
)
|
|
1853
|
+
|
|
1854
|
+
# Create the extension object for peer agents.
|
|
1855
|
+
if peer_agents:
|
|
1856
|
+
peer_topology_extension = AgentExtension(
|
|
1857
|
+
uri=PEER_TOPOLOGY_EXTENSION_URI,
|
|
1858
|
+
description="A list of peer agents this agent is configured to communicate with.",
|
|
1859
|
+
params={"peer_agent_names": list(peer_agents.keys())},
|
|
1860
|
+
)
|
|
1861
|
+
extensions_list.append(peer_topology_extension)
|
|
1862
|
+
|
|
1863
|
+
# Create the extension object for the UI display name.
|
|
1864
|
+
if display_name:
|
|
1865
|
+
display_name_extension = AgentExtension(
|
|
1866
|
+
uri=DISPLAY_NAME_EXTENSION_URI,
|
|
1867
|
+
description="A UI-friendly display name for the agent.",
|
|
1868
|
+
params={"display_name": display_name},
|
|
1869
|
+
)
|
|
1870
|
+
extensions_list.append(display_name_extension)
|
|
1871
|
+
|
|
1872
|
+
# Create the extension object for the agent's tools.
|
|
1873
|
+
dynamic_tools = getattr(component, "agent_card_tool_manifest", [])
|
|
1874
|
+
if dynamic_tools:
|
|
1875
|
+
# Ensure all tools have a 'tags' field to prevent validation errors.
|
|
1876
|
+
processed_tools = []
|
|
1877
|
+
for tool in dynamic_tools:
|
|
1878
|
+
if "tags" not in tool:
|
|
1879
|
+
log.debug(
|
|
1880
|
+
"%s Tool '%s' in manifest is missing 'tags' field. Defaulting to empty list.",
|
|
1881
|
+
component.log_identifier,
|
|
1882
|
+
tool.get("id", "unknown"),
|
|
1883
|
+
)
|
|
1884
|
+
tool["tags"] = []
|
|
1885
|
+
processed_tools.append(tool)
|
|
1886
|
+
|
|
1887
|
+
tools_params = ToolsExtensionParams(tools=processed_tools)
|
|
1888
|
+
tools_extension = AgentExtension(
|
|
1889
|
+
uri=TOOLS_EXTENSION_URI,
|
|
1890
|
+
description="A list of tools available to the agent.",
|
|
1891
|
+
params=tools_params.model_dump(exclude_none=True),
|
|
1892
|
+
)
|
|
1893
|
+
extensions_list.append(tools_extension)
|
|
1894
|
+
|
|
1895
|
+
# Build the capabilities object, including our custom extensions.
|
|
1896
|
+
capabilities = AgentCapabilities(
|
|
1897
|
+
streaming=supports_streaming,
|
|
1898
|
+
push_notifications=False,
|
|
1899
|
+
state_transition_history=False,
|
|
1900
|
+
extensions=extensions_list if extensions_list else None,
|
|
1901
|
+
)
|
|
1902
|
+
|
|
1903
|
+
skills_from_config = card_config.get("skills", [])
|
|
1904
|
+
# The 'tools' field is not part of the official AgentCard spec.
|
|
1905
|
+
# The tools are now included as an extension.
|
|
1906
|
+
|
|
1907
|
+
# Ensure all skills have a 'tags' field to prevent validation errors.
|
|
1908
|
+
processed_skills = []
|
|
1909
|
+
for skill in skills_from_config:
|
|
1910
|
+
if "tags" not in skill:
|
|
1911
|
+
skill["tags"] = []
|
|
1912
|
+
processed_skills.append(skill)
|
|
1913
|
+
|
|
1914
|
+
agent_card = AgentCard(
|
|
1915
|
+
name=agent_name,
|
|
1916
|
+
protocol_version=card_config.get("protocolVersion", "0.3.0"),
|
|
1917
|
+
version=component.HOST_COMPONENT_VERSION,
|
|
1918
|
+
url=dynamic_url,
|
|
1919
|
+
capabilities=capabilities,
|
|
1920
|
+
description=card_config.get("description", ""),
|
|
1921
|
+
skills=processed_skills,
|
|
1922
|
+
default_input_modes=card_config.get("defaultInputModes", ["text"]),
|
|
1923
|
+
default_output_modes=card_config.get("defaultOutputModes", ["text"]),
|
|
1924
|
+
documentation_url=card_config.get("documentationUrl"),
|
|
1925
|
+
provider=card_config.get("provider"),
|
|
1926
|
+
)
|
|
1927
|
+
|
|
1928
|
+
discovery_topic = get_discovery_topic(namespace)
|
|
1929
|
+
|
|
1930
|
+
component.publish_a2a_message(
|
|
1931
|
+
agent_card.model_dump(exclude_none=True), discovery_topic
|
|
1932
|
+
)
|
|
1933
|
+
log.debug(
|
|
1934
|
+
"%s Successfully published Agent Card to %s",
|
|
1935
|
+
component.log_identifier,
|
|
1936
|
+
discovery_topic,
|
|
1937
|
+
)
|
|
1938
|
+
|
|
1939
|
+
except Exception as e:
|
|
1940
|
+
log.exception(
|
|
1941
|
+
"%s Failed to publish Agent Card: %s", component.log_identifier, e
|
|
1942
|
+
)
|
|
1943
|
+
component.handle_error(e, None)
|
|
1944
|
+
|
|
1945
|
+
|
|
1946
|
+
def handle_sam_event(component, message, topic):
|
|
1947
|
+
"""Handle incoming SAM system events."""
|
|
1948
|
+
try:
|
|
1949
|
+
payload = message.get_payload()
|
|
1950
|
+
|
|
1951
|
+
if not isinstance(payload, dict):
|
|
1952
|
+
log.warning("Invalid SAM event payload - not a dict")
|
|
1953
|
+
message.call_acknowledgements()
|
|
1954
|
+
return
|
|
1955
|
+
|
|
1956
|
+
event_type = payload.get("event_type")
|
|
1957
|
+
if not event_type:
|
|
1958
|
+
log.warning("SAM event missing event_type field")
|
|
1959
|
+
message.call_acknowledgements()
|
|
1960
|
+
return
|
|
1961
|
+
|
|
1962
|
+
log.info("%s Received SAM event: %s", component.log_identifier, event_type)
|
|
1963
|
+
|
|
1964
|
+
if event_type == "session.deleted":
|
|
1965
|
+
data = payload.get("data", {})
|
|
1966
|
+
session_id = data.get("session_id")
|
|
1967
|
+
user_id = data.get("user_id")
|
|
1968
|
+
agent_id = data.get("agent_id")
|
|
1969
|
+
|
|
1970
|
+
if not all([session_id, user_id, agent_id]):
|
|
1971
|
+
log.warning("Missing required fields in session.deleted event")
|
|
1972
|
+
message.call_acknowledgements()
|
|
1973
|
+
return
|
|
1974
|
+
|
|
1975
|
+
current_agent = component.get_config("agent_name")
|
|
1976
|
+
|
|
1977
|
+
if agent_id == current_agent:
|
|
1978
|
+
log.info(
|
|
1979
|
+
"%s Processing session.deleted event for session %s",
|
|
1980
|
+
component.log_identifier,
|
|
1981
|
+
session_id,
|
|
1982
|
+
)
|
|
1983
|
+
asyncio.create_task(
|
|
1984
|
+
cleanup_agent_session(component, session_id, user_id)
|
|
1985
|
+
)
|
|
1986
|
+
else:
|
|
1987
|
+
log.debug(
|
|
1988
|
+
"Session deletion event for different agent: %s != %s",
|
|
1989
|
+
agent_id,
|
|
1990
|
+
current_agent,
|
|
1991
|
+
)
|
|
1992
|
+
else:
|
|
1993
|
+
log.debug("Unhandled SAM event type: %s", event_type)
|
|
1994
|
+
|
|
1995
|
+
message.call_acknowledgements()
|
|
1996
|
+
|
|
1997
|
+
except Exception as e:
|
|
1998
|
+
log.error("Error handling SAM event %s: %s", topic, e)
|
|
1999
|
+
message.call_acknowledgements()
|
|
2000
|
+
|
|
2001
|
+
|
|
2002
|
+
async def cleanup_agent_session(component, session_id: str, user_id: str):
|
|
2003
|
+
"""Clean up agent-side session data."""
|
|
2004
|
+
try:
|
|
2005
|
+
log.info("Starting cleanup for session %s, user %s", session_id, user_id)
|
|
2006
|
+
|
|
2007
|
+
if hasattr(component, "session_service") and component.session_service:
|
|
2008
|
+
agent_name = component.get_config("agent_name")
|
|
2009
|
+
log.info(
|
|
2010
|
+
"Deleting session %s from agent %s session service",
|
|
2011
|
+
session_id,
|
|
2012
|
+
agent_name,
|
|
2013
|
+
)
|
|
2014
|
+
await component.session_service.delete_session(
|
|
2015
|
+
app_name=agent_name, user_id=user_id, session_id=session_id
|
|
2016
|
+
)
|
|
2017
|
+
log.info("Successfully deleted session %s from session service", session_id)
|
|
2018
|
+
else:
|
|
2019
|
+
log.info("No session service available for cleanup")
|
|
2020
|
+
|
|
2021
|
+
with component.active_tasks_lock:
|
|
2022
|
+
tasks_to_cancel = []
|
|
2023
|
+
for task_id, context in component.active_tasks.items():
|
|
2024
|
+
if (
|
|
2025
|
+
hasattr(context, "a2a_context")
|
|
2026
|
+
and context.a2a_context.get("session_id") == session_id
|
|
2027
|
+
):
|
|
2028
|
+
tasks_to_cancel.append(task_id)
|
|
2029
|
+
|
|
2030
|
+
for task_id in tasks_to_cancel:
|
|
2031
|
+
context = component.active_tasks.get(task_id)
|
|
2032
|
+
if context:
|
|
2033
|
+
context.cancel()
|
|
2034
|
+
log.info(
|
|
2035
|
+
"Cancelled task %s for deleted session %s", task_id, session_id
|
|
2036
|
+
)
|
|
2037
|
+
|
|
2038
|
+
log.info("Session cleanup completed for session %s", session_id)
|
|
2039
|
+
|
|
2040
|
+
except Exception as e:
|
|
2041
|
+
log.error("Error cleaning up session %s: %s", session_id, e)
|