solace-agent-mesh 0.2.3__py3-none-any.whl → 1.0.1__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.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
- solace_agent_mesh/agent/adk/callbacks.py +1694 -0
- solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
- solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
- solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
- solace_agent_mesh/agent/adk/runner.py +353 -0
- solace_agent_mesh/agent/adk/services.py +240 -0
- solace_agent_mesh/agent/adk/setup.py +751 -0
- solace_agent_mesh/agent/adk/stream_parser.py +214 -0
- solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
- solace_agent_mesh/agent/agent_llm.txt +41 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +1469 -0
- solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
- solace_agent_mesh/agent/sac/app.py +640 -0
- solace_agent_mesh/agent/sac/component.py +3388 -0
- solace_agent_mesh/agent/sac/patch_adk.py +111 -0
- solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
- solace_agent_mesh/agent/sac/task_execution_context.py +176 -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 +90 -0
- solace_agent_mesh/agent/tools/__init__.py +14 -0
- solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
- solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
- solace_agent_mesh/agent/tools/general_agent_tools.py +569 -0
- solace_agent_mesh/agent/tools/image_tools.py +1184 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +289 -0
- solace_agent_mesh/agent/tools/registry.py +36 -0
- solace_agent_mesh/agent/tools/test_tools.py +135 -0
- solace_agent_mesh/agent/tools/tool_definition.py +45 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
- solace_agent_mesh/agent/tools/web_tools.py +381 -0
- solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
- solace_agent_mesh/agent/utils/config_parser.py +47 -0
- solace_agent_mesh/agent/utils/context_helpers.py +60 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
- solace_agent_mesh/assets/docs/404.html +16 -0
- solace_agent_mesh/assets/docs/assets/css/styles.906a1503.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/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
- solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
- solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.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/1523c6b4.91c7bc01.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/166ab619.7d97ccaf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
- solace_agent_mesh/assets/docs/assets/js/1c6e87d2.23bccffb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3624.b524e433.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/3d406171.f722eaf5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.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/4c2787c2.66ee00e9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.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/5b4258a4.bda20761.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.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/7040.cb436723.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.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/8356.8a379c04.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.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.d7c16be6.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.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.49e930c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.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/9eff14a2.1bf8f61c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f897a61a.2c2e152c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js.LICENSE.txt +81 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9520ae2.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +60 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -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/solace-logo.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1753813536522.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -0
- solace_agent_mesh/assets/docs/search-doc-1753813536522.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 -1
- 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 +659 -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 +93 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
- solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
- solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
- solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +197 -0
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +387 -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 +110 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
- solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
- solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +138 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +174 -0
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
- solace_agent_mesh/cli/commands/run_cmd.py +158 -0
- solace_agent_mesh/cli/main.py +17 -294
- solace_agent_mesh/cli/utils.py +135 -204
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
- solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-An0a5j5k.js +663 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Bu5-4Bac.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
- solace_agent_mesh/common/__init__.py +1 -0
- solace_agent_mesh/common/a2a_protocol.py +564 -0
- solace_agent_mesh/common/agent_registry.py +42 -0
- solace_agent_mesh/common/client/__init__.py +4 -0
- solace_agent_mesh/common/client/card_resolver.py +21 -0
- solace_agent_mesh/common/client/client.py +85 -0
- solace_agent_mesh/common/client/client_llm.txt +133 -0
- solace_agent_mesh/common/common_llm.txt +144 -0
- solace_agent_mesh/common/constants.py +1 -14
- solace_agent_mesh/common/middleware/__init__.py +12 -0
- solace_agent_mesh/common/middleware/config_resolver.py +130 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
- solace_agent_mesh/common/middleware/registry.py +125 -0
- solace_agent_mesh/common/server/__init__.py +4 -0
- solace_agent_mesh/common/server/server.py +122 -0
- solace_agent_mesh/common/server/server_llm.txt +169 -0
- solace_agent_mesh/common/server/task_manager.py +291 -0
- solace_agent_mesh/common/server/utils.py +28 -0
- solace_agent_mesh/common/services/__init__.py +4 -0
- solace_agent_mesh/common/services/employee_service.py +162 -0
- solace_agent_mesh/common/services/identity_service.py +129 -0
- solace_agent_mesh/common/services/providers/__init__.py +4 -0
- solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
- solace_agent_mesh/common/services/services_llm.txt +132 -0
- solace_agent_mesh/common/types.py +411 -0
- solace_agent_mesh/common/utils/__init__.py +7 -0
- solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
- solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
- solace_agent_mesh/common/utils/embeds/constants.py +55 -0
- solace_agent_mesh/common/utils/embeds/converter.py +452 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
- solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
- solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
- solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
- solace_agent_mesh/common/utils/embeds/types.py +14 -0
- solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
- solace_agent_mesh/common/utils/log_formatters.py +44 -0
- solace_agent_mesh/common/utils/mime_helpers.py +106 -0
- solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
- solace_agent_mesh/common/utils/utils_llm.txt +67 -0
- solace_agent_mesh/config_portal/backend/common.py +66 -24
- solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +23 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +160 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +525 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +216 -0
- solace_agent_mesh/config_portal/backend/server.py +550 -140
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DNxCwAGB.js +48 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d2b54a97.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/__init__.py +1 -0
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
- solace_agent_mesh/core_a2a/service.py +331 -0
- solace_agent_mesh/evaluation/config_loader.py +657 -0
- solace_agent_mesh/evaluation/evaluator.py +667 -0
- solace_agent_mesh/evaluation/message_organizer.py +568 -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 +972 -0
- solace_agent_mesh/evaluation/report_generator.py +613 -0
- solace_agent_mesh/evaluation/run.py +613 -0
- solace_agent_mesh/evaluation/subscriber.py +872 -0
- solace_agent_mesh/evaluation/summary_builder.py +775 -0
- solace_agent_mesh/evaluation/test_case_loader.py +714 -0
- solace_agent_mesh/gateway/base/__init__.py +1 -0
- solace_agent_mesh/gateway/base/app.py +266 -0
- solace_agent_mesh/gateway/base/base_llm.txt +119 -0
- solace_agent_mesh/gateway/base/component.py +1542 -0
- solace_agent_mesh/gateway/base/task_context.py +74 -0
- solace_agent_mesh/gateway/gateway_llm.txt +125 -0
- solace_agent_mesh/gateway/http_sse/app.py +190 -0
- solace_agent_mesh/gateway/http_sse/component.py +1602 -0
- solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
- solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
- solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
- solace_agent_mesh/gateway/http_sse/main.py +442 -0
- solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +821 -0
- solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
- solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
- solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
- solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
- solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
- solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
- solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
- solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
- solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
- solace_agent_mesh/llm.txt +228 -0
- solace_agent_mesh/llm_detail.txt +2835 -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 +73 -0
- solace_agent_mesh/templates/gateway_component_template.py +400 -0
- solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +55 -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 +63 -0
- solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
- solace_agent_mesh/templates/plugin_readme_template.md +34 -0
- solace_agent_mesh/templates/plugin_tools_template.py +224 -0
- solace_agent_mesh/templates/shared_config.yaml +66 -0
- solace_agent_mesh/templates/templates_llm.txt +147 -0
- solace_agent_mesh/templates/webui.yaml +53 -0
- solace_agent_mesh-1.0.1.dist-info/METADATA +432 -0
- solace_agent_mesh-1.0.1.dist-info/RECORD +359 -0
- solace_agent_mesh-1.0.1.dist-info/entry_points.txt +3 -0
- {solace_agent_mesh-0.2.3.dist-info → solace_agent_mesh-1.0.1.dist-info}/licenses/LICENSE +1 -1
- solace_agent_mesh/agents/base_agent_component.py +0 -226
- solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
- solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
- solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
- solace_agent_mesh/agents/global/actions/create_file.py +0 -70
- solace_agent_mesh/agents/global/actions/error_action.py +0 -45
- solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
- solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
- solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
- solace_agent_mesh/agents/global/global_agent_component.py +0 -38
- solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
- solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
- solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
- solace_agent_mesh/agents/slack/__init__.py +0 -1
- solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
- solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
- solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
- solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
- solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
- solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
- solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
- solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
- solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
- solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
- solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
- solace_agent_mesh/assets/web-visualizer/index.html +0 -14
- solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
- solace_agent_mesh/cli/commands/add/__init__.py +0 -3
- solace_agent_mesh/cli/commands/add/add.py +0 -88
- solace_agent_mesh/cli/commands/add/agent.py +0 -110
- solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
- solace_agent_mesh/cli/commands/add/gateway.py +0 -374
- solace_agent_mesh/cli/commands/build.py +0 -670
- solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
- solace_agent_mesh/cli/commands/chat/chat.py +0 -361
- solace_agent_mesh/cli/commands/config.py +0 -29
- solace_agent_mesh/cli/commands/init/__init__.py +0 -3
- solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
- solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
- solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
- solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
- solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
- solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
- solace_agent_mesh/cli/commands/init/init.py +0 -92
- solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
- solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
- solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
- solace_agent_mesh/cli/commands/plugin/add.py +0 -100
- solace_agent_mesh/cli/commands/plugin/build.py +0 -268
- solace_agent_mesh/cli/commands/plugin/create.py +0 -117
- solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
- solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
- solace_agent_mesh/cli/commands/run.py +0 -68
- solace_agent_mesh/cli/commands/visualizer.py +0 -138
- solace_agent_mesh/cli/config.py +0 -85
- solace_agent_mesh/common/action.py +0 -91
- solace_agent_mesh/common/action_list.py +0 -37
- solace_agent_mesh/common/action_response.py +0 -340
- solace_agent_mesh/common/mysql_database.py +0 -40
- solace_agent_mesh/common/postgres_database.py +0 -85
- solace_agent_mesh/common/prompt_templates.py +0 -28
- solace_agent_mesh/common/stimulus_utils.py +0 -152
- solace_agent_mesh/common/time.py +0 -24
- solace_agent_mesh/common/utils.py +0 -712
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DMmCawWe.js +0 -42
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-dd988f05.js +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
- solace_agent_mesh/configs/agent_global.yaml +0 -74
- solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
- solace_agent_mesh/configs/agent_slack.yaml +0 -64
- solace_agent_mesh/configs/agent_web_request.yaml +0 -75
- solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
- solace_agent_mesh/configs/error_catcher.yaml +0 -56
- solace_agent_mesh/configs/monitor.yaml +0 -0
- solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
- solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
- solace_agent_mesh/configs/orchestrator.yaml +0 -241
- solace_agent_mesh/configs/service_embedding.yaml +0 -81
- solace_agent_mesh/configs/service_llm.yaml +0 -265
- solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
- solace_agent_mesh/gateway/components/gateway_base.py +0 -47
- solace_agent_mesh/gateway/components/gateway_input.py +0 -278
- solace_agent_mesh/gateway/components/gateway_output.py +0 -298
- solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
- solace_agent_mesh/gateway/identity/identity_base.py +0 -10
- solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
- solace_agent_mesh/gateway/identity/no_identity.py +0 -9
- solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
- solace_agent_mesh/monitors/base_monitor_component.py +0 -26
- solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
- solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
- solace_agent_mesh/orchestrator/__init__.py +0 -0
- solace_agent_mesh/orchestrator/action_manager.py +0 -237
- solace_agent_mesh/orchestrator/components/__init__.py +0 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
- solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
- solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
- solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
- solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
- solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
- solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
- solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
- solace_agent_mesh/services/common/__init__.py +0 -4
- solace_agent_mesh/services/common/auto_expiry.py +0 -45
- solace_agent_mesh/services/common/singleton.py +0 -18
- solace_agent_mesh/services/file_service/__init__.py +0 -14
- solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
- solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
- solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
- solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
- solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
- solace_agent_mesh/services/file_service/file_service.py +0 -437
- solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
- solace_agent_mesh/services/file_service/file_transformations.py +0 -141
- solace_agent_mesh/services/file_service/file_utils.py +0 -324
- solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
- solace_agent_mesh/services/history_service/__init__.py +0 -3
- solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
- solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
- solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
- solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
- solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
- solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
- solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
- solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
- solace_agent_mesh/services/history_service/history_service.py +0 -413
- solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
- solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
- solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
- solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
- solace_agent_mesh/services/middleware_service/__init__.py +0 -0
- solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
- solace_agent_mesh/templates/action.py +0 -38
- solace_agent_mesh/templates/agent.py +0 -29
- solace_agent_mesh/templates/agent.yaml +0 -70
- solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
- solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
- solace_agent_mesh/templates/gateway-flows.yaml +0 -78
- solace_agent_mesh/templates/gateway-header.yaml +0 -16
- solace_agent_mesh/templates/gateway_base.py +0 -15
- solace_agent_mesh/templates/gateway_input.py +0 -98
- solace_agent_mesh/templates/gateway_output.py +0 -71
- solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
- solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
- solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
- solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
- solace_agent_mesh/templates/slack-default-config.yaml +0 -16
- solace_agent_mesh/templates/slack-flows.yaml +0 -81
- solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
- solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
- solace_agent_mesh/templates/web-default-config.yaml +0 -10
- solace_agent_mesh/templates/web-flows.yaml +0 -76
- solace_agent_mesh/tools/__init__.py +0 -0
- solace_agent_mesh/tools/components/__init__.py +0 -0
- solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
- solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
- solace_agent_mesh/tools/config/runtime_config.py +0 -26
- solace_agent_mesh-0.2.3.dist-info/METADATA +0 -172
- solace_agent_mesh-0.2.3.dist-info/RECORD +0 -193
- solace_agent_mesh-0.2.3.dist-info/entry_points.txt +0 -3
- /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
- /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
- /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
- /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
- /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
- /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
- /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
- /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
- {solace_agent_mesh-0.2.3.dist-info → solace_agent_mesh-1.0.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1602 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Solace AI Connector Component to host the FastAPI backend for the Web UI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import queue
|
|
7
|
+
import uuid
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import threading
|
|
11
|
+
from typing import Any, Dict, Optional, List, Tuple, Union, Set
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from fastapi import UploadFile, Request as FastAPIRequest
|
|
14
|
+
|
|
15
|
+
import uvicorn
|
|
16
|
+
from fastapi import FastAPI
|
|
17
|
+
|
|
18
|
+
from solace_ai_connector.common.log import log
|
|
19
|
+
from solace_ai_connector.flow.app import App as SACApp
|
|
20
|
+
from solace_ai_connector.components.inputs_outputs.broker_input import (
|
|
21
|
+
BrokerInput,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from ...gateway.http_sse.sse_manager import SSEManager
|
|
25
|
+
|
|
26
|
+
from .components import VisualizationForwarderComponent
|
|
27
|
+
from ...gateway.http_sse.session_manager import SessionManager
|
|
28
|
+
from ...gateway.base.component import BaseGatewayComponent
|
|
29
|
+
from ...common.agent_registry import AgentRegistry
|
|
30
|
+
from ...core_a2a.service import CoreA2AService
|
|
31
|
+
from google.adk.artifacts import BaseArtifactService
|
|
32
|
+
|
|
33
|
+
from ...common.types import (
|
|
34
|
+
AgentCard,
|
|
35
|
+
Part as A2APart,
|
|
36
|
+
Task,
|
|
37
|
+
TaskStatusUpdateEvent,
|
|
38
|
+
TaskArtifactUpdateEvent,
|
|
39
|
+
JSONRPCError,
|
|
40
|
+
JSONRPCResponse,
|
|
41
|
+
TextPart,
|
|
42
|
+
FilePart,
|
|
43
|
+
FileContent,
|
|
44
|
+
)
|
|
45
|
+
from ...common.a2a_protocol import (
|
|
46
|
+
_topic_matches_subscription,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
from ...agent.utils.artifact_helpers import save_artifact_with_metadata
|
|
50
|
+
from ...common.middleware.config_resolver import ConfigResolver
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
info = {
|
|
54
|
+
"class_name": "WebUIBackendComponent",
|
|
55
|
+
"description": (
|
|
56
|
+
"Hosts the FastAPI backend server for the A2A Web UI, manages messaging via SAC, "
|
|
57
|
+
"and implements GDK abstract methods for Web UI interaction. "
|
|
58
|
+
"Configuration is derived from WebUIBackendApp's app_config."
|
|
59
|
+
),
|
|
60
|
+
"config_parameters": [
|
|
61
|
+
# Configuration parameters are defined and validated by WebUIBackendApp.app_schema.
|
|
62
|
+
],
|
|
63
|
+
"input_schema": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"description": "Not typically used; component reacts to events.",
|
|
66
|
+
"properties": {},
|
|
67
|
+
},
|
|
68
|
+
"output_schema": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"description": "Not typically used; component publishes results via FastAPI/SSE.",
|
|
71
|
+
"properties": {},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class WebUIBackendComponent(BaseGatewayComponent):
|
|
77
|
+
"""
|
|
78
|
+
Hosts the FastAPI backend, manages messaging via SAC, and bridges threads.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, **kwargs):
|
|
82
|
+
"""
|
|
83
|
+
Initializes the WebUIBackendComponent, inheriting from BaseGatewayComponent.
|
|
84
|
+
"""
|
|
85
|
+
super().__init__(**kwargs)
|
|
86
|
+
log.info("%s Initializing Web UI Backend Component...", self.log_identifier)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
self.namespace = self.get_config("namespace")
|
|
90
|
+
self.gateway_id = self.get_config("gateway_id")
|
|
91
|
+
if not self.gateway_id:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"Internal Error: Gateway ID missing after app initialization."
|
|
94
|
+
)
|
|
95
|
+
self.fastapi_host = self.get_config("fastapi_host", "127.0.0.1")
|
|
96
|
+
self.fastapi_port = self.get_config("fastapi_port", 8000)
|
|
97
|
+
self.fastapi_https_port = self.get_config("fastapi_https_port", 8443)
|
|
98
|
+
self.session_secret_key = self.get_config("session_secret_key")
|
|
99
|
+
self.cors_allowed_origins = self.get_config("cors_allowed_origins", ["*"])
|
|
100
|
+
self.resolve_artifact_uris_in_gateway = self.get_config(
|
|
101
|
+
"resolve_artifact_uris_in_gateway", True
|
|
102
|
+
)
|
|
103
|
+
self.ssl_keyfile = self.get_config("ssl_keyfile", "")
|
|
104
|
+
self.ssl_certfile = self.get_config("ssl_certfile", "")
|
|
105
|
+
self.ssl_keyfile_password = self.get_config("ssl_keyfile_password", "")
|
|
106
|
+
|
|
107
|
+
log.info(
|
|
108
|
+
"%s WebUI-specific configuration retrieved (Host: %s, Port: %d).",
|
|
109
|
+
self.log_identifier,
|
|
110
|
+
self.fastapi_host,
|
|
111
|
+
self.fastapi_port,
|
|
112
|
+
)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
log.error("%s Failed to retrieve configuration: %s", self.log_identifier, e)
|
|
115
|
+
raise ValueError(f"Configuration retrieval error: {e}") from e
|
|
116
|
+
|
|
117
|
+
sse_max_queue_size = self.get_config("sse_max_queue_size", 200)
|
|
118
|
+
|
|
119
|
+
self.sse_manager = SSEManager(max_queue_size=sse_max_queue_size)
|
|
120
|
+
|
|
121
|
+
component_config = self.get_config("component_config", {})
|
|
122
|
+
app_config = component_config.get("app_config", {})
|
|
123
|
+
|
|
124
|
+
self.session_manager = SessionManager(
|
|
125
|
+
secret_key=self.session_secret_key,
|
|
126
|
+
app_config=app_config,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.fastapi_app: Optional[FastAPI] = None
|
|
130
|
+
self.uvicorn_server: Optional[uvicorn.Server] = None
|
|
131
|
+
self.fastapi_thread: Optional[threading.Thread] = None
|
|
132
|
+
self.fastapi_event_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
133
|
+
|
|
134
|
+
self._visualization_internal_app: Optional[SACApp] = None
|
|
135
|
+
self._visualization_broker_input: Optional[BrokerInput] = None
|
|
136
|
+
self._visualization_message_queue: queue.Queue = queue.Queue(maxsize=200)
|
|
137
|
+
self._active_visualization_streams: Dict[str, Dict[str, Any]] = {}
|
|
138
|
+
self._visualization_locks: Dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
|
|
139
|
+
self._visualization_locks_lock = threading.Lock()
|
|
140
|
+
self._global_visualization_subscriptions: Dict[str, int] = {}
|
|
141
|
+
self._visualization_processor_task: Optional[asyncio.Task] = None
|
|
142
|
+
|
|
143
|
+
log.info("%s Web UI Backend Component initialized.", self.log_identifier)
|
|
144
|
+
|
|
145
|
+
def _get_visualization_lock(self) -> asyncio.Lock:
|
|
146
|
+
"""Get or create a visualization lock for the current event loop."""
|
|
147
|
+
try:
|
|
148
|
+
current_loop = asyncio.get_running_loop()
|
|
149
|
+
except RuntimeError:
|
|
150
|
+
raise RuntimeError(
|
|
151
|
+
"Visualization lock methods must be called from within an async context"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
with self._visualization_locks_lock:
|
|
155
|
+
if current_loop not in self._visualization_locks:
|
|
156
|
+
self._visualization_locks[current_loop] = asyncio.Lock()
|
|
157
|
+
log.debug(
|
|
158
|
+
"%s Created new visualization lock for event loop %s",
|
|
159
|
+
self.log_identifier,
|
|
160
|
+
id(current_loop),
|
|
161
|
+
)
|
|
162
|
+
return self._visualization_locks[current_loop]
|
|
163
|
+
|
|
164
|
+
def _ensure_visualization_flow_is_running(self) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Ensures the internal SAC flow for A2A message visualization is created and running.
|
|
167
|
+
This method is designed to be called once during component startup.
|
|
168
|
+
"""
|
|
169
|
+
log_id_prefix = f"{self.log_identifier}[EnsureVizFlow]"
|
|
170
|
+
if self._visualization_internal_app is not None:
|
|
171
|
+
log.debug("%s Visualization flow already running.", log_id_prefix)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
log.info("%s Initializing internal A2A visualization flow...", log_id_prefix)
|
|
175
|
+
try:
|
|
176
|
+
main_app = self.get_app()
|
|
177
|
+
if not main_app or not main_app.connector:
|
|
178
|
+
log.error(
|
|
179
|
+
"%s Cannot get main app or connector instance. Visualization flow NOT started.",
|
|
180
|
+
log_id_prefix,
|
|
181
|
+
)
|
|
182
|
+
raise RuntimeError(
|
|
183
|
+
"Main app or connector not available for internal flow creation."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
main_broker_config = main_app.app_info.get("broker", {})
|
|
187
|
+
if not main_broker_config:
|
|
188
|
+
log.error(
|
|
189
|
+
"%s Main app broker configuration not found. Visualization flow NOT started.",
|
|
190
|
+
log_id_prefix,
|
|
191
|
+
)
|
|
192
|
+
raise ValueError("Main app broker configuration is missing.")
|
|
193
|
+
|
|
194
|
+
broker_input_cfg = {
|
|
195
|
+
"component_module": "broker_input",
|
|
196
|
+
"component_name": f"{self.gateway_id}_viz_broker_input",
|
|
197
|
+
"broker_queue_name": f"{self.namespace.strip('/')}/q/gdk/viz/{self.gateway_id}/{uuid.uuid4().hex}",
|
|
198
|
+
"create_queue_on_start": True,
|
|
199
|
+
"component_config": {
|
|
200
|
+
"broker_url": main_broker_config.get("broker_url"),
|
|
201
|
+
"broker_username": main_broker_config.get("broker_username"),
|
|
202
|
+
"broker_password": main_broker_config.get("broker_password"),
|
|
203
|
+
"broker_vpn": main_broker_config.get("broker_vpn"),
|
|
204
|
+
"trust_store_path": main_broker_config.get("trust_store_path"),
|
|
205
|
+
"dev_mode": main_broker_config.get("dev_mode"),
|
|
206
|
+
"broker_subscriptions": [],
|
|
207
|
+
"reconnection_strategy": main_broker_config.get(
|
|
208
|
+
"reconnection_strategy"
|
|
209
|
+
),
|
|
210
|
+
"retry_interval": main_broker_config.get("retry_interval"),
|
|
211
|
+
"retry_count": main_broker_config.get("retry_count"),
|
|
212
|
+
"temporary_queue": True,
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
forwarder_cfg = {
|
|
217
|
+
"component_class": VisualizationForwarderComponent,
|
|
218
|
+
"component_name": f"{self.gateway_id}_viz_forwarder",
|
|
219
|
+
"component_config": {
|
|
220
|
+
"target_queue_ref": self._visualization_message_queue
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
flow_config = {
|
|
225
|
+
"name": f"{self.gateway_id}_viz_flow",
|
|
226
|
+
"components": [broker_input_cfg, forwarder_cfg],
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
internal_app_broker_config = main_broker_config.copy()
|
|
230
|
+
internal_app_broker_config["input_enabled"] = True
|
|
231
|
+
internal_app_broker_config["output_enabled"] = False
|
|
232
|
+
|
|
233
|
+
app_config_for_internal_flow = {
|
|
234
|
+
"name": f"{self.gateway_id}_viz_internal_app",
|
|
235
|
+
"flows": [flow_config],
|
|
236
|
+
"broker": internal_app_broker_config,
|
|
237
|
+
"app_config": {},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
self._visualization_internal_app = main_app.connector.create_internal_app(
|
|
241
|
+
app_name=app_config_for_internal_flow["name"],
|
|
242
|
+
flows=app_config_for_internal_flow["flows"],
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if (
|
|
246
|
+
not self._visualization_internal_app
|
|
247
|
+
or not self._visualization_internal_app.flows
|
|
248
|
+
):
|
|
249
|
+
log.error(
|
|
250
|
+
"%s Failed to create internal visualization app/flow.",
|
|
251
|
+
log_id_prefix,
|
|
252
|
+
)
|
|
253
|
+
self._visualization_internal_app = None
|
|
254
|
+
raise RuntimeError("Internal visualization app/flow creation failed.")
|
|
255
|
+
|
|
256
|
+
self._visualization_internal_app.run()
|
|
257
|
+
log.info("%s Internal visualization app started.", log_id_prefix)
|
|
258
|
+
|
|
259
|
+
flow_instance = self._visualization_internal_app.flows[0]
|
|
260
|
+
if flow_instance.component_groups and flow_instance.component_groups[0]:
|
|
261
|
+
self._visualization_broker_input = flow_instance.component_groups[0][0]
|
|
262
|
+
if not isinstance(self._visualization_broker_input, BrokerInput):
|
|
263
|
+
log.error(
|
|
264
|
+
"%s First component in viz flow is not BrokerInput. Type: %s",
|
|
265
|
+
log_id_prefix,
|
|
266
|
+
type(self._visualization_broker_input).__name__,
|
|
267
|
+
)
|
|
268
|
+
self._visualization_broker_input = None
|
|
269
|
+
raise RuntimeError(
|
|
270
|
+
"Visualization flow setup error: BrokerInput not found."
|
|
271
|
+
)
|
|
272
|
+
log.info(
|
|
273
|
+
"%s Obtained reference to internal BrokerInput component.",
|
|
274
|
+
log_id_prefix,
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
log.error(
|
|
278
|
+
"%s Could not get BrokerInput instance from internal flow.",
|
|
279
|
+
log_id_prefix,
|
|
280
|
+
)
|
|
281
|
+
raise RuntimeError(
|
|
282
|
+
"Visualization flow setup error: BrokerInput instance not accessible."
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
log.exception(
|
|
287
|
+
"%s Failed to ensure visualization flow is running: %s",
|
|
288
|
+
log_id_prefix,
|
|
289
|
+
e,
|
|
290
|
+
)
|
|
291
|
+
if self._visualization_internal_app:
|
|
292
|
+
try:
|
|
293
|
+
self._visualization_internal_app.cleanup()
|
|
294
|
+
except Exception as cleanup_err:
|
|
295
|
+
log.error(
|
|
296
|
+
"%s Error during cleanup after viz flow init failure: %s",
|
|
297
|
+
log_id_prefix,
|
|
298
|
+
cleanup_err,
|
|
299
|
+
)
|
|
300
|
+
self._visualization_internal_app = None
|
|
301
|
+
self._visualization_broker_input = None
|
|
302
|
+
raise
|
|
303
|
+
|
|
304
|
+
async def _visualization_message_processor_loop(self) -> None:
|
|
305
|
+
"""
|
|
306
|
+
Asynchronously consumes messages from the _visualization_message_queue,
|
|
307
|
+
filters them, and forwards them to relevant SSE connections.
|
|
308
|
+
Placeholder for Phase 2: Just logs messages.
|
|
309
|
+
"""
|
|
310
|
+
log_id_prefix = f"{self.log_identifier}[VizMsgProcessor]"
|
|
311
|
+
log.info("%s Starting visualization message processor loop...", log_id_prefix)
|
|
312
|
+
loop = asyncio.get_running_loop()
|
|
313
|
+
|
|
314
|
+
while not self.stop_signal.is_set():
|
|
315
|
+
msg_data = None
|
|
316
|
+
try:
|
|
317
|
+
msg_data = await loop.run_in_executor(
|
|
318
|
+
None,
|
|
319
|
+
self._visualization_message_queue.get,
|
|
320
|
+
True,
|
|
321
|
+
1.0,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if msg_data is None:
|
|
325
|
+
log.info(
|
|
326
|
+
"%s Received shutdown signal for viz processor loop.",
|
|
327
|
+
log_id_prefix,
|
|
328
|
+
)
|
|
329
|
+
break
|
|
330
|
+
|
|
331
|
+
current_size = self._visualization_message_queue.qsize()
|
|
332
|
+
max_size = self._visualization_message_queue.maxsize
|
|
333
|
+
if max_size > 0 and (current_size / max_size) > 0.90:
|
|
334
|
+
log.warning(
|
|
335
|
+
"%s Visualization queue is over 90%% full. Current size: %d/%d",
|
|
336
|
+
log_id_prefix,
|
|
337
|
+
current_size,
|
|
338
|
+
max_size,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
topic = msg_data.get("topic")
|
|
342
|
+
payload_dict = msg_data.get("payload")
|
|
343
|
+
|
|
344
|
+
log.debug("%s [VIZ_DATA_RAW] Topic: %s", log_id_prefix, topic)
|
|
345
|
+
|
|
346
|
+
if "/a2a/v1/discovery/" in topic:
|
|
347
|
+
self._visualization_message_queue.task_done()
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
event_details_for_owner = self._infer_visualization_event_details(
|
|
351
|
+
topic, payload_dict
|
|
352
|
+
)
|
|
353
|
+
task_id_for_context = event_details_for_owner.get("task_id")
|
|
354
|
+
message_owner_id = None
|
|
355
|
+
if task_id_for_context:
|
|
356
|
+
root_task_id = task_id_for_context.split(":", 1)[0]
|
|
357
|
+
context = self.task_context_manager.get_context(root_task_id)
|
|
358
|
+
if context and "user_identity" in context:
|
|
359
|
+
message_owner_id = context["user_identity"].get("id")
|
|
360
|
+
log.debug(
|
|
361
|
+
"%s Found owner '%s' for task %s via local context (root: %s).",
|
|
362
|
+
log_id_prefix,
|
|
363
|
+
message_owner_id,
|
|
364
|
+
task_id_for_context,
|
|
365
|
+
root_task_id,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if not message_owner_id:
|
|
369
|
+
user_properties = msg_data.get("user_properties") or {}
|
|
370
|
+
|
|
371
|
+
if not user_properties:
|
|
372
|
+
log.warning(
|
|
373
|
+
"%s No user_properties found for task %s (root: %s). Cannot determine owner via message properties.",
|
|
374
|
+
log_id_prefix,
|
|
375
|
+
task_id_for_context,
|
|
376
|
+
root_task_id,
|
|
377
|
+
)
|
|
378
|
+
user_config = user_properties.get(
|
|
379
|
+
"a2aUserConfig"
|
|
380
|
+
) or user_properties.get("a2a_user_config")
|
|
381
|
+
|
|
382
|
+
if (
|
|
383
|
+
isinstance(user_config, dict)
|
|
384
|
+
and "user_profile" in user_config
|
|
385
|
+
and isinstance(user_config.get("user_profile"), dict)
|
|
386
|
+
):
|
|
387
|
+
message_owner_id = user_config["user_profile"].get("id")
|
|
388
|
+
if message_owner_id:
|
|
389
|
+
log.debug(
|
|
390
|
+
"%s Found owner '%s' for task %s via message properties.",
|
|
391
|
+
log_id_prefix,
|
|
392
|
+
message_owner_id,
|
|
393
|
+
task_id_for_context,
|
|
394
|
+
)
|
|
395
|
+
async with self._get_visualization_lock():
|
|
396
|
+
for (
|
|
397
|
+
stream_id,
|
|
398
|
+
stream_config,
|
|
399
|
+
) in self._active_visualization_streams.items():
|
|
400
|
+
sse_queue_for_stream = stream_config.get("sse_queue")
|
|
401
|
+
if not sse_queue_for_stream:
|
|
402
|
+
log.warning(
|
|
403
|
+
"%s SSE queue not found for stream %s. Skipping.",
|
|
404
|
+
log_id_prefix,
|
|
405
|
+
stream_id,
|
|
406
|
+
)
|
|
407
|
+
continue
|
|
408
|
+
|
|
409
|
+
is_permitted = False
|
|
410
|
+
stream_owner_id = stream_config.get("user_id")
|
|
411
|
+
abstract_targets = stream_config.get("abstract_targets", [])
|
|
412
|
+
|
|
413
|
+
for abstract_target in abstract_targets:
|
|
414
|
+
if abstract_target.status != "subscribed":
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
if abstract_target.type == "my_a2a_messages":
|
|
418
|
+
if (
|
|
419
|
+
stream_owner_id
|
|
420
|
+
and message_owner_id
|
|
421
|
+
and stream_owner_id == message_owner_id
|
|
422
|
+
):
|
|
423
|
+
is_permitted = True
|
|
424
|
+
break
|
|
425
|
+
else:
|
|
426
|
+
subscribed_topics_for_stream = stream_config.get(
|
|
427
|
+
"solace_topics", set()
|
|
428
|
+
)
|
|
429
|
+
if any(
|
|
430
|
+
_topic_matches_subscription(topic, pattern)
|
|
431
|
+
for pattern in subscribed_topics_for_stream
|
|
432
|
+
):
|
|
433
|
+
is_permitted = True
|
|
434
|
+
break
|
|
435
|
+
|
|
436
|
+
if is_permitted:
|
|
437
|
+
event_details = self._infer_visualization_event_details(
|
|
438
|
+
topic, payload_dict
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
sse_event_payload = {
|
|
442
|
+
"event_type": "a2a_message",
|
|
443
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
444
|
+
"solace_topic": topic,
|
|
445
|
+
"direction": event_details["direction"],
|
|
446
|
+
"source_entity": event_details["source_entity"],
|
|
447
|
+
"target_entity": event_details["target_entity"],
|
|
448
|
+
"message_id": event_details["message_id"],
|
|
449
|
+
"task_id": event_details["task_id"],
|
|
450
|
+
"payload_summary": event_details["payload_summary"],
|
|
451
|
+
"full_payload": payload_dict,
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
try:
|
|
455
|
+
log.debug(
|
|
456
|
+
"%s Attempting to put message on SSE queue for stream %s. Queue size: %d",
|
|
457
|
+
log_id_prefix,
|
|
458
|
+
stream_id,
|
|
459
|
+
sse_queue_for_stream.qsize(),
|
|
460
|
+
)
|
|
461
|
+
sse_queue_for_stream.put_nowait(
|
|
462
|
+
{
|
|
463
|
+
"event": "a2a_message",
|
|
464
|
+
"data": json.dumps(sse_event_payload),
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
log.debug(
|
|
468
|
+
"%s [VIZ_DATA_SENT] Stream %s: Topic: %s, Direction: %s",
|
|
469
|
+
log_id_prefix,
|
|
470
|
+
stream_id,
|
|
471
|
+
topic,
|
|
472
|
+
event_details["direction"],
|
|
473
|
+
)
|
|
474
|
+
except asyncio.QueueFull:
|
|
475
|
+
log.warning(
|
|
476
|
+
"%s SSE queue full for stream %s. Visualization message dropped.",
|
|
477
|
+
log_id_prefix,
|
|
478
|
+
stream_id,
|
|
479
|
+
)
|
|
480
|
+
except Exception as send_err:
|
|
481
|
+
log.error(
|
|
482
|
+
"%s Error sending formatted message to SSE queue for stream %s: %s",
|
|
483
|
+
log_id_prefix,
|
|
484
|
+
stream_id,
|
|
485
|
+
send_err,
|
|
486
|
+
)
|
|
487
|
+
else:
|
|
488
|
+
pass
|
|
489
|
+
|
|
490
|
+
self._visualization_message_queue.task_done()
|
|
491
|
+
|
|
492
|
+
except queue.Empty:
|
|
493
|
+
continue
|
|
494
|
+
except asyncio.CancelledError:
|
|
495
|
+
log.info(
|
|
496
|
+
"%s Visualization message processor loop cancelled.", log_id_prefix
|
|
497
|
+
)
|
|
498
|
+
break
|
|
499
|
+
except Exception as e:
|
|
500
|
+
log.exception(
|
|
501
|
+
"%s Error in visualization message processor loop: %s",
|
|
502
|
+
log_id_prefix,
|
|
503
|
+
e,
|
|
504
|
+
)
|
|
505
|
+
if msg_data and self._visualization_message_queue:
|
|
506
|
+
self._visualization_message_queue.task_done()
|
|
507
|
+
await asyncio.sleep(1)
|
|
508
|
+
|
|
509
|
+
log.info("%s Visualization message processor loop finished.", log_id_prefix)
|
|
510
|
+
|
|
511
|
+
async def _add_visualization_subscription(
|
|
512
|
+
self, topic_str: str, stream_id: str
|
|
513
|
+
) -> bool:
|
|
514
|
+
"""
|
|
515
|
+
Adds a Solace topic subscription to the internal BrokerInput for visualization.
|
|
516
|
+
Manages global subscription reference counts.
|
|
517
|
+
"""
|
|
518
|
+
log_id_prefix = f"{self.log_identifier}[AddVizSub:{stream_id}]"
|
|
519
|
+
log.info(
|
|
520
|
+
"%s Attempting to add subscription to topic: %s", log_id_prefix, topic_str
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
if not self._visualization_broker_input:
|
|
524
|
+
log.error(
|
|
525
|
+
"%s Visualization BrokerInput is not initialized. Cannot add subscription.",
|
|
526
|
+
log_id_prefix,
|
|
527
|
+
)
|
|
528
|
+
return False
|
|
529
|
+
if (
|
|
530
|
+
not hasattr(self._visualization_broker_input, "messaging_service")
|
|
531
|
+
or not self._visualization_broker_input.messaging_service
|
|
532
|
+
):
|
|
533
|
+
log.error(
|
|
534
|
+
"%s Visualization BrokerInput's messaging_service not available or not initialized. Cannot add subscription.",
|
|
535
|
+
log_id_prefix,
|
|
536
|
+
)
|
|
537
|
+
return False
|
|
538
|
+
|
|
539
|
+
log.debug(
|
|
540
|
+
"%s Acquiring visualization stream lock for topic '%s'...",
|
|
541
|
+
log_id_prefix,
|
|
542
|
+
topic_str,
|
|
543
|
+
)
|
|
544
|
+
async with self._get_visualization_lock():
|
|
545
|
+
log.debug(
|
|
546
|
+
"%s Acquired visualization stream lock for topic '%s'.",
|
|
547
|
+
log_id_prefix,
|
|
548
|
+
topic_str,
|
|
549
|
+
)
|
|
550
|
+
self._global_visualization_subscriptions[topic_str] = (
|
|
551
|
+
self._global_visualization_subscriptions.get(topic_str, 0) + 1
|
|
552
|
+
)
|
|
553
|
+
log.debug(
|
|
554
|
+
"%s Global subscription count for topic '%s' is now %d.",
|
|
555
|
+
log_id_prefix,
|
|
556
|
+
topic_str,
|
|
557
|
+
self._global_visualization_subscriptions[topic_str],
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
if self._global_visualization_subscriptions[topic_str] == 1:
|
|
561
|
+
log.info(
|
|
562
|
+
"%s First global subscription for topic '%s'. Attempting to subscribe on broker.",
|
|
563
|
+
log_id_prefix,
|
|
564
|
+
topic_str,
|
|
565
|
+
)
|
|
566
|
+
try:
|
|
567
|
+
if not hasattr(
|
|
568
|
+
self._visualization_broker_input, "add_subscription"
|
|
569
|
+
) or not callable(
|
|
570
|
+
getattr(self._visualization_broker_input, "add_subscription")
|
|
571
|
+
):
|
|
572
|
+
log.error(
|
|
573
|
+
"%s Visualization BrokerInput does not support dynamic 'add_subscription'. "
|
|
574
|
+
"Please upgrade the 'solace-ai-connector' module. Cannot add subscription '%s'.",
|
|
575
|
+
log_id_prefix,
|
|
576
|
+
topic_str,
|
|
577
|
+
)
|
|
578
|
+
self._global_visualization_subscriptions[topic_str] -= 1
|
|
579
|
+
if self._global_visualization_subscriptions[topic_str] == 0:
|
|
580
|
+
del self._global_visualization_subscriptions[topic_str]
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
loop = asyncio.get_event_loop()
|
|
584
|
+
add_result = await loop.run_in_executor(
|
|
585
|
+
None,
|
|
586
|
+
self._visualization_broker_input.add_subscription,
|
|
587
|
+
topic_str,
|
|
588
|
+
)
|
|
589
|
+
if not add_result:
|
|
590
|
+
log.error(
|
|
591
|
+
"%s Failed to add subscription '%s' via BrokerInput.",
|
|
592
|
+
log_id_prefix,
|
|
593
|
+
topic_str,
|
|
594
|
+
)
|
|
595
|
+
self._global_visualization_subscriptions[topic_str] -= 1
|
|
596
|
+
if self._global_visualization_subscriptions[topic_str] == 0:
|
|
597
|
+
del self._global_visualization_subscriptions[topic_str]
|
|
598
|
+
return False
|
|
599
|
+
log.info(
|
|
600
|
+
"%s Successfully added subscription '%s' via BrokerInput.",
|
|
601
|
+
log_id_prefix,
|
|
602
|
+
topic_str,
|
|
603
|
+
)
|
|
604
|
+
except Exception as e:
|
|
605
|
+
log.exception(
|
|
606
|
+
"%s Exception calling BrokerInput.add_subscription for topic '%s': %s",
|
|
607
|
+
log_id_prefix,
|
|
608
|
+
topic_str,
|
|
609
|
+
e,
|
|
610
|
+
)
|
|
611
|
+
self._global_visualization_subscriptions[topic_str] -= 1
|
|
612
|
+
if self._global_visualization_subscriptions[topic_str] == 0:
|
|
613
|
+
del self._global_visualization_subscriptions[topic_str]
|
|
614
|
+
return False
|
|
615
|
+
else:
|
|
616
|
+
log.debug(
|
|
617
|
+
"%s Topic '%s' already globally subscribed. Skipping broker subscribe.",
|
|
618
|
+
log_id_prefix,
|
|
619
|
+
topic_str,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
if stream_id in self._active_visualization_streams:
|
|
623
|
+
self._active_visualization_streams[stream_id]["solace_topics"].add(
|
|
624
|
+
topic_str
|
|
625
|
+
)
|
|
626
|
+
log.debug(
|
|
627
|
+
"%s Topic '%s' added to active subscriptions for stream %s.",
|
|
628
|
+
log_id_prefix,
|
|
629
|
+
topic_str,
|
|
630
|
+
stream_id,
|
|
631
|
+
)
|
|
632
|
+
else:
|
|
633
|
+
log.warning(
|
|
634
|
+
"%s Stream ID %s not found in active streams. Cannot add topic.",
|
|
635
|
+
log_id_prefix,
|
|
636
|
+
stream_id,
|
|
637
|
+
)
|
|
638
|
+
return False
|
|
639
|
+
log.debug(
|
|
640
|
+
"%s Releasing visualization stream lock after successful processing for topic '%s'.",
|
|
641
|
+
log_id_prefix,
|
|
642
|
+
topic_str,
|
|
643
|
+
)
|
|
644
|
+
return True
|
|
645
|
+
|
|
646
|
+
async def _remove_visualization_subscription_nolock(
|
|
647
|
+
self, topic_str: str, stream_id: str
|
|
648
|
+
) -> bool:
|
|
649
|
+
"""
|
|
650
|
+
Internal helper to remove a Solace topic subscription.
|
|
651
|
+
Assumes _visualization_stream_lock is already held by the caller.
|
|
652
|
+
Manages global subscription reference counts.
|
|
653
|
+
"""
|
|
654
|
+
log_id_prefix = f"{self.log_identifier}[RemoveVizSubNL:{stream_id}]"
|
|
655
|
+
log.info(
|
|
656
|
+
"%s Removing subscription (no-lock) from topic: %s",
|
|
657
|
+
log_id_prefix,
|
|
658
|
+
topic_str,
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
if not self._visualization_broker_input or not hasattr(
|
|
662
|
+
self._visualization_broker_input, "messaging_service"
|
|
663
|
+
):
|
|
664
|
+
log.error(
|
|
665
|
+
"%s Visualization BrokerInput or its messaging_service not available.",
|
|
666
|
+
log_id_prefix,
|
|
667
|
+
)
|
|
668
|
+
return False
|
|
669
|
+
|
|
670
|
+
if topic_str not in self._global_visualization_subscriptions:
|
|
671
|
+
log.warning(
|
|
672
|
+
"%s Topic '%s' not found in global subscriptions. Cannot remove.",
|
|
673
|
+
log_id_prefix,
|
|
674
|
+
topic_str,
|
|
675
|
+
)
|
|
676
|
+
return False
|
|
677
|
+
|
|
678
|
+
self._global_visualization_subscriptions[topic_str] -= 1
|
|
679
|
+
|
|
680
|
+
if self._global_visualization_subscriptions[topic_str] == 0:
|
|
681
|
+
del self._global_visualization_subscriptions[topic_str]
|
|
682
|
+
try:
|
|
683
|
+
if not hasattr(
|
|
684
|
+
self._visualization_broker_input, "remove_subscription"
|
|
685
|
+
) or not callable(
|
|
686
|
+
getattr(self._visualization_broker_input, "remove_subscription")
|
|
687
|
+
):
|
|
688
|
+
log.error(
|
|
689
|
+
"%s Visualization BrokerInput does not support dynamic 'remove_subscription'. "
|
|
690
|
+
"Please upgrade the 'solace-ai-connector' module. Cannot remove subscription '%s'.",
|
|
691
|
+
log_id_prefix,
|
|
692
|
+
topic_str,
|
|
693
|
+
)
|
|
694
|
+
return False
|
|
695
|
+
|
|
696
|
+
loop = asyncio.get_event_loop()
|
|
697
|
+
remove_result = await loop.run_in_executor(
|
|
698
|
+
None,
|
|
699
|
+
self._visualization_broker_input.remove_subscription,
|
|
700
|
+
topic_str,
|
|
701
|
+
)
|
|
702
|
+
if not remove_result:
|
|
703
|
+
log.error(
|
|
704
|
+
"%s Failed to remove subscription '%s' via BrokerInput. Global count might be inaccurate.",
|
|
705
|
+
log_id_prefix,
|
|
706
|
+
topic_str,
|
|
707
|
+
)
|
|
708
|
+
else:
|
|
709
|
+
log.info(
|
|
710
|
+
"%s Successfully removed subscription '%s' via BrokerInput.",
|
|
711
|
+
log_id_prefix,
|
|
712
|
+
topic_str,
|
|
713
|
+
)
|
|
714
|
+
except Exception as e:
|
|
715
|
+
log.exception(
|
|
716
|
+
"%s Exception calling BrokerInput.remove_subscription for topic '%s': %s",
|
|
717
|
+
log_id_prefix,
|
|
718
|
+
topic_str,
|
|
719
|
+
e,
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
if stream_id in self._active_visualization_streams:
|
|
723
|
+
if (
|
|
724
|
+
topic_str
|
|
725
|
+
in self._active_visualization_streams[stream_id]["solace_topics"]
|
|
726
|
+
):
|
|
727
|
+
self._active_visualization_streams[stream_id]["solace_topics"].remove(
|
|
728
|
+
topic_str
|
|
729
|
+
)
|
|
730
|
+
log.debug(
|
|
731
|
+
"%s Topic '%s' removed from active subscriptions for stream %s.",
|
|
732
|
+
log_id_prefix,
|
|
733
|
+
topic_str,
|
|
734
|
+
stream_id,
|
|
735
|
+
)
|
|
736
|
+
else:
|
|
737
|
+
log.warning(
|
|
738
|
+
"%s Topic '%s' not found in subscriptions for stream %s.",
|
|
739
|
+
log_id_prefix,
|
|
740
|
+
topic_str,
|
|
741
|
+
stream_id,
|
|
742
|
+
)
|
|
743
|
+
else:
|
|
744
|
+
log.warning(
|
|
745
|
+
"%s Stream ID %s not found in active streams. Cannot remove topic.",
|
|
746
|
+
log_id_prefix,
|
|
747
|
+
stream_id,
|
|
748
|
+
)
|
|
749
|
+
return True
|
|
750
|
+
|
|
751
|
+
async def _remove_visualization_subscription(
|
|
752
|
+
self, topic_str: str, stream_id: str
|
|
753
|
+
) -> bool:
|
|
754
|
+
"""
|
|
755
|
+
Public method to remove a Solace topic subscription.
|
|
756
|
+
Acquires the lock before calling the internal no-lock version.
|
|
757
|
+
"""
|
|
758
|
+
log_id_prefix = f"{self.log_identifier}[RemoveVizSubPub:{stream_id}]"
|
|
759
|
+
log.debug(
|
|
760
|
+
"%s Acquiring lock to remove subscription for topic: %s",
|
|
761
|
+
log_id_prefix,
|
|
762
|
+
topic_str,
|
|
763
|
+
)
|
|
764
|
+
async with self._get_visualization_lock():
|
|
765
|
+
log.debug("%s Lock acquired for topic: %s", log_id_prefix, topic_str)
|
|
766
|
+
result = await self._remove_visualization_subscription_nolock(
|
|
767
|
+
topic_str, stream_id
|
|
768
|
+
)
|
|
769
|
+
log.debug("%s Releasing lock for topic: %s", log_id_prefix, topic_str)
|
|
770
|
+
return result
|
|
771
|
+
|
|
772
|
+
async def _extract_initial_claims(
|
|
773
|
+
self, external_event_data: Any
|
|
774
|
+
) -> Optional[Dict[str, Any]]:
|
|
775
|
+
"""
|
|
776
|
+
Extracts initial identity claims from the incoming external event.
|
|
777
|
+
For the WebUI, this means inspecting the FastAPIRequest.
|
|
778
|
+
It prioritizes the authenticated user from `request.state.user`.
|
|
779
|
+
"""
|
|
780
|
+
log_id_prefix = f"{self.log_identifier}[ExtractClaims]"
|
|
781
|
+
|
|
782
|
+
if not isinstance(external_event_data, FastAPIRequest):
|
|
783
|
+
log.warning(
|
|
784
|
+
"%s Expected external_event_data to be a FastAPIRequest, but got %s.",
|
|
785
|
+
log_id_prefix,
|
|
786
|
+
type(external_event_data).__name__,
|
|
787
|
+
)
|
|
788
|
+
return None
|
|
789
|
+
|
|
790
|
+
request = external_event_data
|
|
791
|
+
try:
|
|
792
|
+
if hasattr(request.state, "user") and request.state.user:
|
|
793
|
+
user_info = request.state.user
|
|
794
|
+
username = user_info.get("username")
|
|
795
|
+
if username:
|
|
796
|
+
log.debug(
|
|
797
|
+
"%s Extracted user '%s' from request.state.",
|
|
798
|
+
log_id_prefix,
|
|
799
|
+
username,
|
|
800
|
+
)
|
|
801
|
+
return {"id": username, "name": username, "email": username}
|
|
802
|
+
|
|
803
|
+
log.debug(
|
|
804
|
+
"%s No authenticated user in request.state, falling back to SessionManager.",
|
|
805
|
+
log_id_prefix,
|
|
806
|
+
)
|
|
807
|
+
user_id = self.session_manager.get_a2a_client_id(request)
|
|
808
|
+
log.debug(
|
|
809
|
+
"%s Extracted user_id '%s' via SessionManager.", log_id_prefix, user_id
|
|
810
|
+
)
|
|
811
|
+
return {"id": user_id, "name": user_id}
|
|
812
|
+
|
|
813
|
+
except Exception as e:
|
|
814
|
+
log.error("%s Failed to extract user_id from request: %s", log_id_prefix, e)
|
|
815
|
+
return None
|
|
816
|
+
|
|
817
|
+
def _start_fastapi_server(self):
|
|
818
|
+
"""Starts the Uvicorn server in a separate thread."""
|
|
819
|
+
log.info(
|
|
820
|
+
"%s [_start_listener] Attempting to start FastAPI/Uvicorn server...",
|
|
821
|
+
self.log_identifier,
|
|
822
|
+
)
|
|
823
|
+
if self.fastapi_thread and self.fastapi_thread.is_alive():
|
|
824
|
+
log.warning(
|
|
825
|
+
"%s FastAPI server thread already started.", self.log_identifier
|
|
826
|
+
)
|
|
827
|
+
return
|
|
828
|
+
|
|
829
|
+
try:
|
|
830
|
+
from ...gateway.http_sse.main import (
|
|
831
|
+
app as fastapi_app_instance,
|
|
832
|
+
)
|
|
833
|
+
from ...gateway.http_sse.main import (
|
|
834
|
+
setup_dependencies,
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
self.fastapi_app = fastapi_app_instance
|
|
838
|
+
|
|
839
|
+
setup_dependencies(self)
|
|
840
|
+
|
|
841
|
+
port = self.fastapi_https_port if self.ssl_keyfile and self.ssl_certfile else self.fastapi_port
|
|
842
|
+
|
|
843
|
+
config = uvicorn.Config(
|
|
844
|
+
app=self.fastapi_app,
|
|
845
|
+
host=self.fastapi_host,
|
|
846
|
+
port=port,
|
|
847
|
+
log_level="info",
|
|
848
|
+
lifespan="on",
|
|
849
|
+
ssl_keyfile=self.ssl_keyfile,
|
|
850
|
+
ssl_certfile=self.ssl_certfile,
|
|
851
|
+
ssl_keyfile_password=self.ssl_keyfile_password,
|
|
852
|
+
)
|
|
853
|
+
self.uvicorn_server = uvicorn.Server(config)
|
|
854
|
+
|
|
855
|
+
@self.fastapi_app.on_event("startup")
|
|
856
|
+
async def capture_event_loop():
|
|
857
|
+
log.info(
|
|
858
|
+
"%s [_start_listener] FastAPI startup event triggered.",
|
|
859
|
+
self.log_identifier,
|
|
860
|
+
)
|
|
861
|
+
try:
|
|
862
|
+
self.fastapi_event_loop = asyncio.get_running_loop()
|
|
863
|
+
log.info(
|
|
864
|
+
"%s [_start_listener] Captured FastAPI event loop via startup event: %s",
|
|
865
|
+
self.log_identifier,
|
|
866
|
+
self.fastapi_event_loop,
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
if self.fastapi_event_loop:
|
|
870
|
+
log.info(
|
|
871
|
+
"%s Ensuring visualization flow is running...",
|
|
872
|
+
self.log_identifier,
|
|
873
|
+
)
|
|
874
|
+
self._ensure_visualization_flow_is_running()
|
|
875
|
+
|
|
876
|
+
if (
|
|
877
|
+
self._visualization_processor_task is None
|
|
878
|
+
or self._visualization_processor_task.done()
|
|
879
|
+
):
|
|
880
|
+
log.info(
|
|
881
|
+
"%s Starting visualization message processor task.",
|
|
882
|
+
self.log_identifier,
|
|
883
|
+
)
|
|
884
|
+
self._visualization_processor_task = (
|
|
885
|
+
self.fastapi_event_loop.create_task(
|
|
886
|
+
self._visualization_message_processor_loop()
|
|
887
|
+
)
|
|
888
|
+
)
|
|
889
|
+
else:
|
|
890
|
+
log.info(
|
|
891
|
+
"%s Visualization message processor task already running.",
|
|
892
|
+
self.log_identifier,
|
|
893
|
+
)
|
|
894
|
+
else:
|
|
895
|
+
log.error(
|
|
896
|
+
"%s FastAPI event loop not captured. Cannot start visualization processor.",
|
|
897
|
+
self.log_identifier,
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
except Exception as startup_err:
|
|
901
|
+
log.exception(
|
|
902
|
+
"%s [_start_listener] Error during FastAPI startup event (capture_event_loop or viz setup): %s",
|
|
903
|
+
self.log_identifier,
|
|
904
|
+
startup_err,
|
|
905
|
+
)
|
|
906
|
+
self.stop_signal.set()
|
|
907
|
+
|
|
908
|
+
@self.fastapi_app.on_event("shutdown")
|
|
909
|
+
async def shutdown_event():
|
|
910
|
+
log.info(
|
|
911
|
+
"%s [_start_listener] FastAPI shutdown event triggered.",
|
|
912
|
+
self.log_identifier,
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
self.fastapi_thread = threading.Thread(
|
|
916
|
+
target=self.uvicorn_server.run, daemon=True, name="FastAPI_Thread"
|
|
917
|
+
)
|
|
918
|
+
self.fastapi_thread.start()
|
|
919
|
+
protocol = "https" if self.ssl_keyfile and self.ssl_certfile else "http"
|
|
920
|
+
log.info(
|
|
921
|
+
"%s [_start_listener] FastAPI/Uvicorn server starting in background thread on %s://%s:%d",
|
|
922
|
+
self.log_identifier,
|
|
923
|
+
protocol,
|
|
924
|
+
self.fastapi_host,
|
|
925
|
+
port,
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
except Exception as e:
|
|
929
|
+
log.exception(
|
|
930
|
+
"%s [_start_listener] Failed to start FastAPI/Uvicorn server: %s",
|
|
931
|
+
self.log_identifier,
|
|
932
|
+
e,
|
|
933
|
+
)
|
|
934
|
+
self.stop_signal.set()
|
|
935
|
+
raise
|
|
936
|
+
|
|
937
|
+
def publish_a2a(
|
|
938
|
+
self, topic: str, payload: Dict, user_properties: Optional[Dict] = None
|
|
939
|
+
):
|
|
940
|
+
"""
|
|
941
|
+
Publishes an A2A message using the SAC App's send_message method.
|
|
942
|
+
This method can be called from FastAPI handlers (via dependency injection).
|
|
943
|
+
It's thread-safe as it uses the SAC App instance.
|
|
944
|
+
"""
|
|
945
|
+
super().publish_a2a_message(topic, payload, user_properties)
|
|
946
|
+
|
|
947
|
+
def _cleanup_visualization_locks(self):
|
|
948
|
+
"""Remove locks for closed event loops to prevent memory leaks."""
|
|
949
|
+
with self._visualization_locks_lock:
|
|
950
|
+
closed_loops = [
|
|
951
|
+
loop for loop in self._visualization_locks if loop.is_closed()
|
|
952
|
+
]
|
|
953
|
+
for loop in closed_loops:
|
|
954
|
+
del self._visualization_locks[loop]
|
|
955
|
+
log.debug(
|
|
956
|
+
"%s Cleaned up visualization lock for closed event loop %s",
|
|
957
|
+
self.log_identifier,
|
|
958
|
+
id(loop),
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
def cleanup(self):
|
|
962
|
+
"""Gracefully shuts down the component and the FastAPI server."""
|
|
963
|
+
log.info("%s Cleaning up Web UI Backend Component...", self.log_identifier)
|
|
964
|
+
log.info("%s Cleaning up visualization resources...", self.log_identifier)
|
|
965
|
+
if self._visualization_message_queue:
|
|
966
|
+
self._visualization_message_queue.put(None)
|
|
967
|
+
|
|
968
|
+
if (
|
|
969
|
+
self._visualization_processor_task
|
|
970
|
+
and not self._visualization_processor_task.done()
|
|
971
|
+
):
|
|
972
|
+
log.info(
|
|
973
|
+
"%s Cancelling visualization processor task...", self.log_identifier
|
|
974
|
+
)
|
|
975
|
+
self._visualization_processor_task.cancel()
|
|
976
|
+
|
|
977
|
+
if self._visualization_internal_app:
|
|
978
|
+
log.info(
|
|
979
|
+
"%s Cleaning up internal visualization app...", self.log_identifier
|
|
980
|
+
)
|
|
981
|
+
try:
|
|
982
|
+
self._visualization_internal_app.cleanup()
|
|
983
|
+
except Exception as e:
|
|
984
|
+
log.error(
|
|
985
|
+
"%s Error cleaning up internal visualization app: %s",
|
|
986
|
+
self.log_identifier,
|
|
987
|
+
e,
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
self._active_visualization_streams.clear()
|
|
991
|
+
self._global_visualization_subscriptions.clear()
|
|
992
|
+
self._cleanup_visualization_locks()
|
|
993
|
+
log.info("%s Visualization resources cleaned up.", self.log_identifier)
|
|
994
|
+
|
|
995
|
+
def _infer_visualization_event_details(
|
|
996
|
+
self, topic: str, payload: Dict[str, Any]
|
|
997
|
+
) -> Dict[str, Any]:
|
|
998
|
+
"""
|
|
999
|
+
Infers details for the visualization SSE payload from the Solace topic and A2A message.
|
|
1000
|
+
"""
|
|
1001
|
+
details = {
|
|
1002
|
+
"direction": "unknown",
|
|
1003
|
+
"source_entity": "unknown",
|
|
1004
|
+
"target_entity": "unknown",
|
|
1005
|
+
"message_id": payload.get("id"),
|
|
1006
|
+
"task_id": None,
|
|
1007
|
+
"payload_summary": {
|
|
1008
|
+
"method": payload.get("method", "N/A"),
|
|
1009
|
+
"params_preview": None,
|
|
1010
|
+
},
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
topic_parts = topic.split("/")
|
|
1014
|
+
|
|
1015
|
+
try:
|
|
1016
|
+
a2a_base_index = topic_parts.index("a2a")
|
|
1017
|
+
domain_index = a2a_base_index + 2
|
|
1018
|
+
action_type_index = a2a_base_index + 3
|
|
1019
|
+
entity_name_index = a2a_base_index + 4
|
|
1020
|
+
task_id_from_topic_index = a2a_base_index + 5
|
|
1021
|
+
|
|
1022
|
+
domain = (
|
|
1023
|
+
topic_parts[domain_index] if len(topic_parts) > domain_index else None
|
|
1024
|
+
)
|
|
1025
|
+
action_type = (
|
|
1026
|
+
topic_parts[action_type_index]
|
|
1027
|
+
if len(topic_parts) > action_type_index
|
|
1028
|
+
else None
|
|
1029
|
+
)
|
|
1030
|
+
entity_name = (
|
|
1031
|
+
topic_parts[entity_name_index]
|
|
1032
|
+
if len(topic_parts) > entity_name_index
|
|
1033
|
+
else None
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
if domain == "agent":
|
|
1037
|
+
if action_type == "request":
|
|
1038
|
+
details["direction"] = "request"
|
|
1039
|
+
details["target_entity"] = entity_name
|
|
1040
|
+
user_props = (
|
|
1041
|
+
payload.get("params", {})
|
|
1042
|
+
.get("metadata", {})
|
|
1043
|
+
.get("solaceUserProperties", {})
|
|
1044
|
+
)
|
|
1045
|
+
details["source_entity"] = (
|
|
1046
|
+
user_props.get("clientId")
|
|
1047
|
+
or user_props.get("delegating_agent_name")
|
|
1048
|
+
or self.gateway_id
|
|
1049
|
+
)
|
|
1050
|
+
elif action_type == "response":
|
|
1051
|
+
details["direction"] = "response"
|
|
1052
|
+
details["source_entity"] = entity_name
|
|
1053
|
+
details["target_entity"] = (
|
|
1054
|
+
payload.get("result", {}).get("metadata", {}).get("clientId")
|
|
1055
|
+
)
|
|
1056
|
+
elif action_type == "status":
|
|
1057
|
+
details["direction"] = "status_update"
|
|
1058
|
+
details["source_entity"] = entity_name
|
|
1059
|
+
details["target_entity"] = (
|
|
1060
|
+
payload.get("result", {}).get("metadata", {}).get("clientId")
|
|
1061
|
+
)
|
|
1062
|
+
elif domain == "gateway":
|
|
1063
|
+
if action_type == "response":
|
|
1064
|
+
details["direction"] = "response"
|
|
1065
|
+
details["source_entity"] = (
|
|
1066
|
+
payload.get("result", {})
|
|
1067
|
+
.get("status", {})
|
|
1068
|
+
.get("message", {})
|
|
1069
|
+
.get("metadata", {})
|
|
1070
|
+
.get("agent_name", "unknown_agent")
|
|
1071
|
+
)
|
|
1072
|
+
details["target_entity"] = entity_name
|
|
1073
|
+
elif action_type == "status":
|
|
1074
|
+
details["direction"] = "status_update"
|
|
1075
|
+
details["source_entity"] = (
|
|
1076
|
+
payload.get("result", {})
|
|
1077
|
+
.get("status", {})
|
|
1078
|
+
.get("message", {})
|
|
1079
|
+
.get("metadata", {})
|
|
1080
|
+
.get("agent_name", "unknown_agent")
|
|
1081
|
+
)
|
|
1082
|
+
details["target_entity"] = entity_name
|
|
1083
|
+
elif domain == "discovery" and action_type == "agentcards":
|
|
1084
|
+
details["direction"] = "discovery"
|
|
1085
|
+
details["source_entity"] = payload.get("name", "unknown_agent")
|
|
1086
|
+
details["target_entity"] = "broadcast"
|
|
1087
|
+
|
|
1088
|
+
if payload.get("method") in [
|
|
1089
|
+
"tasks/send",
|
|
1090
|
+
"tasks/sendSubscribe",
|
|
1091
|
+
"tasks/cancel",
|
|
1092
|
+
]:
|
|
1093
|
+
details["task_id"] = payload.get("params", {}).get("id")
|
|
1094
|
+
elif "result" in payload and isinstance(payload["result"], dict):
|
|
1095
|
+
details["task_id"] = payload["result"].get("id")
|
|
1096
|
+
elif len(topic_parts) > task_id_from_topic_index and (
|
|
1097
|
+
action_type == "status" or action_type == "response"
|
|
1098
|
+
):
|
|
1099
|
+
details["task_id"] = topic_parts[task_id_from_topic_index]
|
|
1100
|
+
|
|
1101
|
+
except (ValueError, IndexError):
|
|
1102
|
+
log.debug(
|
|
1103
|
+
"%s Could not parse A2A structure from topic: %s",
|
|
1104
|
+
self.log_identifier,
|
|
1105
|
+
topic,
|
|
1106
|
+
)
|
|
1107
|
+
if "request" in topic:
|
|
1108
|
+
details["direction"] = "request"
|
|
1109
|
+
elif "response" in topic:
|
|
1110
|
+
details["direction"] = "response"
|
|
1111
|
+
elif "status" in topic:
|
|
1112
|
+
details["direction"] = "status_update"
|
|
1113
|
+
elif "discovery" in topic:
|
|
1114
|
+
details["direction"] = "discovery"
|
|
1115
|
+
|
|
1116
|
+
if "params" in payload:
|
|
1117
|
+
params_str = json.dumps(payload["params"])
|
|
1118
|
+
details["payload_summary"]["params_preview"] = (
|
|
1119
|
+
(params_str[:100] + "...") if len(params_str) > 100 else params_str
|
|
1120
|
+
)
|
|
1121
|
+
elif "result" in payload:
|
|
1122
|
+
result_str = json.dumps(payload["result"])
|
|
1123
|
+
details["payload_summary"]["params_preview"] = (
|
|
1124
|
+
(result_str[:100] + "...") if len(result_str) > 100 else result_str
|
|
1125
|
+
)
|
|
1126
|
+
elif "error" in payload:
|
|
1127
|
+
details["payload_summary"]["method"] = "JSONRPCError"
|
|
1128
|
+
error_str = json.dumps(payload["error"])
|
|
1129
|
+
details["payload_summary"]["params_preview"] = (
|
|
1130
|
+
(error_str[:100] + "...") if len(error_str) > 100 else error_str
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
return details
|
|
1134
|
+
|
|
1135
|
+
def _extract_involved_agents_for_viz(
|
|
1136
|
+
self, topic: str, payload_dict: Dict[str, Any]
|
|
1137
|
+
) -> Set[str]:
|
|
1138
|
+
"""
|
|
1139
|
+
Extracts agent names involved in a message from its topic and payload.
|
|
1140
|
+
"""
|
|
1141
|
+
agents: Set[str] = set()
|
|
1142
|
+
log_id_prefix = f"{self.log_identifier}[ExtractAgentsViz]"
|
|
1143
|
+
|
|
1144
|
+
topic_agent_match = re.match(
|
|
1145
|
+
rf"^{re.escape(self.namespace)}/a2a/v1/agent/(?:request|response|status)/([^/]+)",
|
|
1146
|
+
topic,
|
|
1147
|
+
)
|
|
1148
|
+
if topic_agent_match:
|
|
1149
|
+
agents.add(topic_agent_match.group(1))
|
|
1150
|
+
log.debug(
|
|
1151
|
+
"%s Found agent '%s' in topic.",
|
|
1152
|
+
log_id_prefix,
|
|
1153
|
+
topic_agent_match.group(1),
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
if isinstance(payload_dict, dict):
|
|
1157
|
+
if (
|
|
1158
|
+
"name" in payload_dict
|
|
1159
|
+
and "capabilities" in payload_dict
|
|
1160
|
+
and "skills" in payload_dict
|
|
1161
|
+
):
|
|
1162
|
+
try:
|
|
1163
|
+
card = AgentCard(**payload_dict)
|
|
1164
|
+
if card.name:
|
|
1165
|
+
agents.add(card.name)
|
|
1166
|
+
log.debug(
|
|
1167
|
+
"%s Found agent '%s' in AgentCard payload.",
|
|
1168
|
+
log_id_prefix,
|
|
1169
|
+
card.name,
|
|
1170
|
+
)
|
|
1171
|
+
except Exception:
|
|
1172
|
+
pass
|
|
1173
|
+
result = payload_dict.get("result")
|
|
1174
|
+
if isinstance(result, dict):
|
|
1175
|
+
status_info = result.get("status")
|
|
1176
|
+
if isinstance(status_info, dict):
|
|
1177
|
+
message_info = status_info.get("message")
|
|
1178
|
+
if isinstance(message_info, dict):
|
|
1179
|
+
metadata = message_info.get("metadata")
|
|
1180
|
+
if isinstance(metadata, dict) and "agent_name" in metadata:
|
|
1181
|
+
if metadata["agent_name"]:
|
|
1182
|
+
agents.add(metadata["agent_name"])
|
|
1183
|
+
log.debug(
|
|
1184
|
+
"%s Found agent '%s' in status.message.metadata.",
|
|
1185
|
+
log_id_prefix,
|
|
1186
|
+
metadata["agent_name"],
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
artifact_info = result.get("artifact")
|
|
1190
|
+
if isinstance(artifact_info, dict):
|
|
1191
|
+
metadata = artifact_info.get("metadata")
|
|
1192
|
+
if isinstance(metadata, dict) and "agent_name" in metadata:
|
|
1193
|
+
if metadata["agent_name"]:
|
|
1194
|
+
agents.add(metadata["agent_name"])
|
|
1195
|
+
log.debug(
|
|
1196
|
+
"%s Found agent '%s' in artifact.metadata.",
|
|
1197
|
+
log_id_prefix,
|
|
1198
|
+
metadata["agent_name"],
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
params = payload_dict.get("params")
|
|
1202
|
+
if isinstance(params, dict):
|
|
1203
|
+
message_info = params.get("message")
|
|
1204
|
+
if isinstance(message_info, dict):
|
|
1205
|
+
metadata = message_info.get("metadata")
|
|
1206
|
+
if isinstance(metadata, dict) and "agent_name" in metadata:
|
|
1207
|
+
if metadata["agent_name"]:
|
|
1208
|
+
agents.add(metadata["agent_name"])
|
|
1209
|
+
log.debug(
|
|
1210
|
+
"%s Found agent '%s' in params.message.metadata.",
|
|
1211
|
+
log_id_prefix,
|
|
1212
|
+
metadata["agent_name"],
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
if not agents:
|
|
1216
|
+
log.debug(
|
|
1217
|
+
"%s No specific agents identified from topic '%s' or payload.",
|
|
1218
|
+
log_id_prefix,
|
|
1219
|
+
topic,
|
|
1220
|
+
)
|
|
1221
|
+
return agents
|
|
1222
|
+
|
|
1223
|
+
super().cleanup()
|
|
1224
|
+
|
|
1225
|
+
if self.fastapi_thread and self.fastapi_thread.is_alive():
|
|
1226
|
+
log.info(
|
|
1227
|
+
"%s Waiting for FastAPI server thread to exit...", self.log_identifier
|
|
1228
|
+
)
|
|
1229
|
+
self.fastapi_thread.join(timeout=10)
|
|
1230
|
+
if self.fastapi_thread.is_alive():
|
|
1231
|
+
log.warning(
|
|
1232
|
+
"%s FastAPI server thread did not exit gracefully.",
|
|
1233
|
+
self.log_identifier,
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1236
|
+
if self.sse_manager:
|
|
1237
|
+
log.info(
|
|
1238
|
+
"%s Closing active SSE connections (best effort)...",
|
|
1239
|
+
self.log_identifier,
|
|
1240
|
+
)
|
|
1241
|
+
try:
|
|
1242
|
+
asyncio.run(self.sse_manager.close_all())
|
|
1243
|
+
except Exception as sse_close_err:
|
|
1244
|
+
log.error(
|
|
1245
|
+
"%s Error closing SSE connections during cleanup: %s",
|
|
1246
|
+
self.log_identifier,
|
|
1247
|
+
sse_close_err,
|
|
1248
|
+
)
|
|
1249
|
+
|
|
1250
|
+
log.info("%s Web UI Backend Component cleanup finished.", self.log_identifier)
|
|
1251
|
+
|
|
1252
|
+
def get_agent_registry(self) -> AgentRegistry:
|
|
1253
|
+
return self.agent_registry
|
|
1254
|
+
|
|
1255
|
+
def get_sse_manager(self) -> SSEManager:
|
|
1256
|
+
return self.sse_manager
|
|
1257
|
+
|
|
1258
|
+
def get_session_manager(self) -> SessionManager:
|
|
1259
|
+
return self.session_manager
|
|
1260
|
+
|
|
1261
|
+
def get_namespace(self) -> str:
|
|
1262
|
+
return self.namespace
|
|
1263
|
+
|
|
1264
|
+
def get_gateway_id(self) -> str:
|
|
1265
|
+
"""Returns the unique identifier for this gateway instance."""
|
|
1266
|
+
return self.gateway_id
|
|
1267
|
+
|
|
1268
|
+
def get_cors_origins(self) -> List[str]:
|
|
1269
|
+
return self.cors_allowed_origins
|
|
1270
|
+
|
|
1271
|
+
def get_shared_artifact_service(self) -> Optional[BaseArtifactService]:
|
|
1272
|
+
return self.shared_artifact_service
|
|
1273
|
+
|
|
1274
|
+
def get_embed_config(self) -> Dict[str, Any]:
|
|
1275
|
+
"""Returns embed-related configuration needed by dependencies."""
|
|
1276
|
+
return {
|
|
1277
|
+
"enable_embed_resolution": self.enable_embed_resolution,
|
|
1278
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1279
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
def get_core_a2a_service(self) -> CoreA2AService:
|
|
1283
|
+
"""Returns the CoreA2AService instance."""
|
|
1284
|
+
return self.core_a2a_service
|
|
1285
|
+
|
|
1286
|
+
def get_config_resolver(self) -> ConfigResolver:
|
|
1287
|
+
"""Returns the instance of the ConfigResolver."""
|
|
1288
|
+
return self._config_resolver
|
|
1289
|
+
|
|
1290
|
+
def _start_listener(self) -> None:
|
|
1291
|
+
"""
|
|
1292
|
+
GDK Hook: Starts the FastAPI/Uvicorn server.
|
|
1293
|
+
This method is called by BaseGatewayComponent.run().
|
|
1294
|
+
"""
|
|
1295
|
+
self._start_fastapi_server()
|
|
1296
|
+
|
|
1297
|
+
def _stop_listener(self) -> None:
|
|
1298
|
+
"""
|
|
1299
|
+
GDK Hook: Signals the Uvicorn server to shut down.
|
|
1300
|
+
This method is called by BaseGatewayComponent.cleanup().
|
|
1301
|
+
"""
|
|
1302
|
+
log.info(
|
|
1303
|
+
"%s _stop_listener called. Signaling Uvicorn server to exit.",
|
|
1304
|
+
self.log_identifier,
|
|
1305
|
+
)
|
|
1306
|
+
if self.uvicorn_server:
|
|
1307
|
+
self.uvicorn_server.should_exit = True
|
|
1308
|
+
pass
|
|
1309
|
+
|
|
1310
|
+
async def _translate_external_input(
|
|
1311
|
+
self, external_event_data: Dict[str, Any]
|
|
1312
|
+
) -> Tuple[str, List[A2APart], Dict[str, Any]]:
|
|
1313
|
+
"""
|
|
1314
|
+
Translates raw HTTP request data (from FastAPI form) into A2A task parameters.
|
|
1315
|
+
|
|
1316
|
+
Args:
|
|
1317
|
+
external_event_data: A dictionary containing data from the HTTP request,
|
|
1318
|
+
expected to have keys like 'agent_name', 'message',
|
|
1319
|
+
'files' (List[UploadFile]), 'client_id', 'a2a_session_id'.
|
|
1320
|
+
|
|
1321
|
+
Returns:
|
|
1322
|
+
A tuple containing:
|
|
1323
|
+
- target_agent_name (str): The name of the A2A agent to target.
|
|
1324
|
+
- a2a_parts (List[A2APart]): A list of A2A Part objects for the message.
|
|
1325
|
+
- external_request_context (Dict[str, Any]): Context for TaskContextManager.
|
|
1326
|
+
"""
|
|
1327
|
+
log_id_prefix = f"{self.log_identifier}[TranslateInput]"
|
|
1328
|
+
log.debug(
|
|
1329
|
+
"%s Received external event data: %s",
|
|
1330
|
+
log_id_prefix,
|
|
1331
|
+
{k: type(v) for k, v in external_event_data.items()},
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
target_agent_name: str = external_event_data.get("agent_name")
|
|
1335
|
+
user_message: str = external_event_data.get("message", "")
|
|
1336
|
+
files: Optional[List[UploadFile]] = external_event_data.get("files")
|
|
1337
|
+
client_id: str = external_event_data.get("client_id")
|
|
1338
|
+
a2a_session_id: str = external_event_data.get("a2a_session_id")
|
|
1339
|
+
|
|
1340
|
+
if not target_agent_name:
|
|
1341
|
+
raise ValueError("Target agent name is missing in external_event_data.")
|
|
1342
|
+
if not client_id or not a2a_session_id:
|
|
1343
|
+
raise ValueError(
|
|
1344
|
+
"Client ID or A2A Session ID is missing in external_event_data."
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
a2a_parts: List[A2APart] = []
|
|
1348
|
+
|
|
1349
|
+
if files and self.shared_artifact_service:
|
|
1350
|
+
file_metadata_summary_parts = []
|
|
1351
|
+
for upload_file in files:
|
|
1352
|
+
try:
|
|
1353
|
+
content_bytes = await upload_file.read()
|
|
1354
|
+
if not content_bytes:
|
|
1355
|
+
log.warning(
|
|
1356
|
+
"%s Skipping empty uploaded file: %s",
|
|
1357
|
+
log_id_prefix,
|
|
1358
|
+
upload_file.filename,
|
|
1359
|
+
)
|
|
1360
|
+
continue
|
|
1361
|
+
save_result = await save_artifact_with_metadata(
|
|
1362
|
+
artifact_service=self.shared_artifact_service,
|
|
1363
|
+
app_name=self.gateway_id,
|
|
1364
|
+
user_id=client_id,
|
|
1365
|
+
session_id=a2a_session_id,
|
|
1366
|
+
filename=upload_file.filename,
|
|
1367
|
+
content_bytes=content_bytes,
|
|
1368
|
+
mime_type=upload_file.content_type
|
|
1369
|
+
or "application/octet-stream",
|
|
1370
|
+
metadata_dict={
|
|
1371
|
+
"source": "webui_gateway_upload",
|
|
1372
|
+
"original_filename": upload_file.filename,
|
|
1373
|
+
"upload_timestamp_utc": datetime.now(
|
|
1374
|
+
timezone.utc
|
|
1375
|
+
).isoformat(),
|
|
1376
|
+
"gateway_id": self.gateway_id,
|
|
1377
|
+
"web_client_id": client_id,
|
|
1378
|
+
"a2a_session_id": a2a_session_id,
|
|
1379
|
+
},
|
|
1380
|
+
timestamp=datetime.now(timezone.utc),
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
if save_result["status"] in ["success", "partial_success"]:
|
|
1384
|
+
data_version = save_result.get("data_version", 0)
|
|
1385
|
+
artifact_uri = f"artifact://{self.gateway_id}/{client_id}/{a2a_session_id}/{upload_file.filename}?version={data_version}"
|
|
1386
|
+
file_content = FileContent(
|
|
1387
|
+
name=upload_file.filename,
|
|
1388
|
+
mimeType=upload_file.content_type,
|
|
1389
|
+
uri=artifact_uri,
|
|
1390
|
+
)
|
|
1391
|
+
a2a_parts.append(FilePart(file=file_content))
|
|
1392
|
+
file_metadata_summary_parts.append(
|
|
1393
|
+
f"- {upload_file.filename} ({upload_file.content_type}, {len(content_bytes)} bytes, URI: {artifact_uri})"
|
|
1394
|
+
)
|
|
1395
|
+
log.info(
|
|
1396
|
+
"%s Processed and created URI for uploaded file: %s",
|
|
1397
|
+
log_id_prefix,
|
|
1398
|
+
artifact_uri,
|
|
1399
|
+
)
|
|
1400
|
+
else:
|
|
1401
|
+
log.error(
|
|
1402
|
+
"%s Failed to save artifact %s: %s",
|
|
1403
|
+
log_id_prefix,
|
|
1404
|
+
upload_file.filename,
|
|
1405
|
+
save_result.get("message"),
|
|
1406
|
+
)
|
|
1407
|
+
|
|
1408
|
+
except Exception as e:
|
|
1409
|
+
log.exception(
|
|
1410
|
+
"%s Error processing uploaded file %s: %s",
|
|
1411
|
+
log_id_prefix,
|
|
1412
|
+
upload_file.filename,
|
|
1413
|
+
e,
|
|
1414
|
+
)
|
|
1415
|
+
finally:
|
|
1416
|
+
await upload_file.close()
|
|
1417
|
+
|
|
1418
|
+
if file_metadata_summary_parts:
|
|
1419
|
+
user_message = (
|
|
1420
|
+
"The user uploaded the following file(s):\n"
|
|
1421
|
+
+ "\n".join(file_metadata_summary_parts)
|
|
1422
|
+
+ f"\n\nUser message: {user_message}"
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
if user_message:
|
|
1426
|
+
a2a_parts.append(TextPart(text=user_message))
|
|
1427
|
+
|
|
1428
|
+
external_request_context = {
|
|
1429
|
+
"app_name_for_artifacts": self.gateway_id,
|
|
1430
|
+
"user_id_for_artifacts": client_id,
|
|
1431
|
+
"a2a_session_id": a2a_session_id,
|
|
1432
|
+
"user_id_for_a2a": client_id,
|
|
1433
|
+
"target_agent_name": target_agent_name,
|
|
1434
|
+
}
|
|
1435
|
+
log.debug(
|
|
1436
|
+
"%s Translated input. Target: %s, Parts: %d, Context: %s",
|
|
1437
|
+
log_id_prefix,
|
|
1438
|
+
target_agent_name,
|
|
1439
|
+
len(a2a_parts),
|
|
1440
|
+
external_request_context,
|
|
1441
|
+
)
|
|
1442
|
+
return target_agent_name, a2a_parts, external_request_context
|
|
1443
|
+
|
|
1444
|
+
async def _send_update_to_external(
|
|
1445
|
+
self,
|
|
1446
|
+
external_request_context: Dict[str, Any],
|
|
1447
|
+
event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
|
|
1448
|
+
is_final_chunk_of_update: bool,
|
|
1449
|
+
) -> None:
|
|
1450
|
+
"""
|
|
1451
|
+
Sends an intermediate update (TaskStatusUpdateEvent or TaskArtifactUpdateEvent)
|
|
1452
|
+
to the external platform (Web UI via SSE).
|
|
1453
|
+
"""
|
|
1454
|
+
log_id_prefix = f"{self.log_identifier}[SendUpdate]"
|
|
1455
|
+
sse_task_id = external_request_context.get("a2a_task_id_for_event")
|
|
1456
|
+
a2a_task_id = event_data.id
|
|
1457
|
+
|
|
1458
|
+
if not sse_task_id:
|
|
1459
|
+
log.error(
|
|
1460
|
+
"%s Cannot send update: 'a2a_task_id_for_event' missing from external_request_context.",
|
|
1461
|
+
log_id_prefix,
|
|
1462
|
+
)
|
|
1463
|
+
return
|
|
1464
|
+
|
|
1465
|
+
log.debug(
|
|
1466
|
+
"%s Sending update for A2A Task ID %s to SSE Task ID %s. Final chunk: %s",
|
|
1467
|
+
log_id_prefix,
|
|
1468
|
+
a2a_task_id,
|
|
1469
|
+
sse_task_id,
|
|
1470
|
+
is_final_chunk_of_update,
|
|
1471
|
+
)
|
|
1472
|
+
|
|
1473
|
+
sse_event_type = "status_update"
|
|
1474
|
+
if isinstance(event_data, TaskArtifactUpdateEvent):
|
|
1475
|
+
sse_event_type = "artifact_update"
|
|
1476
|
+
|
|
1477
|
+
sse_payload = JSONRPCResponse(id=a2a_task_id, result=event_data).model_dump(
|
|
1478
|
+
exclude_none=True
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
try:
|
|
1482
|
+
await self.sse_manager.send_event(
|
|
1483
|
+
task_id=sse_task_id, event_data=sse_payload, event_type=sse_event_type
|
|
1484
|
+
)
|
|
1485
|
+
log.info(
|
|
1486
|
+
"%s Successfully sent %s via SSE for A2A Task ID %s.",
|
|
1487
|
+
log_id_prefix,
|
|
1488
|
+
sse_event_type,
|
|
1489
|
+
a2a_task_id,
|
|
1490
|
+
)
|
|
1491
|
+
except Exception as e:
|
|
1492
|
+
log.exception(
|
|
1493
|
+
"%s Failed to send %s via SSE for A2A Task ID %s: %s",
|
|
1494
|
+
log_id_prefix,
|
|
1495
|
+
sse_event_type,
|
|
1496
|
+
a2a_task_id,
|
|
1497
|
+
e,
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
async def _send_final_response_to_external(
|
|
1501
|
+
self, external_request_context: Dict[str, Any], task_data: Task
|
|
1502
|
+
) -> None:
|
|
1503
|
+
"""
|
|
1504
|
+
Sends the final A2A Task result to the external platform (Web UI via SSE).
|
|
1505
|
+
"""
|
|
1506
|
+
log_id_prefix = f"{self.log_identifier}[SendFinalResponse]"
|
|
1507
|
+
sse_task_id = external_request_context.get("a2a_task_id_for_event")
|
|
1508
|
+
a2a_task_id = task_data.id
|
|
1509
|
+
|
|
1510
|
+
if not sse_task_id:
|
|
1511
|
+
log.error(
|
|
1512
|
+
"%s Cannot send final response: 'a2a_task_id_for_event' missing from external_request_context.",
|
|
1513
|
+
log_id_prefix,
|
|
1514
|
+
)
|
|
1515
|
+
return
|
|
1516
|
+
|
|
1517
|
+
log.debug(
|
|
1518
|
+
"%s Sending final response for A2A Task ID %s to SSE Task ID %s.",
|
|
1519
|
+
log_id_prefix,
|
|
1520
|
+
a2a_task_id,
|
|
1521
|
+
sse_task_id,
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
sse_payload = JSONRPCResponse(id=a2a_task_id, result=task_data).model_dump(
|
|
1525
|
+
exclude_none=True
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1528
|
+
try:
|
|
1529
|
+
await self.sse_manager.send_event(
|
|
1530
|
+
task_id=sse_task_id, event_data=sse_payload, event_type="final_response"
|
|
1531
|
+
)
|
|
1532
|
+
log.info(
|
|
1533
|
+
"%s Successfully sent final_response via SSE for A2A Task ID %s.",
|
|
1534
|
+
log_id_prefix,
|
|
1535
|
+
a2a_task_id,
|
|
1536
|
+
)
|
|
1537
|
+
except Exception as e:
|
|
1538
|
+
log.exception(
|
|
1539
|
+
"%s Failed to send final_response via SSE for A2A Task ID %s: %s",
|
|
1540
|
+
log_id_prefix,
|
|
1541
|
+
a2a_task_id,
|
|
1542
|
+
e,
|
|
1543
|
+
)
|
|
1544
|
+
finally:
|
|
1545
|
+
await self.sse_manager.close_all_for_task(sse_task_id)
|
|
1546
|
+
log.info(
|
|
1547
|
+
"%s Closed SSE connections for SSE Task ID %s.",
|
|
1548
|
+
log_id_prefix,
|
|
1549
|
+
sse_task_id,
|
|
1550
|
+
)
|
|
1551
|
+
|
|
1552
|
+
async def _send_error_to_external(
|
|
1553
|
+
self, external_request_context: Dict[str, Any], error_data: JSONRPCError
|
|
1554
|
+
) -> None:
|
|
1555
|
+
"""
|
|
1556
|
+
Sends an error notification to the external platform (Web UI via SSE).
|
|
1557
|
+
"""
|
|
1558
|
+
log_id_prefix = f"{self.log_identifier}[SendError]"
|
|
1559
|
+
sse_task_id = external_request_context.get("a2a_task_id_for_event")
|
|
1560
|
+
|
|
1561
|
+
if not sse_task_id:
|
|
1562
|
+
log.error(
|
|
1563
|
+
"%s Cannot send error: 'a2a_task_id_for_event' missing from external_request_context.",
|
|
1564
|
+
log_id_prefix,
|
|
1565
|
+
)
|
|
1566
|
+
return
|
|
1567
|
+
|
|
1568
|
+
log.debug(
|
|
1569
|
+
"%s Sending error to SSE Task ID %s. Error: %s",
|
|
1570
|
+
log_id_prefix,
|
|
1571
|
+
sse_task_id,
|
|
1572
|
+
error_data,
|
|
1573
|
+
)
|
|
1574
|
+
|
|
1575
|
+
sse_payload = JSONRPCResponse(
|
|
1576
|
+
id=external_request_context.get("original_rpc_id", sse_task_id),
|
|
1577
|
+
error=error_data,
|
|
1578
|
+
).model_dump(exclude_none=True)
|
|
1579
|
+
|
|
1580
|
+
try:
|
|
1581
|
+
await self.sse_manager.send_event(
|
|
1582
|
+
task_id=sse_task_id, event_data=sse_payload, event_type="final_response"
|
|
1583
|
+
)
|
|
1584
|
+
log.info(
|
|
1585
|
+
"%s Successfully sent A2A error as 'final_response' via SSE for SSE Task ID %s.",
|
|
1586
|
+
log_id_prefix,
|
|
1587
|
+
sse_task_id,
|
|
1588
|
+
)
|
|
1589
|
+
except Exception as e:
|
|
1590
|
+
log.exception(
|
|
1591
|
+
"%s Failed to send error via SSE for SSE Task ID %s: %s",
|
|
1592
|
+
log_id_prefix,
|
|
1593
|
+
sse_task_id,
|
|
1594
|
+
e,
|
|
1595
|
+
)
|
|
1596
|
+
finally:
|
|
1597
|
+
await self.sse_manager.close_all_for_task(sse_task_id)
|
|
1598
|
+
log.info(
|
|
1599
|
+
"%s Closed SSE connections for SSE Task ID %s after error.",
|
|
1600
|
+
log_id_prefix,
|
|
1601
|
+
sse_task_id,
|
|
1602
|
+
)
|