solace-agent-mesh 0.2.4__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.

Files changed (518) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
  2. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  3. solace_agent_mesh/agent/adk/callbacks.py +1694 -0
  4. solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
  5. solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
  6. solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
  7. solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
  8. solace_agent_mesh/agent/adk/runner.py +353 -0
  9. solace_agent_mesh/agent/adk/services.py +240 -0
  10. solace_agent_mesh/agent/adk/setup.py +751 -0
  11. solace_agent_mesh/agent/adk/stream_parser.py +214 -0
  12. solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
  13. solace_agent_mesh/agent/agent_llm.txt +41 -0
  14. solace_agent_mesh/agent/protocol/event_handlers.py +1469 -0
  15. solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
  16. solace_agent_mesh/agent/sac/app.py +640 -0
  17. solace_agent_mesh/agent/sac/component.py +3388 -0
  18. solace_agent_mesh/agent/sac/patch_adk.py +111 -0
  19. solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
  20. solace_agent_mesh/agent/sac/task_execution_context.py +176 -0
  21. solace_agent_mesh/agent/testing/__init__.py +3 -0
  22. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  23. solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
  24. solace_agent_mesh/agent/tools/__init__.py +14 -0
  25. solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
  26. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
  27. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
  28. solace_agent_mesh/agent/tools/general_agent_tools.py +569 -0
  29. solace_agent_mesh/agent/tools/image_tools.py +1184 -0
  30. solace_agent_mesh/agent/tools/peer_agent_tool.py +289 -0
  31. solace_agent_mesh/agent/tools/registry.py +36 -0
  32. solace_agent_mesh/agent/tools/test_tools.py +135 -0
  33. solace_agent_mesh/agent/tools/tool_definition.py +45 -0
  34. solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
  35. solace_agent_mesh/agent/tools/web_tools.py +381 -0
  36. solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
  37. solace_agent_mesh/agent/utils/config_parser.py +47 -0
  38. solace_agent_mesh/agent/utils/context_helpers.py +60 -0
  39. solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
  40. solace_agent_mesh/assets/docs/404.html +16 -0
  41. solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
  42. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  43. solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
  44. solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
  45. solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
  46. solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  53. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  54. solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
  55. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
  56. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
  57. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.23bccffb.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
  95. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  96. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  101. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  102. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
  103. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
  104. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  105. solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/f897a61a.2c2e152c.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js +2 -0
  131. solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js.LICENSE.txt +81 -0
  132. solace_agent_mesh/assets/docs/assets/js/runtime~main.d9520ae2.js +1 -0
  133. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
  134. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
  135. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
  137. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
  138. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
  139. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +60 -0
  140. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
  141. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
  142. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
  143. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
  146. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
  147. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
  148. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
  149. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
  150. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
  151. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
  152. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
  153. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
  154. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
  155. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
  156. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
  157. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
  158. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
  159. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
  160. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
  161. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
  162. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
  163. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
  164. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
  165. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
  166. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  167. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  168. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  169. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  170. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  171. solace_agent_mesh/assets/docs/lunr-index-1753813536522.json +1 -0
  172. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  173. solace_agent_mesh/assets/docs/search-doc-1753813536522.json +1 -0
  174. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  175. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  176. solace_agent_mesh/cli/__init__.py +1 -1
  177. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  178. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  179. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
  180. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  181. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
  182. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
  183. solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
  184. solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
  185. solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
  186. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  187. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  188. solace_agent_mesh/cli/commands/init_cmd/env_step.py +197 -0
  189. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  190. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +387 -0
  191. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  192. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
  193. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
  194. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
  195. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
  196. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  197. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +138 -0
  198. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
  199. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +174 -0
  200. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  201. solace_agent_mesh/cli/commands/run_cmd.py +158 -0
  202. solace_agent_mesh/cli/main.py +17 -294
  203. solace_agent_mesh/cli/utils.py +135 -204
  204. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
  205. solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
  206. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  207. solace_agent_mesh/client/webui/frontend/static/assets/main-An0a5j5k.js +663 -0
  208. solace_agent_mesh/client/webui/frontend/static/assets/main-Bu5-4Bac.css +1 -0
  209. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
  210. solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
  211. solace_agent_mesh/common/__init__.py +1 -0
  212. solace_agent_mesh/common/a2a_protocol.py +564 -0
  213. solace_agent_mesh/common/agent_registry.py +42 -0
  214. solace_agent_mesh/common/client/__init__.py +4 -0
  215. solace_agent_mesh/common/client/card_resolver.py +21 -0
  216. solace_agent_mesh/common/client/client.py +85 -0
  217. solace_agent_mesh/common/client/client_llm.txt +133 -0
  218. solace_agent_mesh/common/common_llm.txt +144 -0
  219. solace_agent_mesh/common/constants.py +1 -14
  220. solace_agent_mesh/common/middleware/__init__.py +12 -0
  221. solace_agent_mesh/common/middleware/config_resolver.py +130 -0
  222. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  223. solace_agent_mesh/common/middleware/registry.py +125 -0
  224. solace_agent_mesh/common/server/__init__.py +4 -0
  225. solace_agent_mesh/common/server/server.py +122 -0
  226. solace_agent_mesh/common/server/server_llm.txt +169 -0
  227. solace_agent_mesh/common/server/task_manager.py +291 -0
  228. solace_agent_mesh/common/server/utils.py +28 -0
  229. solace_agent_mesh/common/services/__init__.py +4 -0
  230. solace_agent_mesh/common/services/employee_service.py +162 -0
  231. solace_agent_mesh/common/services/identity_service.py +129 -0
  232. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  233. solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
  234. solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
  235. solace_agent_mesh/common/services/services_llm.txt +132 -0
  236. solace_agent_mesh/common/types.py +411 -0
  237. solace_agent_mesh/common/utils/__init__.py +7 -0
  238. solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
  239. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  240. solace_agent_mesh/common/utils/embeds/constants.py +55 -0
  241. solace_agent_mesh/common/utils/embeds/converter.py +452 -0
  242. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
  243. solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
  244. solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
  245. solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
  246. solace_agent_mesh/common/utils/embeds/types.py +14 -0
  247. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  248. solace_agent_mesh/common/utils/log_formatters.py +44 -0
  249. solace_agent_mesh/common/utils/mime_helpers.py +106 -0
  250. solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
  251. solace_agent_mesh/common/utils/utils_llm.txt +67 -0
  252. solace_agent_mesh/config_portal/backend/common.py +66 -24
  253. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +23 -0
  254. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  255. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +160 -0
  256. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +525 -0
  257. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +216 -0
  258. solace_agent_mesh/config_portal/backend/server.py +550 -181
  259. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DNxCwAGB.js +48 -0
  260. solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
  261. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
  262. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
  263. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d2b54a97.js +1 -0
  264. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
  265. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
  266. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  267. solace_agent_mesh/core_a2a/__init__.py +1 -0
  268. solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
  269. solace_agent_mesh/core_a2a/service.py +331 -0
  270. solace_agent_mesh/evaluation/config_loader.py +657 -0
  271. solace_agent_mesh/evaluation/evaluator.py +667 -0
  272. solace_agent_mesh/evaluation/message_organizer.py +568 -0
  273. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  274. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  275. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  276. solace_agent_mesh/evaluation/report/modal.html +59 -0
  277. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  278. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  279. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  280. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  281. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  282. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  283. solace_agent_mesh/evaluation/report_data_processor.py +972 -0
  284. solace_agent_mesh/evaluation/report_generator.py +613 -0
  285. solace_agent_mesh/evaluation/run.py +613 -0
  286. solace_agent_mesh/evaluation/subscriber.py +872 -0
  287. solace_agent_mesh/evaluation/summary_builder.py +775 -0
  288. solace_agent_mesh/evaluation/test_case_loader.py +714 -0
  289. solace_agent_mesh/gateway/base/__init__.py +1 -0
  290. solace_agent_mesh/gateway/base/app.py +266 -0
  291. solace_agent_mesh/gateway/base/base_llm.txt +119 -0
  292. solace_agent_mesh/gateway/base/component.py +1542 -0
  293. solace_agent_mesh/gateway/base/task_context.py +74 -0
  294. solace_agent_mesh/gateway/gateway_llm.txt +125 -0
  295. solace_agent_mesh/gateway/http_sse/app.py +190 -0
  296. solace_agent_mesh/gateway/http_sse/component.py +1602 -0
  297. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  298. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
  299. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
  300. solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
  301. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
  302. solace_agent_mesh/gateway/http_sse/main.py +442 -0
  303. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  304. solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
  305. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +821 -0
  306. solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
  307. solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
  308. solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
  309. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
  310. solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
  311. solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
  312. solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
  313. solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
  314. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
  315. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  316. solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
  317. solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
  318. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
  319. solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
  320. solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
  321. solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
  322. solace_agent_mesh/llm.txt +228 -0
  323. solace_agent_mesh/llm_detail.txt +2835 -0
  324. solace_agent_mesh/templates/agent_template.yaml +53 -0
  325. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  326. solace_agent_mesh/templates/gateway_app_template.py +73 -0
  327. solace_agent_mesh/templates/gateway_component_template.py +400 -0
  328. solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
  329. solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
  330. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  331. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  332. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  333. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
  334. solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
  335. solace_agent_mesh/templates/plugin_readme_template.md +34 -0
  336. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  337. solace_agent_mesh/templates/shared_config.yaml +66 -0
  338. solace_agent_mesh/templates/templates_llm.txt +147 -0
  339. solace_agent_mesh/templates/webui.yaml +53 -0
  340. solace_agent_mesh-1.0.1.dist-info/METADATA +432 -0
  341. solace_agent_mesh-1.0.1.dist-info/RECORD +359 -0
  342. solace_agent_mesh-1.0.1.dist-info/entry_points.txt +3 -0
  343. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.1.dist-info}/licenses/LICENSE +1 -1
  344. solace_agent_mesh/agents/base_agent_component.py +0 -256
  345. solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
  346. solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
  347. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
  348. solace_agent_mesh/agents/global/actions/create_file.py +0 -70
  349. solace_agent_mesh/agents/global/actions/error_action.py +0 -45
  350. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
  351. solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
  352. solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
  353. solace_agent_mesh/agents/global/global_agent_component.py +0 -38
  354. solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
  355. solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
  356. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
  357. solace_agent_mesh/agents/slack/__init__.py +0 -1
  358. solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
  359. solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
  360. solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
  361. solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
  362. solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
  363. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
  364. solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
  365. solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
  366. solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
  367. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
  368. solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
  369. solace_agent_mesh/assets/web-visualizer/index.html +0 -14
  370. solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
  371. solace_agent_mesh/cli/commands/add/__init__.py +0 -3
  372. solace_agent_mesh/cli/commands/add/add.py +0 -88
  373. solace_agent_mesh/cli/commands/add/agent.py +0 -110
  374. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
  375. solace_agent_mesh/cli/commands/add/gateway.py +0 -374
  376. solace_agent_mesh/cli/commands/build.py +0 -670
  377. solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
  378. solace_agent_mesh/cli/commands/chat/chat.py +0 -361
  379. solace_agent_mesh/cli/commands/config.py +0 -29
  380. solace_agent_mesh/cli/commands/init/__init__.py +0 -3
  381. solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
  382. solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
  383. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
  384. solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
  385. solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
  386. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
  387. solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
  388. solace_agent_mesh/cli/commands/init/init.py +0 -92
  389. solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
  390. solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
  391. solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
  392. solace_agent_mesh/cli/commands/plugin/add.py +0 -100
  393. solace_agent_mesh/cli/commands/plugin/build.py +0 -268
  394. solace_agent_mesh/cli/commands/plugin/create.py +0 -117
  395. solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
  396. solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
  397. solace_agent_mesh/cli/commands/run.py +0 -68
  398. solace_agent_mesh/cli/commands/visualizer.py +0 -138
  399. solace_agent_mesh/cli/config.py +0 -85
  400. solace_agent_mesh/common/action.py +0 -91
  401. solace_agent_mesh/common/action_list.py +0 -37
  402. solace_agent_mesh/common/action_response.py +0 -340
  403. solace_agent_mesh/common/mysql_database.py +0 -40
  404. solace_agent_mesh/common/postgres_database.py +0 -85
  405. solace_agent_mesh/common/prompt_templates.py +0 -28
  406. solace_agent_mesh/common/stimulus_utils.py +0 -152
  407. solace_agent_mesh/common/time.py +0 -24
  408. solace_agent_mesh/common/utils.py +0 -712
  409. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-a-zJ6rLx.js +0 -46
  410. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
  411. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-44c41103.js +0 -1
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
  414. solace_agent_mesh/configs/agent_global.yaml +0 -74
  415. solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
  416. solace_agent_mesh/configs/agent_slack.yaml +0 -64
  417. solace_agent_mesh/configs/agent_web_request.yaml +0 -75
  418. solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
  419. solace_agent_mesh/configs/error_catcher.yaml +0 -56
  420. solace_agent_mesh/configs/monitor.yaml +0 -0
  421. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
  422. solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
  423. solace_agent_mesh/configs/orchestrator.yaml +0 -241
  424. solace_agent_mesh/configs/service_embedding.yaml +0 -81
  425. solace_agent_mesh/configs/service_llm.yaml +0 -265
  426. solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
  427. solace_agent_mesh/gateway/components/gateway_base.py +0 -47
  428. solace_agent_mesh/gateway/components/gateway_input.py +0 -278
  429. solace_agent_mesh/gateway/components/gateway_output.py +0 -298
  430. solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
  431. solace_agent_mesh/gateway/identity/identity_base.py +0 -10
  432. solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
  433. solace_agent_mesh/gateway/identity/no_identity.py +0 -9
  434. solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
  435. solace_agent_mesh/monitors/base_monitor_component.py +0 -26
  436. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
  437. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
  438. solace_agent_mesh/orchestrator/__init__.py +0 -0
  439. solace_agent_mesh/orchestrator/action_manager.py +0 -237
  440. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  441. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
  442. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
  443. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
  444. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
  445. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
  446. solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
  447. solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
  448. solace_agent_mesh/services/__init__.py +0 -0
  449. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
  450. solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
  451. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
  452. solace_agent_mesh/services/common/__init__.py +0 -4
  453. solace_agent_mesh/services/common/auto_expiry.py +0 -45
  454. solace_agent_mesh/services/common/singleton.py +0 -18
  455. solace_agent_mesh/services/file_service/__init__.py +0 -14
  456. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  457. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
  458. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
  459. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
  460. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
  461. solace_agent_mesh/services/file_service/file_service.py +0 -437
  462. solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
  463. solace_agent_mesh/services/file_service/file_transformations.py +0 -141
  464. solace_agent_mesh/services/file_service/file_utils.py +0 -324
  465. solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
  466. solace_agent_mesh/services/history_service/__init__.py +0 -3
  467. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  468. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
  469. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
  470. solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
  471. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
  472. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
  473. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
  474. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
  475. solace_agent_mesh/services/history_service/history_service.py +0 -413
  476. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  477. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
  478. solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
  479. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
  480. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  481. solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
  482. solace_agent_mesh/templates/action.py +0 -38
  483. solace_agent_mesh/templates/agent.py +0 -29
  484. solace_agent_mesh/templates/agent.yaml +0 -70
  485. solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
  486. solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
  487. solace_agent_mesh/templates/gateway-flows.yaml +0 -78
  488. solace_agent_mesh/templates/gateway-header.yaml +0 -16
  489. solace_agent_mesh/templates/gateway_base.py +0 -15
  490. solace_agent_mesh/templates/gateway_input.py +0 -98
  491. solace_agent_mesh/templates/gateway_output.py +0 -71
  492. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
  493. solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
  494. solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
  495. solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
  496. solace_agent_mesh/templates/slack-default-config.yaml +0 -16
  497. solace_agent_mesh/templates/slack-flows.yaml +0 -81
  498. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
  499. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
  500. solace_agent_mesh/templates/web-default-config.yaml +0 -10
  501. solace_agent_mesh/templates/web-flows.yaml +0 -76
  502. solace_agent_mesh/tools/__init__.py +0 -0
  503. solace_agent_mesh/tools/components/__init__.py +0 -0
  504. solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
  505. solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
  506. solace_agent_mesh/tools/config/runtime_config.py +0 -26
  507. solace_agent_mesh-0.2.4.dist-info/METADATA +0 -176
  508. solace_agent_mesh-0.2.4.dist-info/RECORD +0 -193
  509. solace_agent_mesh-0.2.4.dist-info/entry_points.txt +0 -3
  510. /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
  511. /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
  512. /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
  513. /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
  514. /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
  515. /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
  516. /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
  517. /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
  518. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1131 @@
