solace-agent-mesh 1.11.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. solace_agent_mesh/__init__.py +0 -0
  2. solace_agent_mesh/agent/__init__.py +0 -0
  3. solace_agent_mesh/agent/adk/__init__.py +0 -0
  4. solace_agent_mesh/agent/adk/adk_llm.txt +226 -0
  5. solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
  6. solace_agent_mesh/agent/adk/alembic/README +74 -0
  7. solace_agent_mesh/agent/adk/alembic/env.py +77 -0
  8. solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
  9. solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
  10. solace_agent_mesh/agent/adk/alembic.ini +112 -0
  11. solace_agent_mesh/agent/adk/app_llm_agent.py +52 -0
  12. solace_agent_mesh/agent/adk/artifacts/__init__.py +1 -0
  13. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
  14. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +545 -0
  15. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +609 -0
  16. solace_agent_mesh/agent/adk/callbacks.py +2318 -0
  17. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +406 -0
  18. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +415 -0
  19. solace_agent_mesh/agent/adk/mcp_content_processor.py +666 -0
  20. solace_agent_mesh/agent/adk/models/lite_llm.py +1026 -0
  21. solace_agent_mesh/agent/adk/models/models_llm.txt +189 -0
  22. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +132 -0
  23. solace_agent_mesh/agent/adk/runner.py +390 -0
  24. solace_agent_mesh/agent/adk/schema_migration.py +88 -0
  25. solace_agent_mesh/agent/adk/services.py +468 -0
  26. solace_agent_mesh/agent/adk/setup.py +1325 -0
  27. solace_agent_mesh/agent/adk/stream_parser.py +415 -0
  28. solace_agent_mesh/agent/adk/tool_wrapper.py +165 -0
  29. solace_agent_mesh/agent/agent_llm.txt +369 -0
  30. solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
  31. solace_agent_mesh/agent/protocol/__init__.py +0 -0
  32. solace_agent_mesh/agent/protocol/event_handlers.py +2041 -0
  33. solace_agent_mesh/agent/protocol/protocol_llm.txt +81 -0
  34. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
  35. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  36. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  37. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
  38. solace_agent_mesh/agent/proxies/a2a/app.py +56 -0
  39. solace_agent_mesh/agent/proxies/a2a/component.py +1585 -0
  40. solace_agent_mesh/agent/proxies/a2a/config.py +216 -0
  41. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  42. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  43. solace_agent_mesh/agent/proxies/base/app.py +100 -0
  44. solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
  45. solace_agent_mesh/agent/proxies/base/component.py +816 -0
  46. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  47. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +19 -0
  48. solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
  49. solace_agent_mesh/agent/sac/__init__.py +0 -0
  50. solace_agent_mesh/agent/sac/app.py +595 -0
  51. solace_agent_mesh/agent/sac/component.py +3668 -0
  52. solace_agent_mesh/agent/sac/patch_adk.py +103 -0
  53. solace_agent_mesh/agent/sac/sac_llm.txt +189 -0
  54. solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
  55. solace_agent_mesh/agent/sac/task_execution_context.py +415 -0
  56. solace_agent_mesh/agent/testing/__init__.py +3 -0
  57. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  58. solace_agent_mesh/agent/testing/testing_llm.txt +58 -0
  59. solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
  60. solace_agent_mesh/agent/tools/__init__.py +16 -0
  61. solace_agent_mesh/agent/tools/audio_tools.py +1740 -0
  62. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +2500 -0
  63. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +244 -0
  64. solace_agent_mesh/agent/tools/dynamic_tool.py +396 -0
  65. solace_agent_mesh/agent/tools/general_agent_tools.py +572 -0
  66. solace_agent_mesh/agent/tools/image_tools.py +1185 -0
  67. solace_agent_mesh/agent/tools/peer_agent_tool.py +363 -0
  68. solace_agent_mesh/agent/tools/registry.py +38 -0
  69. solace_agent_mesh/agent/tools/test_tools.py +136 -0
  70. solace_agent_mesh/agent/tools/time_tools.py +126 -0
  71. solace_agent_mesh/agent/tools/tool_config_types.py +93 -0
  72. solace_agent_mesh/agent/tools/tool_definition.py +53 -0
  73. solace_agent_mesh/agent/tools/tools_llm.txt +276 -0
  74. solace_agent_mesh/agent/tools/tools_llm_detail.txt +275 -0
  75. solace_agent_mesh/agent/tools/web_tools.py +392 -0
  76. solace_agent_mesh/agent/utils/__init__.py +0 -0
  77. solace_agent_mesh/agent/utils/artifact_helpers.py +1353 -0
  78. solace_agent_mesh/agent/utils/config_parser.py +49 -0
  79. solace_agent_mesh/agent/utils/context_helpers.py +77 -0
  80. solace_agent_mesh/agent/utils/utils_llm.txt +152 -0
  81. solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
  82. solace_agent_mesh/assets/docs/404.html +16 -0
  83. solace_agent_mesh/assets/docs/assets/css/styles.8162edfb.css +1 -0
  84. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  85. solace_agent_mesh/assets/docs/assets/images/sam-enterprise-credentials-b269f095349473118b2b33bdfcc40122.png +0 -0
  86. solace_agent_mesh/assets/docs/assets/js/032c2d61.f3d37824.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/0bcf40b7.c019ad46.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  95. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  96. solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/2131ec11.5c7a1f6e.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
  101. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js.LICENSE.txt +13 -0
  102. solace_agent_mesh/assets/docs/assets/js/2334.1cf50a20.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/240a0364.9ad94d1b.js +1 -0
  104. solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
  105. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/341393d4.0fac2613.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/3624.0eaa1fd0.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/3ac1795d.28b7c67b.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/3ff0015d.2ddc75c0.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/41adc471.48b12a4e.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/509e993c.a1fbf45a.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.90a87880.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  131. solace_agent_mesh/assets/docs/assets/js/6063ff4c.ef84f702.js +1 -0
  132. solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
  133. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  134. solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
  135. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  136. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
  137. solace_agent_mesh/assets/docs/assets/js/66d4869e.b77431fc.js +1 -0
  138. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  139. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  140. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  141. solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
  142. solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
  143. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
  144. solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
  145. solace_agent_mesh/assets/docs/assets/js/6fdfefc7.99de744e.js +1 -0
  146. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  147. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  148. solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
  149. solace_agent_mesh/assets/docs/assets/js/722f809d.965da774.js +1 -0
  150. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  151. solace_agent_mesh/assets/docs/assets/js/742f027b.46c07808.js +1 -0
  152. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  153. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  154. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  155. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
  156. solace_agent_mesh/assets/docs/assets/js/81a99df0.2484b8d9.js +1 -0
  157. solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
  158. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  159. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  160. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  161. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  162. solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js +2 -0
  163. solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js.LICENSE.txt +61 -0
  164. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  165. solace_agent_mesh/assets/docs/assets/js/8731.6c1dbf0c.js +1 -0
  166. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  167. solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
  168. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  169. solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
  170. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  171. solace_agent_mesh/assets/docs/assets/js/945fb41e.6f4cdffd.js +1 -0
  172. solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
  173. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  174. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  175. solace_agent_mesh/assets/docs/assets/js/9bb13469.b2333011.js +1 -0
  176. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  177. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  178. solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
  179. solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +1 -0
  180. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  181. solace_agent_mesh/assets/docs/assets/js/ad71b5ed.af3ecfd1.js +1 -0
  182. solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
  183. solace_agent_mesh/assets/docs/assets/js/c198a0dc.8f31f867.js +1 -0
  184. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
  185. solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
  186. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
  187. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  188. solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
  189. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
  190. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  191. solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
  192. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
  193. solace_agent_mesh/assets/docs/assets/js/de5f4c65.e8241890.js +1 -0
  194. solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
  195. solace_agent_mesh/assets/docs/assets/js/e04b235d.52cb25ed.js +1 -0
  196. solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.b1068f9b.js +1 -0
  197. solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
  198. solace_agent_mesh/assets/docs/assets/js/e6f9706b.4488e34c.js +1 -0
  199. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
  200. solace_agent_mesh/assets/docs/assets/js/f284c35a.250993bf.js +1 -0
  201. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
  202. solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js +2 -0
  203. solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js.LICENSE.txt +81 -0
  204. solace_agent_mesh/assets/docs/assets/js/runtime~main.9e0813a2.js +1 -0
  205. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +154 -0
  206. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +99 -0
  207. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +90 -0
  208. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +107 -0
  209. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +166 -0
  210. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +101 -0
  211. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +219 -0
  212. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +92 -0
  213. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +29 -0
  214. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +55 -0
  215. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +110 -0
  216. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
  217. solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
  218. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +345 -0
  219. solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
  220. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +83 -0
  221. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +84 -0
  222. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +25 -0
  223. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
  224. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +85 -0
  225. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +60 -0
  226. solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
  227. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +144 -0
  228. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +191 -0
  229. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +128 -0
  230. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +54 -0
  231. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  232. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +34 -0
  233. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +55 -0
  234. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +267 -0
  235. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +142 -0
  236. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +116 -0
  237. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +86 -0
  238. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +164 -0
  239. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +140 -0
  240. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +57 -0
  241. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +72 -0
  242. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +102 -0
  243. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
  244. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
  245. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
  246. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +37 -0
  247. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +86 -0
  248. solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
  249. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +247 -0
  250. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
  251. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +184 -0
  252. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
  253. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +75 -0
  254. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +54 -0
  255. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +85 -0
  256. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +41 -0
  257. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
  258. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +78 -0
  259. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +25 -0
  260. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +78 -0
  261. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +160 -0
  262. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +142 -0
  263. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
  264. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
  265. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +100 -0
  266. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +52 -0
  267. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  268. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  269. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  270. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  271. solace_agent_mesh/assets/docs/img/sam-enterprise-credentials.png +0 -0
  272. solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
  273. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  274. solace_agent_mesh/assets/docs/lunr-index-1765810064709.json +1 -0
  275. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  276. solace_agent_mesh/assets/docs/search-doc-1765810064709.json +1 -0
  277. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  278. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  279. solace_agent_mesh/cli/__init__.py +1 -0
  280. solace_agent_mesh/cli/commands/__init__.py +0 -0
  281. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  282. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  283. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +729 -0
  284. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  285. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +102 -0
  286. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +114 -0
  287. solace_agent_mesh/cli/commands/docs_cmd.py +60 -0
  288. solace_agent_mesh/cli/commands/eval_cmd.py +46 -0
  289. solace_agent_mesh/cli/commands/init_cmd/__init__.py +439 -0
  290. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  291. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  292. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  293. solace_agent_mesh/cli/commands/init_cmd/env_step.py +238 -0
  294. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  295. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +464 -0
  296. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  297. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +119 -0
  298. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +215 -0
  299. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +20 -0
  300. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +137 -0
  301. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  302. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +144 -0
  303. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +306 -0
  304. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +283 -0
  305. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
  306. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  307. solace_agent_mesh/cli/commands/run_cmd.py +215 -0
  308. solace_agent_mesh/cli/main.py +52 -0
  309. solace_agent_mesh/cli/utils.py +262 -0
  310. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-Dj3JtK42.js +1 -0
  311. solace_agent_mesh/client/webui/frontend/static/assets/client-ZKk9kEJ5.js +25 -0
  312. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  313. solace_agent_mesh/client/webui/frontend/static/assets/main-BcUaNZ-Q.css +1 -0
  314. solace_agent_mesh/client/webui/frontend/static/assets/main-vjch4RYc.js +435 -0
  315. solace_agent_mesh/client/webui/frontend/static/assets/vendor-BNV4kZN0.js +535 -0
  316. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +15 -0
  317. solace_agent_mesh/client/webui/frontend/static/index.html +16 -0
  318. solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
  319. solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
  320. solace_agent_mesh/common/__init__.py +1 -0
  321. solace_agent_mesh/common/a2a/__init__.py +241 -0
  322. solace_agent_mesh/common/a2a/a2a_llm.txt +175 -0
  323. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
  324. solace_agent_mesh/common/a2a/artifact.py +368 -0
  325. solace_agent_mesh/common/a2a/events.py +213 -0
  326. solace_agent_mesh/common/a2a/message.py +375 -0
  327. solace_agent_mesh/common/a2a/protocol.py +689 -0
  328. solace_agent_mesh/common/a2a/task.py +127 -0
  329. solace_agent_mesh/common/a2a/translation.py +655 -0
  330. solace_agent_mesh/common/a2a/types.py +55 -0
  331. solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
  332. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +445 -0
  333. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
  334. solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
  335. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +48 -0
  336. solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
  337. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +41 -0
  338. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +330 -0
  339. solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
  340. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +48 -0
  341. solace_agent_mesh/common/agent_registry.py +122 -0
  342. solace_agent_mesh/common/common_llm.txt +230 -0
  343. solace_agent_mesh/common/common_llm_detail.txt +2562 -0
  344. solace_agent_mesh/common/constants.py +6 -0
  345. solace_agent_mesh/common/data_parts.py +150 -0
  346. solace_agent_mesh/common/exceptions.py +49 -0
  347. solace_agent_mesh/common/middleware/__init__.py +12 -0
  348. solace_agent_mesh/common/middleware/config_resolver.py +132 -0
  349. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  350. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
  351. solace_agent_mesh/common/middleware/registry.py +127 -0
  352. solace_agent_mesh/common/oauth/__init__.py +17 -0
  353. solace_agent_mesh/common/oauth/oauth_client.py +408 -0
  354. solace_agent_mesh/common/oauth/utils.py +50 -0
  355. solace_agent_mesh/common/sac/__init__.py +0 -0
  356. solace_agent_mesh/common/sac/sac_llm.txt +71 -0
  357. solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
  358. solace_agent_mesh/common/sac/sam_component_base.py +730 -0
  359. solace_agent_mesh/common/sam_events/__init__.py +9 -0
  360. solace_agent_mesh/common/sam_events/event_service.py +208 -0
  361. solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
  362. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
  363. solace_agent_mesh/common/services/__init__.py +4 -0
  364. solace_agent_mesh/common/services/employee_service.py +164 -0
  365. solace_agent_mesh/common/services/identity_service.py +134 -0
  366. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  367. solace_agent_mesh/common/services/providers/local_file_identity_service.py +151 -0
  368. solace_agent_mesh/common/services/providers/providers_llm.txt +81 -0
  369. solace_agent_mesh/common/services/services_llm.txt +368 -0
  370. solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
  371. solace_agent_mesh/common/utils/__init__.py +7 -0
  372. solace_agent_mesh/common/utils/artifact_utils.py +31 -0
  373. solace_agent_mesh/common/utils/asyncio_macos_fix.py +88 -0
  374. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  375. solace_agent_mesh/common/utils/embeds/constants.py +56 -0
  376. solace_agent_mesh/common/utils/embeds/converter.py +447 -0
  377. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +220 -0
  378. solace_agent_mesh/common/utils/embeds/evaluators.py +395 -0
  379. solace_agent_mesh/common/utils/embeds/modifiers.py +793 -0
  380. solace_agent_mesh/common/utils/embeds/resolver.py +967 -0
  381. solace_agent_mesh/common/utils/embeds/types.py +23 -0
  382. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  383. solace_agent_mesh/common/utils/initializer.py +52 -0
  384. solace_agent_mesh/common/utils/log_formatters.py +64 -0
  385. solace_agent_mesh/common/utils/message_utils.py +80 -0
  386. solace_agent_mesh/common/utils/mime_helpers.py +172 -0
  387. solace_agent_mesh/common/utils/push_notification_auth.py +135 -0
  388. solace_agent_mesh/common/utils/pydantic_utils.py +159 -0
  389. solace_agent_mesh/common/utils/rbac_utils.py +69 -0
  390. solace_agent_mesh/common/utils/templates/__init__.py +8 -0
  391. solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
  392. solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
  393. solace_agent_mesh/common/utils/type_utils.py +28 -0
  394. solace_agent_mesh/common/utils/utils_llm.txt +335 -0
  395. solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
  396. solace_agent_mesh/config_portal/__init__.py +0 -0
  397. solace_agent_mesh/config_portal/backend/__init__.py +0 -0
  398. solace_agent_mesh/config_portal/backend/common.py +77 -0
  399. solace_agent_mesh/config_portal/backend/plugin_catalog/__init__.py +0 -0
  400. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
  401. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  402. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +166 -0
  403. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
  404. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
  405. solace_agent_mesh/config_portal/backend/server.py +644 -0
  406. solace_agent_mesh/config_portal/frontend/static/client/Solace_community_logo.png +0 -0
  407. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DiOiAjzL.js +103 -0
  408. solace_agent_mesh/config_portal/frontend/static/client/assets/components-Rk0n-9cK.js +140 -0
  409. solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-mvZjNKiz.js +19 -0
  410. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DzNKzXrc.js +68 -0
  411. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-ba77705e.js +1 -0
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/root-B17tZKK7.css +1 -0
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/root-V2BeTIUc.js +10 -0
  414. solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
  415. solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
  416. solace_agent_mesh/core_a2a/__init__.py +1 -0
  417. solace_agent_mesh/core_a2a/core_a2a_llm.txt +90 -0
  418. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
  419. solace_agent_mesh/core_a2a/service.py +307 -0
  420. solace_agent_mesh/evaluation/__init__.py +0 -0
  421. solace_agent_mesh/evaluation/evaluator.py +691 -0
  422. solace_agent_mesh/evaluation/message_organizer.py +553 -0
  423. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  424. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  425. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  426. solace_agent_mesh/evaluation/report/modal.html +59 -0
  427. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  428. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  429. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  430. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  431. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  432. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  433. solace_agent_mesh/evaluation/report_data_processor.py +970 -0
  434. solace_agent_mesh/evaluation/report_generator.py +607 -0
  435. solace_agent_mesh/evaluation/run.py +954 -0
  436. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  437. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  438. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  439. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  440. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  441. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  442. solace_agent_mesh/evaluation/subscriber.py +776 -0
  443. solace_agent_mesh/evaluation/summary_builder.py +880 -0
  444. solace_agent_mesh/gateway/__init__.py +0 -0
  445. solace_agent_mesh/gateway/adapter/__init__.py +1 -0
  446. solace_agent_mesh/gateway/adapter/base.py +143 -0
  447. solace_agent_mesh/gateway/adapter/types.py +221 -0
  448. solace_agent_mesh/gateway/base/__init__.py +1 -0
  449. solace_agent_mesh/gateway/base/app.py +345 -0
  450. solace_agent_mesh/gateway/base/base_llm.txt +226 -0
  451. solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
  452. solace_agent_mesh/gateway/base/component.py +2030 -0
  453. solace_agent_mesh/gateway/base/task_context.py +75 -0
  454. solace_agent_mesh/gateway/gateway_llm.txt +369 -0
  455. solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
  456. solace_agent_mesh/gateway/generic/__init__.py +1 -0
  457. solace_agent_mesh/gateway/generic/app.py +50 -0
  458. solace_agent_mesh/gateway/generic/component.py +727 -0
  459. solace_agent_mesh/gateway/http_sse/__init__.py +0 -0
  460. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +345 -0
  461. solace_agent_mesh/gateway/http_sse/alembic/env.py +87 -0
  462. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  463. solace_agent_mesh/gateway/http_sse/alembic/versions/20250910_d5b3f8f2e9a0_create_initial_database.py +58 -0
  464. solace_agent_mesh/gateway/http_sse/alembic/versions/20250911_b1c2d3e4f5g6_add_database_indexes.py +83 -0
  465. solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +412 -0
  466. solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
  467. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  468. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
  469. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
  470. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
  471. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
  472. solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
  473. solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
  474. solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
  475. solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
  476. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +161 -0
  477. solace_agent_mesh/gateway/http_sse/alembic.ini +109 -0
  478. solace_agent_mesh/gateway/http_sse/app.py +351 -0
  479. solace_agent_mesh/gateway/http_sse/component.py +2360 -0
  480. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  481. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +105 -0
  482. solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +109 -0
  483. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +110 -0
  484. solace_agent_mesh/gateway/http_sse/dependencies.py +653 -0
  485. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +299 -0
  486. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
  487. solace_agent_mesh/gateway/http_sse/main.py +789 -0
  488. solace_agent_mesh/gateway/http_sse/repository/__init__.py +46 -0
  489. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +102 -0
  490. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +11 -0
  491. solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
  492. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +221 -0
  493. solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
  494. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
  495. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
  496. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +66 -0
  497. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -0
  498. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +32 -0
  499. solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
  500. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +125 -0
  501. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +239 -0
  502. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +34 -0
  503. solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
  504. solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
  505. solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
  506. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +257 -0
  507. solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
  508. solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
  509. solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
  510. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +53 -0
  511. solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
  512. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +39 -0
  513. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
  514. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
  515. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +308 -0
  516. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +268 -0
  517. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +248 -0
  518. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  519. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +74 -0
  520. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1137 -0
  521. solace_agent_mesh/gateway/http_sse/routers/auth.py +311 -0
  522. solace_agent_mesh/gateway/http_sse/routers/config.py +371 -0
  523. solace_agent_mesh/gateway/http_sse/routers/dto/__init__.py +10 -0
  524. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +450 -0
  525. solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
  526. solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
  527. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +15 -0
  528. solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
  529. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +133 -0
  530. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +33 -0
  531. solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
  532. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +18 -0
  533. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
  534. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
  535. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +123 -0
  536. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +33 -0
  537. solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
  538. solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
  539. solace_agent_mesh/gateway/http_sse/routers/feedback.py +168 -0
  540. solace_agent_mesh/gateway/http_sse/routers/people.py +38 -0
  541. solace_agent_mesh/gateway/http_sse/routers/projects.py +767 -0
  542. solace_agent_mesh/gateway/http_sse/routers/prompts.py +1415 -0
  543. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +312 -0
  544. solace_agent_mesh/gateway/http_sse/routers/sessions.py +634 -0
  545. solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
  546. solace_agent_mesh/gateway/http_sse/routers/sse.py +230 -0
  547. solace_agent_mesh/gateway/http_sse/routers/tasks.py +1089 -0
  548. solace_agent_mesh/gateway/http_sse/routers/users.py +83 -0
  549. solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
  550. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1220 -0
  551. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  552. solace_agent_mesh/gateway/http_sse/services/agent_card_service.py +71 -0
  553. solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
  554. solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
  555. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +273 -0
  556. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +250 -0
  557. solace_agent_mesh/gateway/http_sse/services/people_service.py +78 -0
  558. solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
  559. solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
  560. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +303 -0
  561. solace_agent_mesh/gateway/http_sse/services/session_service.py +702 -0
  562. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +593 -0
  563. solace_agent_mesh/gateway/http_sse/services/task_service.py +119 -0
  564. solace_agent_mesh/gateway/http_sse/session_manager.py +219 -0
  565. solace_agent_mesh/gateway/http_sse/shared/__init__.py +146 -0
  566. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  567. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +252 -0
  568. solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
  569. solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
  570. solace_agent_mesh/gateway/http_sse/shared/enums.py +40 -0
  571. solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
  572. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +217 -0
  573. solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
  574. solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
  575. solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
  576. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +319 -0
  577. solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
  578. solace_agent_mesh/gateway/http_sse/shared/types.py +50 -0
  579. solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
  580. solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +88 -0
  581. solace_agent_mesh/gateway/http_sse/sse_manager.py +491 -0
  582. solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
  583. solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
  584. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +72 -0
  585. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
  586. solace_agent_mesh/llm.txt +228 -0
  587. solace_agent_mesh/llm_detail.txt +2835 -0
  588. solace_agent_mesh/services/__init__.py +0 -0
  589. solace_agent_mesh/services/platform/__init__.py +18 -0
  590. solace_agent_mesh/services/platform/alembic/env.py +85 -0
  591. solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
  592. solace_agent_mesh/services/platform/alembic.ini +109 -0
  593. solace_agent_mesh/services/platform/api/__init__.py +3 -0
  594. solace_agent_mesh/services/platform/api/dependencies.py +147 -0
  595. solace_agent_mesh/services/platform/api/main.py +280 -0
  596. solace_agent_mesh/services/platform/api/middleware.py +51 -0
  597. solace_agent_mesh/services/platform/api/routers/__init__.py +24 -0
  598. solace_agent_mesh/services/platform/app.py +114 -0
  599. solace_agent_mesh/services/platform/component.py +235 -0
  600. solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
  601. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
  602. solace_agent_mesh/templates/agent_template.yaml +53 -0
  603. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  604. solace_agent_mesh/templates/gateway_app_template.py +75 -0
  605. solace_agent_mesh/templates/gateway_component_template.py +484 -0
  606. solace_agent_mesh/templates/gateway_config_template.yaml +38 -0
  607. solace_agent_mesh/templates/logging_config_template.yaml +48 -0
  608. solace_agent_mesh/templates/main_orchestrator.yaml +66 -0
  609. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  610. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  611. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  612. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +60 -0
  613. solace_agent_mesh/templates/plugin_pyproject_template.toml +32 -0
  614. solace_agent_mesh/templates/plugin_readme_template.md +12 -0
  615. solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
  616. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  617. solace_agent_mesh/templates/shared_config.yaml +112 -0
  618. solace_agent_mesh/templates/templates_llm.txt +147 -0
  619. solace_agent_mesh/templates/webui.yaml +177 -0
  620. solace_agent_mesh-1.11.2.dist-info/METADATA +504 -0
  621. solace_agent_mesh-1.11.2.dist-info/RECORD +624 -0
  622. solace_agent_mesh-1.11.2.dist-info/WHEEL +4 -0
  623. solace_agent_mesh-1.11.2.dist-info/entry_points.txt +3 -0
  624. solace_agent_mesh-1.11.2.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,2360 @@
