solace-agent-mesh 0.2.4__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (521) hide show
  1. solace_agent_mesh/__init__.py +5 -0
  2. solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
  3. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  4. solace_agent_mesh/agent/adk/callbacks.py +1716 -0
  5. solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
  6. solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
  7. solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
  8. solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
  9. solace_agent_mesh/agent/adk/runner.py +357 -0
  10. solace_agent_mesh/agent/adk/services.py +240 -0
  11. solace_agent_mesh/agent/adk/setup.py +751 -0
  12. solace_agent_mesh/agent/adk/stream_parser.py +214 -0
  13. solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
  14. solace_agent_mesh/agent/agent_llm.txt +41 -0
  15. solace_agent_mesh/agent/protocol/event_handlers.py +1444 -0
  16. solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
  17. solace_agent_mesh/agent/sac/app.py +640 -0
  18. solace_agent_mesh/agent/sac/component.py +3496 -0
  19. solace_agent_mesh/agent/sac/patch_adk.py +111 -0
  20. solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
  21. solace_agent_mesh/agent/sac/task_execution_context.py +185 -0
  22. solace_agent_mesh/agent/testing/__init__.py +3 -0
  23. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  24. solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
  25. solace_agent_mesh/agent/tools/__init__.py +14 -0
  26. solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
  27. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
  28. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
  29. solace_agent_mesh/agent/tools/general_agent_tools.py +571 -0
  30. solace_agent_mesh/agent/tools/image_tools.py +1184 -0
  31. solace_agent_mesh/agent/tools/peer_agent_tool.py +290 -0
  32. solace_agent_mesh/agent/tools/registry.py +36 -0
  33. solace_agent_mesh/agent/tools/test_tools.py +135 -0
  34. solace_agent_mesh/agent/tools/tool_definition.py +45 -0
  35. solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
  36. solace_agent_mesh/agent/tools/web_tools.py +381 -0
  37. solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
  38. solace_agent_mesh/agent/utils/config_parser.py +47 -0
  39. solace_agent_mesh/agent/utils/context_helpers.py +60 -0
  40. solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
  41. solace_agent_mesh/assets/docs/404.html +16 -0
  42. solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
  43. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  44. solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
  45. solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
  46. solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  54. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  55. solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
  56. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
  57. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
  58. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.a8c5ce5a.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  95. solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
  96. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  101. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  102. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
  104. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
  105. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/f897a61a.f8c53b0f.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
  131. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +2 -0
  132. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js.LICENSE.txt +81 -0
  133. solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +1 -0
  134. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
  135. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
  137. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
  138. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
  139. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
  140. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +77 -0
  141. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
  142. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
  143. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
  146. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
  147. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
  148. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
  149. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
  150. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
  151. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
  152. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
  153. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
  154. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
  155. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
  156. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
  157. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
  158. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
  159. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
  160. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
  161. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
  162. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
  163. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
  164. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
  165. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
  166. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
  167. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  168. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  169. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  170. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  171. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  172. solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +1 -0
  173. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  174. solace_agent_mesh/assets/docs/search-doc-1754075282978.json +1 -0
  175. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  176. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  177. solace_agent_mesh/cli/__init__.py +1 -1
  178. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  179. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  180. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
  181. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  182. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
  183. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
  184. solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
  185. solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
  186. solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
  187. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  188. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  189. solace_agent_mesh/cli/commands/init_cmd/env_step.py +205 -0
  190. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  191. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +407 -0
  192. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  193. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
  194. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
  195. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
  196. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
  197. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  198. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +139 -0
  199. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
  200. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
  201. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  202. solace_agent_mesh/cli/commands/run_cmd.py +158 -0
  203. solace_agent_mesh/cli/main.py +17 -294
  204. solace_agent_mesh/cli/utils.py +135 -204
  205. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
  206. solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
  207. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  208. solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +1 -0
  209. solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +663 -0
  210. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
  211. solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
  212. solace_agent_mesh/common/__init__.py +1 -0
  213. solace_agent_mesh/common/a2a_protocol.py +564 -0
  214. solace_agent_mesh/common/agent_registry.py +42 -0
  215. solace_agent_mesh/common/client/__init__.py +4 -0
  216. solace_agent_mesh/common/client/card_resolver.py +21 -0
  217. solace_agent_mesh/common/client/client.py +85 -0
  218. solace_agent_mesh/common/client/client_llm.txt +133 -0
  219. solace_agent_mesh/common/common_llm.txt +144 -0
  220. solace_agent_mesh/common/constants.py +1 -14
  221. solace_agent_mesh/common/middleware/__init__.py +12 -0
  222. solace_agent_mesh/common/middleware/config_resolver.py +130 -0
  223. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  224. solace_agent_mesh/common/middleware/registry.py +125 -0
  225. solace_agent_mesh/common/server/__init__.py +4 -0
  226. solace_agent_mesh/common/server/server.py +122 -0
  227. solace_agent_mesh/common/server/server_llm.txt +169 -0
  228. solace_agent_mesh/common/server/task_manager.py +291 -0
  229. solace_agent_mesh/common/server/utils.py +28 -0
  230. solace_agent_mesh/common/services/__init__.py +4 -0
  231. solace_agent_mesh/common/services/employee_service.py +162 -0
  232. solace_agent_mesh/common/services/identity_service.py +129 -0
  233. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  234. solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
  235. solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
  236. solace_agent_mesh/common/services/services_llm.txt +132 -0
  237. solace_agent_mesh/common/types.py +411 -0
  238. solace_agent_mesh/common/utils/__init__.py +7 -0
  239. solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
  240. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  241. solace_agent_mesh/common/utils/embeds/constants.py +55 -0
  242. solace_agent_mesh/common/utils/embeds/converter.py +452 -0
  243. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
  244. solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
  245. solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
  246. solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
  247. solace_agent_mesh/common/utils/embeds/types.py +14 -0
  248. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  249. solace_agent_mesh/common/utils/initializer.py +51 -0
  250. solace_agent_mesh/common/utils/log_formatters.py +44 -0
  251. solace_agent_mesh/common/utils/mime_helpers.py +106 -0
  252. solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
  253. solace_agent_mesh/common/utils/utils_llm.txt +67 -0
  254. solace_agent_mesh/config_portal/backend/common.py +66 -24
  255. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
  256. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  257. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +164 -0
  258. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
  259. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
  260. solace_agent_mesh/config_portal/backend/server.py +551 -181
  261. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-_7yox_eh.js +48 -0
  262. solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
  263. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
  264. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
  265. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-e5c3acfe.js +1 -0
  266. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
  267. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
  268. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  269. solace_agent_mesh/core_a2a/__init__.py +1 -0
  270. solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
  271. solace_agent_mesh/core_a2a/service.py +331 -0
  272. solace_agent_mesh/evaluation/config_loader.py +657 -0
  273. solace_agent_mesh/evaluation/evaluator.py +667 -0
  274. solace_agent_mesh/evaluation/message_organizer.py +568 -0
  275. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  276. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  277. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  278. solace_agent_mesh/evaluation/report/modal.html +59 -0
  279. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  280. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  281. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  282. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  283. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  284. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  285. solace_agent_mesh/evaluation/report_data_processor.py +972 -0
  286. solace_agent_mesh/evaluation/report_generator.py +613 -0
  287. solace_agent_mesh/evaluation/run.py +613 -0
  288. solace_agent_mesh/evaluation/subscriber.py +872 -0
  289. solace_agent_mesh/evaluation/summary_builder.py +775 -0
  290. solace_agent_mesh/evaluation/test_case_loader.py +714 -0
  291. solace_agent_mesh/gateway/base/__init__.py +1 -0
  292. solace_agent_mesh/gateway/base/app.py +266 -0
  293. solace_agent_mesh/gateway/base/base_llm.txt +119 -0
  294. solace_agent_mesh/gateway/base/component.py +1542 -0
  295. solace_agent_mesh/gateway/base/task_context.py +74 -0
  296. solace_agent_mesh/gateway/gateway_llm.txt +125 -0
  297. solace_agent_mesh/gateway/http_sse/app.py +190 -0
  298. solace_agent_mesh/gateway/http_sse/component.py +1602 -0
  299. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  300. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
  301. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
  302. solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
  303. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
  304. solace_agent_mesh/gateway/http_sse/main.py +442 -0
  305. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  306. solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
  307. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +827 -0
  308. solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
  309. solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
  310. solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
  311. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
  312. solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
  313. solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
  314. solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
  315. solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
  316. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
  317. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  318. solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
  319. solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
  320. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
  321. solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
  322. solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
  323. solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
  324. solace_agent_mesh/llm.txt +228 -0
  325. solace_agent_mesh/llm_detail.txt +2835 -0
  326. solace_agent_mesh/templates/agent_template.yaml +53 -0
  327. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  328. solace_agent_mesh/templates/gateway_app_template.py +73 -0
  329. solace_agent_mesh/templates/gateway_component_template.py +431 -0
  330. solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
  331. solace_agent_mesh/templates/logging_config_template.ini +64 -0
  332. solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
  333. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  334. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  335. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  336. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
  337. solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
  338. solace_agent_mesh/templates/plugin_readme_template.md +34 -0
  339. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  340. solace_agent_mesh/templates/shared_config.yaml +66 -0
  341. solace_agent_mesh/templates/templates_llm.txt +147 -0
  342. solace_agent_mesh/templates/webui.yaml +53 -0
  343. solace_agent_mesh-1.0.2.dist-info/METADATA +432 -0
  344. solace_agent_mesh-1.0.2.dist-info/RECORD +361 -0
  345. solace_agent_mesh-1.0.2.dist-info/entry_points.txt +3 -0
  346. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/licenses/LICENSE +1 -1
  347. solace_agent_mesh/agents/base_agent_component.py +0 -256
  348. solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
  349. solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
  350. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
  351. solace_agent_mesh/agents/global/actions/create_file.py +0 -70
  352. solace_agent_mesh/agents/global/actions/error_action.py +0 -45
  353. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
  354. solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
  355. solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
  356. solace_agent_mesh/agents/global/global_agent_component.py +0 -38
  357. solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
  358. solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
  359. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
  360. solace_agent_mesh/agents/slack/__init__.py +0 -1
  361. solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
  362. solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
  363. solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
  364. solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
  365. solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
  366. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
  367. solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
  368. solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
  369. solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
  370. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
  371. solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
  372. solace_agent_mesh/assets/web-visualizer/index.html +0 -14
  373. solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
  374. solace_agent_mesh/cli/commands/add/__init__.py +0 -3
  375. solace_agent_mesh/cli/commands/add/add.py +0 -88
  376. solace_agent_mesh/cli/commands/add/agent.py +0 -110
  377. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
  378. solace_agent_mesh/cli/commands/add/gateway.py +0 -374
  379. solace_agent_mesh/cli/commands/build.py +0 -670
  380. solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
  381. solace_agent_mesh/cli/commands/chat/chat.py +0 -361
  382. solace_agent_mesh/cli/commands/config.py +0 -29
  383. solace_agent_mesh/cli/commands/init/__init__.py +0 -3
  384. solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
  385. solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
  386. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
  387. solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
  388. solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
  389. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
  390. solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
  391. solace_agent_mesh/cli/commands/init/init.py +0 -92
  392. solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
  393. solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
  394. solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
  395. solace_agent_mesh/cli/commands/plugin/add.py +0 -100
  396. solace_agent_mesh/cli/commands/plugin/build.py +0 -268
  397. solace_agent_mesh/cli/commands/plugin/create.py +0 -117
  398. solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
  399. solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
  400. solace_agent_mesh/cli/commands/run.py +0 -68
  401. solace_agent_mesh/cli/commands/visualizer.py +0 -138
  402. solace_agent_mesh/cli/config.py +0 -85
  403. solace_agent_mesh/common/action.py +0 -91
  404. solace_agent_mesh/common/action_list.py +0 -37
  405. solace_agent_mesh/common/action_response.py +0 -340
  406. solace_agent_mesh/common/mysql_database.py +0 -40
  407. solace_agent_mesh/common/postgres_database.py +0 -85
  408. solace_agent_mesh/common/prompt_templates.py +0 -28
  409. solace_agent_mesh/common/stimulus_utils.py +0 -152
  410. solace_agent_mesh/common/time.py +0 -24
  411. solace_agent_mesh/common/utils.py +0 -712
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-a-zJ6rLx.js +0 -46
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
  414. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
  415. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-44c41103.js +0 -1
  416. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
  417. solace_agent_mesh/configs/agent_global.yaml +0 -74
  418. solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
  419. solace_agent_mesh/configs/agent_slack.yaml +0 -64
  420. solace_agent_mesh/configs/agent_web_request.yaml +0 -75
  421. solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
  422. solace_agent_mesh/configs/error_catcher.yaml +0 -56
  423. solace_agent_mesh/configs/monitor.yaml +0 -0
  424. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
  425. solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
  426. solace_agent_mesh/configs/orchestrator.yaml +0 -241
  427. solace_agent_mesh/configs/service_embedding.yaml +0 -81
  428. solace_agent_mesh/configs/service_llm.yaml +0 -265
  429. solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
  430. solace_agent_mesh/gateway/components/gateway_base.py +0 -47
  431. solace_agent_mesh/gateway/components/gateway_input.py +0 -278
  432. solace_agent_mesh/gateway/components/gateway_output.py +0 -298
  433. solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
  434. solace_agent_mesh/gateway/identity/identity_base.py +0 -10
  435. solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
  436. solace_agent_mesh/gateway/identity/no_identity.py +0 -9
  437. solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
  438. solace_agent_mesh/monitors/base_monitor_component.py +0 -26
  439. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
  440. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
  441. solace_agent_mesh/orchestrator/__init__.py +0 -0
  442. solace_agent_mesh/orchestrator/action_manager.py +0 -237
  443. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  444. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
  445. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
  446. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
  447. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
  448. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
  449. solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
  450. solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
  451. solace_agent_mesh/services/__init__.py +0 -0
  452. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
  453. solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
  454. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
  455. solace_agent_mesh/services/common/__init__.py +0 -4
  456. solace_agent_mesh/services/common/auto_expiry.py +0 -45
  457. solace_agent_mesh/services/common/singleton.py +0 -18
  458. solace_agent_mesh/services/file_service/__init__.py +0 -14
  459. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  460. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
  461. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
  462. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
  463. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
  464. solace_agent_mesh/services/file_service/file_service.py +0 -437
  465. solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
  466. solace_agent_mesh/services/file_service/file_transformations.py +0 -141
  467. solace_agent_mesh/services/file_service/file_utils.py +0 -324
  468. solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
  469. solace_agent_mesh/services/history_service/__init__.py +0 -3
  470. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  471. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
  472. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
  473. solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
  474. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
  475. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
  476. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
  477. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
  478. solace_agent_mesh/services/history_service/history_service.py +0 -413
  479. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  480. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
  481. solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
  482. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
  483. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  484. solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
  485. solace_agent_mesh/templates/action.py +0 -38
  486. solace_agent_mesh/templates/agent.py +0 -29
  487. solace_agent_mesh/templates/agent.yaml +0 -70
  488. solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
  489. solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
  490. solace_agent_mesh/templates/gateway-flows.yaml +0 -78
  491. solace_agent_mesh/templates/gateway-header.yaml +0 -16
  492. solace_agent_mesh/templates/gateway_base.py +0 -15
  493. solace_agent_mesh/templates/gateway_input.py +0 -98
  494. solace_agent_mesh/templates/gateway_output.py +0 -71
  495. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
  496. solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
  497. solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
  498. solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
  499. solace_agent_mesh/templates/slack-default-config.yaml +0 -16
  500. solace_agent_mesh/templates/slack-flows.yaml +0 -81
  501. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
  502. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
  503. solace_agent_mesh/templates/web-default-config.yaml +0 -10
  504. solace_agent_mesh/templates/web-flows.yaml +0 -76
  505. solace_agent_mesh/tools/__init__.py +0 -0
  506. solace_agent_mesh/tools/components/__init__.py +0 -0
  507. solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
  508. solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
  509. solace_agent_mesh/tools/config/runtime_config.py +0 -26
  510. solace_agent_mesh-0.2.4.dist-info/METADATA +0 -176
  511. solace_agent_mesh-0.2.4.dist-info/RECORD +0 -193
  512. solace_agent_mesh-0.2.4.dist-info/entry_points.txt +0 -3
  513. /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
  514. /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
  515. /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
  516. /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
  517. /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
  518. /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
  519. /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
  520. /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
  521. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.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.")