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,2030 @@
1
+ """
2
+ Base Component class for Gateway implementations in the Solace AI Connector.
3
+ """
4
+
5
+ import logging
6
+ import asyncio
7
+ import base64
8
+ import queue
9
+ import re
10
+ import uuid
11
+ from datetime import datetime, timezone
12
+ from typing import Any, Dict, Optional, List, Tuple, Union
13
+
14
+ from google.adk.artifacts import BaseArtifactService
15
+
16
+ from ...common.agent_registry import AgentRegistry
17
+ from ...common.sac.sam_component_base import SamComponentBase
18
+ from ...core_a2a.service import CoreA2AService
19
+ from ...agent.adk.services import initialize_artifact_service
20
+ from ...common.services.identity_service import (
21
+ BaseIdentityService,
22
+ create_identity_service,
23
+ )
24
+ from .task_context import TaskContextManager
25
+ from ...common.a2a.types import ContentPart
26
+ from ...common.utils.rbac_utils import validate_agent_access
27
+ from a2a.types import (
28
+ Message as A2AMessage,
29
+ AgentCard,
30
+ JSONRPCResponse,
31
+ Task,
32
+ TaskStatusUpdateEvent,
33
+ TaskArtifactUpdateEvent,
34
+ JSONRPCError,
35
+ TextPart,
36
+ DataPart,
37
+ FilePart,
38
+ FileWithBytes,
39
+ Artifact as A2AArtifact,
40
+ )
41
+ from ...common import a2a
42
+ from ...common.utils.embeds import (
43
+ resolve_embeds_in_string,
44
+ evaluate_embed,
45
+ LATE_EMBED_TYPES,
46
+ EARLY_EMBED_TYPES,
47
+ resolve_embeds_recursively_in_string,
48
+ )
49
+ from ...common.utils.embeds.types import ResolutionMode
50
+ from ...agent.utils.artifact_helpers import (
51
+ load_artifact_content_or_metadata,
52
+ format_artifact_uri,
53
+ )
54
+ from ...common.utils.mime_helpers import is_text_based_mime_type
55
+ from solace_ai_connector.common.message import (
56
+ Message as SolaceMessage,
57
+ )
58
+ from solace_ai_connector.common.event import Event, EventType
59
+ from abc import abstractmethod
60
+
61
+ from ...common.middleware.registry import MiddlewareRegistry
62
+
63
+ log = logging.getLogger(__name__)
64
+
65
+ info = {
66
+ "class_name": "BaseGatewayComponent",
67
+ "description": (
68
+ "Abstract base component for A2A gateways. Handles common service "
69
+ "initialization and provides a framework for platform-specific logic. "
70
+ "Configuration is typically derived from the parent BaseGatewayApp's app_config."
71
+ ),
72
+ "config_parameters": [],
73
+ "input_schema": {
74
+ "type": "object",
75
+ "description": "Not typically used directly; component reacts to events from its input queue.",
76
+ },
77
+ "output_schema": {
78
+ "type": "object",
79
+ "description": "Not typically used directly; component sends data to external systems.",
80
+ },
81
+ }
82
+
83
+
84
+ class BaseGatewayComponent(SamComponentBase):
85
+ """
86
+ Abstract base class for Gateway components.
87
+
88
+ Initializes shared services and manages the core lifecycle for processing
89
+ A2A messages and interacting with an external communication platform.
90
+ """
91
+
92
+ _RESOLVE_EMBEDS_IN_FINAL_RESPONSE = False
93
+
94
+ def get_config(self, key: str, default: Any = None) -> Any:
95
+ """
96
+ Overrides the default get_config to first look inside the nested
97
+ 'app_config' dictionary that BaseGatewayApp places in the component_config.
98
+ This is the primary way gateway components should access their configuration.
99
+ """
100
+ if "app_config" in self.component_config:
101
+ value = self.component_config["app_config"].get(key)
102
+ if value is not None:
103
+ return value
104
+
105
+ return super().get_config(key, default)
106
+
107
+ def __init__(
108
+ self,
109
+ resolve_artifact_uris_in_gateway: bool = True,
110
+ supports_inline_artifact_resolution: bool = False,
111
+ filter_tool_data_parts: bool = True,
112
+ **kwargs: Any
113
+ ):
114
+ """
115
+ Initialize the BaseGatewayComponent.
116
+
117
+ Args:
118
+ resolve_artifact_uris_in_gateway: If True, resolves artifact URIs before sending to external.
119
+ supports_inline_artifact_resolution: If True, SIGNAL_ARTIFACT_RETURN embeds are converted
120
+ to FileParts during embed resolution. If False (default), signals are passed through
121
+ for the gateway to handle manually. Use False for legacy gateways (e.g., Slack),
122
+ True for modern gateways that support inline artifact rendering (e.g., HTTP SSE).
123
+ filter_tool_data_parts: If True (default), filters out tool-related DataParts (tool_call,
124
+ tool_result, etc.) from final Task messages before sending to gateway. Use True for
125
+ gateways that don't want to display internal tool execution details (e.g., Slack),
126
+ False for gateways that display all parts (e.g., HTTP SSE Web UI).
127
+ **kwargs: Additional arguments passed to parent class.
128
+ """
129
+ super().__init__(info, **kwargs)
130
+ self.resolve_artifact_uris_in_gateway = resolve_artifact_uris_in_gateway
131
+ self.supports_inline_artifact_resolution = supports_inline_artifact_resolution
132
+ self.filter_tool_data_parts = filter_tool_data_parts
133
+ log.info("%s Initializing Base Gateway Component...", self.log_identifier)
134
+
135
+ try:
136
+ # Note: self.namespace and self.max_message_size_bytes are initialized in SamComponentBase
137
+ self.gateway_id: str = self.get_config("gateway_id")
138
+ if not self.gateway_id:
139
+ raise ValueError("Gateway ID must be configured in the app_config.")
140
+
141
+ self.enable_embed_resolution: bool = self.get_config(
142
+ "enable_embed_resolution", True
143
+ )
144
+ self.gateway_max_artifact_resolve_size_bytes: int = self.get_config(
145
+ "gateway_max_artifact_resolve_size_bytes"
146
+ )
147
+ self.gateway_recursive_embed_depth: int = self.get_config(
148
+ "gateway_recursive_embed_depth"
149
+ )
150
+ self.artifact_handling_mode: str = self.get_config(
151
+ "artifact_handling_mode", "embed"
152
+ )
153
+ _ = self.get_config("artifact_service")
154
+
155
+ log.info(
156
+ "%s Retrieved common configs: Namespace=%s, GatewayID=%s",
157
+ self.log_identifier,
158
+ self.namespace,
159
+ self.gateway_id,
160
+ )
161
+
162
+ except Exception as e:
163
+ log.error(
164
+ "%s Failed to retrieve essential configuration: %s",
165
+ self.log_identifier,
166
+ e,
167
+ )
168
+ raise ValueError(f"Configuration retrieval error: {e}") from e
169
+
170
+ self.agent_registry: AgentRegistry = AgentRegistry()
171
+ self.core_a2a_service: CoreA2AService = CoreA2AService(
172
+ agent_registry=self.agent_registry, namespace=self.namespace
173
+ )
174
+ self.shared_artifact_service: Optional[BaseArtifactService] = (
175
+ initialize_artifact_service(self)
176
+ )
177
+
178
+ self.task_context_manager: TaskContextManager = TaskContextManager()
179
+ self.internal_event_queue: queue.Queue = queue.Queue()
180
+
181
+ identity_service_config = self.get_config("identity_service")
182
+ self.identity_service: Optional[BaseIdentityService] = create_identity_service(
183
+ identity_service_config, self
184
+ )
185
+
186
+ self._config_resolver = MiddlewareRegistry.get_config_resolver()
187
+ log.info(
188
+ "%s Middleware system initialized (using default configuration resolver).",
189
+ self.log_identifier,
190
+ )
191
+
192
+ log.info(
193
+ "%s Initialized Base Gateway Component.", self.log_identifier
194
+ )
195
+
196
+ async def authenticate_and_enrich_user(
197
+ self, external_event_data: Any
198
+ ) -> Optional[Dict[str, Any]]:
199
+ """
200
+ Orchestrates the full authentication and identity enrichment flow.
201
+ This method should be called by gateway handlers.
202
+ """
203
+ log_id_prefix = f"{self.log_identifier}[AuthAndEnrich]"
204
+
205
+ auth_claims = await self._extract_initial_claims(external_event_data)
206
+ if not auth_claims:
207
+ log.warning(
208
+ "%s Initial claims extraction failed or returned no identity.",
209
+ log_id_prefix,
210
+ )
211
+ return None
212
+
213
+ if self.identity_service:
214
+ enriched_profile = await self.identity_service.get_user_profile(auth_claims)
215
+ if enriched_profile:
216
+ final_profile = enriched_profile.copy()
217
+ final_profile.update(auth_claims)
218
+ log.info(
219
+ "%s Successfully merged auth claims and enriched profile for user: %s",
220
+ log_id_prefix,
221
+ auth_claims.get("id"),
222
+ )
223
+ return final_profile
224
+ else:
225
+ log.debug(
226
+ "%s IdentityService found no profile for user: %s. Using claims only.",
227
+ log_id_prefix,
228
+ auth_claims.get("id"),
229
+ )
230
+
231
+ return auth_claims
232
+
233
+ async def submit_a2a_task(
234
+ self,
235
+ target_agent_name: str,
236
+ a2a_parts: List[ContentPart],
237
+ external_request_context: Dict[str, Any],
238
+ user_identity: Any,
239
+ is_streaming: bool = True,
240
+ api_version: str = "v2",
241
+ task_id_override: str | None = None,
242
+ metadata: dict[str, Any] | None = None,
243
+ ) -> str:
244
+ log_id_prefix = f"{self.log_identifier}[SubmitA2ATask]"
245
+ log.info(
246
+ "%s Submitting task for user_identity: %s",
247
+ log_id_prefix,
248
+ user_identity.get("id", user_identity),
249
+ )
250
+
251
+ if not isinstance(user_identity, dict) or not user_identity.get("id"):
252
+ log.error(
253
+ "%s Authentication failed or returned invalid profile. Denying task submission.",
254
+ log_id_prefix,
255
+ )
256
+ raise PermissionError("User not authenticated or identity is invalid.")
257
+
258
+ force_identity_str = self.get_config("force_user_identity")
259
+ if force_identity_str:
260
+ original_identity_id = user_identity.get("id")
261
+ user_identity = {"id": force_identity_str, "name": force_identity_str}
262
+ log.warning(
263
+ "%s DEVELOPMENT MODE: Forcing user_identity from '%s' to '%s'",
264
+ log_id_prefix,
265
+ original_identity_id,
266
+ force_identity_str,
267
+ )
268
+
269
+ config_resolver = MiddlewareRegistry.get_config_resolver()
270
+ gateway_context = {"gateway_id": self.gateway_id}
271
+
272
+ try:
273
+ user_config = await config_resolver.resolve_user_config(
274
+ user_identity, gateway_context, {}
275
+ )
276
+ log.debug(
277
+ "%s Resolved user configuration for user_identity '%s': %s",
278
+ log_id_prefix,
279
+ user_identity.get("id"),
280
+ {k: v for k, v in user_config.items() if not k.startswith("_")},
281
+ )
282
+ except Exception as config_err:
283
+ log.exception(
284
+ "%s Error resolving user configuration for '%s': %s. Proceeding with default configuration.",
285
+ log_id_prefix,
286
+ user_identity.get("id"),
287
+ config_err,
288
+ )
289
+ user_config = {}
290
+
291
+ user_config["user_profile"] = user_identity
292
+
293
+ # Validate user has permission to access this target agent
294
+ validate_agent_access(
295
+ user_config=user_config,
296
+ target_agent_name=target_agent_name,
297
+ validation_context={
298
+ "gateway_id": self.gateway_id,
299
+ "source": "gateway_request",
300
+ },
301
+ log_identifier=log_id_prefix,
302
+ )
303
+
304
+ external_request_context["user_identity"] = user_identity
305
+ external_request_context["a2a_user_config"] = user_config
306
+ external_request_context["api_version"] = api_version
307
+ external_request_context["is_streaming"] = is_streaming
308
+ log.debug(
309
+ "%s Stored user_identity, configuration, api_version (%s), and is_streaming (%s) in external_request_context.",
310
+ log_id_prefix,
311
+ api_version,
312
+ is_streaming,
313
+ )
314
+
315
+ now = datetime.now(timezone.utc)
316
+ timestamp_str = now.isoformat()
317
+ timestamp_header_part = TextPart(
318
+ text=f"Request received by gateway at: {timestamp_str}"
319
+ )
320
+ if not isinstance(a2a_parts, list):
321
+ a2a_parts = list(a2a_parts)
322
+ a2a_parts.insert(0, timestamp_header_part)
323
+ log.debug("%s Prepended timestamp to a2a_parts.", log_id_prefix)
324
+
325
+ a2a_session_id = external_request_context.get("a2a_session_id")
326
+ user_id_for_a2a = external_request_context.get(
327
+ "user_id_for_a2a", user_identity.get("id")
328
+ )
329
+
330
+ system_purpose = self.get_config("system_purpose", "")
331
+ response_format = self.get_config("response_format", "")
332
+
333
+ if not a2a_session_id:
334
+ a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
335
+ log.warning(
336
+ "%s 'a2a_session_id' not found in external_request_context, generated: %s",
337
+ self.log_identifier,
338
+ a2a_session_id,
339
+ )
340
+ external_request_context["a2a_session_id"] = a2a_session_id
341
+
342
+ a2a_metadata = {
343
+ "agent_name": target_agent_name,
344
+ "system_purpose": system_purpose,
345
+ "response_format": response_format,
346
+ }
347
+ invoked_artifacts = external_request_context.get("invoked_with_artifacts")
348
+ if invoked_artifacts:
349
+ a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
350
+ log.debug(
351
+ "%s Found %d artifact identifiers in external context to pass to agent.",
352
+ log_id_prefix,
353
+ len(invoked_artifacts),
354
+ )
355
+
356
+ if metadata:
357
+ a2a_metadata.update(metadata)
358
+
359
+ # This correlation ID is used by the gateway to track the task
360
+ if task_id_override:
361
+ task_id = task_id_override
362
+ else:
363
+ task_id = f"gdk-task-{uuid.uuid4().hex}"
364
+
365
+ prepared_a2a_parts = await self._prepare_parts_for_publishing(
366
+ parts=a2a_parts,
367
+ user_id=user_id_for_a2a,
368
+ session_id=a2a_session_id,
369
+ target_agent_name=target_agent_name,
370
+ )
371
+
372
+ a2a_message = a2a.create_user_message(
373
+ parts=prepared_a2a_parts,
374
+ metadata=a2a_metadata,
375
+ context_id=a2a_session_id,
376
+ )
377
+
378
+ if is_streaming:
379
+ a2a_request = a2a.create_send_streaming_message_request(
380
+ message=a2a_message, task_id=task_id
381
+ )
382
+ else:
383
+ a2a_request = a2a.create_send_message_request(
384
+ message=a2a_message, task_id=task_id
385
+ )
386
+
387
+ payload = a2a_request.model_dump(by_alias=True, exclude_none=True)
388
+ target_topic = a2a.get_agent_request_topic(self.namespace, target_agent_name)
389
+
390
+ user_properties = {
391
+ "clientId": self.gateway_id,
392
+ "userId": user_id_for_a2a,
393
+ }
394
+ if user_config:
395
+ user_properties["a2aUserConfig"] = user_config
396
+
397
+ # Enterprise feature: Add signed user claims if trust manager available
398
+ if hasattr(self, "trust_manager") and self.trust_manager:
399
+ log.debug(
400
+ "%s Attempting to sign user claims for task %s",
401
+ log_id_prefix,
402
+ task_id,
403
+ )
404
+ try:
405
+ auth_token = self.trust_manager.sign_user_claims(
406
+ user_info=user_identity, task_id=task_id
407
+ )
408
+ user_properties["authToken"] = auth_token
409
+ log.debug(
410
+ "%s Successfully signed user claims for task %s",
411
+ log_id_prefix,
412
+ task_id,
413
+ )
414
+ except Exception as e:
415
+ log.error(
416
+ "%s Failed to sign user claims for task %s: %s",
417
+ log_id_prefix,
418
+ task_id,
419
+ e,
420
+ )
421
+ # Continue without token - enterprise feature is optional
422
+ else:
423
+ log.debug(
424
+ "%s Trust Manager not available, proceeding without authentication token",
425
+ log_id_prefix,
426
+ )
427
+
428
+ user_properties["replyTo"] = a2a.get_gateway_response_topic(
429
+ self.namespace, self.gateway_id, task_id
430
+ )
431
+ if is_streaming:
432
+ user_properties["a2aStatusTopic"] = a2a.get_gateway_status_topic(
433
+ self.namespace, self.gateway_id, task_id
434
+ )
435
+
436
+ self.task_context_manager.store_context(task_id, external_request_context)
437
+ log.info("%s Stored external context for task_id: %s", log_id_prefix, task_id)
438
+
439
+ self.publish_a2a_message(
440
+ payload=payload, topic=target_topic, user_properties=user_properties
441
+ )
442
+ log.info(
443
+ "%s Submitted A2A task %s to agent %s. Streaming: %s",
444
+ log_id_prefix,
445
+ task_id,
446
+ target_agent_name,
447
+ is_streaming,
448
+ )
449
+ return task_id
450
+
451
+ def _handle_message(self, message: SolaceMessage, topic: str) -> None:
452
+ """
453
+ Override to use queue-based pattern instead of direct async.
454
+
455
+ Gateway uses an internal queue for message processing to ensure
456
+ strict ordering and backpressure handling.
457
+
458
+ Args:
459
+ message: The Solace message
460
+ topic: The topic the message was received on
461
+ """
462
+ log.debug(
463
+ "%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
464
+ self.log_identifier,
465
+ topic,
466
+ )
467
+
468
+ try:
469
+ msg_data_for_processor = {
470
+ "topic": topic,
471
+ "payload": message.get_payload(),
472
+ "user_properties": message.get_user_properties(),
473
+ "_original_broker_message": message,
474
+ }
475
+ self.internal_event_queue.put_nowait(msg_data_for_processor)
476
+ except queue.Full:
477
+ log.error(
478
+ "%s Internal event queue full. Cannot bridge message.",
479
+ self.log_identifier,
480
+ )
481
+ raise
482
+ except Exception as e:
483
+ log.exception(
484
+ "%s Error bridging message to internal queue: %s",
485
+ self.log_identifier,
486
+ e,
487
+ )
488
+ raise
489
+
490
+ async def _handle_message_async(self, message, topic: str) -> None:
491
+ """
492
+ Not used by gateway - we override _handle_message() instead.
493
+
494
+ This is here to satisfy the abstract method requirement, but the
495
+ gateway uses the queue-based pattern via _handle_message() override.
496
+ """
497
+ raise NotImplementedError(
498
+ "Gateway uses queue-based message handling via _handle_message() override"
499
+ )
500
+
501
+ async def _handle_resolved_signals(
502
+ self,
503
+ external_request_context: Dict,
504
+ signals: List[Tuple[None, str, Any]],
505
+ original_rpc_id: Optional[str],
506
+ is_finalizing_context: bool = False,
507
+ ):
508
+ log_id_prefix = f"{self.log_identifier}[SignalHandler]"
509
+ if not signals:
510
+ return
511
+
512
+ for signal_tuple in signals:
513
+ if (
514
+ isinstance(signal_tuple, tuple)
515
+ and len(signal_tuple) == 3
516
+ and signal_tuple[0] is None
517
+ ):
518
+ signal_type = signal_tuple[1]
519
+ signal_data = signal_tuple[2]
520
+
521
+ if signal_type == "SIGNAL_STATUS_UPDATE":
522
+ status_text = signal_data
523
+ log.info(
524
+ "%s Handling SIGNAL_STATUS_UPDATE: '%s'",
525
+ log_id_prefix,
526
+ status_text,
527
+ )
528
+ if is_finalizing_context:
529
+ log.debug(
530
+ "%s Suppressing SIGNAL_STATUS_UPDATE ('%s') during finalizing context.",
531
+ log_id_prefix,
532
+ status_text,
533
+ )
534
+ continue
535
+ try:
536
+ signal_a2a_message = a2a.create_agent_data_message(
537
+ data={
538
+ "type": "agent_progress_update",
539
+ "status_text": status_text,
540
+ },
541
+ part_metadata={"source": "gateway_signal"},
542
+ )
543
+ a2a_task_id_for_signal = external_request_context.get(
544
+ "a2a_task_id_for_event", original_rpc_id
545
+ )
546
+ if not a2a_task_id_for_signal:
547
+ log.error(
548
+ "%s Cannot determine A2A task ID for signal event. Skipping.",
549
+ log_id_prefix,
550
+ )
551
+ continue
552
+
553
+ signal_event = a2a.create_status_update(
554
+ task_id=a2a_task_id_for_signal,
555
+ context_id=external_request_context.get("a2a_session_id"),
556
+ message=signal_a2a_message,
557
+ is_final=False,
558
+ )
559
+ await self._send_update_to_external(
560
+ external_request_context=external_request_context,
561
+ event_data=signal_event,
562
+ is_final_chunk_of_update=True,
563
+ )
564
+ log.debug(
565
+ "%s Sent status signal as TaskStatusUpdateEvent.",
566
+ log_id_prefix,
567
+ )
568
+ except Exception as e:
569
+ log.exception(
570
+ "%s Error sending status signal: %s", log_id_prefix, e
571
+ )
572
+ elif signal_type == "SIGNAL_ARTIFACT_RETURN":
573
+ # Handle artifact return signal for legacy gateways
574
+ # During finalizing context (final Task), suppress this to avoid duplicates
575
+ # since the same signal might appear in both streaming and final responses
576
+ if is_finalizing_context:
577
+ log.debug(
578
+ "%s Suppressing SIGNAL_ARTIFACT_RETURN during finalizing context to avoid duplicate: %s",
579
+ log_id_prefix,
580
+ signal_data,
581
+ )
582
+ continue
583
+
584
+ log.info(
585
+ "%s Handling SIGNAL_ARTIFACT_RETURN for legacy gateway: %s",
586
+ log_id_prefix,
587
+ signal_data,
588
+ )
589
+ try:
590
+ filename = signal_data.get("filename")
591
+ version = signal_data.get("version")
592
+
593
+ if not filename:
594
+ log.error(
595
+ "%s SIGNAL_ARTIFACT_RETURN missing filename. Skipping.",
596
+ log_id_prefix,
597
+ )
598
+ continue
599
+
600
+ # Load artifact content (not just metadata) for legacy gateways
601
+ # Legacy gateways like Slack need the actual bytes to upload files
602
+ artifact_data = await load_artifact_content_or_metadata(
603
+ self.shared_artifact_service,
604
+ app_name=external_request_context.get(
605
+ "app_name_for_artifacts", self.gateway_id
606
+ ),
607
+ user_id=external_request_context.get("user_id_for_artifacts"),
608
+ session_id=external_request_context.get("a2a_session_id"),
609
+ filename=filename,
610
+ version=version,
611
+ load_metadata_only=False, # Load full content for legacy gateways
612
+ )
613
+
614
+ if artifact_data.get("status") != "success":
615
+ log.error(
616
+ "%s Failed to load artifact content for %s v%s",
617
+ log_id_prefix,
618
+ filename,
619
+ version,
620
+ )
621
+ continue
622
+
623
+ # Get content and ensure it's bytes
624
+ content = artifact_data.get("content")
625
+ if not content:
626
+ log.error(
627
+ "%s No content found in artifact %s v%s",
628
+ log_id_prefix,
629
+ filename,
630
+ version,
631
+ )
632
+ continue
633
+
634
+ # Convert to bytes if it's a string (text-based artifacts)
635
+ if isinstance(content, str):
636
+ content_bytes = content.encode("utf-8")
637
+ elif isinstance(content, bytes):
638
+ content_bytes = content
639
+ else:
640
+ log.error(
641
+ "%s Artifact content is neither string nor bytes: %s",
642
+ log_id_prefix,
643
+ type(content),
644
+ )
645
+ continue
646
+
647
+ # Resolve any late embeds inside the artifact content before returning.
648
+ content_bytes = await self._resolve_embeds_in_artifact_content(
649
+ content_bytes=content_bytes,
650
+ mime_type=artifact_data.get("metadata", {}).get(
651
+ "mime_type"
652
+ ),
653
+ filename=filename,
654
+ external_request_context=external_request_context,
655
+ log_id_prefix=log_id_prefix,
656
+ )
657
+
658
+ # Create FilePart with bytes for legacy gateway to upload
659
+ file_part = a2a.create_file_part_from_bytes(
660
+ content_bytes=content_bytes,
661
+ name=filename,
662
+ mime_type=artifact_data.get("metadata", {}).get(
663
+ "mime_type"
664
+ ),
665
+ )
666
+
667
+ # Create artifact with the file part
668
+ # Import Part type for wrapping
669
+ from a2a.types import Artifact, Part
670
+ artifact = Artifact(
671
+ artifact_id=str(uuid.uuid4().hex),
672
+ parts=[Part(root=file_part)],
673
+ name=filename,
674
+ description=f"Artifact: {filename}",
675
+ )
676
+
677
+ # Send as TaskArtifactUpdateEvent
678
+ a2a_task_id_for_signal = external_request_context.get(
679
+ "a2a_task_id_for_event", original_rpc_id
680
+ )
681
+
682
+ if not a2a_task_id_for_signal:
683
+ log.error(
684
+ "%s Cannot determine A2A task ID for artifact signal. Skipping.",
685
+ log_id_prefix,
686
+ )
687
+ continue
688
+
689
+ artifact_event = a2a.create_artifact_update(
690
+ task_id=a2a_task_id_for_signal,
691
+ context_id=external_request_context.get("a2a_session_id"),
692
+ artifact=artifact,
693
+ )
694
+
695
+ await self._send_update_to_external(
696
+ external_request_context=external_request_context,
697
+ event_data=artifact_event,
698
+ is_final_chunk_of_update=False,
699
+ )
700
+ log.info(
701
+ "%s Sent artifact signal as TaskArtifactUpdateEvent for %s",
702
+ log_id_prefix,
703
+ filename,
704
+ )
705
+ except Exception as e:
706
+ log.exception(
707
+ "%s Error sending artifact signal: %s", log_id_prefix, e
708
+ )
709
+ elif signal_type == "SIGNAL_ARTIFACT_CREATION_COMPLETE":
710
+ # Handle artifact creation completion for legacy gateways
711
+ # This is similar to SIGNAL_ARTIFACT_RETURN but for newly created artifacts
712
+ log.info(
713
+ "%s Handling SIGNAL_ARTIFACT_CREATION_COMPLETE for legacy gateway: %s",
714
+ log_id_prefix,
715
+ signal_data,
716
+ )
717
+ try:
718
+ filename = signal_data.get("filename")
719
+ version = signal_data.get("version")
720
+
721
+ if not filename:
722
+ log.error(
723
+ "%s SIGNAL_ARTIFACT_CREATION_COMPLETE missing filename. Skipping.",
724
+ log_id_prefix,
725
+ )
726
+ continue
727
+
728
+ # Load artifact content (not just metadata) for legacy gateways
729
+ # Legacy gateways like Slack need the actual bytes to upload files
730
+ artifact_data = await load_artifact_content_or_metadata(
731
+ self.shared_artifact_service,
732
+ app_name=external_request_context.get(
733
+ "app_name_for_artifacts", self.gateway_id
734
+ ),
735
+ user_id=external_request_context.get("user_id_for_artifacts"),
736
+ session_id=external_request_context.get("a2a_session_id"),
737
+ filename=filename,
738
+ version=version,
739
+ load_metadata_only=False, # Load full content for legacy gateways
740
+ )
741
+
742
+ if artifact_data.get("status") != "success":
743
+ log.error(
744
+ "%s Failed to load artifact content for %s v%s",
745
+ log_id_prefix,
746
+ filename,
747
+ version,
748
+ )
749
+ continue
750
+
751
+ # Get content and ensure it's bytes
752
+ content = artifact_data.get("content")
753
+ if not content:
754
+ log.error(
755
+ "%s No content found in artifact %s v%s",
756
+ log_id_prefix,
757
+ filename,
758
+ version,
759
+ )
760
+ continue
761
+
762
+ # Convert to bytes if it's a string (text-based artifacts)
763
+ if isinstance(content, str):
764
+ content_bytes = content.encode("utf-8")
765
+ elif isinstance(content, bytes):
766
+ content_bytes = content
767
+ else:
768
+ log.error(
769
+ "%s Artifact content is neither string nor bytes: %s",
770
+ log_id_prefix,
771
+ type(content),
772
+ )
773
+ continue
774
+
775
+ # Create FilePart with bytes for legacy gateway to upload
776
+ file_part = a2a.create_file_part_from_bytes(
777
+ content_bytes=content_bytes,
778
+ name=filename,
779
+ mime_type=signal_data.get("mime_type") or artifact_data.get("metadata", {}).get("mime_type"),
780
+ )
781
+
782
+ # Create artifact with the file part
783
+ # Import Part type for wrapping
784
+ from a2a.types import Artifact, Part
785
+ artifact = Artifact(
786
+ artifact_id=str(uuid.uuid4().hex),
787
+ parts=[Part(root=file_part)],
788
+ name=filename,
789
+ description=f"Artifact: {filename}",
790
+ )
791
+
792
+ # Send as TaskArtifactUpdateEvent
793
+ a2a_task_id_for_signal = external_request_context.get(
794
+ "a2a_task_id_for_event", original_rpc_id
795
+ )
796
+
797
+ if not a2a_task_id_for_signal:
798
+ log.error(
799
+ "%s Cannot determine A2A task ID for artifact creation signal. Skipping.",
800
+ log_id_prefix,
801
+ )
802
+ continue
803
+
804
+ artifact_event = a2a.create_artifact_update(
805
+ task_id=a2a_task_id_for_signal,
806
+ context_id=external_request_context.get("a2a_session_id"),
807
+ artifact=artifact,
808
+ )
809
+
810
+ await self._send_update_to_external(
811
+ external_request_context=external_request_context,
812
+ event_data=artifact_event,
813
+ is_final_chunk_of_update=False,
814
+ )
815
+ log.info(
816
+ "%s Sent artifact creation completion as TaskArtifactUpdateEvent for %s",
817
+ log_id_prefix,
818
+ filename,
819
+ )
820
+ except Exception as e:
821
+ log.exception(
822
+ "%s Error sending artifact creation completion signal: %s", log_id_prefix, e
823
+ )
824
+ else:
825
+ log.warning(
826
+ "%s Received unhandled signal type during embed resolution: %s",
827
+ log_id_prefix,
828
+ signal_type,
829
+ )
830
+
831
+ async def _resolve_embeds_in_artifact_content(
832
+ self,
833
+ content_bytes: bytes,
834
+ mime_type: Optional[str],
835
+ filename: str,
836
+ external_request_context: Dict[str, Any],
837
+ log_id_prefix: str,
838
+ ) -> bytes:
839
+ """
840
+ Checks if content is text-based and, if so, resolves late embeds within it.
841
+ Returns the (potentially modified) content as bytes.
842
+ """
843
+ if is_text_based_mime_type(mime_type):
844
+ log.info(
845
+ "%s Artifact '%s' is text-based (%s). Resolving late embeds.",
846
+ log_id_prefix,
847
+ filename,
848
+ mime_type,
849
+ )
850
+ try:
851
+ decoded_content = content_bytes.decode("utf-8")
852
+
853
+ # Construct context and config for the resolver
854
+ embed_eval_context = {
855
+ "artifact_service": self.shared_artifact_service,
856
+ "session_context": {
857
+ "app_name": external_request_context.get(
858
+ "app_name_for_artifacts", self.gateway_id
859
+ ),
860
+ "user_id": external_request_context.get(
861
+ "user_id_for_artifacts"
862
+ ),
863
+ "session_id": external_request_context.get("a2a_session_id"),
864
+ },
865
+ }
866
+ embed_eval_config = {
867
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
868
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
869
+ }
870
+
871
+ resolved_string = await resolve_embeds_recursively_in_string(
872
+ text=decoded_content,
873
+ context=embed_eval_context,
874
+ resolver_func=evaluate_embed,
875
+ types_to_resolve=LATE_EMBED_TYPES,
876
+ resolution_mode=ResolutionMode.RECURSIVE_ARTIFACT_CONTENT,
877
+ log_identifier=f"{log_id_prefix}[RecursiveResolve]",
878
+ config=embed_eval_config,
879
+ max_depth=self.gateway_recursive_embed_depth,
880
+ )
881
+ resolved_bytes = resolved_string.encode("utf-8")
882
+ log.info(
883
+ "%s Successfully resolved embeds in '%s'. New size: %d bytes.",
884
+ log_id_prefix,
885
+ filename,
886
+ len(resolved_bytes),
887
+ )
888
+ return resolved_bytes
889
+ except Exception as resolve_err:
890
+ log.error(
891
+ "%s Failed to resolve embeds within artifact '%s': %s. Returning raw content.",
892
+ log_id_prefix,
893
+ filename,
894
+ resolve_err,
895
+ )
896
+ return content_bytes
897
+
898
+ async def _resolve_uri_in_file_part(
899
+ self, file_part: FilePart, external_request_context: Dict[str, Any]
900
+ ):
901
+ """
902
+ Checks if a FilePart has a resolvable URI and, if so,
903
+ resolves it and mutates the part in-place by calling the common utility.
904
+ After resolving the URI, it also resolves any late embeds within the content.
905
+ """
906
+ await a2a.resolve_file_part_uri(
907
+ part=file_part,
908
+ artifact_service=self.shared_artifact_service,
909
+ log_identifier=self.log_identifier,
910
+ )
911
+
912
+ # After resolving the URI to get the content, resolve any late embeds inside it.
913
+ if file_part.file and isinstance(file_part.file, FileWithBytes):
914
+ # The content is a base64 encoded string in the `bytes` attribute.
915
+ # We need to decode it to raw bytes for processing.
916
+ try:
917
+ content_bytes = base64.b64decode(file_part.file.bytes)
918
+ except Exception as e:
919
+ log.error(
920
+ "%s Failed to base64 decode file content for embed resolution: %s",
921
+ f"{self.log_identifier}[UriResolve]",
922
+ e,
923
+ )
924
+ return
925
+
926
+ resolved_bytes = await self._resolve_embeds_in_artifact_content(
927
+ content_bytes=content_bytes,
928
+ mime_type=file_part.file.mime_type,
929
+ filename=file_part.file.name,
930
+ external_request_context=external_request_context,
931
+ log_id_prefix=f"{self.log_identifier}[UriResolve]",
932
+ )
933
+ # Re-encode the resolved content back to a base64 string for the FileWithBytes model.
934
+ file_part.file.bytes = base64.b64encode(resolved_bytes).decode("utf-8")
935
+
936
+ async def _resolve_uris_in_parts_list(
937
+ self, parts: List[ContentPart], external_request_context: Dict[str, Any]
938
+ ):
939
+ """Iterates over a list of part objects and resolves any FilePart URIs."""
940
+ if not parts:
941
+ return
942
+ for part in parts:
943
+ if isinstance(part, FilePart):
944
+ await self._resolve_uri_in_file_part(part, external_request_context)
945
+
946
+ async def _resolve_uris_in_payload(
947
+ self, parsed_event: Any, external_request_context: Dict[str, Any]
948
+ ):
949
+ """
950
+ Dispatcher that calls the appropriate targeted URI resolver based on the
951
+ Pydantic model type of the event.
952
+ """
953
+ parts_to_resolve: List[ContentPart] = []
954
+ if isinstance(parsed_event, TaskStatusUpdateEvent):
955
+ message = a2a.get_message_from_status_update(parsed_event)
956
+ if message:
957
+ parts_to_resolve.extend(a2a.get_parts_from_message(message))
958
+ elif isinstance(parsed_event, TaskArtifactUpdateEvent):
959
+ artifact = a2a.get_artifact_from_artifact_update(parsed_event)
960
+ if artifact:
961
+ parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
962
+ elif isinstance(parsed_event, Task):
963
+ if parsed_event.status and parsed_event.status.message:
964
+ parts_to_resolve.extend(
965
+ a2a.get_parts_from_message(parsed_event.status.message)
966
+ )
967
+ if parsed_event.artifacts:
968
+ for artifact in parsed_event.artifacts:
969
+ parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
970
+
971
+ if parts_to_resolve:
972
+ await self._resolve_uris_in_parts_list(
973
+ parts_to_resolve, external_request_context
974
+ )
975
+ else:
976
+ log.debug(
977
+ "%s Payload type '%s' did not yield any parts for URI resolution. Skipping.",
978
+ self.log_identifier,
979
+ type(parsed_event).__name__,
980
+ )
981
+
982
+ async def _handle_discovery_message(self, payload: Dict) -> bool:
983
+ """Handles incoming agent discovery messages."""
984
+ try:
985
+ agent_card = AgentCard(**payload)
986
+ self.core_a2a_service.process_discovery_message(agent_card)
987
+ return True
988
+ except Exception as e:
989
+ log.error(
990
+ "%s Failed to process discovery message: %s. Payload: %s",
991
+ self.log_identifier,
992
+ e,
993
+ payload,
994
+ )
995
+ return False
996
+
997
+ async def _prepare_parts_for_publishing(
998
+ self,
999
+ parts: List[ContentPart],
1000
+ user_id: str,
1001
+ session_id: str,
1002
+ target_agent_name: str,
1003
+ ) -> List[ContentPart]:
1004
+ """
1005
+ Prepares message parts for publishing according to the configured artifact_handling_mode
1006
+ by calling the common utility function.
1007
+ """
1008
+ processed_parts: List[ContentPart] = []
1009
+ for part in parts:
1010
+ if isinstance(part, FilePart):
1011
+ processed_part = await a2a.prepare_file_part_for_publishing(
1012
+ part=part,
1013
+ mode=self.artifact_handling_mode,
1014
+ artifact_service=self.shared_artifact_service,
1015
+ user_id=user_id,
1016
+ session_id=session_id,
1017
+ target_agent_name=target_agent_name,
1018
+ log_identifier=self.log_identifier,
1019
+ )
1020
+ if processed_part:
1021
+ processed_parts.append(processed_part)
1022
+ else:
1023
+ processed_parts.append(part)
1024
+ return processed_parts
1025
+
1026
+ def _should_include_data_part_in_final_output(self, part: Any) -> bool:
1027
+ """
1028
+ Determines if a DataPart should be included in the final output sent to the gateway.
1029
+
1030
+ This filters out internal/tool-related DataParts that shouldn't be shown to end users.
1031
+ Gateways can override this method for custom filtering logic.
1032
+
1033
+ Args:
1034
+ part: The part to check (expected to be a DataPart)
1035
+
1036
+ Returns:
1037
+ True if the part should be included, False if it should be filtered out
1038
+ """
1039
+ from a2a.types import DataPart
1040
+
1041
+ if not isinstance(part, DataPart):
1042
+ return True
1043
+
1044
+ # Check if this is a tool result by looking at metadata
1045
+ # Tool results have metadata.tool_name set
1046
+ if part.metadata and part.metadata.get("tool_name"):
1047
+ # This is a tool result - filter it out
1048
+ return False
1049
+
1050
+ # Get the type of the data part
1051
+ data_type = part.data.get("type") if part.data else None
1052
+
1053
+ # Filter out tool-related data parts that are internal
1054
+ tool_related_types = {
1055
+ "tool_call",
1056
+ "tool_result",
1057
+ "tool_error",
1058
+ "tool_execution",
1059
+ }
1060
+
1061
+ if data_type in tool_related_types:
1062
+ return False
1063
+
1064
+ # Handle artifact_creation_progress based on gateway capabilities
1065
+ if data_type == "artifact_creation_progress":
1066
+ # For modern gateways (HTTP SSE), keep these to display progress bubbles
1067
+ # For legacy gateways (Slack), filter them out as they'll be converted to FileParts
1068
+ if self.supports_inline_artifact_resolution:
1069
+ return True # Keep for HTTP SSE
1070
+ else:
1071
+ return False # Filter for Slack (will be converted to FileParts instead)
1072
+
1073
+ # Keep user-facing data parts like general progress updates
1074
+ user_facing_types = {
1075
+ "agent_progress_update",
1076
+ }
1077
+
1078
+ if data_type in user_facing_types:
1079
+ return True
1080
+
1081
+ # Default: include unknown types (to avoid hiding potentially useful info)
1082
+ return True
1083
+
1084
+ async def _resolve_embeds_and_handle_signals(
1085
+ self,
1086
+ event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
1087
+ external_request_context: Dict[str, Any],
1088
+ a2a_task_id: str,
1089
+ original_rpc_id: Optional[str],
1090
+ is_finalizing_context: bool = False,
1091
+ ) -> bool:
1092
+ if not self.enable_embed_resolution:
1093
+ return False
1094
+
1095
+ log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
1096
+ content_modified = False
1097
+
1098
+ embed_eval_context = {
1099
+ "artifact_service": self.shared_artifact_service,
1100
+ "session_context": {
1101
+ "app_name": external_request_context.get(
1102
+ "app_name_for_artifacts", self.gateway_id
1103
+ ),
1104
+ "user_id": external_request_context.get("user_id_for_artifacts"),
1105
+ "session_id": external_request_context.get("a2a_session_id"),
1106
+ },
1107
+ }
1108
+ embed_eval_config = {
1109
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
1110
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
1111
+ }
1112
+
1113
+ parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
1114
+ if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
1115
+ if event_with_parts.status and event_with_parts.status.message:
1116
+ parts_owner = event_with_parts.status.message
1117
+ elif isinstance(event_with_parts, TaskArtifactUpdateEvent):
1118
+ if event_with_parts.artifact:
1119
+ parts_owner = event_with_parts.artifact
1120
+
1121
+ if not (parts_owner and parts_owner.parts):
1122
+ return False
1123
+
1124
+ is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
1125
+ stream_buffer_key = f"{a2a_task_id}_stream_buffer"
1126
+ current_buffer = ""
1127
+ if is_streaming_status_update:
1128
+ current_buffer = (
1129
+ self.task_context_manager.get_context(stream_buffer_key) or ""
1130
+ )
1131
+
1132
+ original_parts: List[ContentPart] = (
1133
+ a2a.get_parts_from_message(parts_owner)
1134
+ if isinstance(parts_owner, A2AMessage)
1135
+ else a2a.get_parts_from_artifact(parts_owner)
1136
+ )
1137
+
1138
+ new_parts: List[ContentPart] = []
1139
+ other_signals = []
1140
+
1141
+ for part in original_parts:
1142
+ if isinstance(part, TextPart) and part.text:
1143
+ text_to_resolve = current_buffer + part.text
1144
+ current_buffer = "" # Buffer is now being processed
1145
+
1146
+ (
1147
+ resolved_text,
1148
+ processed_idx,
1149
+ signals_with_placeholders,
1150
+ ) = await resolve_embeds_in_string(
1151
+ text=text_to_resolve,
1152
+ context=embed_eval_context,
1153
+ resolver_func=evaluate_embed,
1154
+ types_to_resolve=LATE_EMBED_TYPES.union({"status_update"}),
1155
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1156
+ log_identifier=log_id_prefix,
1157
+ config=embed_eval_config,
1158
+ )
1159
+
1160
+ if not signals_with_placeholders:
1161
+ new_parts.append(a2a.create_text_part(text=resolved_text))
1162
+ else:
1163
+ placeholder_map = {p: s for _, s, p in signals_with_placeholders}
1164
+ split_pattern = (
1165
+ f"({'|'.join(re.escape(p) for p in placeholder_map.keys())})"
1166
+ )
1167
+ text_fragments = re.split(split_pattern, resolved_text)
1168
+
1169
+ for i, fragment in enumerate(text_fragments):
1170
+ if not fragment:
1171
+ continue
1172
+ if fragment in placeholder_map:
1173
+ signal_tuple = placeholder_map[fragment]
1174
+ signal_type, signal_data = signal_tuple[1], signal_tuple[2]
1175
+ if signal_type == "SIGNAL_ARTIFACT_RETURN":
1176
+ # Only convert to FilePart if gateway supports inline artifact resolution
1177
+ if self.supports_inline_artifact_resolution:
1178
+ try:
1179
+ filename, version = (
1180
+ signal_data["filename"],
1181
+ signal_data["version"],
1182
+ )
1183
+ artifact_data = (
1184
+ await load_artifact_content_or_metadata(
1185
+ self.shared_artifact_service,
1186
+ **embed_eval_context["session_context"],
1187
+ filename=filename,
1188
+ version=version,
1189
+ load_metadata_only=True,
1190
+ )
1191
+ )
1192
+ if artifact_data.get("status") == "success":
1193
+ uri = format_artifact_uri(
1194
+ **embed_eval_context["session_context"],
1195
+ filename=filename,
1196
+ version=artifact_data.get("version"),
1197
+ )
1198
+ new_parts.append(
1199
+ a2a.create_file_part_from_uri(
1200
+ uri,
1201
+ filename,
1202
+ artifact_data.get("metadata", {}).get(
1203
+ "mime_type"
1204
+ ),
1205
+ )
1206
+ )
1207
+ else:
1208
+ new_parts.append(
1209
+ a2a.create_text_part(
1210
+ f"[Error: Artifact '{filename}' v{version} not found.]"
1211
+ )
1212
+ )
1213
+ except Exception as e:
1214
+ log.exception(
1215
+ "%s Error handling SIGNAL_ARTIFACT_RETURN: %s",
1216
+ log_id_prefix,
1217
+ e,
1218
+ )
1219
+ new_parts.append(
1220
+ a2a.create_text_part(
1221
+ f"[Error: Could not retrieve artifact '{signal_data.get('filename')}'.]"
1222
+ )
1223
+ )
1224
+ else:
1225
+ # Legacy gateway mode: pass signal through for gateway to handle
1226
+ other_signals.append(signal_tuple)
1227
+ elif signal_type == "SIGNAL_INLINE_BINARY_CONTENT":
1228
+ signal_data["content_bytes"] = signal_data.get("bytes")
1229
+ del signal_data["bytes"]
1230
+ new_parts.append(
1231
+ a2a.create_file_part_from_bytes(**signal_data)
1232
+ )
1233
+ else:
1234
+ other_signals.append(signal_tuple)
1235
+ else:
1236
+ # Check if the non-placeholder fragment is just whitespace
1237
+ # and is between two placeholders. If so, drop it.
1238
+ is_just_whitespace = not fragment.strip()
1239
+ prev_fragment_was_placeholder = (
1240
+ i > 0 and text_fragments[i - 1] in placeholder_map
1241
+ )
1242
+ next_fragment_is_placeholder = (
1243
+ i < len(text_fragments) - 1
1244
+ and text_fragments[i + 1] in placeholder_map
1245
+ )
1246
+
1247
+ if (
1248
+ is_just_whitespace
1249
+ and prev_fragment_was_placeholder
1250
+ and next_fragment_is_placeholder
1251
+ ):
1252
+ log.debug(
1253
+ "%s Dropping whitespace fragment between two file signals.",
1254
+ log_id_prefix,
1255
+ )
1256
+ continue
1257
+
1258
+ new_parts.append(a2a.create_text_part(text=fragment))
1259
+
1260
+ if is_streaming_status_update:
1261
+ current_buffer = text_to_resolve[processed_idx:]
1262
+
1263
+ elif isinstance(part, FilePart) and part.file:
1264
+ # Handle recursive embeds in text-based FileParts
1265
+ new_parts.append(part) # Placeholder for now
1266
+ elif isinstance(part, DataPart):
1267
+ # Handle special DataPart types
1268
+ data_type = part.data.get("type") if part.data else None
1269
+
1270
+ if data_type == "template_block":
1271
+ # Resolve template block and replace with resolved text
1272
+ try:
1273
+ from ...common.utils.templates import resolve_template_blocks_in_string
1274
+
1275
+ # Reconstruct the template block syntax
1276
+ data_artifact = part.data.get("data_artifact", "")
1277
+ jsonpath = part.data.get("jsonpath")
1278
+ limit = part.data.get("limit")
1279
+ template_content = part.data.get("template_content", "")
1280
+
1281
+ # Build params string
1282
+ params_parts = [f'data="{data_artifact}"']
1283
+ if jsonpath:
1284
+ params_parts.append(f'jsonpath="{jsonpath}"')
1285
+ if limit is not None:
1286
+ params_parts.append(f'limit="{limit}"')
1287
+ params_str = " ".join(params_parts)
1288
+
1289
+ # Reconstruct full template block
1290
+ template_block = f"«««template: {params_str}\n{template_content}\n»»»"
1291
+
1292
+ log.debug(
1293
+ "%s Resolving template block inline: data=%s",
1294
+ log_id_prefix,
1295
+ data_artifact,
1296
+ )
1297
+
1298
+ # Resolve the template
1299
+ resolved_text = await resolve_template_blocks_in_string(
1300
+ text=template_block,
1301
+ artifact_service=self.shared_artifact_service,
1302
+ session_context={
1303
+ "app_name": external_request_context.get(
1304
+ "app_name_for_artifacts", self.gateway_id
1305
+ ),
1306
+ "user_id": external_request_context.get("user_id_for_artifacts"),
1307
+ "session_id": external_request_context.get("a2a_session_id"),
1308
+ },
1309
+ log_identifier=f"{log_id_prefix}[TemplateResolve]",
1310
+ )
1311
+
1312
+ log.info(
1313
+ "%s Template resolved successfully. Output length: %d",
1314
+ log_id_prefix,
1315
+ len(resolved_text),
1316
+ )
1317
+
1318
+ # Replace the DataPart with a TextPart containing the resolved content
1319
+ new_parts.append(a2a.create_text_part(text=resolved_text))
1320
+
1321
+ except Exception as e:
1322
+ log.error(
1323
+ "%s Failed to resolve template block: %s",
1324
+ log_id_prefix,
1325
+ e,
1326
+ exc_info=True,
1327
+ )
1328
+ # Send error message as TextPart
1329
+ error_text = f"[Template rendering error: {str(e)}]"
1330
+ new_parts.append(a2a.create_text_part(text=error_text))
1331
+
1332
+ elif (
1333
+ data_type == "artifact_creation_progress"
1334
+ and not self.supports_inline_artifact_resolution
1335
+ ):
1336
+ # Legacy gateway mode: convert completed artifact creation to FilePart
1337
+ status = part.data.get("status")
1338
+ if status == "completed" and not is_finalizing_context:
1339
+ # Extract artifact info from the DataPart
1340
+ filename = part.data.get("filename")
1341
+ version = part.data.get("version")
1342
+ mime_type = part.data.get("mime_type")
1343
+
1344
+ if filename and version is not None:
1345
+ log.info(
1346
+ "%s Converting artifact creation completion to FilePart for legacy gateway: %s v%s",
1347
+ log_id_prefix,
1348
+ filename,
1349
+ version,
1350
+ )
1351
+ # This will be sent as an artifact signal, so don't add to new_parts
1352
+ # Instead, add to other_signals for processing
1353
+ signal_tuple = (
1354
+ None,
1355
+ "SIGNAL_ARTIFACT_CREATION_COMPLETE",
1356
+ {
1357
+ "filename": filename,
1358
+ "version": version,
1359
+ "mime_type": mime_type,
1360
+ },
1361
+ )
1362
+ other_signals.append(signal_tuple)
1363
+ else:
1364
+ # Missing required info, keep the DataPart as-is
1365
+ new_parts.append(part)
1366
+ elif status == "completed" and is_finalizing_context:
1367
+ # Suppress during finalizing to avoid duplicates
1368
+ log.debug(
1369
+ "%s Suppressing artifact creation completion during finalizing context for %s",
1370
+ log_id_prefix,
1371
+ part.data.get("filename"),
1372
+ )
1373
+ continue
1374
+ else:
1375
+ # Keep in-progress or failed status DataParts
1376
+ new_parts.append(part)
1377
+ else:
1378
+ # Not an artifact creation DataPart, or modern gateway - keep as-is
1379
+ new_parts.append(part)
1380
+ else:
1381
+ new_parts.append(part)
1382
+
1383
+ if other_signals:
1384
+ await self._handle_resolved_signals(
1385
+ external_request_context,
1386
+ other_signals,
1387
+ original_rpc_id,
1388
+ is_finalizing_context,
1389
+ )
1390
+
1391
+ if new_parts != original_parts:
1392
+ content_modified = True
1393
+ if isinstance(parts_owner, A2AMessage):
1394
+ if isinstance(event_with_parts, TaskStatusUpdateEvent):
1395
+ event_with_parts.status.message = a2a.update_message_parts(
1396
+ parts_owner, new_parts
1397
+ )
1398
+ elif isinstance(event_with_parts, Task):
1399
+ event_with_parts.status.message = a2a.update_message_parts(
1400
+ parts_owner, new_parts
1401
+ )
1402
+ elif isinstance(parts_owner, A2AArtifact):
1403
+ event_with_parts.artifact = a2a.update_artifact_parts(
1404
+ parts_owner, new_parts
1405
+ )
1406
+
1407
+ if is_streaming_status_update:
1408
+ self.task_context_manager.store_context(stream_buffer_key, current_buffer)
1409
+
1410
+ return content_modified or bool(other_signals)
1411
+
1412
+ async def _process_parsed_a2a_event(
1413
+ self,
1414
+ parsed_event: Union[
1415
+ Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError
1416
+ ],
1417
+ external_request_context: Dict[str, Any],
1418
+ a2a_task_id: str,
1419
+ original_rpc_id: Optional[str],
1420
+ ) -> None:
1421
+ """
1422
+ Processes a parsed A2A event: resolves embeds, handles signals,
1423
+ sends to external, and manages context.
1424
+ """
1425
+ log_id_prefix = f"{self.log_identifier}[ProcessParsed:{a2a_task_id}]"
1426
+ is_truly_final_event_for_context_cleanup = False
1427
+ is_finalizing_context_for_embeds = False
1428
+
1429
+ if isinstance(parsed_event, JSONRPCError):
1430
+ log.warning(
1431
+ "%s Handling JSONRPCError for task %s.", log_id_prefix, a2a_task_id
1432
+ )
1433
+ await self._send_error_to_external(external_request_context, parsed_event)
1434
+ is_truly_final_event_for_context_cleanup = True
1435
+ else:
1436
+ content_was_modified_or_signals_handled = False
1437
+
1438
+ if isinstance(parsed_event, TaskStatusUpdateEvent) and parsed_event.final:
1439
+ is_finalizing_context_for_embeds = True
1440
+ elif isinstance(parsed_event, Task):
1441
+ is_finalizing_context_for_embeds = True
1442
+
1443
+ if not isinstance(parsed_event, JSONRPCError):
1444
+ content_was_modified_or_signals_handled = (
1445
+ await self._resolve_embeds_and_handle_signals(
1446
+ parsed_event,
1447
+ external_request_context,
1448
+ a2a_task_id,
1449
+ original_rpc_id,
1450
+ is_finalizing_context=is_finalizing_context_for_embeds,
1451
+ )
1452
+ )
1453
+
1454
+ if self.resolve_artifact_uris_in_gateway:
1455
+ log.debug(
1456
+ "%s Resolving artifact URIs before sending to external...",
1457
+ log_id_prefix,
1458
+ )
1459
+ await self._resolve_uris_in_payload(
1460
+ parsed_event, external_request_context
1461
+ )
1462
+
1463
+ send_this_event_to_external = True
1464
+ is_final_chunk_of_status_update = False
1465
+
1466
+ if isinstance(parsed_event, TaskStatusUpdateEvent):
1467
+ is_final_chunk_of_status_update = parsed_event.final
1468
+ if (
1469
+ not (
1470
+ parsed_event.status
1471
+ and parsed_event.status.message
1472
+ and parsed_event.status.message.parts
1473
+ )
1474
+ and not parsed_event.metadata
1475
+ and not is_final_chunk_of_status_update
1476
+ and not content_was_modified_or_signals_handled
1477
+ ):
1478
+ send_this_event_to_external = False
1479
+ log.debug(
1480
+ "%s Suppressing empty intermediate status update.",
1481
+ log_id_prefix,
1482
+ )
1483
+ elif isinstance(parsed_event, TaskArtifactUpdateEvent):
1484
+ if (
1485
+ not (parsed_event.artifact and parsed_event.artifact.parts)
1486
+ and not content_was_modified_or_signals_handled
1487
+ ):
1488
+ send_this_event_to_external = False
1489
+ log.debug("%s Suppressing empty artifact update.", log_id_prefix)
1490
+ elif isinstance(parsed_event, Task):
1491
+ is_truly_final_event_for_context_cleanup = True
1492
+
1493
+ if (
1494
+ self._RESOLVE_EMBEDS_IN_FINAL_RESPONSE
1495
+ and parsed_event.status
1496
+ and parsed_event.status.message
1497
+ and parsed_event.status.message.parts
1498
+ ):
1499
+ log.debug(
1500
+ "%s Resolving embeds in final task response...", log_id_prefix
1501
+ )
1502
+ message = parsed_event.status.message
1503
+ combined_text = a2a.get_text_from_message(message)
1504
+ data_parts = a2a.get_data_parts_from_message(message)
1505
+ file_parts = a2a.get_file_parts_from_message(message)
1506
+ non_text_parts = data_parts + file_parts
1507
+
1508
+ if combined_text:
1509
+ embed_eval_context = {
1510
+ "artifact_service": self.shared_artifact_service,
1511
+ "session_context": {
1512
+ "app_name": external_request_context.get(
1513
+ "app_name_for_artifacts", self.gateway_id
1514
+ ),
1515
+ "user_id": external_request_context.get(
1516
+ "user_id_for_artifacts"
1517
+ ),
1518
+ "session_id": external_request_context.get(
1519
+ "a2a_session_id"
1520
+ ),
1521
+ },
1522
+ }
1523
+ embed_eval_config = {
1524
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
1525
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
1526
+ }
1527
+ all_embed_types = EARLY_EMBED_TYPES.union(LATE_EMBED_TYPES)
1528
+ resolved_text, _, signals = await resolve_embeds_in_string(
1529
+ text=combined_text,
1530
+ context=embed_eval_context,
1531
+ resolver_func=evaluate_embed,
1532
+ types_to_resolve=all_embed_types,
1533
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1534
+ log_identifier=log_id_prefix,
1535
+ config=embed_eval_config,
1536
+ )
1537
+ if signals:
1538
+ log.debug(
1539
+ "%s Handling %d signals found during final response embed resolution.",
1540
+ log_id_prefix,
1541
+ len(signals),
1542
+ )
1543
+ await self._handle_resolved_signals(
1544
+ external_request_context,
1545
+ signals,
1546
+ original_rpc_id,
1547
+ is_finalizing_context=True,
1548
+ )
1549
+
1550
+ new_parts = (
1551
+ [a2a.create_text_part(text=resolved_text)]
1552
+ if resolved_text
1553
+ else []
1554
+ )
1555
+ new_parts.extend(non_text_parts)
1556
+ parsed_event.status.message = a2a.update_message_parts(
1557
+ message=parsed_event.status.message,
1558
+ new_parts=new_parts,
1559
+ )
1560
+ log.info(
1561
+ "%s Final response text updated with resolved embeds.",
1562
+ log_id_prefix,
1563
+ )
1564
+
1565
+ final_buffer_key = f"{a2a_task_id}_stream_buffer"
1566
+ remaining_buffer = self.task_context_manager.get_context(
1567
+ final_buffer_key
1568
+ )
1569
+ if remaining_buffer:
1570
+ log.info(
1571
+ "%s Flushing remaining buffer for task %s before final response.",
1572
+ log_id_prefix,
1573
+ a2a_task_id,
1574
+ )
1575
+ embed_eval_context = {
1576
+ "artifact_service": self.shared_artifact_service,
1577
+ "session_context": {
1578
+ "app_name": external_request_context.get(
1579
+ "app_name_for_artifacts", self.gateway_id
1580
+ ),
1581
+ "user_id": external_request_context.get(
1582
+ "user_id_for_artifacts"
1583
+ ),
1584
+ "session_id": external_request_context.get(
1585
+ "a2a_session_id"
1586
+ ),
1587
+ },
1588
+ }
1589
+ embed_eval_config = {
1590
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
1591
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
1592
+ }
1593
+ resolved_remaining_text, _, signals = (
1594
+ await resolve_embeds_in_string(
1595
+ text=remaining_buffer,
1596
+ context=embed_eval_context,
1597
+ resolver_func=evaluate_embed,
1598
+ types_to_resolve=LATE_EMBED_TYPES.copy(),
1599
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1600
+ log_identifier=log_id_prefix,
1601
+ config=embed_eval_config,
1602
+ )
1603
+ )
1604
+ await self._handle_resolved_signals(
1605
+ external_request_context,
1606
+ signals,
1607
+ original_rpc_id,
1608
+ is_finalizing_context=True,
1609
+ )
1610
+ if resolved_remaining_text:
1611
+ flush_message = a2a.create_agent_text_message(
1612
+ text=resolved_remaining_text
1613
+ )
1614
+ flush_event = a2a.create_status_update(
1615
+ task_id=a2a_task_id,
1616
+ context_id=external_request_context.get("a2a_session_id"),
1617
+ message=flush_message,
1618
+ is_final=False,
1619
+ )
1620
+ await self._send_update_to_external(
1621
+ external_request_context, flush_event, True
1622
+ )
1623
+ self.task_context_manager.remove_context(final_buffer_key)
1624
+
1625
+ if send_this_event_to_external:
1626
+ if isinstance(parsed_event, Task):
1627
+ # Filter DataParts from final Task if gateway has filtering enabled
1628
+ # This prevents tool results and other internal data from appearing in user-facing output
1629
+ if (
1630
+ self.filter_tool_data_parts
1631
+ and parsed_event.status
1632
+ and parsed_event.status.message
1633
+ and parsed_event.status.message.parts
1634
+ ):
1635
+ original_parts = a2a.get_parts_from_message(
1636
+ parsed_event.status.message
1637
+ )
1638
+ filtered_parts = [
1639
+ part
1640
+ for part in original_parts
1641
+ if self._should_include_data_part_in_final_output(part)
1642
+ ]
1643
+ if len(filtered_parts) != len(original_parts):
1644
+ log.debug(
1645
+ "%s Filtered %d DataParts from final Task message",
1646
+ log_id_prefix,
1647
+ len(original_parts) - len(filtered_parts),
1648
+ )
1649
+ parsed_event.status.message = a2a.update_message_parts(
1650
+ parsed_event.status.message, filtered_parts
1651
+ )
1652
+
1653
+ await self._send_final_response_to_external(
1654
+ external_request_context, parsed_event
1655
+ )
1656
+ elif isinstance(
1657
+ parsed_event, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
1658
+ ):
1659
+ final_chunk_flag = (
1660
+ is_final_chunk_of_status_update
1661
+ if isinstance(parsed_event, TaskStatusUpdateEvent)
1662
+ else False
1663
+ )
1664
+ await self._send_update_to_external(
1665
+ external_request_context, parsed_event, final_chunk_flag
1666
+ )
1667
+
1668
+ if is_truly_final_event_for_context_cleanup:
1669
+ log.info(
1670
+ "%s Truly final event processed for task %s. Removing context.",
1671
+ log_id_prefix,
1672
+ a2a_task_id,
1673
+ )
1674
+ self.task_context_manager.remove_context(a2a_task_id)
1675
+ self.task_context_manager.remove_context(f"{a2a_task_id}_stream_buffer")
1676
+
1677
+ async def _handle_agent_event(
1678
+ self, topic: str, payload: Dict, task_id_from_topic: str
1679
+ ) -> bool:
1680
+ """
1681
+ Handles messages received on gateway response and status topics.
1682
+ Parses the payload, retrieves context using task_id_from_topic, and dispatches for processing.
1683
+ """
1684
+ try:
1685
+ rpc_response = JSONRPCResponse.model_validate(payload)
1686
+ except Exception as e:
1687
+ log.error(
1688
+ "%s Failed to parse payload as JSONRPCResponse for topic %s (Task ID from topic: %s): %s. Payload: %s",
1689
+ self.log_identifier,
1690
+ topic,
1691
+ task_id_from_topic,
1692
+ e,
1693
+ payload,
1694
+ )
1695
+ return False
1696
+
1697
+ original_rpc_id = str(a2a.get_response_id(rpc_response))
1698
+
1699
+ external_request_context = self.task_context_manager.get_context(
1700
+ task_id_from_topic
1701
+ )
1702
+ if not external_request_context:
1703
+ log.warning(
1704
+ "%s No external context found for A2A Task ID: %s (from topic). Ignoring message. Topic: %s, RPC ID: %s",
1705
+ self.log_identifier,
1706
+ task_id_from_topic,
1707
+ topic,
1708
+ original_rpc_id,
1709
+ )
1710
+ return True
1711
+
1712
+ external_request_context["a2a_task_id_for_event"] = task_id_from_topic
1713
+ external_request_context["original_rpc_id"] = original_rpc_id
1714
+
1715
+ parsed_event_obj: Union[
1716
+ Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError, None
1717
+ ] = None
1718
+ error = a2a.get_response_error(rpc_response)
1719
+ if error:
1720
+ parsed_event_obj = error
1721
+ else:
1722
+ result = a2a.get_response_result(rpc_response)
1723
+ if result:
1724
+ # The result is already a parsed Pydantic model.
1725
+ parsed_event_obj = result
1726
+
1727
+ # Validate task ID match
1728
+ actual_task_id = None
1729
+ if isinstance(parsed_event_obj, Task):
1730
+ actual_task_id = parsed_event_obj.id
1731
+ elif isinstance(
1732
+ parsed_event_obj, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
1733
+ ):
1734
+ actual_task_id = parsed_event_obj.task_id
1735
+
1736
+ if (
1737
+ task_id_from_topic
1738
+ and actual_task_id
1739
+ and actual_task_id != task_id_from_topic
1740
+ ):
1741
+ log.error(
1742
+ "%s Task ID mismatch! Expected: %s, Got from payload: %s.",
1743
+ self.log_identifier,
1744
+ task_id_from_topic,
1745
+ actual_task_id,
1746
+ )
1747
+ parsed_event_obj = None
1748
+
1749
+ if not parsed_event_obj:
1750
+ log.error(
1751
+ "%s Failed to parse or validate A2A event from RPC result for task %s. Result: %s",
1752
+ self.log_identifier,
1753
+ task_id_from_topic,
1754
+ a2a.get_response_result(rpc_response) or "N/A",
1755
+ )
1756
+ generic_error = JSONRPCError(
1757
+ code=-32000, message="Invalid event structure received from agent."
1758
+ )
1759
+ await self._send_error_to_external(external_request_context, generic_error)
1760
+ self.task_context_manager.remove_context(task_id_from_topic)
1761
+ self.task_context_manager.remove_context(
1762
+ f"{task_id_from_topic}_stream_buffer"
1763
+ )
1764
+ return False
1765
+
1766
+ try:
1767
+ await self._process_parsed_a2a_event(
1768
+ parsed_event_obj,
1769
+ external_request_context,
1770
+ task_id_from_topic,
1771
+ original_rpc_id,
1772
+ )
1773
+ return True
1774
+ except Exception as e:
1775
+ log.exception(
1776
+ "%s Error in _process_parsed_a2a_event for task %s: %s",
1777
+ self.log_identifier,
1778
+ task_id_from_topic,
1779
+ e,
1780
+ )
1781
+ error_obj = JSONRPCError(
1782
+ code=-32000, message=f"Gateway processing error: {e}"
1783
+ )
1784
+ await self._send_error_to_external(external_request_context, error_obj)
1785
+ self.task_context_manager.remove_context(task_id_from_topic)
1786
+ self.task_context_manager.remove_context(
1787
+ f"{task_id_from_topic}_stream_buffer"
1788
+ )
1789
+ return False
1790
+
1791
+ async def _async_setup_and_run(self) -> None:
1792
+ """Main async logic for the gateway component."""
1793
+ # Call base class to initialize Trust Manager
1794
+ await super()._async_setup_and_run()
1795
+
1796
+ log.info(
1797
+ "%s Starting _start_listener() to initiate external platform connection.",
1798
+ self.log_identifier,
1799
+ )
1800
+ self._start_listener()
1801
+
1802
+ await self._message_processor_loop()
1803
+
1804
+ def _pre_async_cleanup(self) -> None:
1805
+ """Pre-cleanup actions for the gateway component."""
1806
+ # Cleanup Trust Manager if present (ENTERPRISE FEATURE)
1807
+ if self.trust_manager:
1808
+ try:
1809
+ log.info("%s Cleaning up Trust Manager...", self.log_identifier)
1810
+ self.trust_manager.cleanup(self.cancel_timer)
1811
+ log.info("%s Trust Manager cleanup complete", self.log_identifier)
1812
+ except Exception as e:
1813
+ log.error(
1814
+ "%s Error during Trust Manager cleanup: %s", self.log_identifier, e
1815
+ )
1816
+
1817
+ log.info("%s Calling _stop_listener()...", self.log_identifier)
1818
+ self._stop_listener()
1819
+
1820
+ if self.internal_event_queue:
1821
+ log.info(
1822
+ "%s Signaling _message_processor_loop to stop by putting sentinel on queue...",
1823
+ self.log_identifier,
1824
+ )
1825
+ # This unblocks the `self.internal_event_queue.get()` call in the loop
1826
+ self.internal_event_queue.put(None)
1827
+
1828
+ async def _message_processor_loop(self):
1829
+ log.debug("%s Starting message processor loop as an asyncio task...", self.log_identifier)
1830
+ loop = self.get_async_loop()
1831
+
1832
+ while not self.stop_signal.is_set():
1833
+ original_broker_message: Optional[SolaceMessage] = None
1834
+ item = None
1835
+ processed_successfully = False
1836
+ topic = None
1837
+
1838
+ try:
1839
+ item = await loop.run_in_executor(None, self.internal_event_queue.get)
1840
+
1841
+ if item is None:
1842
+ log.info(
1843
+ "%s Received shutdown sentinel. Exiting message processor loop.",
1844
+ self.log_identifier,
1845
+ )
1846
+ break
1847
+
1848
+ topic = item.get("topic")
1849
+ payload = item.get("payload")
1850
+ original_broker_message = item.get("_original_broker_message")
1851
+
1852
+ if not topic or payload is None or not original_broker_message:
1853
+ log.warning(
1854
+ "%s Invalid item received from internal queue: %s",
1855
+ self.log_identifier,
1856
+ item,
1857
+ )
1858
+ processed_successfully = False
1859
+ continue
1860
+
1861
+ if a2a.topic_matches_subscription(
1862
+ topic, a2a.get_discovery_topic(self.namespace)
1863
+ ):
1864
+ processed_successfully = await self._handle_discovery_message(
1865
+ payload
1866
+ )
1867
+ elif (
1868
+ hasattr(self, "trust_manager")
1869
+ and self.trust_manager
1870
+ and self.trust_manager.is_trust_card_topic(topic)
1871
+ ):
1872
+ await self.trust_manager.handle_trust_card_message(payload, topic)
1873
+ processed_successfully = True
1874
+ elif a2a.topic_matches_subscription(
1875
+ topic,
1876
+ a2a.get_gateway_response_subscription_topic(
1877
+ self.namespace, self.gateway_id
1878
+ ),
1879
+ ) or a2a.topic_matches_subscription(
1880
+ topic,
1881
+ a2a.get_gateway_status_subscription_topic(
1882
+ self.namespace, self.gateway_id
1883
+ ),
1884
+ ):
1885
+ task_id_from_topic: Optional[str] = None
1886
+ response_sub = a2a.get_gateway_response_subscription_topic(
1887
+ self.namespace, self.gateway_id
1888
+ )
1889
+ status_sub = a2a.get_gateway_status_subscription_topic(
1890
+ self.namespace, self.gateway_id
1891
+ )
1892
+
1893
+ if a2a.topic_matches_subscription(topic, response_sub):
1894
+ task_id_from_topic = a2a.extract_task_id_from_topic(
1895
+ topic, response_sub, self.log_identifier
1896
+ )
1897
+ elif a2a.topic_matches_subscription(topic, status_sub):
1898
+ task_id_from_topic = a2a.extract_task_id_from_topic(
1899
+ topic, status_sub, self.log_identifier
1900
+ )
1901
+
1902
+ if task_id_from_topic:
1903
+ processed_successfully = await self._handle_agent_event(
1904
+ topic, payload, task_id_from_topic
1905
+ )
1906
+ else:
1907
+ log.error(
1908
+ "%s Could not extract task_id from topic %s for _handle_agent_event. Ignoring.",
1909
+ self.log_identifier,
1910
+ topic,
1911
+ )
1912
+ processed_successfully = False
1913
+ else:
1914
+ log.warning(
1915
+ "%s Received message on unhandled topic: %s. Acknowledging.",
1916
+ self.log_identifier,
1917
+ topic,
1918
+ )
1919
+ processed_successfully = True
1920
+
1921
+ except queue.Empty:
1922
+ continue
1923
+ except asyncio.CancelledError:
1924
+ log.info("%s Message processor loop cancelled.", self.log_identifier)
1925
+ break
1926
+ except Exception as e:
1927
+ log.exception(
1928
+ "%s Unhandled error in message processor loop: %s",
1929
+ self.log_identifier,
1930
+ e,
1931
+ )
1932
+ processed_successfully = False
1933
+ await asyncio.sleep(1)
1934
+ finally:
1935
+ if original_broker_message:
1936
+ if processed_successfully:
1937
+ original_broker_message.call_acknowledgements()
1938
+ else:
1939
+ original_broker_message.call_negative_acknowledgements()
1940
+ log.warning(
1941
+ "%s NACKed SolaceMessage for topic: %s",
1942
+ self.log_identifier,
1943
+ topic or "unknown",
1944
+ )
1945
+
1946
+ if item and item is not None:
1947
+ self.internal_event_queue.task_done()
1948
+
1949
+ log.info("%s Message processor loop finished.", self.log_identifier)
1950
+
1951
+ @abstractmethod
1952
+ async def _extract_initial_claims(
1953
+ self, external_event_data: Any
1954
+ ) -> Optional[Dict[str, Any]]:
1955
+ """
1956
+ Extracts the primary identity claims from a platform-specific event.
1957
+ This method MUST be implemented by derived gateway components.
1958
+
1959
+ Args:
1960
+ external_event_data: Raw event data from the external platform
1961
+ (e.g., FastAPIRequest, Slack event dictionary).
1962
+
1963
+ Returns:
1964
+ A dictionary of initial claims, which MUST include an 'id' key.
1965
+ Example: {"id": "user@example.com", "source": "slack_api"}
1966
+ Return None if authentication fails.
1967
+ """
1968
+ pass
1969
+
1970
+ @abstractmethod
1971
+ async def _translate_external_input(
1972
+ self, external_event: Any
1973
+ ) -> Tuple[str, List[ContentPart], Dict[str, Any]]:
1974
+ """
1975
+ Translates raw platform-specific event data into A2A task parameters.
1976
+
1977
+ Args:
1978
+ external_event: Raw event data from the external platform
1979
+ (e.g., FastAPIRequest, Slack event dictionary).
1980
+
1981
+ Returns:
1982
+ A tuple containing:
1983
+ - target_agent_name (str): The name of the A2A agent to target.
1984
+ - a2a_parts (List[ContentPart]): A list of A2A Part objects.
1985
+ - external_request_context (Dict[str, Any]): Context for TaskContextManager.
1986
+ """
1987
+ pass
1988
+
1989
+ @abstractmethod
1990
+ def _start_listener(self) -> None:
1991
+ pass
1992
+
1993
+ @abstractmethod
1994
+ def _stop_listener(self) -> None:
1995
+ pass
1996
+
1997
+ @abstractmethod
1998
+ async def _send_update_to_external(
1999
+ self,
2000
+ external_request_context: Dict[str, Any],
2001
+ event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
2002
+ is_final_chunk_of_update: bool,
2003
+ ) -> None:
2004
+ pass
2005
+
2006
+ @abstractmethod
2007
+ async def _send_final_response_to_external(
2008
+ self, external_request_context: Dict[str, Any], task_data: Task
2009
+ ) -> None:
2010
+ pass
2011
+
2012
+ @abstractmethod
2013
+ async def _send_error_to_external(
2014
+ self, external_request_context: Dict[str, Any], error_data: JSONRPCError
2015
+ ) -> None:
2016
+ pass
2017
+
2018
+ def _get_component_id(self) -> str:
2019
+ """Returns the gateway ID as the component identifier."""
2020
+ return self.gateway_id
2021
+
2022
+ def _get_component_type(self) -> str:
2023
+ """Returns 'gateway' as the component type."""
2024
+ return "gateway"
2025
+
2026
+ def invoke(self, message, data):
2027
+ if isinstance(message, SolaceMessage):
2028
+ message.call_acknowledgements()
2029
+ log.warning("%s Invoke method called unexpectedly.", self.log_identifier)
2030
+ return None