1
+ """
2
+ Custom Solace AI Connector Component to host the FastAPI backend for the Web UI.
3
+ """
4
+
5
+ import asyncio
6
+ import json
7
+ import logging
8
+ import queue
9
+ import re
10
+ import threading
11
+ import uuid
12
+ from datetime import datetime, timezone
13
+ from typing import Any
14
+
15
+ import uvicorn
16
+ from fastapi import FastAPI, UploadFile
17
+ from fastapi import Request as FastAPIRequest
18
+ from solace_ai_connector.common.event import Event, EventType
19
+ from solace_ai_connector.components.inputs_outputs.broker_input import BrokerInput
20
+ from solace_ai_connector.flow.app import App as SACApp
21
+
22
+ from ...common.agent_registry import AgentRegistry
23
+ from ...core_a2a.service import CoreA2AService
24
+ from ...gateway.base.component import BaseGatewayComponent
25
+ from ...gateway.http_sse.session_manager import SessionManager
26
+ from ...gateway.http_sse.sse_manager import SSEManager
27
+ from . import dependencies
28
+ from .components import VisualizationForwarderComponent
29
+ from .components.task_logger_forwarder import TaskLoggerForwarderComponent
30
+ from .services.task_logger_service import TaskLoggerService
31
+ from .sse_event_buffer import SSEEventBuffer
32
+
33
+ log = logging.getLogger(__name__)
34
+
35
+ try:
36
+ from google.adk.artifacts import BaseArtifactService
37
+ except ImportError:
38
+
39
+ class BaseArtifactService:
40
+ pass
41
+
42
+
43
+ from a2a.types import (
44
+ A2ARequest,
45
+ AgentCard,
46
+ JSONRPCError,
47
+ JSONRPCResponse,
48
+ Task,
49
+ TaskArtifactUpdateEvent,
50
+ TaskStatusUpdateEvent,
51
+ )
52
+
53
+ from ...common import a2a
54
+ from ...common.a2a.types import ContentPart
55
+ from ...common.middleware.config_resolver import ConfigResolver
56
+ from ...common.utils.embeds import (
57
+ EARLY_EMBED_TYPES,
58
+ evaluate_embed,
59
+ resolve_embeds_in_string,
60
+ )
61
+
62
+ info = {
63
+ "class_name": "WebUIBackendComponent",
64
+ "description": (
65
+ "Hosts the FastAPI backend server for the A2A Web UI, manages messaging via SAC, "
66
+ "and implements GDK abstract methods for Web UI interaction. "
67
+ "Configuration is derived from WebUIBackendApp's app_config."
68
+ ),
69
+ "config_parameters": [
70
+ # Configuration parameters are defined and validated by WebUIBackendApp.app_schema.
71
+ ],
72
+ "input_schema": {
73
+ "type": "object",
74
+ "description": "Not typically used; component reacts to events.",
75
+ "properties": {},
76
+ },
77
+ "output_schema": {
78
+ "type": "object",
79
+ "description": "Not typically used; component publishes results via FastAPI/SSE.",
80
+ "properties": {},
81
+ },
82
+ }
83
+
84
+
85
+ class WebUIBackendComponent(BaseGatewayComponent):
86
+ """
87
+ Hosts the FastAPI backend, manages messaging via SAC, and bridges threads.
88
+ """
89
+
90
+ def __init__(self, **kwargs):
91
+ """
92
+ Initializes the WebUIBackendComponent, inheriting from BaseGatewayComponent.
93
+ """
94
+ component_config = kwargs.get("component_config", {})
95
+ app_config = component_config.get("app_config", {})
96
+ resolve_uris = app_config.get("resolve_artifact_uris_in_gateway", True)
97
+
98
+ # HTTP SSE gateway configuration:
99
+ # - supports_inline_artifact_resolution=True: Artifacts are converted to FileParts
100
+ # during embed resolution and rendered inline in the web UI
101
+ # - filter_tool_data_parts=False: Web UI displays all parts including tool execution details
102
+ super().__init__(
103
+ resolve_artifact_uris_in_gateway=resolve_uris,
104
+ supports_inline_artifact_resolution=True,
105
+ filter_tool_data_parts=False,
106
+ **kwargs
107
+ )
108
+ log.info("%s Initializing Web UI Backend Component...", self.log_identifier)
109
+
110
+ try:
111
+ self.namespace = self.get_config("namespace")
112
+ self.gateway_id = self.get_config("gateway_id")
113
+ if not self.gateway_id:
114
+ raise ValueError(
115
+ "Internal Error: Gateway ID missing after app initialization."
116
+ )
117
+ self.fastapi_host = self.get_config("fastapi_host", "127.0.0.1")
118
+ self.fastapi_port = self.get_config("fastapi_port", 8000)
119
+ self.fastapi_https_port = self.get_config("fastapi_https_port", 8443)
120
+ self.session_secret_key = self.get_config("session_secret_key")
121
+ self.cors_allowed_origins = self.get_config("cors_allowed_origins", ["*"])
122
+ self.ssl_keyfile = self.get_config("ssl_keyfile", "")
123
+ self.ssl_certfile = self.get_config("ssl_certfile", "")
124
+ self.ssl_keyfile_password = self.get_config("ssl_keyfile_password", "")
125
+ self.model_config = self.get_config("model", None)
126
+
127
+ log.info(
128
+ "%s WebUI-specific configuration retrieved (Host: %s, Port: %d).",
129
+ self.log_identifier,
130
+ self.fastapi_host,
131
+ self.fastapi_port,
132
+ )
133
+ except Exception as e:
134
+ log.error("%s Failed to retrieve configuration: %s", self.log_identifier, e)
135
+ raise ValueError(f"Configuration retrieval error: {e}") from e
136
+
137
+ self.sse_max_queue_size = self.get_config("sse_max_queue_size", 200)
138
+ sse_buffer_max_age_seconds = self.get_config("sse_buffer_max_age_seconds", 600)
139
+
140
+ self.sse_event_buffer = SSEEventBuffer(
141
+ max_queue_size=self.sse_max_queue_size,
142
+ max_age_seconds=sse_buffer_max_age_seconds,
143
+ )
144
+ # SSE manager will be initialized after database setup
145
+ self.sse_manager = None
146
+
147
+ self._sse_cleanup_timer_id = f"sse_cleanup_{self.gateway_id}"
148
+ cleanup_interval_sec = self.get_config(
149
+ "sse_buffer_cleanup_interval_seconds", 300
150
+ )
151
+ self.add_timer(
152
+ delay_ms=cleanup_interval_sec * 1000,
153
+ timer_id=self._sse_cleanup_timer_id,
154
+ interval_ms=cleanup_interval_sec * 1000,
155
+ )
156
+
157
+ # Set up health check timer for agent registry
158
+ from ...common.constants import HEALTH_CHECK_INTERVAL_SECONDS
159
+
160
+ self.health_check_timer_id = f"agent_health_check_{self.gateway_id}"
161
+ health_check_interval_seconds = self.get_config(
162
+ "agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS
163
+ )
164
+ if health_check_interval_seconds > 0:
165
+ log.info(
166
+ "%s Scheduling agent health check every %d seconds.",
167
+ self.log_identifier,
168
+ health_check_interval_seconds,
169
+ )
170
+ self.add_timer(
171
+ delay_ms=health_check_interval_seconds * 1000,
172
+ timer_id=self.health_check_timer_id,
173
+ interval_ms=health_check_interval_seconds * 1000,
174
+ )
175
+ else:
176
+ log.warning(
177
+ "%s Agent health check interval not configured or invalid, health checks will not run periodically.",
178
+ self.log_identifier,
179
+ )
180
+
181
+ session_config = self._resolve_session_config()
182
+ if session_config.get("type") == "sql":
183
+ # SQL type explicitly configured - database_url is required
184
+ database_url = session_config.get("database_url")
185
+ if not database_url:
186
+ raise ValueError(
187
+ f"{self.log_identifier} Session service type is 'sql' but no database_url provided. "
188
+ "Please provide a database_url in the session_service configuration or use type 'memory'."
189
+ )
190
+ self.database_url = database_url
191
+ else:
192
+ # Memory storage or no explicit configuration - no persistence service needed
193
+ self.database_url = None
194
+
195
+ # Validate that features requiring runtime database persistence are not enabled without database
196
+ if self.database_url is None:
197
+ task_logging_config = self.get_config("task_logging", {})
198
+ if task_logging_config.get("enabled", False):
199
+ raise ValueError(
200
+ f"{self.log_identifier} Task logging requires SQL session storage. "
201
+ "Either set session_service.type='sql' with a valid database_url, "
202
+ "or disable task_logging.enabled."
203
+ )
204
+
205
+ feedback_config = self.get_config("feedback_publishing", {})
206
+ if feedback_config.get("enabled", False):
207
+ log.warning(
208
+ "%s Feedback publishing is enabled but database persistence is not configured. "
209
+ "Feedback will only be published to the broker, not stored locally.",
210
+ self.log_identifier,
211
+ )
212
+
213
+ platform_config = self.get_config("platform_service", {})
214
+ self.platform_database_url = platform_config.get("database_url")
215
+ component_config = self.get_config("component_config", {})
216
+ app_config = component_config.get("app_config", {})
217
+
218
+ self.session_manager = SessionManager(
219
+ secret_key=self.session_secret_key,
220
+ app_config=app_config,
221
+ )
222
+
223
+ self.fastapi_app: FastAPI | None = None
224
+ self.uvicorn_server: uvicorn.Server | None = None
225
+ self.fastapi_thread: threading.Thread | None = None
226
+ self.fastapi_event_loop: asyncio.AbstractEventLoop | None = None
227
+
228
+ self._visualization_internal_app: SACApp | None = None
229
+ self._visualization_broker_input: BrokerInput | None = None
230
+ self._visualization_message_queue: queue.Queue = queue.Queue(maxsize=200)
231
+ self._task_logger_queue: queue.Queue = queue.Queue(maxsize=200)
232
+ self._active_visualization_streams: dict[str, dict[str, Any]] = {}
233
+ self._visualization_locks: dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
234
+ self._visualization_locks_lock = threading.Lock()
235
+ self._global_visualization_subscriptions: dict[str, int] = {}
236
+ self._visualization_processor_task: asyncio.Task | None = None
237
+
238
+ self._task_logger_internal_app: SACApp | None = None
239
+ self._task_logger_broker_input: BrokerInput | None = None
240
+ self._task_logger_processor_task: asyncio.Task | None = None
241
+ self.task_logger_service: TaskLoggerService | None = None
242
+
243
+ # Background task monitor
244
+ self.background_task_monitor = None
245
+ self._background_task_monitor_timer_id = None
246
+
247
+ # Initialize SAM Events service for system events
248
+ from ...common.sam_events import SamEventService
249
+
250
+ self.sam_events = SamEventService(
251
+ namespace=self.namespace,
252
+ component_name=f"{self.name}_gateway",
253
+ publish_func=self.publish_a2a,
254
+ )
255
+
256
+ # Initialize data retention service and timer
257
+ self.data_retention_service = None
258
+ self._data_retention_timer_id = None
259
+ data_retention_config = self.get_config("data_retention", {})
260
+ if data_retention_config.get("enabled", True):
261
+ log.info(
262
+ "%s Data retention is enabled. Initializing service and timer...",
263
+ self.log_identifier,
264
+ )
265
+
266
+ # Import and initialize the DataRetentionService
267
+ from .services.data_retention_service import DataRetentionService
268
+
269
+ session_factory = None
270
+ if self.database_url:
271
+ # SessionLocal will be initialized later in setup_dependencies
272
+ # We'll pass a lambda that returns SessionLocal when called
273
+ session_factory = lambda: (
274
+ dependencies.SessionLocal() if dependencies.SessionLocal else None
275
+ )
276
+
277
+ self.data_retention_service = DataRetentionService(
278
+ session_factory=session_factory, config=data_retention_config
279
+ )
280
+
281
+ # Create and start the cleanup timer
282
+ cleanup_interval_hours = data_retention_config.get(
283
+ "cleanup_interval_hours", 24
284
+ )
285
+ cleanup_interval_ms = cleanup_interval_hours * 60 * 60 * 1000
286
+ self._data_retention_timer_id = f"data_retention_cleanup_{self.gateway_id}"
287
+
288
+ self.add_timer(
289
+ delay_ms=cleanup_interval_ms,
290
+ timer_id=self._data_retention_timer_id,
291
+ interval_ms=cleanup_interval_ms,
292
+ )
293
+ log.info(
294
+ "%s Data retention timer created with ID '%s' and interval %d hours.",
295
+ self.log_identifier,
296
+ self._data_retention_timer_id,
297
+ cleanup_interval_hours,
298
+ )
299
+ else:
300
+ log.info(
301
+ "%s Data retention is disabled via configuration.", self.log_identifier
302
+ )
303
+
304
+ log.info("%s Web UI Backend Component initialized.", self.log_identifier)
305
+
306
+ def process_event(self, event: Event):
307
+ if event.event_type == EventType.TIMER:
308
+ timer_id = event.data.get("timer_id")
309
+
310
+ if timer_id == self._sse_cleanup_timer_id:
311
+ log.debug("%s SSE buffer cleanup timer triggered.", self.log_identifier)
312
+ self.sse_event_buffer.cleanup_stale_buffers()
313
+ return
314
+ elif event.data.get("timer_id") == self.health_check_timer_id:
315
+ log.debug("%s Agent health check timer triggered.", self.log_identifier)
316
+ self._check_agent_health()
317
+ return
318
+
319
+ if timer_id == self._data_retention_timer_id:
320
+ log.debug(
321
+ "%s Data retention cleanup timer triggered.", self.log_identifier
322
+ )
323
+ if self.data_retention_service:
324
+ try:
325
+ self.data_retention_service.cleanup_old_data()
326
+ except Exception as e:
327
+ log.error(
328
+ "%s Error during data retention cleanup: %s",
329
+ self.log_identifier,
330
+ e,
331
+ exc_info=True,
332
+ )
333
+ else:
334
+ log.warning(
335
+ "%s Data retention timer fired but service is not initialized.",
336
+ self.log_identifier,
337
+ )
338
+ return
339
+
340
+ if timer_id == self._background_task_monitor_timer_id:
341
+ log.debug("%s Background task monitor timer triggered.", self.log_identifier)
342
+ if self.background_task_monitor:
343
+ loop = self.get_async_loop()
344
+ if loop and loop.is_running():
345
+ asyncio.run_coroutine_threadsafe(
346
+ self.background_task_monitor.check_timeouts(),
347
+ loop
348
+ )
349
+ else:
350
+ log.warning(
351
+ "%s Async loop not available for background task monitor.",
352
+ self.log_identifier
353
+ )
354
+ else:
355
+ log.warning(
356
+ "%s Background task monitor timer fired but service is not initialized.",
357
+ self.log_identifier,
358
+ )
359
+ return
360
+
361
+ super().process_event(event)
362
+
363
+ def _get_visualization_lock(self) -> asyncio.Lock:
364
+ """Get or create a visualization lock for the current event loop."""
365
+ try:
366
+ current_loop = asyncio.get_running_loop()
367
+ except RuntimeError:
368
+ raise RuntimeError(
369
+ "Visualization lock methods must be called from within an async context"
370
+ )
371
+
372
+ with self._visualization_locks_lock:
373
+ if current_loop not in self._visualization_locks:
374
+ self._visualization_locks[current_loop] = asyncio.Lock()
375
+ log.debug(
376
+ "%s Created new visualization lock for event loop %s",
377
+ self.log_identifier,
378
+ id(current_loop),
379
+ )
380
+ return self._visualization_locks[current_loop]
381
+
382
+ def _ensure_visualization_flow_is_running(self) -> None:
383
+ """
384
+ Ensures the internal SAC flow for A2A message visualization is created and running.
385
+ This method is designed to be called once during component startup.
386
+ """
387
+ log_id_prefix = f"{self.log_identifier}[EnsureVizFlow]"
388
+ if self._visualization_internal_app is not None:
389
+ log.debug("%s Visualization flow already running.", log_id_prefix)
390
+ return
391
+
392
+ log.info("%s Initializing internal A2A visualization flow...", log_id_prefix)
393
+ try:
394
+ main_app = self.get_app()
395
+ if not main_app or not main_app.connector:
396
+ log.error(
397
+ "%s Cannot get main app or connector instance. Visualization flow NOT started.",
398
+ log_id_prefix,
399
+ )
400
+ raise RuntimeError(
401
+ "Main app or connector not available for internal flow creation."
402
+ )
403
+
404
+ main_broker_config = main_app.app_info.get("broker", {})
405
+ if not main_broker_config:
406
+ log.error(
407
+ "%s Main app broker configuration not found. Visualization flow NOT started.",
408
+ log_id_prefix,
409
+ )
410
+ raise ValueError("Main app broker configuration is missing.")
411
+
412
+ broker_input_cfg = {
413
+ "component_module": "broker_input",
414
+ "component_name": f"{self.gateway_id}_viz_broker_input",
415
+ "broker_queue_name": f"{self.namespace.strip('/')}/q/gdk/viz/{self.gateway_id}",
416
+ "create_queue_on_start": True,
417
+ "component_config": {
418
+ "broker_url": main_broker_config.get("broker_url"),
419
+ "broker_username": main_broker_config.get("broker_username"),
420
+ "broker_password": main_broker_config.get("broker_password"),
421
+ "broker_vpn": main_broker_config.get("broker_vpn"),
422
+ "trust_store_path": main_broker_config.get("trust_store_path"),
423
+ "dev_mode": main_broker_config.get("dev_mode"),
424
+ "broker_subscriptions": [],
425
+ "reconnection_strategy": main_broker_config.get(
426
+ "reconnection_strategy"
427
+ ),
428
+ "retry_interval": main_broker_config.get("retry_interval"),
429
+ "retry_count": main_broker_config.get("retry_count"),
430
+ "temporary_queue": main_broker_config.get("temporary_queue", True),
431
+ },
432
+ }
433
+
434
+ forwarder_cfg = {
435
+ "component_class": VisualizationForwarderComponent,
436
+ "component_name": f"{self.gateway_id}_viz_forwarder",
437
+ "component_config": {
438
+ "target_queue_ref": self._visualization_message_queue
439
+ },
440
+ }
441
+
442
+ flow_config = {
443
+ "name": f"{self.gateway_id}_viz_flow",
444
+ "components": [broker_input_cfg, forwarder_cfg],
445
+ }
446
+
447
+ internal_app_broker_config = main_broker_config.copy()
448
+ internal_app_broker_config["input_enabled"] = True
449
+ internal_app_broker_config["output_enabled"] = False
450
+
451
+ app_config_for_internal_flow = {
452
+ "name": f"{self.gateway_id}_viz_internal_app",
453
+ "flows": [flow_config],
454
+ "broker": internal_app_broker_config,
455
+ "app_config": {},
456
+ }
457
+
458
+ self._visualization_internal_app = main_app.connector.create_internal_app(
459
+ app_name=app_config_for_internal_flow["name"],
460
+ flows=app_config_for_internal_flow["flows"],
461
+ )
462
+
463
+ if (
464
+ not self._visualization_internal_app
465
+ or not self._visualization_internal_app.flows
466
+ ):
467
+ log.error(
468
+ "%s Failed to create internal visualization app/flow.",
469
+ log_id_prefix,
470
+ )
471
+ self._visualization_internal_app = None
472
+ raise RuntimeError("Internal visualization app/flow creation failed.")
473
+
474
+ self._visualization_internal_app.run()
475
+ log.info("%s Internal visualization app started.", log_id_prefix)
476
+
477
+ flow_instance = self._visualization_internal_app.flows[0]
478
+ if flow_instance.component_groups and flow_instance.component_groups[0]:
479
+ self._visualization_broker_input = flow_instance.component_groups[0][0]
480
+ if not isinstance(self._visualization_broker_input, BrokerInput):
481
+ log.error(
482
+ "%s First component in viz flow is not BrokerInput. Type: %s",
483
+ log_id_prefix,
484
+ type(self._visualization_broker_input).__name__,
485
+ )
486
+ self._visualization_broker_input = None
487
+ raise RuntimeError(
488
+ "Visualization flow setup error: BrokerInput not found."
489
+ )
490
+ log.debug(
491
+ "%s Obtained reference to internal BrokerInput component.",
492
+ log_id_prefix,
493
+ )
494
+ else:
495
+ log.error(
496
+ "%s Could not get BrokerInput instance from internal flow.",
497
+ log_id_prefix,
498
+ )
499
+ raise RuntimeError(
500
+ "Visualization flow setup error: BrokerInput instance not accessible."
501
+ )
502
+
503
+ except Exception as e:
504
+ log.exception(
505
+ "%s Failed to ensure visualization flow is running: %s",
506
+ log_id_prefix,
507
+ e,
508
+ )
509
+ if self._visualization_internal_app:
510
+ try:
511
+ self._visualization_internal_app.cleanup()
512
+ except Exception as cleanup_err:
513
+ log.error(
514
+ "%s Error during cleanup after viz flow init failure: %s",
515
+ log_id_prefix,
516
+ cleanup_err,
517
+ )
518
+ self._visualization_internal_app = None
519
+ self._visualization_broker_input = None
520
+ raise
521
+
522
+ def _ensure_task_logger_flow_is_running(self) -> None:
523
+ """
524
+ Ensures the internal SAC flow for A2A task logging is created and running.
525
+ """
526
+ log_id_prefix = f"{self.log_identifier}[EnsureTaskLogFlow]"
527
+ if self._task_logger_internal_app is not None:
528
+ log.debug("%s Task logger flow already running.", log_id_prefix)
529
+ return
530
+
531
+ log.info("%s Initializing internal A2A task logger flow...", log_id_prefix)
532
+ try:
533
+ main_app = self.get_app()
534
+ if not main_app or not main_app.connector:
535
+ raise RuntimeError(
536
+ "Main app or connector not available for internal flow creation."
537
+ )
538
+
539
+ main_broker_config = main_app.app_info.get("broker", {})
540
+ if not main_broker_config:
541
+ raise ValueError("Main app broker configuration is missing.")
542
+
543
+ # The task logger needs to see ALL messages.
544
+ subscriptions = [{"topic": f"{self.namespace.rstrip('/')}/a2a/>"}]
545
+
546
+ broker_input_cfg = {
547
+ "component_module": "broker_input",
548
+ "component_name": f"{self.gateway_id}_task_log_broker_input",
549
+ "broker_queue_name": f"{self.namespace.strip('/')}/q/gdk/task_log/{self.gateway_id}",
550
+ "create_queue_on_start": True,
551
+ "component_config": {
552
+ "broker_url": main_broker_config.get("broker_url"),
553
+ "broker_username": main_broker_config.get("broker_username"),
554
+ "broker_password": main_broker_config.get("broker_password"),
555
+ "broker_vpn": main_broker_config.get("broker_vpn"),
556
+ "trust_store_path": main_broker_config.get("trust_store_path"),
557
+ "dev_mode": main_broker_config.get("dev_mode"),
558
+ "broker_subscriptions": subscriptions,
559
+ "reconnection_strategy": main_broker_config.get(
560
+ "reconnection_strategy"
561
+ ),
562
+ "retry_interval": main_broker_config.get("retry_interval"),
563
+ "retry_count": main_broker_config.get("retry_count"),
564
+ "temporary_queue": main_broker_config.get("temporary_queue", True),
565
+ },
566
+ }
567
+
568
+ forwarder_cfg = {
569
+ "component_class": TaskLoggerForwarderComponent,
570
+ "component_name": f"{self.gateway_id}_task_log_forwarder",
571
+ "component_config": {"target_queue_ref": self._task_logger_queue},
572
+ }
573
+
574
+ flow_config = {
575
+ "name": f"{self.gateway_id}_task_log_flow",
576
+ "components": [broker_input_cfg, forwarder_cfg],
577
+ }
578
+
579
+ internal_app_broker_config = main_broker_config.copy()
580
+ internal_app_broker_config["input_enabled"] = True
581
+ internal_app_broker_config["output_enabled"] = False
582
+
583
+ app_config_for_internal_flow = {
584
+ "name": f"{self.gateway_id}_task_log_internal_app",
585
+ "flows": [flow_config],
586
+ "broker": internal_app_broker_config,
587
+ "app_config": {},
588
+ }
589
+
590
+ self._task_logger_internal_app = main_app.connector.create_internal_app(
591
+ app_name=app_config_for_internal_flow["name"],
592
+ flows=app_config_for_internal_flow["flows"],
593
+ )
594
+
595
+ if (
596
+ not self._task_logger_internal_app
597
+ or not self._task_logger_internal_app.flows
598
+ ):
599
+ raise RuntimeError("Internal task logger app/flow creation failed.")
600
+
601
+ self._task_logger_internal_app.run()
602
+ log.info("%s Internal task logger app started.", log_id_prefix)
603
+
604
+ flow_instance = self._task_logger_internal_app.flows[0]
605
+ if flow_instance.component_groups and flow_instance.component_groups[0]:
606
+ self._task_logger_broker_input = flow_instance.component_groups[0][0]
607
+ if not isinstance(self._task_logger_broker_input, BrokerInput):
608
+ raise RuntimeError(
609
+ "Task logger flow setup error: BrokerInput not found."
610
+ )
611
+ log.info(
612
+ "%s Obtained reference to internal task logger BrokerInput component.",
613
+ log_id_prefix,
614
+ )
615
+ else:
616
+ raise RuntimeError(
617
+ "Task logger flow setup error: BrokerInput instance not accessible."
618
+ )
619
+
620
+ except Exception as e:
621
+ log.exception(
622
+ "%s Failed to ensure task logger flow is running: %s", log_id_prefix, e
623
+ )
624
+ if self._task_logger_internal_app:
625
+ try:
626
+ self._task_logger_internal_app.cleanup()
627
+ except Exception as cleanup_err:
628
+ log.error(
629
+ "%s Error during cleanup after task logger flow init failure: %s",
630
+ log_id_prefix,
631
+ cleanup_err,
632
+ )
633
+ self._task_logger_internal_app = None
634
+ self._task_logger_broker_input = None
635
+ raise
636
+
637
+ def _resolve_session_config(self) -> dict:
638
+ """
639
+ Resolve session service configuration with backward compatibility.
640
+
641
+ Priority order:
642
+ 1. Component-specific session_service config (new approach)
643
+ 2. Shared default_session_service config (deprecated, with warning)
644
+ 3. Hardcoded default (SQLite for Web UI)
645
+ """
646
+ # Check component-specific session_service config first
647
+ component_session_config = self.get_config("session_service")
648
+ if component_session_config:
649
+ log.debug("Using component-specific session_service configuration")
650
+ return component_session_config
651
+
652
+ # Backward compatibility: check shared config
653
+ shared_session_config = self.get_config("default_session_service")
654
+ if shared_session_config:
655
+ log.warning(
656
+ "Using session_service from shared config is deprecated. "
657
+ "Move to component-specific configuration in app_config.session_service"
658
+ )
659
+ return shared_session_config
660
+
661
+ # Default configuration for Web UI (backward compatibility)
662
+ default_config = {"type": "memory", "default_behavior": "PERSISTENT"}
663
+ log.info(
664
+ "Using default memory session configuration for Web UI (backward compatibility)"
665
+ )
666
+ return default_config
667
+
668
+ async def _visualization_message_processor_loop(self) -> None:
669
+ """
670
+ Asynchronously consumes messages from the _visualization_message_queue,
671
+ filters them, and forwards them to relevant SSE connections.
672
+ """
673
+ log_id_prefix = f"{self.log_identifier}[VizMsgProcessor]"
674
+ log.info("%s Starting visualization message processor loop...", log_id_prefix)
675
+ loop = asyncio.get_running_loop()
676
+
677
+ while not self.stop_signal.is_set():
678
+ msg_data = None
679
+ try:
680
+ msg_data = await loop.run_in_executor(
681
+ None,
682
+ self._visualization_message_queue.get,
683
+ True,
684
+ 1.0,
685
+ )
686
+
687
+ if msg_data is None:
688
+ log.info(
689
+ "%s Received shutdown signal for viz processor loop.",
690
+ log_id_prefix,
691
+ )
692
+ break
693
+
694
+ current_size = self._visualization_message_queue.qsize()
695
+ max_size = self._visualization_message_queue.maxsize
696
+ if max_size > 0 and (current_size / max_size) > 0.90:
697
+ log.warning(
698
+ "%s Visualization message queue is over 90%% full. Current size: %d/%d",
699
+ log_id_prefix,
700
+ current_size,
701
+ max_size,
702
+ )
703
+
704
+ topic = msg_data.get("topic")
705
+ payload_dict = msg_data.get("payload")
706
+
707
+ log.debug("%s [VIZ_DATA_RAW] Topic: %s", log_id_prefix, topic)
708
+
709
+ if "/a2a/v1/discovery/" in topic:
710
+ self._visualization_message_queue.task_done()
711
+ continue
712
+
713
+ event_details_for_owner = self._infer_visualization_event_details(
714
+ topic, payload_dict
715
+ )
716
+ task_id_for_context = event_details_for_owner.get("task_id")
717
+ message_owner_id = None
718
+ if task_id_for_context:
719
+ root_task_id = task_id_for_context.split(":", 1)[0]
720
+ context = self.task_context_manager.get_context(root_task_id)
721
+ if context and "user_identity" in context:
722
+ message_owner_id = context["user_identity"].get("id")
723
+ log.debug(
724
+ "%s Found owner '%s' for task %s via local context (root: %s).",
725
+ log_id_prefix,
726
+ message_owner_id,
727
+ task_id_for_context,
728
+ root_task_id,
729
+ )
730
+
731
+ if not message_owner_id:
732
+ user_properties = msg_data.get("user_properties") or {}
733
+
734
+ if not user_properties:
735
+ log.warning(
736
+ "%s No user_properties found for task %s (root: %s). Cannot determine owner via message properties.",
737
+ log_id_prefix,
738
+ task_id_for_context,
739
+ root_task_id,
740
+ )
741
+ user_config = user_properties.get(
742
+ "a2aUserConfig"
743
+ ) or user_properties.get("a2a_user_config")
744
+
745
+ if (
746
+ isinstance(user_config, dict)
747
+ and "user_profile" in user_config
748
+ and isinstance(user_config.get("user_profile"), dict)
749
+ ):
750
+ message_owner_id = user_config["user_profile"].get("id")
751
+ if message_owner_id:
752
+ log.debug(
753
+ "%s Found owner '%s' for task %s via message properties.",
754
+ log_id_prefix,
755
+ message_owner_id,
756
+ task_id_for_context,
757
+ )
758
+ async with self._get_visualization_lock():
759
+ for (
760
+ stream_id,
761
+ stream_config,
762
+ ) in self._active_visualization_streams.items():
763
+ sse_queue_for_stream = stream_config.get("sse_queue")
764
+ if not sse_queue_for_stream:
765
+ log.warning(
766
+ "%s SSE queue not found for stream %s. Skipping.",
767
+ log_id_prefix,
768
+ stream_id,
769
+ )
770
+ continue
771
+
772
+ is_permitted = False
773
+ stream_owner_id = stream_config.get("user_id")
774
+ abstract_targets = stream_config.get("abstract_targets", [])
775
+
776
+ for abstract_target in abstract_targets:
777
+ if abstract_target.status != "subscribed":
778
+ continue
779
+
780
+ if abstract_target.type == "my_a2a_messages":
781
+ if (
782
+ stream_owner_id
783
+ and message_owner_id
784
+ and stream_owner_id == message_owner_id
785
+ ):
786
+ is_permitted = True
787
+ break
788
+ else:
789
+ subscribed_topics_for_stream = stream_config.get(
790
+ "solace_topics", set()
791
+ )
792
+ if any(
793
+ a2a.topic_matches_subscription(topic, pattern)
794
+ for pattern in subscribed_topics_for_stream
795
+ ):
796
+ is_permitted = True
797
+ break
798
+
799
+ if is_permitted:
800
+ event_details = self._infer_visualization_event_details(
801
+ topic, payload_dict
802
+ )
803
+
804
+ sse_event_payload = {
805
+ "event_type": "a2a_message",
806
+ "timestamp": datetime.now(timezone.utc).isoformat(),
807
+ "solace_topic": topic,
808
+ "direction": event_details["direction"],
809
+ "source_entity": event_details["source_entity"],
810
+ "target_entity": event_details["target_entity"],
811
+ "message_id": event_details["message_id"],
812
+ "task_id": event_details["task_id"],
813
+ "payload_summary": event_details["payload_summary"],
814
+ "full_payload": payload_dict,
815
+ "debug_type": event_details["debug_type"],
816
+ }
817
+
818
+ try:
819
+ log.debug(
820
+ "%s Attempting to put message on SSE queue for stream %s. Queue size: %d",
821
+ log_id_prefix,
822
+ stream_id,
823
+ sse_queue_for_stream.qsize(),
824
+ )
825
+ sse_queue_for_stream.put_nowait(
826
+ {
827
+ "event": "a2a_message",
828
+ "data": json.dumps(sse_event_payload),
829
+ }
830
+ )
831
+ log.debug(
832
+ "%s [VIZ_DATA_SENT] Stream %s: Topic: %s, Direction: %s",
833
+ log_id_prefix,
834
+ stream_id,
835
+ topic,
836
+ event_details["direction"],
837
+ )
838
+ except asyncio.QueueFull:
839
+ # Check if this is a background task
840
+ is_background = False
841
+ if task_id_for_context and self.database_url:
842
+ try:
843
+ from .repository.task_repository import TaskRepository
844
+ db = dependencies.SessionLocal()
845
+ try:
846
+ repo = TaskRepository()
847
+ task = repo.find_by_id(db, task_id_for_context)
848
+ is_background = task and task.background_execution_enabled
849
+ finally:
850
+ db.close()
851
+ except Exception:
852
+ pass
853
+
854
+ if is_background:
855
+ log.debug(
856
+ "%s SSE queue full for stream %s. Dropping visualization message for background task %s.",
857
+ log_id_prefix,
858
+ stream_id,
859
+ task_id_for_context,
860
+ )
861
+ else:
862
+ log.warning(
863
+ "%s SSE queue full for stream %s. Visualization message dropped.",
864
+ log_id_prefix,
865
+ stream_id,
866
+ )
867
+ except Exception as send_err:
868
+ log.error(
869
+ "%s Error sending formatted message to SSE queue for stream %s: %s",
870
+ log_id_prefix,
871
+ stream_id,
872
+ send_err,
873
+ )
874
+ else:
875
+ pass
876
+
877
+ self._visualization_message_queue.task_done()
878
+
879
+ except queue.Empty:
880
+ continue
881
+ except asyncio.CancelledError:
882
+ log.info(
883
+ "%s Visualization message processor loop cancelled.", log_id_prefix
884
+ )
885
+ break
886
+ except Exception as e:
887
+ log.exception(
888
+ "%s Error in visualization message processor loop: %s",
889
+ log_id_prefix,
890
+ e,
891
+ )
892
+ if msg_data and self._visualization_message_queue:
893
+ self._visualization_message_queue.task_done()
894
+ await asyncio.sleep(1)
895
+
896
+ log.info("%s Visualization message processor loop finished.", log_id_prefix)
897
+
898
+ async def _task_logger_loop(self) -> None:
899
+ """
900
+ Asynchronously consumes messages from the _task_logger_queue and
901
+ passes them to the TaskLoggerService for persistence.
902
+ """
903
+ log_id_prefix = f"{self.log_identifier}[TaskLoggerLoop]"
904
+ log.info("%s Starting task logger loop...", log_id_prefix)
905
+ loop = asyncio.get_running_loop()
906
+
907
+ while not self.stop_signal.is_set():
908
+ msg_data = None
909
+ try:
910
+ msg_data = await loop.run_in_executor(
911
+ None,
912
+ self._task_logger_queue.get,
913
+ True,
914
+ 1.0,
915
+ )
916
+
917
+ if msg_data is None:
918
+ log.info(
919
+ "%s Received shutdown signal for task logger loop.",
920
+ log_id_prefix,
921
+ )
922
+ break
923
+
924
+ if self.task_logger_service:
925
+ self.task_logger_service.log_event(msg_data)
926
+ else:
927
+ log.warning(
928
+ "%s Task logger service not available. Cannot log event.",
929
+ log_id_prefix,
930
+ )
931
+
932
+ self._task_logger_queue.task_done()
933
+
934
+ except queue.Empty:
935
+ continue
936
+ except asyncio.CancelledError:
937
+ log.info("%s Task logger loop cancelled.", log_id_prefix)
938
+ break
939
+ except Exception as e:
940
+ log.exception(
941
+ "%s Error in task logger loop: %s",
942
+ log_id_prefix,
943
+ e,
944
+ )
945
+ if msg_data and self._task_logger_queue:
946
+ self._task_logger_queue.task_done()
947
+ await asyncio.sleep(1)
948
+
949
+ log.info("%s Task logger loop finished.", log_id_prefix)
950
+
951
+ async def _add_visualization_subscription(
952
+ self, topic_str: str, stream_id: str
953
+ ) -> bool:
954
+ """
955
+ Adds a Solace topic subscription to the internal BrokerInput for visualization.
956
+ Manages global subscription reference counts.
957
+ """
958
+ log_id_prefix = f"{self.log_identifier}[AddVizSub:{stream_id}]"
959
+ log.debug(
960
+ "%s Attempting to add subscription to topic: %s", log_id_prefix, topic_str
961
+ )
962
+
963
+ if not self._visualization_broker_input:
964
+ log.error(
965
+ "%s Visualization BrokerInput is not initialized. Cannot add subscription.",
966
+ log_id_prefix,
967
+ )
968
+ return False
969
+ if (
970
+ not hasattr(self._visualization_broker_input, "messaging_service")
971
+ or not self._visualization_broker_input.messaging_service
972
+ ):
973
+ log.error(
974
+ "%s Visualization BrokerInput's messaging_service not available or not initialized. Cannot add subscription.",
975
+ log_id_prefix,
976
+ )
977
+ return False
978
+
979
+ log.debug(
980
+ "%s Acquiring visualization stream lock for topic '%s'...",
981
+ log_id_prefix,
982
+ topic_str,
983
+ )
984
+ async with self._get_visualization_lock():
985
+ log.debug(
986
+ "%s Acquired visualization stream lock for topic '%s'.",
987
+ log_id_prefix,
988
+ topic_str,
989
+ )
990
+ self._global_visualization_subscriptions[topic_str] = (
991
+ self._global_visualization_subscriptions.get(topic_str, 0) + 1
992
+ )
993
+ log.debug(
994
+ "%s Global subscription count for topic '%s' is now %d.",
995
+ log_id_prefix,
996
+ topic_str,
997
+ self._global_visualization_subscriptions[topic_str],
998
+ )
999
+
1000
+ if self._global_visualization_subscriptions[topic_str] == 1:
1001
+ log.info(
1002
+ "%s First global subscription for topic '%s'. Attempting to subscribe on broker.",
1003
+ log_id_prefix,
1004
+ topic_str,
1005
+ )
1006
+ try:
1007
+ if not hasattr(
1008
+ self._visualization_broker_input, "add_subscription"
1009
+ ) or not callable(
1010
+ self._visualization_broker_input.add_subscription
1011
+ ):
1012
+ log.error(
1013
+ "%s Visualization BrokerInput does not support dynamic 'add_subscription'. "
1014
+ "Please upgrade the 'solace-ai-connector' module. Cannot add subscription '%s'.",
1015
+ log_id_prefix,
1016
+ topic_str,
1017
+ )
1018
+ self._global_visualization_subscriptions[topic_str] -= 1
1019
+ if self._global_visualization_subscriptions[topic_str] == 0:
1020
+ del self._global_visualization_subscriptions[topic_str]
1021
+ return False
1022
+
1023
+ loop = asyncio.get_event_loop()
1024
+ add_result = await loop.run_in_executor(
1025
+ None,
1026
+ self._visualization_broker_input.add_subscription,
1027
+ topic_str,
1028
+ )
1029
+ if not add_result:
1030
+ log.error(
1031
+ "%s Failed to add subscription '%s' via BrokerInput.",
1032
+ log_id_prefix,
1033
+ topic_str,
1034
+ )
1035
+ self._global_visualization_subscriptions[topic_str] -= 1
1036
+ if self._global_visualization_subscriptions[topic_str] == 0:
1037
+ del self._global_visualization_subscriptions[topic_str]
1038
+ return False
1039
+ log.info(
1040
+ "%s Successfully added subscription '%s' via BrokerInput.",
1041
+ log_id_prefix,
1042
+ topic_str,
1043
+ )
1044
+ except Exception as e:
1045
+ log.exception(
1046
+ "%s Exception calling BrokerInput.add_subscription for topic '%s': %s",
1047
+ log_id_prefix,
1048
+ topic_str,
1049
+ e,
1050
+ )
1051
+ self._global_visualization_subscriptions[topic_str] -= 1
1052
+ if self._global_visualization_subscriptions[topic_str] == 0:
1053
+ del self._global_visualization_subscriptions[topic_str]
1054
+ return False
1055
+ else:
1056
+ log.debug(
1057
+ "%s Topic '%s' already globally subscribed. Skipping broker subscribe.",
1058
+ log_id_prefix,
1059
+ topic_str,
1060
+ )
1061
+
1062
+ if stream_id in self._active_visualization_streams:
1063
+ self._active_visualization_streams[stream_id]["solace_topics"].add(
1064
+ topic_str
1065
+ )
1066
+ log.debug(
1067
+ "%s Topic '%s' added to active subscriptions for stream %s.",
1068
+ log_id_prefix,
1069
+ topic_str,
1070
+ stream_id,
1071
+ )
1072
+ else:
1073
+ log.warning(
1074
+ "%s Stream ID %s not found in active streams. Cannot add topic.",
1075
+ log_id_prefix,
1076
+ stream_id,
1077
+ )
1078
+ return False
1079
+ log.debug(
1080
+ "%s Releasing visualization stream lock after successful processing for topic '%s'.",
1081
+ log_id_prefix,
1082
+ topic_str,
1083
+ )
1084
+ return True
1085
+
1086
+ async def _remove_visualization_subscription_nolock(
1087
+ self, topic_str: str, stream_id: str
1088
+ ) -> bool:
1089
+ """
1090
+ Internal helper to remove a Solace topic subscription.
1091
+ Assumes _visualization_stream_lock is already held by the caller.
1092
+ Manages global subscription reference counts.
1093
+ """
1094
+ log_id_prefix = f"{self.log_identifier}[RemoveVizSubNL:{stream_id}]"
1095
+ log.info(
1096
+ "%s Removing subscription (no-lock) from topic: %s",
1097
+ log_id_prefix,
1098
+ topic_str,
1099
+ )
1100
+
1101
+ if not self._visualization_broker_input or not hasattr(
1102
+ self._visualization_broker_input, "messaging_service"
1103
+ ):
1104
+ log.error(
1105
+ "%s Visualization BrokerInput or its messaging_service not available.",
1106
+ log_id_prefix,
1107
+ )
1108
+ return False
1109
+
1110
+ if topic_str not in self._global_visualization_subscriptions:
1111
+ log.warning(
1112
+ "%s Topic '%s' not found in global subscriptions. Cannot remove.",
1113
+ log_id_prefix,
1114
+ topic_str,
1115
+ )
1116
+ return False
1117
+
1118
+ self._global_visualization_subscriptions[topic_str] -= 1
1119
+
1120
+ if self._global_visualization_subscriptions[topic_str] == 0:
1121
+ del self._global_visualization_subscriptions[topic_str]
1122
+ try:
1123
+ if not hasattr(
1124
+ self._visualization_broker_input, "remove_subscription"
1125
+ ) or not callable(self._visualization_broker_input.remove_subscription):
1126
+ log.error(
1127
+ "%s Visualization BrokerInput does not support dynamic 'remove_subscription'. "
1128
+ "Please upgrade the 'solace-ai-connector' module. Cannot remove subscription '%s'.",
1129
+ log_id_prefix,
1130
+ topic_str,
1131
+ )
1132
+ return False
1133
+
1134
+ loop = asyncio.get_event_loop()
1135
+ remove_result = await loop.run_in_executor(
1136
+ None,
1137
+ self._visualization_broker_input.remove_subscription,
1138
+ topic_str,
1139
+ )
1140
+ if not remove_result:
1141
+ log.error(
1142
+ "%s Failed to remove subscription '%s' via BrokerInput. Global count might be inaccurate.",
1143
+ log_id_prefix,
1144
+ topic_str,
1145
+ )
1146
+ else:
1147
+ log.info(
1148
+ "%s Successfully removed subscription '%s' via BrokerInput.",
1149
+ log_id_prefix,
1150
+ topic_str,
1151
+ )
1152
+ except Exception as e:
1153
+ log.exception(
1154
+ "%s Exception calling BrokerInput.remove_subscription for topic '%s': %s",
1155
+ log_id_prefix,
1156
+ topic_str,
1157
+ e,
1158
+ )
1159
+
1160
+ if stream_id in self._active_visualization_streams:
1161
+ if (
1162
+ topic_str
1163
+ in self._active_visualization_streams[stream_id]["solace_topics"]
1164
+ ):
1165
+ self._active_visualization_streams[stream_id]["solace_topics"].remove(
1166
+ topic_str
1167
+ )
1168
+ log.debug(
1169
+ "%s Topic '%s' removed from active subscriptions for stream %s.",
1170
+ log_id_prefix,
1171
+ topic_str,
1172
+ stream_id,
1173
+ )
1174
+ else:
1175
+ log.warning(
1176
+ "%s Topic '%s' not found in subscriptions for stream %s.",
1177
+ log_id_prefix,
1178
+ topic_str,
1179
+ stream_id,
1180
+ )
1181
+ else:
1182
+ log.warning(
1183
+ "%s Stream ID %s not found in active streams. Cannot remove topic.",
1184
+ log_id_prefix,
1185
+ stream_id,
1186
+ )
1187
+ return True
1188
+
1189
+ async def _remove_visualization_subscription(
1190
+ self, topic_str: str, stream_id: str
1191
+ ) -> bool:
1192
+ """
1193
+ Public method to remove a Solace topic subscription.
1194
+ Acquires the lock before calling the internal no-lock version.
1195
+ """
1196
+ log_id_prefix = f"{self.log_identifier}[RemoveVizSubPub:{stream_id}]"
1197
+ log.debug(
1198
+ "%s Acquiring lock to remove subscription for topic: %s",
1199
+ log_id_prefix,
1200
+ topic_str,
1201
+ )
1202
+ async with self._get_visualization_lock():
1203
+ log.debug("%s Lock acquired for topic: %s", log_id_prefix, topic_str)
1204
+ result = await self._remove_visualization_subscription_nolock(
1205
+ topic_str, stream_id
1206
+ )
1207
+ log.debug("%s Releasing lock for topic: %s", log_id_prefix, topic_str)
1208
+ return result
1209
+
1210
+ async def _extract_initial_claims(
1211
+ self, external_event_data: Any
1212
+ ) -> dict[str, Any] | None:
1213
+ """
1214
+ Extracts initial identity claims from the incoming external event.
1215
+ For the WebUI, this means inspecting the FastAPIRequest.
1216
+ It prioritizes the authenticated user from `request.state.user`.
1217
+ """
1218
+ log_id_prefix = f"{self.log_identifier}[ExtractClaims]"
1219
+
1220
+ if not isinstance(external_event_data, FastAPIRequest):
1221
+ log.warning(
1222
+ "%s Expected external_event_data to be a FastAPIRequest, but got %s.",
1223
+ log_id_prefix,
1224
+ type(external_event_data).__name__,
1225
+ )
1226
+ return None
1227
+
1228
+ request = external_event_data
1229
+ try:
1230
+ user_info = {}
1231
+ if hasattr(request.state, "user") and request.state.user:
1232
+ user_info = request.state.user
1233
+ username = user_info.get("username")
1234
+ if username:
1235
+ log.debug(
1236
+ "%s Extracted user '%s' from request.state.",
1237
+ log_id_prefix,
1238
+ username,
1239
+ )
1240
+ return {
1241
+ "id": username,
1242
+ "name": username,
1243
+ "email": username,
1244
+ "user_info": user_info,
1245
+ }
1246
+
1247
+ log.debug(
1248
+ "%s No authenticated user in request.state, falling back to SessionManager.",
1249
+ log_id_prefix,
1250
+ )
1251
+ user_id = self.session_manager.get_a2a_client_id(request)
1252
+ log.debug(
1253
+ "%s Extracted user_id '%s' via SessionManager.", log_id_prefix, user_id
1254
+ )
1255
+ return {"id": user_id, "name": user_id, "user_info": user_info}
1256
+
1257
+ except Exception as e:
1258
+ log.error("%s Failed to extract user_id from request: %s", log_id_prefix, e)
1259
+ return None
1260
+
1261
+ def _start_fastapi_server(self):
1262
+ """Starts the Uvicorn server in a separate thread."""
1263
+ log.info(
1264
+ "%s [_start_listener] Attempting to start FastAPI/Uvicorn server...",
1265
+ self.log_identifier,
1266
+ )
1267
+ if self.fastapi_thread and self.fastapi_thread.is_alive():
1268
+ log.warning(
1269
+ "%s FastAPI server thread already started.", self.log_identifier
1270
+ )
1271
+ return
1272
+
1273
+ try:
1274
+ from ...gateway.http_sse.main import app as fastapi_app_instance
1275
+ from ...gateway.http_sse.main import setup_dependencies
1276
+
1277
+ self.fastapi_app = fastapi_app_instance
1278
+
1279
+ setup_dependencies(self, self.database_url, self.platform_database_url)
1280
+
1281
+ # Instantiate services that depend on the database session factory.
1282
+ # This must be done *after* setup_dependencies has run.
1283
+ session_factory = dependencies.SessionLocal if self.database_url else None
1284
+
1285
+ # Initialize SSE manager with session factory for background task detection
1286
+ self.sse_manager = SSEManager(
1287
+ max_queue_size=self.sse_max_queue_size,
1288
+ event_buffer=self.sse_event_buffer,
1289
+ session_factory=session_factory
1290
+ )
1291
+ log.debug(
1292
+ "%s SSE manager initialized with database session factory.",
1293
+ self.log_identifier,
1294
+ )
1295
+ task_logging_config = self.get_config("task_logging", {})
1296
+ self.task_logger_service = TaskLoggerService(
1297
+ session_factory=session_factory, config=task_logging_config
1298
+ )
1299
+ log.debug(
1300
+ "%s Services dependent on database session factory have been initialized.",
1301
+ self.log_identifier,
1302
+ )
1303
+
1304
+ # Initialize background task monitor if task logging is enabled
1305
+ if self.database_url and task_logging_config.get("enabled", False):
1306
+ from .services.background_task_monitor import BackgroundTaskMonitor
1307
+ from .services.task_service import TaskService
1308
+
1309
+ # Create task service for cancellation operations
1310
+ task_service = TaskService(
1311
+ core_a2a_service=self.core_a2a_service,
1312
+ publish_func=self.publish_a2a,
1313
+ namespace=self.namespace,
1314
+ gateway_id=self.gateway_id,
1315
+ sse_manager=self.sse_manager,
1316
+ task_context_map=self.task_context_manager._contexts,
1317
+ task_context_lock=self.task_context_manager._lock,
1318
+ app_name=self.name,
1319
+ )
1320
+
1321
+ # Get timeout configuration
1322
+ background_config = self.get_config("background_tasks", {})
1323
+ default_timeout_ms = background_config.get("default_timeout_ms", 3600000) # 1 hour
1324
+
1325
+ self.background_task_monitor = BackgroundTaskMonitor(
1326
+ session_factory=session_factory,
1327
+ task_service=task_service,
1328
+ default_timeout_ms=default_timeout_ms,
1329
+ )
1330
+
1331
+ # Create timer for periodic timeout checks
1332
+ monitor_interval_ms = background_config.get("monitor_interval_ms", 300000) # 5 minutes
1333
+ self._background_task_monitor_timer_id = f"background_task_monitor_{self.gateway_id}"
1334
+
1335
+ self.add_timer(
1336
+ delay_ms=monitor_interval_ms,
1337
+ timer_id=self._background_task_monitor_timer_id,
1338
+ interval_ms=monitor_interval_ms,
1339
+ )
1340
+
1341
+ log.info(
1342
+ "%s Background task monitor initialized with %dms check interval and %dms default timeout",
1343
+ self.log_identifier,
1344
+ monitor_interval_ms,
1345
+ default_timeout_ms
1346
+ )
1347
+ else:
1348
+ log.info(
1349
+ "%s Background task monitor not initialized (task logging disabled or no database)",
1350
+ self.log_identifier
1351
+ )
1352
+
1353
+ port = (
1354
+ self.fastapi_https_port
1355
+ if self.ssl_keyfile and self.ssl_certfile
1356
+ else self.fastapi_port
1357
+ )
1358
+
1359
+ config = uvicorn.Config(
1360
+ app=self.fastapi_app,
1361
+ host=self.fastapi_host,
1362
+ port=port,
1363
+ log_level="warning",
1364
+ lifespan="on",
1365
+ ssl_keyfile=self.ssl_keyfile,
1366
+ ssl_certfile=self.ssl_certfile,
1367
+ ssl_keyfile_password=self.ssl_keyfile_password,
1368
+ log_config=None
1369
+ )
1370
+ self.uvicorn_server = uvicorn.Server(config)
1371
+
1372
+ @self.fastapi_app.on_event("startup")
1373
+ async def capture_event_loop():
1374
+ log.info(
1375
+ "%s [_start_listener] FastAPI startup event triggered.",
1376
+ self.log_identifier,
1377
+ )
1378
+ try:
1379
+ self.fastapi_event_loop = asyncio.get_running_loop()
1380
+ log.debug(
1381
+ "%s [_start_listener] Captured FastAPI event loop via startup event: %s",
1382
+ self.log_identifier,
1383
+ self.fastapi_event_loop,
1384
+ )
1385
+
1386
+ if self.fastapi_event_loop:
1387
+ log.debug(
1388
+ "%s Ensuring visualization flow is running...",
1389
+ self.log_identifier,
1390
+ )
1391
+ self._ensure_visualization_flow_is_running()
1392
+
1393
+ if (
1394
+ self._visualization_processor_task is None
1395
+ or self._visualization_processor_task.done()
1396
+ ):
1397
+ log.debug(
1398
+ "%s Starting visualization message processor task.",
1399
+ self.log_identifier,
1400
+ )
1401
+ self._visualization_processor_task = (
1402
+ self.fastapi_event_loop.create_task(
1403
+ self._visualization_message_processor_loop()
1404
+ )
1405
+ )
1406
+ else:
1407
+ log.debug(
1408
+ "%s Visualization message processor task already running.",
1409
+ self.log_identifier,
1410
+ )
1411
+
1412
+ task_logging_config = self.get_config("task_logging", {})
1413
+ if task_logging_config.get("enabled", False):
1414
+ log.info(
1415
+ "%s Task logging is enabled. Ensuring flow is running...",
1416
+ self.log_identifier,
1417
+ )
1418
+ self._ensure_task_logger_flow_is_running()
1419
+
1420
+ if (
1421
+ self._task_logger_processor_task is None
1422
+ or self._task_logger_processor_task.done()
1423
+ ):
1424
+ log.info(
1425
+ "%s Starting task logger processor task.",
1426
+ self.log_identifier,
1427
+ )
1428
+ self._task_logger_processor_task = (
1429
+ self.fastapi_event_loop.create_task(
1430
+ self._task_logger_loop()
1431
+ )
1432
+ )
1433
+ else:
1434
+ log.info(
1435
+ "%s Task logger processor task already running.",
1436
+ self.log_identifier,
1437
+ )
1438
+ else:
1439
+ log.info(
1440
+ "%s Task logging is disabled.", self.log_identifier
1441
+ )
1442
+ else:
1443
+ log.error(
1444
+ "%s FastAPI event loop not captured. Cannot start visualization processor.",
1445
+ self.log_identifier,
1446
+ )
1447
+
1448
+ except Exception as startup_err:
1449
+ log.exception(
1450
+ "%s [_start_listener] Error during FastAPI startup event (capture_event_loop or viz setup): %s",
1451
+ self.log_identifier,
1452
+ startup_err,
1453
+ )
1454
+ self.stop_signal.set()
1455
+
1456
+ try:
1457
+ from solace_agent_mesh_enterprise.init_enterprise import (
1458
+ start_enterprise_background_tasks,
1459
+ )
1460
+
1461
+ log.info(
1462
+ "%s Starting enterprise background tasks...",
1463
+ self.log_identifier,
1464
+ )
1465
+ await start_enterprise_background_tasks(self)
1466
+ log.info(
1467
+ "%s Enterprise background tasks started successfully",
1468
+ self.log_identifier,
1469
+ )
1470
+ except ImportError:
1471
+ log.debug(
1472
+ "%s Enterprise package not available - skipping background tasks",
1473
+ self.log_identifier,
1474
+ )
1475
+ except RuntimeError as enterprise_err:
1476
+ log.warning(
1477
+ "%s Enterprise background tasks disabled: %s - Community features will continue normally",
1478
+ self.log_identifier,
1479
+ enterprise_err,
1480
+ )
1481
+ except Exception as enterprise_err:
1482
+ log.error(
1483
+ "%s Failed to start enterprise background tasks: %s - Community features will continue normally",
1484
+ self.log_identifier,
1485
+ enterprise_err,
1486
+ exc_info=True,
1487
+ )
1488
+
1489
+ @self.fastapi_app.on_event("shutdown")
1490
+ async def shutdown_event():
1491
+ log.info(
1492
+ "%s [_start_listener] FastAPI shutdown event triggered.",
1493
+ self.log_identifier,
1494
+ )
1495
+
1496
+ try:
1497
+ from solace_agent_mesh_enterprise.init_enterprise import (
1498
+ stop_enterprise_background_tasks,
1499
+ )
1500
+
1501
+ log.info(
1502
+ "%s Stopping enterprise background tasks...",
1503
+ self.log_identifier,
1504
+ )
1505
+ await stop_enterprise_background_tasks()
1506
+ log.info(
1507
+ "%s Enterprise background tasks stopped", self.log_identifier
1508
+ )
1509
+ except ImportError:
1510
+ log.debug(
1511
+ "%s Enterprise package not available - no background tasks to stop",
1512
+ self.log_identifier,
1513
+ )
1514
+ except Exception as enterprise_err:
1515
+ log.error(
1516
+ "%s Failed to stop enterprise background tasks: %s",
1517
+ self.log_identifier,
1518
+ enterprise_err,
1519
+ exc_info=True,
1520
+ )
1521
+
1522
+ self.fastapi_thread = threading.Thread(
1523
+ target=self.uvicorn_server.run, daemon=True, name="FastAPI_Thread"
1524
+ )
1525
+ self.fastapi_thread.start()
1526
+ protocol = "https" if self.ssl_keyfile and self.ssl_certfile else "http"
1527
+ log.info(
1528
+ "%s [_start_listener] FastAPI/Uvicorn server starting in background thread on %s://%s:%d",
1529
+ self.log_identifier,
1530
+ protocol,
1531
+ self.fastapi_host,
1532
+ port,
1533
+ )
1534
+
1535
+ except Exception as e:
1536
+ log.error(
1537
+ "%s [_start_listener] Failed to start FastAPI/Uvicorn server: %s",
1538
+ self.log_identifier,
1539
+ e,
1540
+ )
1541
+ self.stop_signal.set()
1542
+ raise
1543
+
1544
+ def publish_a2a(
1545
+ self, topic: str, payload: dict, user_properties: dict | None = None
1546
+ ):
1547
+ """
1548
+ Publishes an A2A message using the SAC App's send_message method.
1549
+ This method can be called from FastAPI handlers (via dependency injection).
1550
+ It's thread-safe as it uses the SAC App instance.
1551
+ """
1552
+ log.debug(f"[publish_a2a] Starting to publish message to topic: {topic}")
1553
+ log.debug(
1554
+ f"[publish_a2a] Payload type: {type(payload)}, size: {len(str(payload))} chars"
1555
+ )
1556
+ log.debug(f"[publish_a2a] User properties: {user_properties}")
1557
+
1558
+ try:
1559
+ super().publish_a2a_message(payload, topic, user_properties)
1560
+ log.debug(
1561
+ f"[publish_a2a] Successfully called super().publish_a2a_message for topic: {topic}"
1562
+ )
1563
+ except Exception as e:
1564
+ log.error(f"[publish_a2a] Exception in publish_a2a: {e}", exc_info=True)
1565
+ raise
1566
+
1567
+ def _cleanup_visualization_locks(self):
1568
+ """Remove locks for closed event loops to prevent memory leaks."""
1569
+ with self._visualization_locks_lock:
1570
+ closed_loops = [
1571
+ loop for loop in self._visualization_locks if loop.is_closed()
1572
+ ]
1573
+ for loop in closed_loops:
1574
+ del self._visualization_locks[loop]
1575
+ log.debug(
1576
+ "%s Cleaned up visualization lock for closed event loop %s",
1577
+ self.log_identifier,
1578
+ id(loop),
1579
+ )
1580
+
1581
+ def cleanup(self):
1582
+ """Gracefully shuts down the component and the FastAPI server."""
1583
+ log.info("%s Cleaning up Web UI Backend Component...", self.log_identifier)
1584
+
1585
+ # Cancel timers
1586
+ self.cancel_timer(self._sse_cleanup_timer_id)
1587
+ if self._data_retention_timer_id:
1588
+ self.cancel_timer(self._data_retention_timer_id)
1589
+ log.info("%s Cancelled data retention cleanup timer.", self.log_identifier)
1590
+
1591
+ if self._background_task_monitor_timer_id:
1592
+ self.cancel_timer(self._background_task_monitor_timer_id)
1593
+ log.info("%s Cancelled background task monitor timer.", self.log_identifier)
1594
+
1595
+ # Clean up data retention service
1596
+ if self.data_retention_service:
1597
+ self.data_retention_service = None
1598
+ log.info("%s Data retention service cleaned up.", self.log_identifier)
1599
+
1600
+ # Clean up background task monitor
1601
+ if self.background_task_monitor:
1602
+ self.background_task_monitor = None
1603
+ log.info("%s Background task monitor cleaned up.", self.log_identifier)
1604
+
1605
+ self.cancel_timer(self.health_check_timer_id)
1606
+ log.info("%s Cleaning up visualization resources...", self.log_identifier)
1607
+ if self._visualization_message_queue:
1608
+ self._visualization_message_queue.put(None)
1609
+ if self._task_logger_queue:
1610
+ self._task_logger_queue.put(None)
1611
+
1612
+ if (
1613
+ self._visualization_processor_task
1614
+ and not self._visualization_processor_task.done()
1615
+ ):
1616
+ log.info(
1617
+ "%s Cancelling visualization processor task...", self.log_identifier
1618
+ )
1619
+ self._visualization_processor_task.cancel()
1620
+
1621
+ if (
1622
+ self._task_logger_processor_task
1623
+ and not self._task_logger_processor_task.done()
1624
+ ):
1625
+ log.info("%s Cancelling task logger processor task...", self.log_identifier)
1626
+ self._task_logger_processor_task.cancel()
1627
+
1628
+ if self._visualization_internal_app:
1629
+ log.info(
1630
+ "%s Cleaning up internal visualization app...", self.log_identifier
1631
+ )
1632
+ try:
1633
+ self._visualization_internal_app.cleanup()
1634
+ except Exception as e:
1635
+ log.error(
1636
+ "%s Error cleaning up internal visualization app: %s",
1637
+ self.log_identifier,
1638
+ e,
1639
+ )
1640
+
1641
+ if self._task_logger_internal_app:
1642
+ log.info("%s Cleaning up internal task logger app...", self.log_identifier)
1643
+ try:
1644
+ self._task_logger_internal_app.cleanup()
1645
+ except Exception as e:
1646
+ log.error(
1647
+ "%s Error cleaning up internal task logger app: %s",
1648
+ self.log_identifier,
1649
+ e,
1650
+ )
1651
+
1652
+ self._active_visualization_streams.clear()
1653
+ self._global_visualization_subscriptions.clear()
1654
+ self._cleanup_visualization_locks()
1655
+ log.info("%s Visualization resources cleaned up.", self.log_identifier)
1656
+
1657
+ super().cleanup()
1658
+
1659
+ if self.fastapi_thread and self.fastapi_thread.is_alive():
1660
+ log.info(
1661
+ "%s Waiting for FastAPI server thread to exit...", self.log_identifier
1662
+ )
1663
+ self.fastapi_thread.join(timeout=10)
1664
+ if self.fastapi_thread.is_alive():
1665
+ log.warning(
1666
+ "%s FastAPI server thread did not exit gracefully.",
1667
+ self.log_identifier,
1668
+ )
1669
+
1670
+ if self.sse_manager:
1671
+ log.info(
1672
+ "%s Closing active SSE connections (best effort)...",
1673
+ self.log_identifier,
1674
+ )
1675
+ try:
1676
+ asyncio.run(self.sse_manager.close_all())
1677
+ except Exception as sse_close_err:
1678
+ log.error(
1679
+ "%s Error closing SSE connections during cleanup: %s",
1680
+ self.log_identifier,
1681
+ sse_close_err,
1682
+ )
1683
+
1684
+ log.info("%s Web UI Backend Component cleanup finished.", self.log_identifier)
1685
+
1686
+ def _infer_visualization_event_details(
1687
+ self, topic: str, payload: dict[str, Any]
1688
+ ) -> dict[str, Any]:
1689
+ """
1690
+ Infers details for the visualization SSE payload from the Solace topic and A2A message.
1691
+ This version is updated to parse the official A2A SDK message formats.
1692
+ """
1693
+ details = {
1694
+ "direction": "unknown",
1695
+ "source_entity": "unknown",
1696
+ "target_entity": "unknown",
1697
+ "debug_type": "unknown",
1698
+ "message_id": payload.get("id"),
1699
+ "task_id": None,
1700
+ "payload_summary": {
1701
+ "method": payload.get("method", "N/A"),
1702
+ "params_preview": None,
1703
+ },
1704
+ }
1705
+
1706
+ # --- Phase 1: Parse the payload to extract core info ---
1707
+ try:
1708
+ # Handle SAM Events (system events)
1709
+ event_type = payload.get("event_type")
1710
+ if event_type:
1711
+ details["direction"] = "system_event"
1712
+ details["debug_type"] = "sam_event"
1713
+ details["payload_summary"]["method"] = event_type
1714
+ details["source_entity"] = payload.get("source_component", "unknown")
1715
+ details["target_entity"] = "system"
1716
+ return details
1717
+
1718
+ # Try to parse as a JSON-RPC response first
1719
+ if "result" in payload or "error" in payload:
1720
+ rpc_response = JSONRPCResponse.model_validate(payload)
1721
+ result = a2a.get_response_result(rpc_response)
1722
+ error = a2a.get_response_error(rpc_response)
1723
+ details["message_id"] = a2a.get_response_id(rpc_response)
1724
+
1725
+ if result:
1726
+ kind = getattr(result, "kind", None)
1727
+ details["direction"] = kind or "response"
1728
+ details["task_id"] = getattr(result, "task_id", None) or getattr(
1729
+ result, "id", None
1730
+ )
1731
+
1732
+ if isinstance(result, TaskStatusUpdateEvent):
1733
+ details["source_entity"] = (
1734
+ result.metadata.get("agent_name")
1735
+ if result.metadata
1736
+ else None
1737
+ )
1738
+ message = a2a.get_message_from_status_update(result)
1739
+ if message:
1740
+ if not details["source_entity"]:
1741
+ details["source_entity"] = (
1742
+ message.metadata.get("agent_name")
1743
+ if message.metadata
1744
+ else None
1745
+ )
1746
+ data_parts = a2a.get_data_parts_from_message(message)
1747
+ if data_parts:
1748
+ details["debug_type"] = data_parts[0].data.get(
1749
+ "type", "unknown"
1750
+ )
1751
+ elif a2a.get_text_from_message(message):
1752
+ details["debug_type"] = "streaming_text"
1753
+ elif isinstance(result, Task):
1754
+ details["source_entity"] = (
1755
+ result.metadata.get("agent_name")
1756
+ if result.metadata
1757
+ else None
1758
+ )
1759
+ elif isinstance(result, TaskArtifactUpdateEvent):
1760
+ artifact = a2a.get_artifact_from_artifact_update(result)
1761
+ if artifact:
1762
+ details["source_entity"] = (
1763
+ artifact.metadata.get("agent_name")
1764
+ if artifact.metadata
1765
+ else None
1766
+ )
1767
+ elif error:
1768
+ details["direction"] = "error_response"
1769
+ details["task_id"] = (
1770
+ error.data.get("taskId")
1771
+ if isinstance(error.data, dict)
1772
+ else None
1773
+ )
1774
+ details["debug_type"] = "error"
1775
+
1776
+ # Try to parse as a JSON-RPC request
1777
+ elif "method" in payload:
1778
+ rpc_request = A2ARequest.model_validate(payload)
1779
+ method = a2a.get_request_method(rpc_request)
1780
+ details["direction"] = "request"
1781
+ details["payload_summary"]["method"] = method
1782
+ details["message_id"] = a2a.get_request_id(rpc_request)
1783
+
1784
+ if method in ["message/send", "message/stream"]:
1785
+ details["debug_type"] = method
1786
+ message = a2a.get_message_from_send_request(rpc_request)
1787
+ details["task_id"] = a2a.get_request_id(rpc_request)
1788
+ if message:
1789
+ details["target_entity"] = (
1790
+ message.metadata.get("agent_name")
1791
+ if message.metadata
1792
+ else None
1793
+ )
1794
+ elif method == "tasks/cancel":
1795
+ details["task_id"] = a2a.get_task_id_from_cancel_request(
1796
+ rpc_request
1797
+ )
1798
+
1799
+ # Handle Discovery messages (which are not JSON-RPC)
1800
+ elif "/a2a/v1/discovery/" in topic:
1801
+ agent_card = AgentCard.model_validate(payload)
1802
+ details["direction"] = "discovery"
1803
+ details["source_entity"] = agent_card.name
1804
+ details["target_entity"] = "broadcast"
1805
+ details["message_id"] = None # Discovery has no ID
1806
+
1807
+ except Exception as e:
1808
+ log.warning(
1809
+ "[%s] Failed to parse A2A payload for visualization details: %s",
1810
+ self.log_identifier,
1811
+ e,
1812
+ )
1813
+
1814
+ # --- Phase 2: Refine details using topic information as a fallback ---
1815
+ if details["direction"] == "unknown":
1816
+ if "request" in topic:
1817
+ details["direction"] = "request"
1818
+ elif "response" in topic:
1819
+ details["direction"] = "response"
1820
+ elif "status" in topic:
1821
+ details["direction"] = "status_update"
1822
+ # TEMP - add debug_type based on the type in the data
1823
+ details["debug_type"] = "unknown"
1824
+
1825
+ # --- Phase 3: Create a payload summary ---
1826
+ try:
1827
+ summary_source = (
1828
+ payload.get("result")
1829
+ or payload.get("params")
1830
+ or payload.get("error")
1831
+ or payload
1832
+ )
1833
+ summary_str = json.dumps(summary_source)
1834
+ details["payload_summary"]["params_preview"] = (
1835
+ (summary_str[:100] + "...") if len(summary_str) > 100 else summary_str
1836
+ )
1837
+ except Exception:
1838
+ details["payload_summary"][
1839
+ "params_preview"
1840
+ ] = "[Could not serialize payload]"
1841
+
1842
+ return details
1843
+
1844
+ def _extract_involved_agents_for_viz(
1845
+ self, topic: str, payload_dict: dict[str, Any]
1846
+ ) -> set[str]:
1847
+ """
1848
+ Extracts agent names involved in a message from its topic and payload.
1849
+ """
1850
+ agents: set[str] = set()
1851
+ log_id_prefix = f"{self.log_identifier}[ExtractAgentsViz]"
1852
+
1853
+ topic_agent_match = re.match(
1854
+ rf"^{re.escape(self.namespace)}/a2a/v1/agent/(?:request|response|status)/([^/]+)",
1855
+ topic,
1856
+ )
1857
+ if topic_agent_match:
1858
+ agents.add(topic_agent_match.group(1))
1859
+ log.debug(
1860
+ "%s Found agent '%s' in topic.",
1861
+ log_id_prefix,
1862
+ topic_agent_match.group(1),
1863
+ )
1864
+
1865
+ if isinstance(payload_dict, dict):
1866
+ if (
1867
+ "name" in payload_dict
1868
+ and "capabilities" in payload_dict
1869
+ and "skills" in payload_dict
1870
+ ):
1871
+ try:
1872
+ card = AgentCard(**payload_dict)
1873
+ if card.name:
1874
+ agents.add(card.name)
1875
+ log.debug(
1876
+ "%s Found agent '%s' in AgentCard payload.",
1877
+ log_id_prefix,
1878
+ card.name,
1879
+ )
1880
+ except Exception:
1881
+ pass
1882
+ result = payload_dict.get("result")
1883
+ if isinstance(result, dict):
1884
+ status_info = result.get("status")
1885
+ if isinstance(status_info, dict):
1886
+ message_info = status_info.get("message")
1887
+ if isinstance(message_info, dict):
1888
+ metadata = message_info.get("metadata")
1889
+ if isinstance(metadata, dict) and "agent_name" in metadata:
1890
+ if metadata["agent_name"]:
1891
+ agents.add(metadata["agent_name"])
1892
+ log.debug(
1893
+ "%s Found agent '%s' in status.message.metadata.",
1894
+ log_id_prefix,
1895
+ metadata["agent_name"],
1896
+ )
1897
+
1898
+ artifact_info = result.get("artifact")
1899
+ if isinstance(artifact_info, dict):
1900
+ metadata = artifact_info.get("metadata")
1901
+ if isinstance(metadata, dict) and "agent_name" in metadata:
1902
+ if metadata["agent_name"]:
1903
+ agents.add(metadata["agent_name"])
1904
+ log.debug(
1905
+ "%s Found agent '%s' in artifact.metadata.",
1906
+ log_id_prefix,
1907
+ metadata["agent_name"],
1908
+ )
1909
+
1910
+ params = payload_dict.get("params")
1911
+ if isinstance(params, dict):
1912
+ message_info = params.get("message")
1913
+ if isinstance(message_info, dict):
1914
+ metadata = message_info.get("metadata")
1915
+ if isinstance(metadata, dict) and "agent_name" in metadata:
1916
+ if metadata["agent_name"]:
1917
+ agents.add(metadata["agent_name"])
1918
+ log.debug(
1919
+ "%s Found agent '%s' in params.message.metadata.",
1920
+ log_id_prefix,
1921
+ metadata["agent_name"],
1922
+ )
1923
+
1924
+ if not agents:
1925
+ log.debug(
1926
+ "%s No specific agents identified from topic '%s' or payload.",
1927
+ log_id_prefix,
1928
+ topic,
1929
+ )
1930
+ return agents
1931
+
1932
+ def get_agent_registry(self) -> AgentRegistry:
1933
+ return self.agent_registry
1934
+
1935
+ def _check_agent_health(self):
1936
+ """
1937
+ Checks the health of peer agents and de-registers unresponsive ones.
1938
+ This is called periodically by the health check timer.
1939
+ Uses TTL-based expiration to determine if an agent is unresponsive.
1940
+ """
1941
+
1942
+ log.debug("%s Performing agent health check...", self.log_identifier)
1943
+
1944
+ # Get TTL from configuration or use default from constants
1945
+ from ...common.constants import (
1946
+ HEALTH_CHECK_INTERVAL_SECONDS,
1947
+ HEALTH_CHECK_TTL_SECONDS,
1948
+ )
1949
+
1950
+ ttl_seconds = self.get_config(
1951
+ "agent_health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS
1952
+ )
1953
+ health_check_interval = self.get_config(
1954
+ "agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS
1955
+ )
1956
+
1957
+ log.debug(
1958
+ "%s Health check configuration: interval=%d seconds, TTL=%d seconds",
1959
+ self.log_identifier,
1960
+ health_check_interval,
1961
+ ttl_seconds,
1962
+ )
1963
+
1964
+ # Validate configuration values
1965
+ if (
1966
+ ttl_seconds <= 0
1967
+ or health_check_interval <= 0
1968
+ or ttl_seconds < health_check_interval
1969
+ ):
1970
+ log.error(
1971
+ "%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
1972
+ self.log_identifier,
1973
+ ttl_seconds,
1974
+ health_check_interval,
1975
+ )
1976
+ raise ValueError(
1977
+ f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval."
1978
+ )
1979
+
1980
+ # Get all agent names from the registry
1981
+ agent_names = self.agent_registry.get_agent_names()
1982
+ total_agents = len(agent_names)
1983
+ agents_to_deregister = []
1984
+
1985
+ log.debug(
1986
+ "%s Checking health of %d peer agents", self.log_identifier, total_agents
1987
+ )
1988
+
1989
+ for agent_name in agent_names:
1990
+ # Check if the agent's TTL has expired
1991
+ is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(
1992
+ agent_name, ttl_seconds
1993
+ )
1994
+
1995
+ if is_expired:
1996
+ log.warning(
1997
+ "%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
1998
+ self.log_identifier,
1999
+ agent_name,
2000
+ time_since_last_seen,
2001
+ ttl_seconds,
2002
+ )
2003
+ agents_to_deregister.append(agent_name)
2004
+
2005
+ # De-register unresponsive agents
2006
+ for agent_name in agents_to_deregister:
2007
+ self._deregister_agent(agent_name)
2008
+
2009
+ log.debug(
2010
+ "%s Agent health check completed. Total agents: %d, De-registered: %d",
2011
+ self.log_identifier,
2012
+ total_agents,
2013
+ len(agents_to_deregister),
2014
+ )
2015
+
2016
+ def _deregister_agent(self, agent_name: str):
2017
+ """
2018
+ De-registers an agent from the registry and publishes a de-registration event.
2019
+ """
2020
+ # Remove from registry
2021
+ self.agent_registry.remove_agent(agent_name)
2022
+
2023
+ def get_sse_manager(self) -> SSEManager:
2024
+ return self.sse_manager
2025
+
2026
+ def get_session_manager(self) -> SessionManager:
2027
+ return self.session_manager
2028
+
2029
+ def get_task_logger_service(self) -> TaskLoggerService | None:
2030
+ """Returns the shared TaskLoggerService instance."""
2031
+ return self.task_logger_service
2032
+
2033
+ def get_namespace(self) -> str:
2034
+ return self.namespace
2035
+
2036
+ def get_gateway_id(self) -> str:
2037
+ """Returns the unique identifier for this gateway instance."""
2038
+ return self.gateway_id
2039
+
2040
+ def get_cors_origins(self) -> list[str]:
2041
+ return self.cors_allowed_origins
2042
+
2043
+ def get_shared_artifact_service(self) -> BaseArtifactService | None:
2044
+ return self.shared_artifact_service
2045
+
2046
+ def get_embed_config(self) -> dict[str, Any]:
2047
+ """Returns embed-related configuration needed by dependencies."""
2048
+ return {
2049
+ "enable_embed_resolution": self.enable_embed_resolution,
2050
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
2051
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
2052
+ }
2053
+
2054
+ def get_core_a2a_service(self) -> CoreA2AService:
2055
+ """Returns the CoreA2AService instance."""
2056
+ return self.core_a2a_service
2057
+
2058
+ def get_config_resolver(self) -> ConfigResolver:
2059
+ """Returns the instance of the ConfigResolver."""
2060
+ return self._config_resolver
2061
+
2062
+ def _start_listener(self) -> None:
2063
+ """
2064
+ GDK Hook: Starts the FastAPI/Uvicorn server.
2065
+ This method is called by BaseGatewayComponent.run().
2066
+ """
2067
+ self._start_fastapi_server()
2068
+
2069
+ def _stop_listener(self) -> None:
2070
+ """
2071
+ GDK Hook: Signals the Uvicorn server to shut down.
2072
+ This method is called by BaseGatewayComponent.cleanup().
2073
+ """
2074
+ log.info(
2075
+ "%s _stop_listener called. Signaling Uvicorn server to exit.",
2076
+ self.log_identifier,
2077
+ )
2078
+ if self.uvicorn_server:
2079
+ self.uvicorn_server.should_exit = True
2080
+ pass
2081
+
2082
+ async def _translate_external_input(
2083
+ self, external_event_data: dict[str, Any]
2084
+ ) -> tuple[str, list[ContentPart], dict[str, Any]]:
2085
+ """
2086
+ Translates raw HTTP request data (from FastAPI form) into A2A task parameters.
2087
+
2088
+ Args:
2089
+ external_event_data: A dictionary containing data from the HTTP request,
2090
+ expected to have keys like 'agent_name', 'message',
2091
+ 'files' (List[UploadFile]), 'client_id', 'a2a_session_id'.
2092
+
2093
+ Returns:
2094
+ A tuple containing:
2095
+ - target_agent_name (str): The name of the A2A agent to target.
2096
+ - a2a_parts (List[ContentPart]): A list of unwrapped A2A Part objects.
2097
+ - external_request_context (Dict[str, Any]): Context for TaskContextManager.
2098
+ """
2099
+ log_id_prefix = f"{self.log_identifier}[TranslateInput]"
2100
+ log.debug(
2101
+ "%s Received external event data: %s",
2102
+ log_id_prefix,
2103
+ {k: type(v) for k, v in external_event_data.items()},
2104
+ )
2105
+
2106
+ target_agent_name: str = external_event_data.get("agent_name")
2107
+ user_message: str = external_event_data.get("message", "")
2108
+ files: list[UploadFile] | None = external_event_data.get("files")
2109
+ client_id: str = external_event_data.get("client_id")
2110
+ a2a_session_id: str = external_event_data.get("a2a_session_id")
2111
+ if not target_agent_name:
2112
+ raise ValueError("Target agent name is missing in external_event_data.")
2113
+ if not client_id or not a2a_session_id:
2114
+ raise ValueError(
2115
+ "Client ID or A2A Session ID is missing in external_event_data."
2116
+ )
2117
+
2118
+ a2a_parts: list[ContentPart] = []
2119
+
2120
+ if files:
2121
+ for upload_file in files:
2122
+ try:
2123
+ content_bytes = await upload_file.read()
2124
+ if not content_bytes:
2125
+ log.warning(
2126
+ "%s Skipping empty uploaded file: %s",
2127
+ log_id_prefix,
2128
+ upload_file.filename,
2129
+ )
2130
+ continue
2131
+
2132
+ # The BaseGatewayComponent will handle normalization based on policy.
2133
+ # Here, we just create the FilePart with inline bytes.
2134
+ file_part = a2a.create_file_part_from_bytes(
2135
+ content_bytes=content_bytes,
2136
+ name=upload_file.filename,
2137
+ mime_type=upload_file.content_type,
2138
+ )
2139
+ a2a_parts.append(file_part)
2140
+ log.info(
2141
+ "%s Created inline FilePart for uploaded file: %s (%d bytes)",
2142
+ log_id_prefix,
2143
+ upload_file.filename,
2144
+ len(content_bytes),
2145
+ )
2146
+
2147
+ except Exception as e:
2148
+ log.exception(
2149
+ "%s Error processing uploaded file %s: %s",
2150
+ log_id_prefix,
2151
+ upload_file.filename,
2152
+ e,
2153
+ )
2154
+ finally:
2155
+ await upload_file.close()
2156
+
2157
+ if user_message:
2158
+ a2a_parts.append(a2a.create_text_part(text=user_message))
2159
+
2160
+ external_request_context = {
2161
+ "app_name_for_artifacts": self.gateway_id,
2162
+ "user_id_for_artifacts": client_id,
2163
+ "a2a_session_id": a2a_session_id,
2164
+ "user_id_for_a2a": client_id,
2165
+ "target_agent_name": target_agent_name,
2166
+ }
2167
+ log.debug(
2168
+ "%s Translated input. Target: %s, Parts: %d, Context: %s",
2169
+ log_id_prefix,
2170
+ target_agent_name,
2171
+ len(a2a_parts),
2172
+ external_request_context,
2173
+ )
2174
+ return target_agent_name, a2a_parts, external_request_context
2175
+
2176
+ async def _send_update_to_external(
2177
+ self,
2178
+ external_request_context: dict[str, Any],
2179
+ event_data: TaskStatusUpdateEvent | TaskArtifactUpdateEvent,
2180
+ is_final_chunk_of_update: bool,
2181
+ ) -> None:
2182
+ """
2183
+ Sends an intermediate update (TaskStatusUpdateEvent or TaskArtifactUpdateEvent)
2184
+ to the external platform (Web UI via SSE) and stores agent messages in the database.
2185
+ """
2186
+ log_id_prefix = f"{self.log_identifier}[SendUpdate]"
2187
+ sse_task_id = external_request_context.get("a2a_task_id_for_event")
2188
+ a2a_task_id = event_data.task_id
2189
+
2190
+ log.debug(
2191
+ "%s _send_update_to_external called with event_type: %s",
2192
+ log_id_prefix,
2193
+ type(event_data).__name__,
2194
+ )
2195
+
2196
+ if not sse_task_id:
2197
+ log.error(
2198
+ "%s Cannot send update: 'a2a_task_id_for_event' missing from external_request_context.",
2199
+ log_id_prefix,
2200
+ )
2201
+ return
2202
+
2203
+ try:
2204
+ from solace_agent_mesh_enterprise.auth.input_required import (
2205
+ handle_input_required_request,
2206
+ )
2207
+
2208
+ event_data = handle_input_required_request(event_data, sse_task_id, self)
2209
+ except ImportError:
2210
+ pass
2211
+
2212
+ log.debug(
2213
+ "%s Sending update for A2A Task ID %s to SSE Task ID %s. Final chunk: %s",
2214
+ log_id_prefix,
2215
+ a2a_task_id,
2216
+ sse_task_id,
2217
+ is_final_chunk_of_update,
2218
+ )
2219
+
2220
+ sse_event_type = "status_update"
2221
+ if isinstance(event_data, TaskArtifactUpdateEvent):
2222
+ sse_event_type = "artifact_update"
2223
+
2224
+ sse_payload_model = a2a.create_success_response(
2225
+ result=event_data, request_id=a2a_task_id
2226
+ )
2227
+ sse_payload = sse_payload_model.model_dump(by_alias=True, exclude_none=True)
2228
+
2229
+ try:
2230
+ await self.sse_manager.send_event(
2231
+ task_id=sse_task_id, event_data=sse_payload, event_type=sse_event_type
2232
+ )
2233
+ log.debug(
2234
+ "%s Successfully sent %s via SSE for A2A Task ID %s.",
2235
+ log_id_prefix,
2236
+ sse_event_type,
2237
+ a2a_task_id,
2238
+ )
2239
+
2240
+ # Note: Agent message storage is handled in _send_final_response_to_external
2241
+ # to avoid duplicate storage of intermediate status updates
2242
+
2243
+ except Exception as e:
2244
+ log.exception(
2245
+ "%s Failed to send %s via SSE for A2A Task ID %s: %s",
2246
+ log_id_prefix,
2247
+ sse_event_type,
2248
+ a2a_task_id,
2249
+ e,
2250
+ )
2251
+
2252
+ async def _send_final_response_to_external(
2253
+ self, external_request_context: dict[str, Any], task_data: Task
2254
+ ) -> None:
2255
+ """
2256
+ Sends the final A2A Task result to the external platform (Web UI via SSE).
2257
+ """
2258
+ log_id_prefix = f"{self.log_identifier}[SendFinalResponse]"
2259
+ sse_task_id = external_request_context.get("a2a_task_id_for_event")
2260
+ a2a_task_id = task_data.id
2261
+
2262
+ log.debug("%s _send_final_response_to_external called", log_id_prefix)
2263
+
2264
+ if not sse_task_id:
2265
+ log.error(
2266
+ "%s Cannot send final response: 'a2a_task_id_for_event' missing from external_request_context.",
2267
+ log_id_prefix,
2268
+ )
2269
+ return
2270
+
2271
+ log.info(
2272
+ "%s Sending final response for A2A Task ID %s to SSE Task ID %s.",
2273
+ log_id_prefix,
2274
+ a2a_task_id,
2275
+ sse_task_id,
2276
+ )
2277
+
2278
+ sse_payload_model = a2a.create_success_response(
2279
+ result=task_data, request_id=a2a_task_id
2280
+ )
2281
+ sse_payload = sse_payload_model.model_dump(by_alias=True, exclude_none=True)
2282
+
2283
+ try:
2284
+ await self.sse_manager.send_event(
2285
+ task_id=sse_task_id, event_data=sse_payload, event_type="final_response"
2286
+ )
2287
+ log.debug(
2288
+ "%s Successfully sent final_response via SSE for A2A Task ID %s.",
2289
+ log_id_prefix,
2290
+ a2a_task_id,
2291
+ )
2292
+
2293
+ except Exception as e:
2294
+ log.exception(
2295
+ "%s Failed to send final_response via SSE for A2A Task ID %s: %s",
2296
+ log_id_prefix,
2297
+ a2a_task_id,
2298
+ e,
2299
+ )
2300
+ finally:
2301
+ await self.sse_manager.close_all_for_task(sse_task_id)
2302
+ log.info(
2303
+ "%s Closed SSE connections for SSE Task ID %s.",
2304
+ log_id_prefix,
2305
+ sse_task_id,
2306
+ )
2307
+
2308
+
2309
+ async def _send_error_to_external(
2310
+ self, external_request_context: dict[str, Any], error_data: JSONRPCError
2311
+ ) -> None:
2312
+ """
2313
+ Sends an error notification to the external platform (Web UI via SSE).
2314
+ """
2315
+ log_id_prefix = f"{self.log_identifier}[SendError]"
2316
+ sse_task_id = external_request_context.get("a2a_task_id_for_event")
2317
+
2318
+ if not sse_task_id:
2319
+ log.error(
2320
+ "%s Cannot send error: 'a2a_task_id_for_event' missing from external_request_context.",
2321
+ log_id_prefix,
2322
+ )
2323
+ return
2324
+
2325
+ log.debug(
2326
+ "%s Sending error to SSE Task ID %s. Error: %s",
2327
+ log_id_prefix,
2328
+ sse_task_id,
2329
+ error_data,
2330
+ )
2331
+
2332
+ sse_payload_model = a2a.create_error_response(
2333
+ error=error_data,
2334
+ request_id=external_request_context.get("original_rpc_id", sse_task_id),
2335
+ )
2336
+ sse_payload = sse_payload_model.model_dump(by_alias=True, exclude_none=True)
2337
+
2338
+ try:
2339
+ await self.sse_manager.send_event(
2340
+ task_id=sse_task_id, event_data=sse_payload, event_type="final_response"
2341
+ )
2342
+ log.info(
2343
+ "%s Successfully sent A2A error as 'final_response' via SSE for SSE Task ID %s.",
2344
+ log_id_prefix,
2345
+ sse_task_id,
2346
+ )
2347
+ except Exception as e:
2348
+ log.exception(
2349
+ "%s Failed to send error via SSE for SSE Task ID %s: %s",
2350
+ log_id_prefix,
2351
+ sse_task_id,
2352
+ e,
2353
+ )
2354
+ finally:
2355
+ await self.sse_manager.close_all_for_task(sse_task_id)
2356
+ log.info(
2357
+ "%s Closed SSE connections for SSE Task ID %s after error.",
2358
+ log_id_prefix,
2359
+ sse_task_id,
2360
+ )