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