1
+ """
2
+ API Router for managing A2A message visualization streams.
3
+ """
4
+
5
+ import asyncio
6
+ import uuid
7
+ from fastapi import (
8
+ APIRouter,
9
+ Depends,
10
+ HTTPException,
11
+ Request as FastAPIRequest,
12
+ Response,
13
+ status,
14
+ )
15
+ from pydantic import BaseModel, Field
16
+ from typing import List, Optional, Dict, Any, Set
17
+
18
+ from solace_ai_connector.common.log import log
19
+
20
+ from ....gateway.http_sse.dependencies import (
21
+ get_sac_component,
22
+ get_user_id,
23
+ get_sse_manager,
24
+ )
25
+ from ....gateway.http_sse.sse_manager import SSEManager
26
+ from ....common.middleware.registry import MiddlewareRegistry
27
+
28
+ from typing import TYPE_CHECKING
29
+
30
+ if TYPE_CHECKING:
31
+ from ....gateway.http_sse.component import WebUIBackendComponent
32
+
33
+
34
+ router = APIRouter()
35
+
36
+
37
+ class SubscriptionTarget(BaseModel):
38
+ """Defines an abstract target for A2A message visualization."""
39
+
40
+ type: str = Field(
41
+ ...,
42
+ description="Type of the target to monitor.",
43
+ examples=[
44
+ "my_a2a_messages",
45
+ "current_namespace_a2a_messages",
46
+ "namespace_a2a_messages",
47
+ "agent_a2a_messages",
48
+ ],
49
+ )
50
+ identifier: Optional[str] = Field(
51
+ default=None,
52
+ description="Identifier for the target (e.g., namespace string or agent name). Not required if type is 'current_namespace_a2a_messages'.",
53
+ )
54
+
55
+
56
+ class ActualSubscribedTarget(SubscriptionTarget):
57
+ """Represents an abstract target that the gateway attempted to subscribe to, including its status."""
58
+
59
+ status: str = Field(
60
+ ...,
61
+ description="Status of the subscription attempt for this target.",
62
+ examples=["subscribed", "denied_due_to_scope", "error_translating_target"],
63
+ )
64
+
65
+
66
+ class VisualizationSubscribeRequest(BaseModel):
67
+ """Request body for initiating a visualization stream."""
68
+
69
+ subscription_targets: Optional[List[SubscriptionTarget]] = Field(
70
+ default_factory=list,
71
+ description="Optional list of abstract targets to monitor.",
72
+ )
73
+ client_stream_id: Optional[str] = Field(
74
+ default=None,
75
+ description="Optional client-generated ID for idempotency or re-association. If not provided, a new one is generated.",
76
+ )
77
+
78
+
79
+ class VisualizationSubscribeResponse(BaseModel):
80
+ """Response body for a successful visualization subscription."""
81
+
82
+ stream_id: str = Field(..., description="Unique ID for the visualization stream.")
83
+ sse_endpoint_url: str = Field(..., description="URL for the SSE event stream.")
84
+ actual_subscribed_targets: List[ActualSubscribedTarget] = Field(
85
+ default_factory=list,
86
+ description="List of abstract targets processed, with their subscription status.",
87
+ )
88
+ message: str = "Visualization stream initiated. Connect to the SSE endpoint."
89
+
90
+
91
+ class VisualizationConfigUpdateRequest(BaseModel):
92
+ """Request body for updating an active visualization stream's configuration."""
93
+
94
+ subscription_targets_to_add: Optional[List[SubscriptionTarget]] = Field(
95
+ default=None,
96
+ description="List of new abstract targets to add to the subscription.",
97
+ )
98
+ subscription_targets_to_remove: Optional[List[SubscriptionTarget]] = Field(
99
+ default=None,
100
+ description="List of abstract targets to remove from the subscription.",
101
+ )
102
+
103
+
104
+ class VisualizationConfigUpdateResponse(BaseModel):
105
+ """Response body for a successful visualization configuration update."""
106
+
107
+ stream_id: str = Field(..., description="ID of the updated visualization stream.")
108
+ message: str = "Visualization stream configuration updated successfully."
109
+ current_subscribed_targets: List[ActualSubscribedTarget] = Field(
110
+ default_factory=list,
111
+ description="Current list of active abstract targets for this stream, with their status.",
112
+ )
113
+
114
+
115
+ class VisualizationSubscriptionError(BaseModel):
116
+ """Error response for subscription failures."""
117
+
118
+ message: str = Field(..., description="Human-readable error message")
119
+ failed_targets: List[ActualSubscribedTarget] = Field(
120
+ ..., description="List of targets that failed to subscribe"
121
+ )
122
+ error_type: str = Field(
123
+ ...,
124
+ description="Type of error: 'authorization_failure' or 'subscription_failure'",
125
+ )
126
+ suggested_action: Optional[str] = Field(
127
+ default=None, description="Suggested action for the user"
128
+ )
129
+
130
+
131
+ from sse_starlette.sse import EventSourceResponse
132
+
133
+
134
+ def _generate_sse_url(fastapi_request: FastAPIRequest, stream_id: str) -> str:
135
+ """
136
+ Generate SSE endpoint URL with proper scheme detection for reverse proxy scenarios.
137
+
138
+ Args:
139
+ fastapi_request: The FastAPI request object
140
+ stream_id: The stream ID for the SSE endpoint
141
+
142
+ Returns:
143
+ Complete SSE URL with correct scheme (http/https)
144
+ """
145
+ forwarded_proto = fastapi_request.headers.get("x-forwarded-proto")
146
+ if forwarded_proto and forwarded_proto.lower() == "https":
147
+ scheme = "https"
148
+ else:
149
+ scheme = fastapi_request.url.scheme
150
+
151
+ return str(
152
+ fastapi_request.url_for(
153
+ "get_visualization_stream_events", stream_id=stream_id
154
+ ).replace(scheme=scheme)
155
+ )
156
+
157
+
158
+ def _translate_target_to_solace_topics(
159
+ target: SubscriptionTarget, component_namespace: str
160
+ ) -> List[str]:
161
+ """Translates an abstract SubscriptionTarget to a list of Solace topic strings."""
162
+ topics = []
163
+ target_identifier = target.identifier.strip("/") if target.identifier else ""
164
+ component_namespace_formatted = component_namespace.strip("/")
165
+ if target.type == "current_namespace_a2a_messages":
166
+ topics.append(f"{component_namespace_formatted}/a2a/>")
167
+ elif target.type == "namespace_a2a_messages":
168
+ if not target.identifier:
169
+ log.warning(f"Identifier missing for target type {target.type}")
170
+ return []
171
+ topics.append(f"{target_identifier}/a2a/>")
172
+ elif target.type == "agent_a2a_messages":
173
+ if not target.identifier:
174
+ log.warning(f"Identifier missing for target type {target.type}")
175
+ return []
176
+ base_agent_topic = f"{component_namespace_formatted}/a2a/v1/agent"
177
+ topics.append(f"{base_agent_topic}/request/{target_identifier}/>")
178
+ topics.append(f"{base_agent_topic}/response/{target_identifier}/>")
179
+ topics.append(f"{base_agent_topic}/status/{target_identifier}/>")
180
+ else:
181
+ log.warning(f"Unknown subscription target type: {target.type}")
182
+ return topics
183
+
184
+
185
+ def _resolve_user_identity_for_authorization(
186
+ component: "WebUIBackendComponent", raw_user_id: str
187
+ ) -> str:
188
+ """
189
+ Applies the same user identity resolution logic as BaseGatewayComponent.submit_a2a_task().
190
+ This ensures visualization authorization uses the same identity resolution as task submission.
191
+
192
+ Args:
193
+ component: The WebUIBackendComponent instance
194
+ raw_user_id: The raw user ID from the session (e.g., web-client-xxxxx)
195
+
196
+ Returns:
197
+ The resolved user identity to use for authorization
198
+ """
199
+ log_id_prefix = f"{component.log_identifier}[ResolveUserIdentity]"
200
+ user_identity = raw_user_id
201
+
202
+ force_identity = component.get_config("force_user_identity")
203
+ if force_identity:
204
+ original_identity = user_identity
205
+ user_identity = force_identity
206
+ log.info(
207
+ "%s DEVELOPMENT MODE: Forcing user_identity from '%s' to '%s' for visualization",
208
+ log_id_prefix,
209
+ original_identity,
210
+ user_identity,
211
+ )
212
+ return user_identity
213
+
214
+ if not user_identity:
215
+ default_user_identity = component.get_config("default_user_identity")
216
+ if default_user_identity:
217
+ user_identity = default_user_identity
218
+ log.info(
219
+ "%s No user_identity provided, using configured default_user_identity: '%s' for visualization",
220
+ log_id_prefix,
221
+ user_identity,
222
+ )
223
+ else:
224
+ log.warning(
225
+ "%s No user_identity and no default_user_identity configured for visualization",
226
+ log_id_prefix,
227
+ )
228
+
229
+ return user_identity
230
+
231
+
232
+ @router.post(
233
+ "/subscribe",
234
+ response_model=VisualizationSubscribeResponse,
235
+ status_code=status.HTTP_201_CREATED,
236
+ )
237
+ async def subscribe_to_visualization_stream(
238
+ request_data: VisualizationSubscribeRequest,
239
+ fastapi_request: FastAPIRequest,
240
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
241
+ user_id: str = Depends(get_user_id),
242
+ sse_manager: SSEManager = Depends(get_sse_manager),
243
+ ):
244
+ """Initiates a new A2A message visualization stream using abstract targets."""
245
+ log_id_prefix = f"{component.log_identifier}[POST /viz/subscribe]"
246
+ log.info(
247
+ "%s Request received from user %s. Client Stream ID: %s",
248
+ log_id_prefix,
249
+ user_id,
250
+ request_data.client_stream_id,
251
+ )
252
+
253
+ try:
254
+ component._ensure_visualization_flow_is_running()
255
+ except Exception as e:
256
+ log.exception(
257
+ "%s Failed to ensure visualization flow is running: %s", log_id_prefix, e
258
+ )
259
+ raise HTTPException(
260
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
261
+ detail="Failed to initialize visualization backend.",
262
+ )
263
+
264
+ stream_id = request_data.client_stream_id or f"viz-stream-{uuid.uuid4().hex}"
265
+
266
+ log.debug(
267
+ "%s Acquiring viz lock to check for existing stream %s",
268
+ log_id_prefix,
269
+ stream_id,
270
+ )
271
+ async with component._get_visualization_lock():
272
+ if stream_id in component._active_visualization_streams:
273
+ existing_stream_data = component._active_visualization_streams[stream_id]
274
+ if existing_stream_data.get("user_id") != user_id:
275
+ raise HTTPException(
276
+ status_code=status.HTTP_403_FORBIDDEN,
277
+ detail="Client stream ID already in use by another user.",
278
+ )
279
+
280
+ log.warning(
281
+ "%s Stream ID %s (client-provided) already exists. Returning existing info.",
282
+ log_id_prefix,
283
+ stream_id,
284
+ )
285
+ sse_url = _generate_sse_url(fastapi_request, stream_id)
286
+ return VisualizationSubscribeResponse(
287
+ stream_id=stream_id,
288
+ sse_endpoint_url=sse_url,
289
+ actual_subscribed_targets=existing_stream_data.get(
290
+ "abstract_targets", []
291
+ ),
292
+ message="Visualization stream with this client_stream_id already exists and is active.",
293
+ )
294
+ log.debug(
295
+ "%s Released viz lock after checking for existing stream %s",
296
+ log_id_prefix,
297
+ stream_id,
298
+ )
299
+
300
+ try:
301
+ sse_queue = await sse_manager.create_sse_connection(stream_id)
302
+ except Exception as e:
303
+ log.exception(
304
+ "%s Failed to create SSE connection queue for stream %s: %s",
305
+ log_id_prefix,
306
+ stream_id,
307
+ e,
308
+ )
309
+ raise HTTPException(
310
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
311
+ detail="Failed to establish SSE infrastructure.",
312
+ )
313
+
314
+ resolved_user_identity = _resolve_user_identity_for_authorization(
315
+ component, user_id
316
+ )
317
+ log.info(
318
+ "%s Resolved user identity for authorization: '%s' (from raw user_id: '%s')",
319
+ log_id_prefix,
320
+ resolved_user_identity,
321
+ user_id,
322
+ )
323
+
324
+ config_resolver = MiddlewareRegistry.get_config_resolver()
325
+ gateway_context = {
326
+ "gateway_id": component.gateway_id,
327
+ "request": fastapi_request,
328
+ "gateway_namespace": component.namespace,
329
+ }
330
+ user_config: Dict[str, Any] = {}
331
+ try:
332
+ user_config = await config_resolver.resolve_user_config(
333
+ resolved_user_identity, gateway_context, {}
334
+ )
335
+ log.info(
336
+ "%s Resolved user_config for resolved_user_identity '%s': %s",
337
+ log_id_prefix,
338
+ resolved_user_identity,
339
+ {k: v for k, v in user_config.items() if not k.startswith("_")},
340
+ )
341
+ except Exception as config_err:
342
+ log.exception(
343
+ "%s Error resolving user_config for user %s: %s. Proceeding with empty config.",
344
+ log_id_prefix,
345
+ resolved_user_identity,
346
+ config_err,
347
+ )
348
+ user_config = {}
349
+ processed_targets_for_response: List[ActualSubscribedTarget] = []
350
+ current_solace_topics_for_stream: Set[str] = set()
351
+ current_abstract_targets_for_stream: List[ActualSubscribedTarget] = []
352
+
353
+ initial_stream_config = {
354
+ "user_id": user_id,
355
+ "user_config": user_config,
356
+ "solace_topics": current_solace_topics_for_stream,
357
+ "abstract_targets": current_abstract_targets_for_stream,
358
+ "sse_queue": sse_queue,
359
+ "client_stream_id": request_data.client_stream_id,
360
+ }
361
+ log.debug(
362
+ "%s Acquiring viz lock to add initial stream config for %s",
363
+ log_id_prefix,
364
+ stream_id,
365
+ )
366
+ async with component._get_visualization_lock():
367
+ component._active_visualization_streams[stream_id] = initial_stream_config
368
+ log.debug(
369
+ "%s Released viz lock after adding initial stream config for %s",
370
+ log_id_prefix,
371
+ stream_id,
372
+ )
373
+
374
+ targets_to_process = request_data.subscription_targets
375
+ if not targets_to_process:
376
+ log.info(
377
+ "%s No subscription targets provided, defaulting to current namespace.",
378
+ log_id_prefix,
379
+ )
380
+ targets_to_process = [SubscriptionTarget(type="current_namespace_a2a_messages")]
381
+
382
+ log.debug(
383
+ "%s Starting to process %d subscription targets.",
384
+ log_id_prefix,
385
+ len(targets_to_process),
386
+ )
387
+ for target_request_idx, target_request in enumerate(targets_to_process):
388
+ log.debug(
389
+ "%s Processing target %d/%d: %s",
390
+ log_id_prefix,
391
+ target_request_idx + 1,
392
+ len(targets_to_process),
393
+ target_request.model_dump(),
394
+ )
395
+ target_status = "denied_due_to_scope"
396
+ required_scope = ""
397
+ effective_identifier = target_request.identifier
398
+
399
+ if target_request.type == "current_namespace_a2a_messages":
400
+ effective_identifier = component.namespace
401
+ required_scope = (
402
+ f"monitor/namespace/{effective_identifier}:a2a_messages:subscribe"
403
+ )
404
+ elif target_request.type == "namespace_a2a_messages":
405
+ if not effective_identifier:
406
+ log.warning(
407
+ "%s Identifier missing for target type 'namespace_a2a_messages'",
408
+ log_id_prefix,
409
+ )
410
+ target_status = "error_missing_identifier"
411
+ processed_targets_for_response.append(
412
+ ActualSubscribedTarget(
413
+ **target_request.model_dump(), status=target_status
414
+ )
415
+ )
416
+ continue
417
+ required_scope = (
418
+ f"monitor/namespace/{effective_identifier}:a2a_messages:subscribe"
419
+ )
420
+ elif target_request.type == "agent_a2a_messages":
421
+ if not effective_identifier:
422
+ log.warning(
423
+ "%s Identifier missing for target type 'agent_a2a_messages'",
424
+ log_id_prefix,
425
+ )
426
+ target_status = "error_missing_identifier"
427
+ processed_targets_for_response.append(
428
+ ActualSubscribedTarget(
429
+ **target_request.model_dump(), status=target_status
430
+ )
431
+ )
432
+ continue
433
+
434
+ pass
435
+ elif target_request.type == "my_a2a_messages":
436
+ operation_spec = {
437
+ "operation_type": "visualization_subscription",
438
+ "target_type": "my_a2a_messages",
439
+ }
440
+ validation_result = config_resolver.validate_operation_config(
441
+ user_config, operation_spec, gateway_context
442
+ )
443
+ has_permission = validation_result.get("valid", False)
444
+
445
+ if has_permission:
446
+ target_status = "subscribed"
447
+ response_target_data = target_request.model_dump()
448
+ current_abstract_targets_for_stream.append(
449
+ ActualSubscribedTarget(**response_target_data, status=target_status)
450
+ )
451
+
452
+ firehose_topic = f"{component.namespace.strip('/')}/a2a/>"
453
+ log.info(
454
+ "%s Adding firehose subscription '%s' for my_a2a_messages stream.",
455
+ log_id_prefix,
456
+ firehose_topic,
457
+ )
458
+ if not await component._add_visualization_subscription(
459
+ firehose_topic, stream_id
460
+ ):
461
+ log.error(
462
+ "%s Failed to add required firehose subscription for my_a2a_messages.",
463
+ log_id_prefix,
464
+ )
465
+ target_status = "error_adding_subscription"
466
+ current_abstract_targets_for_stream.pop()
467
+
468
+ else:
469
+ log.warning(
470
+ "%s User %s denied subscription to 'my_a2a_messages' due to missing scope.",
471
+ log_id_prefix,
472
+ resolved_user_identity,
473
+ )
474
+ processed_targets_for_response.append(
475
+ ActualSubscribedTarget(
476
+ **target_request.model_dump(), status=target_status
477
+ )
478
+ )
479
+ continue
480
+ else:
481
+ log.warning(
482
+ "%s Unknown subscription target type: %s for identifier %s",
483
+ log_id_prefix,
484
+ target_request.type,
485
+ effective_identifier,
486
+ )
487
+ target_status = "error_unknown_target_type"
488
+ processed_targets_for_response.append(
489
+ ActualSubscribedTarget(
490
+ **target_request.model_dump(), status=target_status
491
+ )
492
+ )
493
+ continue
494
+
495
+ identifier_for_spec = target_request.identifier
496
+ if (
497
+ target_request.type == "current_namespace_a2a_messages"
498
+ and target_request.identifier is None
499
+ ):
500
+ identifier_for_spec = None
501
+
502
+ operation_spec = {
503
+ "operation_type": "visualization_subscription",
504
+ "target_type": target_request.type,
505
+ "target_identifier": identifier_for_spec,
506
+ }
507
+
508
+ validation_result = config_resolver.validate_operation_config(
509
+ user_config, operation_spec, gateway_context
510
+ )
511
+ has_permission = validation_result.get("valid", False)
512
+
513
+ if has_permission:
514
+ target_for_translation = target_request
515
+ if target_request.type == "current_namespace_a2a_messages":
516
+ target_for_translation = SubscriptionTarget(
517
+ type="namespace_a2a_messages", identifier=effective_identifier
518
+ )
519
+
520
+ solace_topics_for_target = _translate_target_to_solace_topics(
521
+ target_for_translation, component.namespace
522
+ )
523
+ if not solace_topics_for_target:
524
+ log.warning(
525
+ "%s No Solace topics derived for target: %s",
526
+ log_id_prefix,
527
+ target_request.model_dump(),
528
+ )
529
+ target_status = "error_translating_target"
530
+ else:
531
+ all_topics_added_successfully = True
532
+ for topic_str in solace_topics_for_target:
533
+ success = await component._add_visualization_subscription(
534
+ topic_str, stream_id
535
+ )
536
+ if success:
537
+ current_solace_topics_for_stream.add(topic_str)
538
+ else:
539
+ all_topics_added_successfully = False
540
+ log.error(
541
+ "%s Failed to add subscription to Solace topic: %s for stream %s (target: %s)",
542
+ log_id_prefix,
543
+ topic_str,
544
+ stream_id,
545
+ effective_identifier,
546
+ )
547
+
548
+ if all_topics_added_successfully:
549
+ target_status = "subscribed"
550
+ response_target_data = target_request.model_dump()
551
+ if target_request.type == "current_namespace_a2a_messages":
552
+ response_target_data["identifier"] = effective_identifier
553
+ current_abstract_targets_for_stream.append(
554
+ ActualSubscribedTarget(
555
+ **response_target_data, status=target_status
556
+ )
557
+ )
558
+ else:
559
+ target_status = "error_adding_subscription"
560
+ else:
561
+ log.warning(
562
+ "%s User %s denied subscription to target %s (type: %s) due to missing scope: %s",
563
+ log_id_prefix,
564
+ resolved_user_identity,
565
+ effective_identifier,
566
+ target_request.type,
567
+ required_scope,
568
+ )
569
+
570
+ response_target_data_for_processed_list = target_request.model_dump()
571
+ if (
572
+ target_request.type == "current_namespace_a2a_messages"
573
+ and target_status == "subscribed"
574
+ ):
575
+ response_target_data_for_processed_list["identifier"] = effective_identifier
576
+
577
+ processed_targets_for_response.append(
578
+ ActualSubscribedTarget(
579
+ **response_target_data_for_processed_list, status=target_status
580
+ )
581
+ )
582
+ log.debug("%s Finished processing all subscription targets.", log_id_prefix)
583
+
584
+ successful_subscriptions = [
585
+ target
586
+ for target in processed_targets_for_response
587
+ if target.status == "subscribed"
588
+ ]
589
+
590
+ if not successful_subscriptions:
591
+ log.warning(
592
+ "%s All subscription targets failed for user %s. Cleaning up stream %s.",
593
+ log_id_prefix,
594
+ user_id,
595
+ stream_id,
596
+ )
597
+
598
+ try:
599
+ await sse_manager.close_all_for_task(stream_id)
600
+ except Exception as cleanup_error:
601
+ log.warning(
602
+ "%s Failed to cleanup SSE connection for stream %s: %s",
603
+ log_id_prefix,
604
+ stream_id,
605
+ cleanup_error,
606
+ )
607
+
608
+ log.debug(
609
+ "%s Acquiring viz lock to clean up failed stream %s",
610
+ log_id_prefix,
611
+ stream_id,
612
+ )
613
+ async with component._get_visualization_lock():
614
+ component._active_visualization_streams.pop(stream_id, None)
615
+ log.debug(
616
+ "%s Released viz lock after cleaning up failed stream %s",
617
+ log_id_prefix,
618
+ stream_id,
619
+ )
620
+
621
+ denied_targets = [
622
+ target
623
+ for target in processed_targets_for_response
624
+ if target.status == "denied_due_to_scope"
625
+ ]
626
+
627
+ if denied_targets:
628
+ raise HTTPException(
629
+ status_code=status.HTTP_403_FORBIDDEN,
630
+ detail={
631
+ "message": "Access denied: insufficient permissions for all requested targets",
632
+ "failed_targets": [
633
+ target.model_dump() for target in processed_targets_for_response
634
+ ],
635
+ "error_type": "authorization_failure",
636
+ "suggested_action": "Please check your permissions or contact your administrator",
637
+ },
638
+ )
639
+ else:
640
+ raise HTTPException(
641
+ status_code=status.HTTP_400_BAD_REQUEST,
642
+ detail={
643
+ "message": "All subscription targets failed to process",
644
+ "failed_targets": [
645
+ target.model_dump() for target in processed_targets_for_response
646
+ ],
647
+ "error_type": "subscription_failure",
648
+ "suggested_action": "Please check your target specifications and try again",
649
+ },
650
+ )
651
+
652
+ failed_targets = [
653
+ target
654
+ for target in processed_targets_for_response
655
+ if target.status != "subscribed"
656
+ ]
657
+
658
+ response_message = "Visualization stream initiated. Connect to the SSE endpoint."
659
+ if failed_targets:
660
+ response_message = f"Visualization stream initiated with {len(successful_subscriptions)} successful and {len(failed_targets)} failed subscriptions."
661
+ log.warning(
662
+ "%s Partial subscription success for user %s: %d successful, %d failed",
663
+ log_id_prefix,
664
+ user_id,
665
+ len(successful_subscriptions),
666
+ len(failed_targets),
667
+ )
668
+
669
+ sse_url = _generate_sse_url(fastapi_request, stream_id)
670
+ log.info(
671
+ "%s Visualization stream %s initiated for user %s. SSE URL: %s. Processed Targets: %s",
672
+ log_id_prefix,
673
+ stream_id,
674
+ user_id,
675
+ sse_url,
676
+ processed_targets_for_response,
677
+ )
678
+
679
+ return VisualizationSubscribeResponse(
680
+ stream_id=stream_id,
681
+ sse_endpoint_url=sse_url,
682
+ actual_subscribed_targets=processed_targets_for_response,
683
+ message=response_message,
684
+ )
685
+
686
+
687
+ @router.get("/{stream_id}/events")
688
+ async def get_visualization_stream_events(
689
+ stream_id: str,
690
+ fastapi_request: FastAPIRequest,
691
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
692
+ sse_manager: SSEManager = Depends(get_sse_manager),
693
+ user_id: str = Depends(get_user_id),
694
+ ):
695
+ """Establishes an SSE connection for receiving filtered A2A messages for a specific stream."""
696
+ log_id_prefix = f"{component.log_identifier}[GET /viz/{stream_id}/events]"
697
+ log.info("%s Client %s requesting SSE connection.", log_id_prefix, user_id)
698
+
699
+ stream_config: Optional[Dict[str, Any]] = None
700
+ log.debug(
701
+ "%s Acquiring viz lock to get stream config for %s", log_id_prefix, stream_id
702
+ )
703
+ async with component._get_visualization_lock():
704
+ stream_config = component._active_visualization_streams.get(stream_id)
705
+ log.debug(
706
+ "%s Released viz lock after getting stream config for %s",
707
+ log_id_prefix,
708
+ stream_id,
709
+ )
710
+
711
+ if not stream_config:
712
+ log.warning("%s Stream ID %s not found.", log_id_prefix, stream_id)
713
+ raise HTTPException(
714
+ status_code=status.HTTP_404_NOT_FOUND,
715
+ detail="Visualization stream not found.",
716
+ )
717
+
718
+ stream_owner_id = stream_config.get("user_id")
719
+ resolved_stream_owner = _resolve_user_identity_for_authorization(
720
+ component, stream_owner_id
721
+ )
722
+ resolved_requester = _resolve_user_identity_for_authorization(component, user_id)
723
+
724
+ if resolved_stream_owner != resolved_requester:
725
+ log.warning(
726
+ "%s User %s (resolved: %s) forbidden to access stream %s owned by %s (resolved: %s).",
727
+ log_id_prefix,
728
+ user_id,
729
+ resolved_requester,
730
+ stream_id,
731
+ stream_owner_id,
732
+ resolved_stream_owner,
733
+ )
734
+ raise HTTPException(
735
+ status_code=status.HTTP_403_FORBIDDEN,
736
+ detail="Access to this visualization stream is forbidden.",
737
+ )
738
+
739
+ sse_queue: Optional[asyncio.Queue] = stream_config.get("sse_queue")
740
+ if not sse_queue:
741
+ log.error(
742
+ "%s SSE queue not found for stream ID %s, though stream config exists.",
743
+ log_id_prefix,
744
+ stream_id,
745
+ )
746
+ raise HTTPException(
747
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
748
+ detail="Internal error: SSE queue missing for stream.",
749
+ )
750
+
751
+ async def event_generator():
752
+ log.debug(
753
+ "%s SSE event generator started for stream %s.", log_id_prefix, stream_id
754
+ )
755
+ try:
756
+ yield {
757
+ "comment": f"SSE connection established for visualization stream {stream_id}"
758
+ }
759
+ while True:
760
+ if await fastapi_request.is_disconnected():
761
+ log.info(
762
+ "%s Client disconnected from stream %s.",
763
+ log_id_prefix,
764
+ stream_id,
765
+ )
766
+ break
767
+ try:
768
+ event_payload = await asyncio.wait_for(sse_queue.get(), timeout=30)
769
+ if event_payload is None:
770
+ log.info(
771
+ "%s SSE queue for stream %s received None sentinel. Closing connection.",
772
+ log_id_prefix,
773
+ stream_id,
774
+ )
775
+ break
776
+ yield event_payload
777
+ sse_queue.task_done()
778
+ except asyncio.TimeoutError:
779
+ yield {"comment": "keep-alive"}
780
+ continue
781
+ except asyncio.CancelledError:
782
+ log.info(
783
+ "%s SSE event generator for stream %s cancelled.",
784
+ log_id_prefix,
785
+ stream_id,
786
+ )
787
+ break
788
+ except Exception as e:
789
+ log.exception(
790
+ "%s Error in SSE event generator for stream %s: %s",
791
+ log_id_prefix,
792
+ stream_id,
793
+ e,
794
+ )
795
+ finally:
796
+ log.info(
797
+ "%s SSE event generator for stream %s finished.",
798
+ log_id_prefix,
799
+ stream_id,
800
+ )
801
+
802
+ return EventSourceResponse(event_generator())
803
+
804
+
805
+ @router.put("/{stream_id}/config", response_model=VisualizationConfigUpdateResponse)
806
+ async def update_visualization_stream_config(
807
+ stream_id: str,
808
+ update_request: VisualizationConfigUpdateRequest,
809
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
810
+ user_id: str = Depends(get_user_id),
811
+ ):
812
+ """Modifies the configuration of an active visualization stream."""
813
+ log_id_prefix = f"{component.log_identifier}[PUT /viz/{stream_id}/config]"
814
+ log.info(
815
+ "%s Request received from user %s to update stream.", log_id_prefix, user_id
816
+ )
817
+
818
+ log.debug(
819
+ "%s Acquiring viz lock to update stream config for %s", log_id_prefix, stream_id
820
+ )
821
+ async with component._get_visualization_lock():
822
+ stream_config = component._active_visualization_streams.get(stream_id)
823
+ if not stream_config:
824
+ raise HTTPException(
825
+ status_code=status.HTTP_404_NOT_FOUND,
826
+ detail="Visualization stream not found.",
827
+ )
828
+
829
+ if stream_config.get("user_id") != user_id:
830
+ raise HTTPException(
831
+ status_code=status.HTTP_403_FORBIDDEN,
832
+ detail="User not authorized to modify this stream.",
833
+ )
834
+
835
+ user_config = stream_config.get("user_config", {})
836
+ config_resolver = MiddlewareRegistry.get_config_resolver()
837
+ gateway_context_for_validation = {
838
+ "gateway_id": component.gateway_id,
839
+ "gateway_namespace": component.namespace,
840
+ }
841
+ current_abstract_targets: List[ActualSubscribedTarget] = [
842
+ ActualSubscribedTarget(**t.model_dump())
843
+ for t in stream_config.get("abstract_targets", [])
844
+ ]
845
+ current_solace_topics: Set[str] = stream_config.get(
846
+ "solace_topics", set()
847
+ ).copy()
848
+
849
+ if update_request.subscription_targets_to_remove:
850
+ targets_actually_removed_abstract = []
851
+ for target_to_remove_req in update_request.subscription_targets_to_remove:
852
+ effective_identifier_remove = target_to_remove_req.identifier
853
+ target_for_translation_remove = target_to_remove_req
854
+
855
+ if target_to_remove_req.type == "current_namespace_a2a_messages":
856
+ effective_identifier_remove = component.namespace
857
+ target_for_translation_remove = SubscriptionTarget(
858
+ type="namespace_a2a_messages",
859
+ identifier=effective_identifier_remove,
860
+ )
861
+
862
+ if (
863
+ not effective_identifier_remove
864
+ and target_to_remove_req.type != "current_namespace_a2a_messages"
865
+ ):
866
+ log.warning(
867
+ "%s Identifier missing for removal target type %s. Skipping.",
868
+ log_id_prefix,
869
+ target_to_remove_req.type,
870
+ )
871
+ continue
872
+
873
+ solace_topics_for_removal = _translate_target_to_solace_topics(
874
+ target_for_translation_remove, component.namespace
875
+ )
876
+ removed_any_solace_topic_for_this_abstract_target = False
877
+ for topic_str in solace_topics_for_removal:
878
+ if topic_str in current_solace_topics:
879
+ if await component._remove_visualization_subscription_nolock(
880
+ topic_str, stream_id
881
+ ):
882
+ log.info(
883
+ "%s Unsubscribed (no-lock) from Solace topic: %s for stream %s (due to removal of %s)",
884
+ log_id_prefix,
885
+ topic_str,
886
+ stream_id,
887
+ effective_identifier_remove,
888
+ )
889
+ current_solace_topics.remove(topic_str)
890
+ removed_any_solace_topic_for_this_abstract_target = True
891
+ else:
892
+ log.error(
893
+ "%s Failed to unsubscribe from Solace topic: %s for stream %s",
894
+ log_id_prefix,
895
+ topic_str,
896
+ stream_id,
897
+ )
898
+
899
+ if removed_any_solace_topic_for_this_abstract_target:
900
+ if target_to_remove_req.type == "current_namespace_a2a_messages":
901
+ current_abstract_targets = [
902
+ t
903
+ for t in current_abstract_targets
904
+ if not (
905
+ t.type == target_to_remove_req.type
906
+ or (
907
+ t.type == "namespace_a2a_messages"
908
+ and t.identifier == effective_identifier_remove
909
+ )
910
+ )
911
+ ]
912
+ else:
913
+ current_abstract_targets = [
914
+ t
915
+ for t in current_abstract_targets
916
+ if not (
917
+ t.type == target_to_remove_req.type
918
+ and t.identifier == effective_identifier_remove
919
+ )
920
+ ]
921
+ targets_actually_removed_abstract.append(
922
+ effective_identifier_remove
923
+ )
924
+
925
+ log.info(
926
+ "%s Processed removals. Abstract targets effectively removed identifiers: %s",
927
+ log_id_prefix,
928
+ targets_actually_removed_abstract,
929
+ )
930
+
931
+ if update_request.subscription_targets_to_add:
932
+ for target_to_add_req in update_request.subscription_targets_to_add:
933
+ effective_identifier_add = target_to_add_req.identifier
934
+ target_for_translation_add = target_to_add_req
935
+ original_type_add = target_to_add_req.type
936
+
937
+ if target_to_add_req.type == "current_namespace_a2a_messages":
938
+ effective_identifier_add = component.namespace
939
+ target_for_translation_add = SubscriptionTarget(
940
+ type="namespace_a2a_messages",
941
+ identifier=effective_identifier_add,
942
+ )
943
+
944
+ is_already_present = False
945
+ for existing_target in current_abstract_targets:
946
+ if existing_target.type == original_type_add and (
947
+ original_type_add == "current_namespace_a2a_messages"
948
+ or existing_target.identifier == effective_identifier_add
949
+ ):
950
+ is_already_present = True
951
+ break
952
+ if (
953
+ original_type_add == "namespace_a2a_messages"
954
+ and existing_target.type == "current_namespace_a2a_messages"
955
+ and effective_identifier_add == component.namespace
956
+ ):
957
+ is_already_present = True
958
+ break
959
+
960
+ if is_already_present:
961
+ log.info(
962
+ "%s Target %s (type: %s) effectively already subscribed. Skipping add.",
963
+ log_id_prefix,
964
+ effective_identifier_add,
965
+ original_type_add,
966
+ )
967
+ continue
968
+
969
+ target_status = "denied_due_to_scope"
970
+ required_scope = ""
971
+
972
+ identifier_for_spec = target_to_add_req.identifier
973
+ if original_type_add == "current_namespace_a2a_messages":
974
+ if target_to_add_req.identifier is None:
975
+ identifier_for_spec = None
976
+
977
+ operation_spec = {
978
+ "operation_type": "visualization_subscription_update",
979
+ "target_type": original_type_add,
980
+ "target_identifier": identifier_for_spec,
981
+ }
982
+
983
+ validation_result = config_resolver.validate_operation_config(
984
+ user_config, operation_spec, gateway_context_for_validation
985
+ )
986
+ has_permission = validation_result.get("valid", False)
987
+
988
+ if has_permission:
989
+ solace_topics_for_target = _translate_target_to_solace_topics(
990
+ target_for_translation_add, component.namespace
991
+ )
992
+ if not solace_topics_for_target:
993
+ target_status = "error_translating_target"
994
+ else:
995
+ all_topics_added_successfully = True
996
+ temp_solace_topics_added_for_this_target = set()
997
+ for topic_str in solace_topics_for_target:
998
+ if await component._add_visualization_subscription(
999
+ topic_str, stream_id
1000
+ ):
1001
+ current_solace_topics.add(topic_str)
1002
+ temp_solace_topics_added_for_this_target.add(topic_str)
1003
+ else:
1004
+ all_topics_added_successfully = False
1005
+ log.error(
1006
+ "%s Failed to add subscription to Solace topic: %s for stream %s (target: %s)",
1007
+ log_id_prefix,
1008
+ topic_str,
1009
+ stream_id,
1010
+ effective_identifier_add,
1011
+ )
1012
+
1013
+ if all_topics_added_successfully:
1014
+ target_status = "subscribed"
1015
+ response_target_data = target_to_add_req.model_dump()
1016
+ if original_type_add == "current_namespace_a2a_messages":
1017
+ response_target_data["identifier"] = (
1018
+ effective_identifier_add
1019
+ )
1020
+ current_abstract_targets.append(
1021
+ ActualSubscribedTarget(
1022
+ **response_target_data, status=target_status
1023
+ )
1024
+ )
1025
+ else:
1026
+ target_status = "error_adding_subscription"
1027
+ for topic_str in temp_solace_topics_added_for_this_target:
1028
+ await component._remove_visualization_subscription_nolock(
1029
+ topic_str, stream_id
1030
+ )
1031
+ current_solace_topics.discard(topic_str)
1032
+ log.warning(
1033
+ "%s Rolled back Solace subscriptions (no-lock) for failed abstract target %s",
1034
+ log_id_prefix,
1035
+ effective_identifier_add,
1036
+ )
1037
+ else:
1038
+ log.warning(
1039
+ "%s User %s denied subscription to target %s (type: %s) due to missing scope: %s",
1040
+ log_id_prefix,
1041
+ user_id,
1042
+ effective_identifier_add,
1043
+ original_type_add,
1044
+ required_scope,
1045
+ )
1046
+
1047
+ if target_status not in ["subscribed", "denied_due_to_scope"]:
1048
+ failed_target_data = target_to_add_req.model_dump()
1049
+ if original_type_add == "current_namespace_a2a_messages":
1050
+ failed_target_data["identifier"] = effective_identifier_add
1051
+
1052
+ component._active_visualization_streams[stream_id][
1053
+ "abstract_targets"
1054
+ ] = current_abstract_targets
1055
+ component._active_visualization_streams[stream_id][
1056
+ "solace_topics"
1057
+ ] = current_solace_topics
1058
+
1059
+ log.info(
1060
+ "%s Stream %s configuration updated. Current abstract targets: %d, Solace topics: %d",
1061
+ log_id_prefix,
1062
+ stream_id,
1063
+ len(current_abstract_targets),
1064
+ len(current_solace_topics),
1065
+ )
1066
+ log.debug(
1067
+ "%s Released viz lock after updating stream config for %s",
1068
+ log_id_prefix,
1069
+ stream_id,
1070
+ )
1071
+
1072
+ return VisualizationConfigUpdateResponse(
1073
+ stream_id=stream_id,
1074
+ current_subscribed_targets=current_abstract_targets,
1075
+ )
1076
+
1077
+
1078
+ @router.delete("/{stream_id}/unsubscribe", status_code=status.HTTP_204_NO_CONTENT)
1079
+ async def unsubscribe_from_visualization_stream(
1080
+ stream_id: str,
1081
+ component: "WebUIBackendComponent" = Depends(get_sac_component),
1082
+ sse_manager: SSEManager = Depends(get_sse_manager),
1083
+ user_id: str = Depends(get_user_id),
1084
+ ):
1085
+ """Terminates an active visualization stream."""
1086
+ log_id_prefix = f"{component.log_identifier}[DELETE /viz/{stream_id}]"
1087
+ log.info(
1088
+ "%s Request received from user %s to unsubscribe from stream.",
1089
+ log_id_prefix,
1090
+ user_id,
1091
+ )
1092
+
1093
+ log.debug(
1094
+ "%s Acquiring viz lock to unsubscribe from stream %s", log_id_prefix, stream_id
1095
+ )
1096
+ async with component._get_visualization_lock():
1097
+ stream_config = component._active_visualization_streams.get(stream_id)
1098
+ if not stream_config:
1099
+ log.info(
1100
+ "%s Stream %s not found, no action needed.", log_id_prefix, stream_id
1101
+ )
1102
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
1103
+
1104
+ if stream_config.get("user_id") != user_id:
1105
+ raise HTTPException(
1106
+ status_code=status.HTTP_403_FORBIDDEN,
1107
+ detail="User not authorized to unsubscribe from this stream.",
1108
+ )
1109
+
1110
+ topics_to_remove = list(stream_config.get("solace_topics", []))
1111
+ for topic_str in topics_to_remove:
1112
+ await component._remove_visualization_subscription_nolock(
1113
+ topic_str, stream_id
1114
+ )
1115
+
1116
+ sse_queue = stream_config.get("sse_queue")
1117
+ if sse_queue:
1118
+ await sse_manager.close_connection(stream_id, sse_queue)
1119
+
1120
+ component._active_visualization_streams.pop(stream_id, None)
1121
+ log.info("%s Stream %s unsubscribed and removed.", log_id_prefix, stream_id)
1122
+ log.debug(
1123
+ "%s Released viz lock after unsubscribing from stream %s",
1124
+ log_id_prefix,
1125
+ stream_id,
1126
+ )
1127
+
1128
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
1129
+
1130
+
1131
+ log.info("Router for A2A Message Visualization initialized.")