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

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

Potentially problematic release.


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

Files changed (521) hide show
  1. solace_agent_mesh/__init__.py +5 -0
  2. solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
  3. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  4. solace_agent_mesh/agent/adk/callbacks.py +1716 -0
  5. solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
  6. solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
  7. solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
  8. solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
  9. solace_agent_mesh/agent/adk/runner.py +357 -0
  10. solace_agent_mesh/agent/adk/services.py +240 -0
  11. solace_agent_mesh/agent/adk/setup.py +751 -0
  12. solace_agent_mesh/agent/adk/stream_parser.py +214 -0
  13. solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
  14. solace_agent_mesh/agent/agent_llm.txt +41 -0
  15. solace_agent_mesh/agent/protocol/event_handlers.py +1444 -0
  16. solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
  17. solace_agent_mesh/agent/sac/app.py +640 -0
  18. solace_agent_mesh/agent/sac/component.py +3496 -0
  19. solace_agent_mesh/agent/sac/patch_adk.py +111 -0
  20. solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
  21. solace_agent_mesh/agent/sac/task_execution_context.py +185 -0
  22. solace_agent_mesh/agent/testing/__init__.py +3 -0
  23. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  24. solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
  25. solace_agent_mesh/agent/tools/__init__.py +14 -0
  26. solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
  27. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
  28. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
  29. solace_agent_mesh/agent/tools/general_agent_tools.py +571 -0
  30. solace_agent_mesh/agent/tools/image_tools.py +1184 -0
  31. solace_agent_mesh/agent/tools/peer_agent_tool.py +290 -0
  32. solace_agent_mesh/agent/tools/registry.py +36 -0
  33. solace_agent_mesh/agent/tools/test_tools.py +135 -0
  34. solace_agent_mesh/agent/tools/tool_definition.py +45 -0
  35. solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
  36. solace_agent_mesh/agent/tools/web_tools.py +381 -0
  37. solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
  38. solace_agent_mesh/agent/utils/config_parser.py +47 -0
  39. solace_agent_mesh/agent/utils/context_helpers.py +60 -0
  40. solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
  41. solace_agent_mesh/assets/docs/404.html +16 -0
  42. solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
  43. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  44. solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
  45. solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
  46. solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  54. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  55. solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
  56. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
  57. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
  58. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.a8c5ce5a.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  95. solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
  96. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  101. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  102. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
  104. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
  105. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/f897a61a.f8c53b0f.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
  131. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +2 -0
  132. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js.LICENSE.txt +81 -0
  133. solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +1 -0
  134. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
  135. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
  137. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
  138. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
  139. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
  140. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +77 -0
  141. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
  142. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
  143. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
  146. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
  147. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
  148. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
  149. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
  150. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
  151. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
  152. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
  153. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
  154. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
  155. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
  156. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
  157. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
  158. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
  159. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
  160. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
  161. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
  162. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
  163. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
  164. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
  165. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
  166. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
  167. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  168. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  169. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  170. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  171. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  172. solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +1 -0
  173. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  174. solace_agent_mesh/assets/docs/search-doc-1754075282978.json +1 -0
  175. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  176. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  177. solace_agent_mesh/cli/__init__.py +1 -1
  178. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  179. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  180. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
  181. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  182. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
  183. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
  184. solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
  185. solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
  186. solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
  187. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  188. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  189. solace_agent_mesh/cli/commands/init_cmd/env_step.py +205 -0
  190. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  191. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +407 -0
  192. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  193. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
  194. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
  195. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
  196. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
  197. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  198. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +139 -0
  199. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
  200. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
  201. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  202. solace_agent_mesh/cli/commands/run_cmd.py +158 -0
  203. solace_agent_mesh/cli/main.py +17 -294
  204. solace_agent_mesh/cli/utils.py +135 -204
  205. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
  206. solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
  207. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  208. solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +1 -0
  209. solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +663 -0
  210. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
  211. solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
  212. solace_agent_mesh/common/__init__.py +1 -0
  213. solace_agent_mesh/common/a2a_protocol.py +564 -0
  214. solace_agent_mesh/common/agent_registry.py +42 -0
  215. solace_agent_mesh/common/client/__init__.py +4 -0
  216. solace_agent_mesh/common/client/card_resolver.py +21 -0
  217. solace_agent_mesh/common/client/client.py +85 -0
  218. solace_agent_mesh/common/client/client_llm.txt +133 -0
  219. solace_agent_mesh/common/common_llm.txt +144 -0
  220. solace_agent_mesh/common/constants.py +1 -14
  221. solace_agent_mesh/common/middleware/__init__.py +12 -0
  222. solace_agent_mesh/common/middleware/config_resolver.py +130 -0
  223. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  224. solace_agent_mesh/common/middleware/registry.py +125 -0
  225. solace_agent_mesh/common/server/__init__.py +4 -0
  226. solace_agent_mesh/common/server/server.py +122 -0
  227. solace_agent_mesh/common/server/server_llm.txt +169 -0
  228. solace_agent_mesh/common/server/task_manager.py +291 -0
  229. solace_agent_mesh/common/server/utils.py +28 -0
  230. solace_agent_mesh/common/services/__init__.py +4 -0
  231. solace_agent_mesh/common/services/employee_service.py +162 -0
  232. solace_agent_mesh/common/services/identity_service.py +129 -0
  233. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  234. solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
  235. solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
  236. solace_agent_mesh/common/services/services_llm.txt +132 -0
  237. solace_agent_mesh/common/types.py +411 -0
  238. solace_agent_mesh/common/utils/__init__.py +7 -0
  239. solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
  240. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  241. solace_agent_mesh/common/utils/embeds/constants.py +55 -0
  242. solace_agent_mesh/common/utils/embeds/converter.py +452 -0
  243. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
  244. solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
  245. solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
  246. solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
  247. solace_agent_mesh/common/utils/embeds/types.py +14 -0
  248. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  249. solace_agent_mesh/common/utils/initializer.py +51 -0
  250. solace_agent_mesh/common/utils/log_formatters.py +44 -0
  251. solace_agent_mesh/common/utils/mime_helpers.py +106 -0
  252. solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
  253. solace_agent_mesh/common/utils/utils_llm.txt +67 -0
  254. solace_agent_mesh/config_portal/backend/common.py +66 -24
  255. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
  256. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  257. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +164 -0
  258. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
  259. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
  260. solace_agent_mesh/config_portal/backend/server.py +551 -181
  261. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-_7yox_eh.js +48 -0
  262. solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
  263. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
  264. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
  265. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-e5c3acfe.js +1 -0
  266. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
  267. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
  268. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  269. solace_agent_mesh/core_a2a/__init__.py +1 -0
  270. solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
  271. solace_agent_mesh/core_a2a/service.py +331 -0
  272. solace_agent_mesh/evaluation/config_loader.py +657 -0
  273. solace_agent_mesh/evaluation/evaluator.py +667 -0
  274. solace_agent_mesh/evaluation/message_organizer.py +568 -0
  275. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  276. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  277. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  278. solace_agent_mesh/evaluation/report/modal.html +59 -0
  279. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  280. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  281. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  282. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  283. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  284. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  285. solace_agent_mesh/evaluation/report_data_processor.py +972 -0
  286. solace_agent_mesh/evaluation/report_generator.py +613 -0
  287. solace_agent_mesh/evaluation/run.py +613 -0
  288. solace_agent_mesh/evaluation/subscriber.py +872 -0
  289. solace_agent_mesh/evaluation/summary_builder.py +775 -0
  290. solace_agent_mesh/evaluation/test_case_loader.py +714 -0
  291. solace_agent_mesh/gateway/base/__init__.py +1 -0
  292. solace_agent_mesh/gateway/base/app.py +266 -0
  293. solace_agent_mesh/gateway/base/base_llm.txt +119 -0
  294. solace_agent_mesh/gateway/base/component.py +1542 -0
  295. solace_agent_mesh/gateway/base/task_context.py +74 -0
  296. solace_agent_mesh/gateway/gateway_llm.txt +125 -0
  297. solace_agent_mesh/gateway/http_sse/app.py +190 -0
  298. solace_agent_mesh/gateway/http_sse/component.py +1602 -0
  299. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  300. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
  301. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
  302. solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
  303. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
  304. solace_agent_mesh/gateway/http_sse/main.py +442 -0
  305. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  306. solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
  307. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +827 -0
  308. solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
  309. solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
  310. solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
  311. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
  312. solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
  313. solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
  314. solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
  315. solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
  316. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
  317. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  318. solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
  319. solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
  320. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
  321. solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
  322. solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
  323. solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
  324. solace_agent_mesh/llm.txt +228 -0
  325. solace_agent_mesh/llm_detail.txt +2835 -0
  326. solace_agent_mesh/templates/agent_template.yaml +53 -0
  327. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  328. solace_agent_mesh/templates/gateway_app_template.py +73 -0
  329. solace_agent_mesh/templates/gateway_component_template.py +431 -0
  330. solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
  331. solace_agent_mesh/templates/logging_config_template.ini +64 -0
  332. solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
  333. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  334. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  335. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  336. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
  337. solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
  338. solace_agent_mesh/templates/plugin_readme_template.md +34 -0
  339. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  340. solace_agent_mesh/templates/shared_config.yaml +66 -0
  341. solace_agent_mesh/templates/templates_llm.txt +147 -0
  342. solace_agent_mesh/templates/webui.yaml +53 -0
  343. solace_agent_mesh-1.0.2.dist-info/METADATA +432 -0
  344. solace_agent_mesh-1.0.2.dist-info/RECORD +361 -0
  345. solace_agent_mesh-1.0.2.dist-info/entry_points.txt +3 -0
  346. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/licenses/LICENSE +1 -1
  347. solace_agent_mesh/agents/base_agent_component.py +0 -256
  348. solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
  349. solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
  350. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
  351. solace_agent_mesh/agents/global/actions/create_file.py +0 -70
  352. solace_agent_mesh/agents/global/actions/error_action.py +0 -45
  353. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
  354. solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
  355. solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
  356. solace_agent_mesh/agents/global/global_agent_component.py +0 -38
  357. solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
  358. solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
  359. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
  360. solace_agent_mesh/agents/slack/__init__.py +0 -1
  361. solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
  362. solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
  363. solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
  364. solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
  365. solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
  366. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
  367. solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
  368. solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
  369. solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
  370. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
  371. solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
  372. solace_agent_mesh/assets/web-visualizer/index.html +0 -14
  373. solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
  374. solace_agent_mesh/cli/commands/add/__init__.py +0 -3
  375. solace_agent_mesh/cli/commands/add/add.py +0 -88
  376. solace_agent_mesh/cli/commands/add/agent.py +0 -110
  377. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
  378. solace_agent_mesh/cli/commands/add/gateway.py +0 -374
  379. solace_agent_mesh/cli/commands/build.py +0 -670
  380. solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
  381. solace_agent_mesh/cli/commands/chat/chat.py +0 -361
  382. solace_agent_mesh/cli/commands/config.py +0 -29
  383. solace_agent_mesh/cli/commands/init/__init__.py +0 -3
  384. solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
  385. solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
  386. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
  387. solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
  388. solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
  389. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
  390. solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
  391. solace_agent_mesh/cli/commands/init/init.py +0 -92
  392. solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
  393. solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
  394. solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
  395. solace_agent_mesh/cli/commands/plugin/add.py +0 -100
  396. solace_agent_mesh/cli/commands/plugin/build.py +0 -268
  397. solace_agent_mesh/cli/commands/plugin/create.py +0 -117
  398. solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
  399. solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
  400. solace_agent_mesh/cli/commands/run.py +0 -68
  401. solace_agent_mesh/cli/commands/visualizer.py +0 -138
  402. solace_agent_mesh/cli/config.py +0 -85
  403. solace_agent_mesh/common/action.py +0 -91
  404. solace_agent_mesh/common/action_list.py +0 -37
  405. solace_agent_mesh/common/action_response.py +0 -340
  406. solace_agent_mesh/common/mysql_database.py +0 -40
  407. solace_agent_mesh/common/postgres_database.py +0 -85
  408. solace_agent_mesh/common/prompt_templates.py +0 -28
  409. solace_agent_mesh/common/stimulus_utils.py +0 -152
  410. solace_agent_mesh/common/time.py +0 -24
  411. solace_agent_mesh/common/utils.py +0 -712
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-a-zJ6rLx.js +0 -46
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
  414. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
  415. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-44c41103.js +0 -1
  416. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
  417. solace_agent_mesh/configs/agent_global.yaml +0 -74
  418. solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
  419. solace_agent_mesh/configs/agent_slack.yaml +0 -64
  420. solace_agent_mesh/configs/agent_web_request.yaml +0 -75
  421. solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
  422. solace_agent_mesh/configs/error_catcher.yaml +0 -56
  423. solace_agent_mesh/configs/monitor.yaml +0 -0
  424. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
  425. solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
  426. solace_agent_mesh/configs/orchestrator.yaml +0 -241
  427. solace_agent_mesh/configs/service_embedding.yaml +0 -81
  428. solace_agent_mesh/configs/service_llm.yaml +0 -265
  429. solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
  430. solace_agent_mesh/gateway/components/gateway_base.py +0 -47
  431. solace_agent_mesh/gateway/components/gateway_input.py +0 -278
  432. solace_agent_mesh/gateway/components/gateway_output.py +0 -298
  433. solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
  434. solace_agent_mesh/gateway/identity/identity_base.py +0 -10
  435. solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
  436. solace_agent_mesh/gateway/identity/no_identity.py +0 -9
  437. solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
  438. solace_agent_mesh/monitors/base_monitor_component.py +0 -26
  439. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
  440. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
  441. solace_agent_mesh/orchestrator/__init__.py +0 -0
  442. solace_agent_mesh/orchestrator/action_manager.py +0 -237
  443. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  444. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
  445. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
  446. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
  447. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
  448. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
  449. solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
  450. solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
  451. solace_agent_mesh/services/__init__.py +0 -0
  452. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
  453. solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
  454. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
  455. solace_agent_mesh/services/common/__init__.py +0 -4
  456. solace_agent_mesh/services/common/auto_expiry.py +0 -45
  457. solace_agent_mesh/services/common/singleton.py +0 -18
  458. solace_agent_mesh/services/file_service/__init__.py +0 -14
  459. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  460. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
  461. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
  462. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
  463. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
  464. solace_agent_mesh/services/file_service/file_service.py +0 -437
  465. solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
  466. solace_agent_mesh/services/file_service/file_transformations.py +0 -141
  467. solace_agent_mesh/services/file_service/file_utils.py +0 -324
  468. solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
  469. solace_agent_mesh/services/history_service/__init__.py +0 -3
  470. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  471. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
  472. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
  473. solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
  474. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
  475. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
  476. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
  477. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
  478. solace_agent_mesh/services/history_service/history_service.py +0 -413
  479. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  480. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
  481. solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
  482. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
  483. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  484. solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
  485. solace_agent_mesh/templates/action.py +0 -38
  486. solace_agent_mesh/templates/agent.py +0 -29
  487. solace_agent_mesh/templates/agent.yaml +0 -70
  488. solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
  489. solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
  490. solace_agent_mesh/templates/gateway-flows.yaml +0 -78
  491. solace_agent_mesh/templates/gateway-header.yaml +0 -16
  492. solace_agent_mesh/templates/gateway_base.py +0 -15
  493. solace_agent_mesh/templates/gateway_input.py +0 -98
  494. solace_agent_mesh/templates/gateway_output.py +0 -71
  495. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
  496. solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
  497. solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
  498. solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
  499. solace_agent_mesh/templates/slack-default-config.yaml +0 -16
  500. solace_agent_mesh/templates/slack-flows.yaml +0 -81
  501. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
  502. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
  503. solace_agent_mesh/templates/web-default-config.yaml +0 -10
  504. solace_agent_mesh/templates/web-flows.yaml +0 -76
  505. solace_agent_mesh/tools/__init__.py +0 -0
  506. solace_agent_mesh/tools/components/__init__.py +0 -0
  507. solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
  508. solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
  509. solace_agent_mesh/tools/config/runtime_config.py +0 -26
  510. solace_agent_mesh-0.2.4.dist-info/METADATA +0 -176
  511. solace_agent_mesh-0.2.4.dist-info/RECORD +0 -193
  512. solace_agent_mesh-0.2.4.dist-info/entry_points.txt +0 -3
  513. /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
  514. /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
  515. /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
  516. /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
  517. /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
  518. /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
  519. /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
  520. /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
  521. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1542 @@
1
+ """
2
+ Base Component class for Gateway implementations in the Solace AI Connector.
3
+ """
4
+
5
+ import asyncio
6
+ import queue
7
+ import re
8
+ import threading
9
+ import base64
10
+ import uuid
11
+ from datetime import datetime, timezone
12
+ from typing import Any, Dict, Optional, List, Tuple, Union
13
+ from urllib.parse import urlparse, parse_qs
14
+
15
+ from solace_ai_connector.components.component_base import ComponentBase
16
+ from solace_ai_connector.common.log import log
17
+ from google.adk.artifacts import BaseArtifactService
18
+
19
+ from ...common.agent_registry import AgentRegistry
20
+ from ...core_a2a.service import CoreA2AService
21
+ from ...agent.adk.services import initialize_artifact_service
22
+ from ...common.services.identity_service import (
23
+ BaseIdentityService,
24
+ create_identity_service,
25
+ )
26
+ from .task_context import TaskContextManager
27
+ from ...common.types import (
28
+ Part as A2APart,
29
+ Message as A2AMessage,
30
+ AgentCard,
31
+ JSONRPCResponse,
32
+ Task,
33
+ TaskStatusUpdateEvent,
34
+ TaskArtifactUpdateEvent,
35
+ JSONRPCError,
36
+ TextPart,
37
+ TaskStatus,
38
+ TaskState,
39
+ FilePart,
40
+ DataPart,
41
+ Artifact as A2AArtifact,
42
+ )
43
+ from ...common.a2a_protocol import (
44
+ get_gateway_response_topic,
45
+ get_gateway_response_subscription_topic,
46
+ get_gateway_status_topic,
47
+ get_gateway_status_subscription_topic,
48
+ get_discovery_topic,
49
+ _topic_matches_subscription,
50
+ _subscription_to_regex,
51
+ )
52
+ from ...common.utils import is_text_based_mime_type
53
+ from ...common.utils.embeds import (
54
+ resolve_embeds_in_string,
55
+ resolve_embeds_recursively_in_string,
56
+ evaluate_embed,
57
+ LATE_EMBED_TYPES,
58
+ EARLY_EMBED_TYPES,
59
+ EMBED_DELIMITER_OPEN,
60
+ )
61
+ from solace_ai_connector.common.message import (
62
+ Message as SolaceMessage,
63
+ )
64
+ from solace_ai_connector.common.event import Event, EventType
65
+ from abc import abstractmethod
66
+
67
+ from ...common.middleware.registry import MiddlewareRegistry
68
+ from ...agent.utils.artifact_helpers import load_artifact_content_or_metadata
69
+
70
+ info = {
71
+ "class_name": "BaseGatewayComponent",
72
+ "description": (
73
+ "Abstract base component for A2A gateways. Handles common service "
74
+ "initialization and provides a framework for platform-specific logic. "
75
+ "Configuration is typically derived from the parent BaseGatewayApp's app_config."
76
+ ),
77
+ "config_parameters": [],
78
+ "input_schema": {
79
+ "type": "object",
80
+ "description": "Not typically used directly; component reacts to events from its input queue.",
81
+ },
82
+ "output_schema": {
83
+ "type": "object",
84
+ "description": "Not typically used directly; component sends data to external systems.",
85
+ },
86
+ }
87
+
88
+
89
+ class BaseGatewayComponent(ComponentBase):
90
+ """
91
+ Abstract base class for Gateway components.
92
+
93
+ Initializes shared services and manages the core lifecycle for processing
94
+ A2A messages and interacting with an external communication platform.
95
+ """
96
+
97
+ _RESOLVE_EMBEDS_IN_FINAL_RESPONSE = False
98
+
99
+ def get_config(self, key: str, default: Any = None) -> Any:
100
+ """
101
+ Overrides the default get_config to first look inside the nested
102
+ 'app_config' dictionary that BaseGatewayApp places in the component_config.
103
+ This is the primary way gateway components should access their configuration.
104
+ """
105
+ if "app_config" in self.component_config:
106
+ value = self.component_config["app_config"].get(key)
107
+ if value is not None:
108
+ return value
109
+
110
+ return super().get_config(key, default)
111
+
112
+ def __init__(self, **kwargs: Any):
113
+ super().__init__(info, **kwargs)
114
+ log.info("%s Initializing Base Gateway Component...", self.log_identifier)
115
+
116
+ try:
117
+ self.namespace: str = self.get_config("namespace")
118
+ self.gateway_id: str = self.get_config("gateway_id")
119
+ if not self.namespace or not self.gateway_id:
120
+ raise ValueError(
121
+ "Namespace and Gateway ID must be configured in the app_config."
122
+ )
123
+
124
+ self.enable_embed_resolution: bool = self.get_config(
125
+ "enable_embed_resolution", True
126
+ )
127
+ self.gateway_max_artifact_resolve_size_bytes: int = self.get_config(
128
+ "gateway_max_artifact_resolve_size_bytes"
129
+ )
130
+ self.gateway_recursive_embed_depth: int = self.get_config(
131
+ "gateway_recursive_embed_depth"
132
+ )
133
+ self.gateway_artifact_content_limit_bytes: int = self.get_config(
134
+ "gateway_artifact_content_limit_bytes"
135
+ )
136
+ _ = self.get_config("artifact_service")
137
+
138
+ log.info(
139
+ "%s Retrieved common configs: Namespace=%s, GatewayID=%s",
140
+ self.log_identifier,
141
+ self.namespace,
142
+ self.gateway_id,
143
+ )
144
+
145
+ except Exception as e:
146
+ log.error(
147
+ "%s Failed to retrieve essential configuration: %s",
148
+ self.log_identifier,
149
+ e,
150
+ )
151
+ raise ValueError(f"Configuration retrieval error: {e}") from e
152
+
153
+ self.agent_registry: AgentRegistry = AgentRegistry()
154
+ self.core_a2a_service: CoreA2AService = CoreA2AService(
155
+ agent_registry=self.agent_registry, namespace=self.namespace
156
+ )
157
+ self.shared_artifact_service: Optional[BaseArtifactService] = (
158
+ initialize_artifact_service(self)
159
+ )
160
+ self.task_context_manager: TaskContextManager = TaskContextManager()
161
+ self.internal_event_queue: queue.Queue = queue.Queue()
162
+ self.message_processor_thread: Optional[threading.Thread] = None
163
+ self.async_loop: Optional[asyncio.AbstractEventLoop] = None
164
+ self.async_thread: Optional[threading.Thread] = None
165
+
166
+ identity_service_config = self.get_config("identity_service")
167
+ self.identity_service: Optional[BaseIdentityService] = create_identity_service(
168
+ identity_service_config
169
+ )
170
+
171
+ self._config_resolver = MiddlewareRegistry.get_config_resolver()
172
+ log.info(
173
+ "%s Middleware system initialized (using default configuration resolver).",
174
+ self.log_identifier,
175
+ )
176
+
177
+ log.info(
178
+ "%s Base Gateway Component initialized successfully.", self.log_identifier
179
+ )
180
+
181
+ def publish_a2a_message(
182
+ self, topic: str, payload: Dict, user_properties: Optional[Dict] = None
183
+ ) -> None:
184
+ log.debug(
185
+ "%s Publishing A2A message to topic: %s via App", self.log_identifier, topic
186
+ )
187
+ try:
188
+ app = self.get_app()
189
+ if app:
190
+ app.send_message(
191
+ payload=payload, topic=topic, user_properties=user_properties
192
+ )
193
+ log.debug(
194
+ "%s Successfully published message to %s via App",
195
+ self.log_identifier,
196
+ topic,
197
+ )
198
+ else:
199
+ log.error(
200
+ "%s Cannot publish message: Not running within a SAC App context.",
201
+ self.log_identifier,
202
+ )
203
+ raise RuntimeError(
204
+ "Cannot publish message: Not running within a SAC App context."
205
+ )
206
+ except Exception as e:
207
+ log.exception(
208
+ "%s Failed to publish A2A message to topic %s via App: %s",
209
+ self.log_identifier,
210
+ topic,
211
+ e,
212
+ )
213
+ raise
214
+
215
+ async def authenticate_and_enrich_user(
216
+ self, external_event_data: Any
217
+ ) -> Optional[Dict[str, Any]]:
218
+ """
219
+ Orchestrates the full authentication and identity enrichment flow.
220
+ This method should be called by gateway handlers.
221
+ """
222
+ log_id_prefix = f"{self.log_identifier}[AuthAndEnrich]"
223
+
224
+ auth_claims = await self._extract_initial_claims(external_event_data)
225
+ if not auth_claims:
226
+ log.warning(
227
+ "%s Initial claims extraction failed or returned no identity.",
228
+ log_id_prefix,
229
+ )
230
+ return None
231
+
232
+ if self.identity_service:
233
+ enriched_profile = await self.identity_service.get_user_profile(auth_claims)
234
+ if enriched_profile:
235
+ final_profile = enriched_profile.copy()
236
+ final_profile.update(auth_claims)
237
+ log.info(
238
+ "%s Successfully merged auth claims and enriched profile for user: %s",
239
+ log_id_prefix,
240
+ auth_claims.get("id"),
241
+ )
242
+ return final_profile
243
+ else:
244
+ log.debug(
245
+ "%s IdentityService found no profile for user: %s. Using claims only.",
246
+ log_id_prefix,
247
+ auth_claims.get("id"),
248
+ )
249
+
250
+ return auth_claims
251
+
252
+ async def submit_a2a_task(
253
+ self,
254
+ target_agent_name: str,
255
+ a2a_parts: List[A2APart],
256
+ external_request_context: Dict[str, Any],
257
+ user_identity: Any,
258
+ is_streaming: bool = True,
259
+ api_version: str = "v2",
260
+ ) -> str:
261
+ log_id_prefix = f"{self.log_identifier}[SubmitA2ATask]"
262
+ log.info(
263
+ "%s Submitting task for user_identity: %s",
264
+ log_id_prefix,
265
+ user_identity.get("id", user_identity),
266
+ )
267
+
268
+ if not isinstance(user_identity, dict) or not user_identity.get("id"):
269
+ log.error(
270
+ "%s Authentication failed or returned invalid profile. Denying task submission.",
271
+ log_id_prefix,
272
+ )
273
+ raise PermissionError("User not authenticated or identity is invalid.")
274
+
275
+ force_identity_str = self.get_config("force_user_identity")
276
+ if force_identity_str:
277
+ original_identity_id = user_identity.get("id")
278
+ user_identity = {"id": force_identity_str, "name": force_identity_str}
279
+ log.warning(
280
+ "%s DEVELOPMENT MODE: Forcing user_identity from '%s' to '%s'",
281
+ log_id_prefix,
282
+ original_identity_id,
283
+ force_identity_str,
284
+ )
285
+
286
+ config_resolver = MiddlewareRegistry.get_config_resolver()
287
+ gateway_context = {"gateway_id": self.gateway_id}
288
+
289
+ try:
290
+ user_config = await config_resolver.resolve_user_config(
291
+ user_identity, gateway_context, {}
292
+ )
293
+ log.info(
294
+ "%s Resolved user configuration for user_identity '%s': %s",
295
+ log_id_prefix,
296
+ user_identity.get("id"),
297
+ {k: v for k, v in user_config.items() if not k.startswith("_")},
298
+ )
299
+ except Exception as config_err:
300
+ log.exception(
301
+ "%s Error resolving user configuration for '%s': %s. Proceeding with default configuration.",
302
+ log_id_prefix,
303
+ user_identity.get("id"),
304
+ config_err,
305
+ )
306
+ user_config = {}
307
+
308
+ user_config["user_profile"] = user_identity
309
+
310
+ external_request_context["user_identity"] = user_identity
311
+ external_request_context["a2a_user_config"] = user_config
312
+ external_request_context["api_version"] = api_version
313
+ log.debug(
314
+ "%s Stored user_identity, configuration, and api_version (%s) in external_request_context.",
315
+ log_id_prefix,
316
+ api_version,
317
+ )
318
+
319
+ now = datetime.now(timezone.utc)
320
+ timestamp_str = now.isoformat()
321
+ timestamp_header_part = TextPart(
322
+ text=f"Request received by gateway at: {timestamp_str}"
323
+ )
324
+ if not isinstance(a2a_parts, list):
325
+ a2a_parts = list(a2a_parts)
326
+ a2a_parts.insert(0, timestamp_header_part)
327
+ log.debug("%s Prepended timestamp to a2a_parts.", log_id_prefix)
328
+
329
+ a2a_session_id = external_request_context.get("a2a_session_id")
330
+ user_id_for_a2a = external_request_context.get(
331
+ "user_id_for_a2a", user_identity.get("id")
332
+ )
333
+
334
+ if not a2a_session_id:
335
+ a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
336
+ log.warning(
337
+ "%s 'a2a_session_id' not found in external_request_context, generated: %s",
338
+ self.log_identifier,
339
+ a2a_session_id,
340
+ )
341
+ external_request_context["a2a_session_id"] = a2a_session_id
342
+
343
+ a2a_message = A2AMessage(role="user", parts=a2a_parts)
344
+ reply_topic_pattern = get_gateway_response_topic(
345
+ self.namespace, self.gateway_id, "{task_id}"
346
+ )
347
+ status_topic_pattern = get_gateway_status_topic(
348
+ self.namespace, self.gateway_id, "{task_id}"
349
+ )
350
+
351
+ task_metadata_override: Dict[str, Any] = {}
352
+ system_purpose = self.get_config("system_purpose", "")
353
+ response_format = self.get_config("response_format", "")
354
+
355
+ if system_purpose:
356
+ task_metadata_override["system_purpose"] = system_purpose
357
+ log.debug("%s Adding system_purpose to task metadata.", log_id_prefix)
358
+ if response_format:
359
+ task_metadata_override["response_format"] = response_format
360
+ log.debug("%s Adding response_format to task metadata.", log_id_prefix)
361
+
362
+ if is_streaming:
363
+ target_topic, payload, user_properties = (
364
+ self.core_a2a_service.submit_streaming_task(
365
+ agent_name=target_agent_name,
366
+ a2a_message=a2a_message,
367
+ session_id=a2a_session_id,
368
+ client_id=self.gateway_id,
369
+ reply_to_topic=reply_topic_pattern,
370
+ status_to_topic=status_topic_pattern,
371
+ user_id=user_id_for_a2a,
372
+ a2a_user_config=user_config,
373
+ metadata_override=task_metadata_override,
374
+ )
375
+ )
376
+ else:
377
+ target_topic, payload, user_properties = self.core_a2a_service.submit_task(
378
+ agent_name=target_agent_name,
379
+ a2a_message=a2a_message,
380
+ session_id=a2a_session_id,
381
+ client_id=self.gateway_id,
382
+ reply_to_topic=reply_topic_pattern,
383
+ user_id=user_id_for_a2a,
384
+ a2a_user_config=user_config,
385
+ metadata_override=task_metadata_override,
386
+ )
387
+
388
+ task_id = payload.get("params", {}).get("id")
389
+ if not task_id:
390
+ log.error(
391
+ "%s CoreA2AService did not return a task ID in the payload.",
392
+ log_id_prefix,
393
+ )
394
+ raise ValueError("CoreA2AService did not return a task ID in the payload.")
395
+
396
+ if user_properties is None:
397
+ user_properties = {}
398
+
399
+ user_properties["replyTo"] = get_gateway_response_topic(
400
+ self.namespace, self.gateway_id, task_id
401
+ )
402
+ if is_streaming:
403
+ user_properties["a2aStatusTopic"] = get_gateway_status_topic(
404
+ self.namespace, self.gateway_id, task_id
405
+ )
406
+
407
+ self.task_context_manager.store_context(task_id, external_request_context)
408
+ log.info("%s Stored external context for task_id: %s", log_id_prefix, task_id)
409
+
410
+ self.publish_a2a_message(
411
+ topic=target_topic, payload=payload, user_properties=user_properties
412
+ )
413
+ log.info(
414
+ "%s Submitted A2A task %s to agent %s. Streaming: %s",
415
+ log_id_prefix,
416
+ task_id,
417
+ target_agent_name,
418
+ is_streaming,
419
+ )
420
+ return task_id
421
+
422
+ def process_event(self, event: Event):
423
+ if event.event_type == EventType.MESSAGE:
424
+ original_broker_message: Optional[SolaceMessage] = event.data
425
+ if not original_broker_message:
426
+ log.warning(
427
+ "%s Received MESSAGE event with no data. Ignoring.",
428
+ self.log_identifier,
429
+ )
430
+ return
431
+
432
+ log.debug(
433
+ "%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
434
+ self.log_identifier,
435
+ original_broker_message.get_topic(),
436
+ )
437
+ try:
438
+ msg_data_for_processor = {
439
+ "topic": original_broker_message.get_topic(),
440
+ "payload": original_broker_message.get_payload(),
441
+ "user_properties": original_broker_message.get_user_properties(),
442
+ "_original_broker_message": original_broker_message,
443
+ }
444
+ self.internal_event_queue.put_nowait(msg_data_for_processor)
445
+ except queue.Full:
446
+ log.error(
447
+ "%s Internal event queue full. Cannot bridge message. NACKing.",
448
+ self.log_identifier,
449
+ )
450
+ original_broker_message.call_negative_acknowledgements()
451
+ except Exception as e:
452
+ log.exception(
453
+ "%s Error bridging message to internal queue: %s. NACKing.",
454
+ self.log_identifier,
455
+ e,
456
+ )
457
+ original_broker_message.call_negative_acknowledgements()
458
+ else:
459
+ log.debug(
460
+ "%s Received non-MESSAGE event type: %s. Passing to super.",
461
+ self.log_identifier,
462
+ event.event_type,
463
+ )
464
+ super().process_event(event)
465
+
466
+ async def _handle_resolved_signals(
467
+ self,
468
+ external_request_context: Dict,
469
+ signals: List[Tuple[int, Any]],
470
+ original_rpc_id: Optional[str],
471
+ is_finalizing_context: bool = False,
472
+ ):
473
+ log_id_prefix = f"{self.log_identifier}[SignalHandler]"
474
+ if not signals:
475
+ return
476
+
477
+ for _, signal_tuple in signals:
478
+ if (
479
+ isinstance(signal_tuple, tuple)
480
+ and len(signal_tuple) == 3
481
+ and signal_tuple[0] is None
482
+ ):
483
+ signal_type = signal_tuple[1]
484
+ signal_data = signal_tuple[2]
485
+
486
+ if signal_type == "SIGNAL_STATUS_UPDATE":
487
+ status_text = signal_data
488
+ log.info(
489
+ "%s Handling SIGNAL_STATUS_UPDATE: '%s'",
490
+ log_id_prefix,
491
+ status_text,
492
+ )
493
+ if is_finalizing_context:
494
+ log.debug(
495
+ "%s Suppressing SIGNAL_STATUS_UPDATE ('%s') during finalizing context.",
496
+ log_id_prefix,
497
+ status_text,
498
+ )
499
+ continue
500
+ try:
501
+ signal_data_part = DataPart(
502
+ data={"type": "agent_status", "text": status_text},
503
+ metadata={"source": "agent_progress_update"},
504
+ )
505
+ signal_a2a_message = A2AMessage(
506
+ role="agent", parts=[signal_data_part]
507
+ )
508
+ signal_task_status = TaskStatus(
509
+ state=TaskState.WORKING, message=signal_a2a_message
510
+ )
511
+ a2a_task_id_for_signal = external_request_context.get(
512
+ "a2a_task_id_for_event", original_rpc_id
513
+ )
514
+ if not a2a_task_id_for_signal:
515
+ log.error(
516
+ "%s Cannot determine A2A task ID for signal event. Skipping.",
517
+ log_id_prefix,
518
+ )
519
+ continue
520
+
521
+ signal_event = TaskStatusUpdateEvent(
522
+ id=a2a_task_id_for_signal,
523
+ status=signal_task_status,
524
+ final=False,
525
+ )
526
+ await self._send_update_to_external(
527
+ external_request_context=external_request_context,
528
+ event_data=signal_event,
529
+ is_final_chunk_of_update=True,
530
+ )
531
+ log.debug(
532
+ "%s Sent status signal as TaskStatusUpdateEvent.",
533
+ log_id_prefix,
534
+ )
535
+ except Exception as e:
536
+ log.exception(
537
+ "%s Error sending status signal: %s", log_id_prefix, e
538
+ )
539
+ else:
540
+ log.warning(
541
+ "%s Received unhandled signal type during embed resolution: %s",
542
+ log_id_prefix,
543
+ signal_type,
544
+ )
545
+
546
+ async def _resolve_uri_in_file_part(self, part: A2APart):
547
+ """
548
+ Checks if a part is a FilePart with a resolvable URI and, if so,
549
+ resolves it and mutates the part in-place.
550
+ """
551
+ if not (
552
+ isinstance(part, FilePart)
553
+ and part.file
554
+ and part.file.uri
555
+ and part.file.uri.startswith("artifact://")
556
+ ):
557
+ return
558
+
559
+ if not self.shared_artifact_service:
560
+ log.warning(
561
+ "%s Cannot resolve artifact URI, shared_artifact_service is not configured.",
562
+ self.log_identifier,
563
+ )
564
+ return
565
+
566
+ uri = part.file.uri
567
+ log_id_prefix = f"{self.log_identifier}[ResolveURI]"
568
+ try:
569
+ log.info("%s Found artifact URI to resolve: %s", log_id_prefix, uri)
570
+ parsed_uri = urlparse(uri)
571
+ app_name = parsed_uri.netloc
572
+ path_parts = parsed_uri.path.strip("/").split("/")
573
+
574
+ if not app_name or len(path_parts) != 3:
575
+ raise ValueError(
576
+ "Invalid URI structure. Expected artifact://app_name/user_id/session_id/filename"
577
+ )
578
+
579
+ user_id, session_id, filename = path_parts
580
+ version = int(parse_qs(parsed_uri.query).get("version", [None])[0])
581
+
582
+ loaded_artifact = await load_artifact_content_or_metadata(
583
+ artifact_service=self.shared_artifact_service,
584
+ app_name=app_name,
585
+ user_id=user_id,
586
+ session_id=session_id,
587
+ filename=filename,
588
+ version=version,
589
+ return_raw_bytes=True,
590
+ )
591
+
592
+ if loaded_artifact.get("status") == "success":
593
+ content_bytes = loaded_artifact.get("raw_bytes")
594
+ part.file.bytes = base64.b64encode(content_bytes).decode("utf-8")
595
+ part.file.uri = None
596
+ log.info(
597
+ "%s Successfully resolved and embedded artifact: %s",
598
+ log_id_prefix,
599
+ uri,
600
+ )
601
+ else:
602
+ log.error(
603
+ "%s Failed to resolve artifact URI '%s': %s",
604
+ log_id_prefix,
605
+ uri,
606
+ loaded_artifact.get("message"),
607
+ )
608
+ except Exception as e:
609
+ log.exception(
610
+ "%s Error resolving artifact URI '%s': %s", log_id_prefix, uri, e
611
+ )
612
+
613
+ async def _resolve_uris_in_parts_list(self, parts: List[A2APart]):
614
+ """Iterates over a list of A2APart objects and resolves any FilePart URIs."""
615
+ if not parts:
616
+ return
617
+ for part in parts:
618
+ await self._resolve_uri_in_file_part(part)
619
+
620
+ async def _resolve_uris_in_payload(self, parsed_event: Any):
621
+ """
622
+ Dispatcher that calls the appropriate targeted URI resolver based on the
623
+ Pydantic model type of the event.
624
+ """
625
+ if isinstance(parsed_event, TaskStatusUpdateEvent):
626
+ if parsed_event.status and parsed_event.status.message:
627
+ await self._resolve_uris_in_parts_list(
628
+ parsed_event.status.message.parts
629
+ )
630
+ elif isinstance(parsed_event, TaskArtifactUpdateEvent):
631
+ if parsed_event.artifact:
632
+ await self._resolve_uris_in_parts_list(parsed_event.artifact.parts)
633
+ elif isinstance(parsed_event, Task):
634
+ if parsed_event.status and parsed_event.status.message:
635
+ await self._resolve_uris_in_parts_list(
636
+ parsed_event.status.message.parts
637
+ )
638
+ if parsed_event.artifacts:
639
+ for artifact in parsed_event.artifacts:
640
+ await self._resolve_uris_in_parts_list(artifact.parts)
641
+ else:
642
+ log.debug(
643
+ "%s Payload type '%s' does not support targeted URI resolution. Skipping.",
644
+ self.log_identifier,
645
+ type(parsed_event).__name__,
646
+ )
647
+
648
+ async def _handle_discovery_message(self, payload: Dict) -> bool:
649
+ """Handles incoming agent discovery messages."""
650
+ try:
651
+ agent_card = AgentCard(**payload)
652
+ self.core_a2a_service.process_discovery_message(agent_card)
653
+ return True
654
+ except Exception as e:
655
+ log.error(
656
+ "%s Failed to process discovery message: %s. Payload: %s",
657
+ self.log_identifier,
658
+ e,
659
+ payload,
660
+ )
661
+ return False
662
+
663
+ def _extract_task_id_from_topic(
664
+ self, topic: str, subscription_pattern: str
665
+ ) -> Optional[str]:
666
+ """Extracts the task ID from the end of a topic string based on the subscription."""
667
+ base_regex_str = _subscription_to_regex(subscription_pattern).replace(r".*", "")
668
+ match = re.match(base_regex_str, topic)
669
+ if match:
670
+ task_id_part = topic[match.end() :]
671
+ task_id = task_id_part.lstrip("/")
672
+ if task_id:
673
+ log.debug(
674
+ "%s Extracted Task ID '%s' from topic '%s'",
675
+ self.log_identifier,
676
+ task_id,
677
+ topic,
678
+ )
679
+ return task_id
680
+ log.warning(
681
+ "%s Could not extract Task ID from topic '%s' using pattern '%s'",
682
+ self.log_identifier,
683
+ topic,
684
+ subscription_pattern,
685
+ )
686
+ return None
687
+
688
+ def _parse_a2a_event_from_rpc_result(
689
+ self, rpc_result: Dict, expected_task_id: Optional[str]
690
+ ) -> Optional[Union[Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent]]:
691
+ """
692
+ Parses the result field of a JSONRPCResponse into a specific A2A Pydantic model.
693
+ Verifies task ID if expected_task_id is provided.
694
+ """
695
+ if not isinstance(rpc_result, dict):
696
+ log.error(
697
+ "%s RPC result is not a dictionary. Cannot parse.", self.log_identifier
698
+ )
699
+ return None
700
+
701
+ actual_task_id = rpc_result.get("id")
702
+ if expected_task_id and actual_task_id != expected_task_id:
703
+ log.error(
704
+ "%s Task ID mismatch! Expected: %s, Got from payload: %s.",
705
+ self.log_identifier,
706
+ expected_task_id,
707
+ actual_task_id,
708
+ )
709
+ return None
710
+
711
+ try:
712
+ if "status" in rpc_result and "final" in rpc_result:
713
+ return TaskStatusUpdateEvent(**rpc_result)
714
+ elif "artifact" in rpc_result:
715
+ return TaskArtifactUpdateEvent(**rpc_result)
716
+ elif "status" in rpc_result and "sessionId" in rpc_result:
717
+ return Task(**rpc_result)
718
+ else:
719
+ log.warning(
720
+ "%s Unknown result structure in RPC response for task %s: %s",
721
+ self.log_identifier,
722
+ actual_task_id or "unknown",
723
+ rpc_result,
724
+ )
725
+ return None
726
+ except Exception as e:
727
+ log.error(
728
+ "%s Failed to parse RPC result into A2A Pydantic model for task %s: %s. Result: %s",
729
+ self.log_identifier,
730
+ actual_task_id or "unknown",
731
+ e,
732
+ rpc_result,
733
+ )
734
+ return None
735
+
736
+ async def _resolve_embeds_and_handle_signals(
737
+ self,
738
+ event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
739
+ external_request_context: Dict[str, Any],
740
+ a2a_task_id: str,
741
+ original_rpc_id: Optional[str],
742
+ is_finalizing_context: bool = False,
743
+ ) -> bool:
744
+ """
745
+ Resolves embeds and handles signals for an event containing parts.
746
+ Modifies event_with_parts in place if text content changes.
747
+ Manages stream buffer for TaskStatusUpdateEvent.
748
+ Returns True if the event content was modified or signals were handled, False otherwise.
749
+ """
750
+ if not self.enable_embed_resolution:
751
+ return False
752
+
753
+ log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
754
+ content_modified_or_signal_handled = False
755
+
756
+ embed_eval_context = {
757
+ "artifact_service": self.shared_artifact_service,
758
+ "session_context": {
759
+ "app_name": external_request_context.get(
760
+ "app_name_for_artifacts", self.gateway_id
761
+ ),
762
+ "user_id": external_request_context.get("user_id_for_artifacts"),
763
+ "session_id": external_request_context.get("a2a_session_id"),
764
+ },
765
+ }
766
+ embed_eval_config = {
767
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
768
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
769
+ }
770
+
771
+ parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
772
+ is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
773
+
774
+ if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
775
+ if event_with_parts.status and event_with_parts.status.message:
776
+ parts_owner = event_with_parts.status.message
777
+ elif isinstance(event_with_parts, TaskArtifactUpdateEvent):
778
+ if event_with_parts.artifact:
779
+ parts_owner = event_with_parts.artifact
780
+
781
+ if parts_owner and parts_owner.parts:
782
+ new_parts_for_owner: List[A2APart] = []
783
+ stream_buffer_key = f"{a2a_task_id}_stream_buffer"
784
+ current_buffer = ""
785
+
786
+ if is_streaming_status_update:
787
+ current_buffer = (
788
+ self.task_context_manager.get_context(stream_buffer_key) or ""
789
+ )
790
+
791
+ for part_obj in parts_owner.parts:
792
+ if isinstance(part_obj, TextPart) and part_obj.text is not None:
793
+ text_to_resolve = part_obj.text
794
+ original_part_text = part_obj.text
795
+
796
+ if is_streaming_status_update:
797
+ current_buffer += part_obj.text
798
+ text_to_resolve = current_buffer
799
+
800
+ resolved_text, processed_idx, signals = (
801
+ await resolve_embeds_in_string(
802
+ text=text_to_resolve,
803
+ context=embed_eval_context,
804
+ resolver_func=evaluate_embed,
805
+ types_to_resolve=LATE_EMBED_TYPES.copy(),
806
+ log_identifier=log_id_prefix,
807
+ config=embed_eval_config,
808
+ )
809
+ )
810
+
811
+ if signals:
812
+ await self._handle_resolved_signals(
813
+ external_request_context,
814
+ signals,
815
+ original_rpc_id,
816
+ is_finalizing_context,
817
+ )
818
+ content_modified_or_signal_handled = True
819
+
820
+ if resolved_text is not None:
821
+ new_parts_for_owner.append(TextPart(text=resolved_text))
822
+ if is_streaming_status_update:
823
+ if resolved_text != text_to_resolve[:processed_idx]:
824
+ content_modified_or_signal_handled = True
825
+ elif resolved_text != original_part_text:
826
+ content_modified_or_signal_handled = True
827
+
828
+ if is_streaming_status_update:
829
+ current_buffer = text_to_resolve[processed_idx:]
830
+ elif (
831
+ processed_idx < len(text_to_resolve)
832
+ and not content_modified_or_signal_handled
833
+ ):
834
+ log.warning(
835
+ "%s Unclosed embed in non-streaming TextPart. Remainder: '%s'",
836
+ log_id_prefix,
837
+ text_to_resolve[processed_idx:],
838
+ )
839
+ content_modified_or_signal_handled = True
840
+
841
+ elif (
842
+ isinstance(part_obj, FilePart)
843
+ and part_obj.file
844
+ and part_obj.file.bytes
845
+ ):
846
+ mime_type = part_obj.file.mimeType or ""
847
+ is_container = is_text_based_mime_type(mime_type)
848
+ try:
849
+ decoded_content_for_check = base64.b64decode(
850
+ part_obj.file.bytes
851
+ ).decode("utf-8", errors="ignore")
852
+ if (
853
+ is_container
854
+ and EMBED_DELIMITER_OPEN in decoded_content_for_check
855
+ ):
856
+ original_content = decoded_content_for_check
857
+ resolved_content = (
858
+ await resolve_embeds_recursively_in_string(
859
+ text=original_content,
860
+ context=embed_eval_context,
861
+ resolver_func=evaluate_embed,
862
+ types_to_resolve=LATE_EMBED_TYPES,
863
+ log_identifier=log_id_prefix,
864
+ config=embed_eval_config,
865
+ max_depth=self.gateway_recursive_embed_depth,
866
+ )
867
+ )
868
+ if resolved_content != original_content:
869
+ new_file_content = part_obj.file.model_copy()
870
+ new_file_content.bytes = base64.b64encode(
871
+ resolved_content.encode("utf-8")
872
+ ).decode("utf-8")
873
+ new_parts_for_owner.append(
874
+ FilePart(
875
+ file=new_file_content,
876
+ metadata=part_obj.metadata,
877
+ )
878
+ )
879
+ content_modified_or_signal_handled = True
880
+ else:
881
+ new_parts_for_owner.append(part_obj)
882
+ else:
883
+ new_parts_for_owner.append(part_obj)
884
+ except Exception as e:
885
+ log.warning(
886
+ "%s Error during recursive FilePart resolution for %s: %s. Using original.",
887
+ log_id_prefix,
888
+ part_obj.file.name,
889
+ e,
890
+ )
891
+ new_parts_for_owner.append(part_obj)
892
+ else:
893
+ new_parts_for_owner.append(part_obj)
894
+
895
+ parts_owner.parts = new_parts_for_owner
896
+
897
+ if is_streaming_status_update:
898
+ self.task_context_manager.store_context(
899
+ stream_buffer_key, current_buffer
900
+ )
901
+
902
+ return content_modified_or_signal_handled
903
+
904
+ async def _process_parsed_a2a_event(
905
+ self,
906
+ parsed_event: Union[
907
+ Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError
908
+ ],
909
+ external_request_context: Dict[str, Any],
910
+ a2a_task_id: str,
911
+ original_rpc_id: Optional[str],
912
+ ) -> None:
913
+ """
914
+ Processes a parsed A2A event: resolves embeds, handles signals,
915
+ sends to external, and manages context.
916
+ """
917
+ log_id_prefix = f"{self.log_identifier}[ProcessParsed:{a2a_task_id}]"
918
+ is_truly_final_event_for_context_cleanup = False
919
+ is_finalizing_context_for_embeds = False
920
+
921
+ if isinstance(parsed_event, JSONRPCError):
922
+ log.warning(
923
+ "%s Handling JSONRPCError for task %s.", log_id_prefix, a2a_task_id
924
+ )
925
+ await self._send_error_to_external(external_request_context, parsed_event)
926
+ is_truly_final_event_for_context_cleanup = True
927
+ else:
928
+ content_was_modified_or_signals_handled = False
929
+
930
+ if isinstance(parsed_event, TaskStatusUpdateEvent) and parsed_event.final:
931
+ is_finalizing_context_for_embeds = True
932
+ elif isinstance(parsed_event, Task):
933
+ is_finalizing_context_for_embeds = True
934
+
935
+ if self.get_config("resolve_artifact_uris_in_gateway", False):
936
+ log.debug(
937
+ "%s Resolving artifact URIs before sending to external...",
938
+ log_id_prefix,
939
+ )
940
+ await self._resolve_uris_in_payload(parsed_event)
941
+
942
+ if not isinstance(parsed_event, JSONRPCError):
943
+ content_was_modified_or_signals_handled = (
944
+ await self._resolve_embeds_and_handle_signals(
945
+ parsed_event,
946
+ external_request_context,
947
+ a2a_task_id,
948
+ original_rpc_id,
949
+ is_finalizing_context=is_finalizing_context_for_embeds,
950
+ )
951
+ )
952
+
953
+ send_this_event_to_external = True
954
+ is_final_chunk_of_status_update = False
955
+
956
+ if isinstance(parsed_event, TaskStatusUpdateEvent):
957
+ is_final_chunk_of_status_update = parsed_event.final
958
+ if (
959
+ not (
960
+ parsed_event.status
961
+ and parsed_event.status.message
962
+ and parsed_event.status.message.parts
963
+ )
964
+ and not parsed_event.metadata
965
+ and not is_final_chunk_of_status_update
966
+ and not content_was_modified_or_signals_handled
967
+ ):
968
+ send_this_event_to_external = False
969
+ log.debug(
970
+ "%s Suppressing empty intermediate status update.",
971
+ log_id_prefix,
972
+ )
973
+ elif isinstance(parsed_event, TaskArtifactUpdateEvent):
974
+ if (
975
+ not (parsed_event.artifact and parsed_event.artifact.parts)
976
+ and not content_was_modified_or_signals_handled
977
+ ):
978
+ send_this_event_to_external = False
979
+ log.debug("%s Suppressing empty artifact update.", log_id_prefix)
980
+ elif isinstance(parsed_event, Task):
981
+ is_truly_final_event_for_context_cleanup = True
982
+
983
+ if (
984
+ self._RESOLVE_EMBEDS_IN_FINAL_RESPONSE
985
+ and parsed_event.status
986
+ and parsed_event.status.message
987
+ and parsed_event.status.message.parts
988
+ ):
989
+ log.debug(
990
+ "%s Resolving embeds in final task response...", log_id_prefix
991
+ )
992
+ combined_text = ""
993
+ non_text_parts = []
994
+ for part in parsed_event.status.message.parts:
995
+ if isinstance(part, TextPart) and part.text:
996
+ combined_text += part.text
997
+ else:
998
+ non_text_parts.append(part)
999
+
1000
+ if combined_text:
1001
+ embed_eval_context = {
1002
+ "artifact_service": self.shared_artifact_service,
1003
+ "session_context": {
1004
+ "app_name": external_request_context.get(
1005
+ "app_name_for_artifacts", self.gateway_id
1006
+ ),
1007
+ "user_id": external_request_context.get(
1008
+ "user_id_for_artifacts"
1009
+ ),
1010
+ "session_id": external_request_context.get(
1011
+ "a2a_session_id"
1012
+ ),
1013
+ },
1014
+ }
1015
+ embed_eval_config = {
1016
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
1017
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
1018
+ }
1019
+ all_embed_types = EARLY_EMBED_TYPES.union(LATE_EMBED_TYPES)
1020
+ resolved_text, _, signals = await resolve_embeds_in_string(
1021
+ text=combined_text,
1022
+ context=embed_eval_context,
1023
+ resolver_func=evaluate_embed,
1024
+ types_to_resolve=all_embed_types,
1025
+ log_identifier=log_id_prefix,
1026
+ config=embed_eval_config,
1027
+ )
1028
+ if signals:
1029
+ log.debug(
1030
+ "%s Handling %d signals found during final response embed resolution.",
1031
+ log_id_prefix,
1032
+ len(signals),
1033
+ )
1034
+ await self._handle_resolved_signals(
1035
+ external_request_context,
1036
+ signals,
1037
+ original_rpc_id,
1038
+ is_finalizing_context=True,
1039
+ )
1040
+
1041
+ new_parts = (
1042
+ [TextPart(text=resolved_text)] if resolved_text else []
1043
+ )
1044
+ new_parts.extend(non_text_parts)
1045
+ parsed_event.status.message.parts = new_parts
1046
+ log.info(
1047
+ "%s Final response text updated with resolved embeds.",
1048
+ log_id_prefix,
1049
+ )
1050
+
1051
+ final_buffer_key = f"{a2a_task_id}_stream_buffer"
1052
+ remaining_buffer = self.task_context_manager.get_context(
1053
+ final_buffer_key
1054
+ )
1055
+ if remaining_buffer:
1056
+ log.info(
1057
+ "%s Flushing remaining buffer for task %s before final response.",
1058
+ log_id_prefix,
1059
+ a2a_task_id,
1060
+ )
1061
+ embed_eval_context = {
1062
+ "artifact_service": self.shared_artifact_service,
1063
+ "session_context": {
1064
+ "app_name": external_request_context.get(
1065
+ "app_name_for_artifacts", self.gateway_id
1066
+ ),
1067
+ "user_id": external_request_context.get(
1068
+ "user_id_for_artifacts"
1069
+ ),
1070
+ "session_id": external_request_context.get(
1071
+ "a2a_session_id"
1072
+ ),
1073
+ },
1074
+ }
1075
+ embed_eval_config = {
1076
+ "gateway_artifact_content_limit_bytes": self.gateway_artifact_content_limit_bytes,
1077
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
1078
+ }
1079
+ resolved_remaining_text, _, signals = (
1080
+ await resolve_embeds_in_string(
1081
+ remaining_buffer,
1082
+ embed_eval_context,
1083
+ evaluate_embed,
1084
+ LATE_EMBED_TYPES.copy(),
1085
+ log_id_prefix,
1086
+ embed_eval_config,
1087
+ )
1088
+ )
1089
+ await self._handle_resolved_signals(
1090
+ external_request_context,
1091
+ signals,
1092
+ original_rpc_id,
1093
+ is_finalizing_context=True,
1094
+ )
1095
+ if resolved_remaining_text:
1096
+ flush_status = TaskStatus(
1097
+ state=TaskState.WORKING,
1098
+ message=A2AMessage(
1099
+ role="agent",
1100
+ parts=[TextPart(text=resolved_remaining_text)],
1101
+ ),
1102
+ )
1103
+ flush_event = TaskStatusUpdateEvent(
1104
+ id=a2a_task_id,
1105
+ status=flush_status,
1106
+ final=False,
1107
+ )
1108
+ await self._send_update_to_external(
1109
+ external_request_context, flush_event, True
1110
+ )
1111
+ self.task_context_manager.remove_context(final_buffer_key)
1112
+
1113
+ if send_this_event_to_external:
1114
+ if isinstance(parsed_event, Task):
1115
+ await self._send_final_response_to_external(
1116
+ external_request_context, parsed_event
1117
+ )
1118
+ elif isinstance(
1119
+ parsed_event, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
1120
+ ):
1121
+ final_chunk_flag = (
1122
+ is_final_chunk_of_status_update
1123
+ if isinstance(parsed_event, TaskStatusUpdateEvent)
1124
+ else False
1125
+ )
1126
+ await self._send_update_to_external(
1127
+ external_request_context, parsed_event, final_chunk_flag
1128
+ )
1129
+
1130
+ if is_truly_final_event_for_context_cleanup:
1131
+ log.info(
1132
+ "%s Truly final event processed for task %s. Removing context.",
1133
+ log_id_prefix,
1134
+ a2a_task_id,
1135
+ )
1136
+ self.task_context_manager.remove_context(a2a_task_id)
1137
+ self.task_context_manager.remove_context(f"{a2a_task_id}_stream_buffer")
1138
+
1139
+ async def _handle_agent_event(
1140
+ self, topic: str, payload: Dict, task_id_from_topic: str
1141
+ ) -> bool:
1142
+ """
1143
+ Handles messages received on gateway response and status topics.
1144
+ Parses the payload, retrieves context using task_id_from_topic, and dispatches for processing.
1145
+ """
1146
+ try:
1147
+ rpc_response = JSONRPCResponse(**payload)
1148
+ except Exception as e:
1149
+ log.error(
1150
+ "%s Failed to parse payload as JSONRPCResponse for topic %s (Task ID from topic: %s): %s. Payload: %s",
1151
+ self.log_identifier,
1152
+ topic,
1153
+ task_id_from_topic,
1154
+ e,
1155
+ payload,
1156
+ )
1157
+ return False
1158
+
1159
+ original_rpc_id = str(rpc_response.id)
1160
+
1161
+ external_request_context = self.task_context_manager.get_context(
1162
+ task_id_from_topic
1163
+ )
1164
+ if not external_request_context:
1165
+ log.warning(
1166
+ "%s No external context found for A2A Task ID: %s (from topic). Ignoring message. Topic: %s, RPC ID: %s",
1167
+ self.log_identifier,
1168
+ task_id_from_topic,
1169
+ topic,
1170
+ original_rpc_id,
1171
+ )
1172
+ return True
1173
+
1174
+ external_request_context["a2a_task_id_for_event"] = task_id_from_topic
1175
+ external_request_context["original_rpc_id"] = original_rpc_id
1176
+
1177
+ parsed_event_obj: Union[
1178
+ Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError, None
1179
+ ] = None
1180
+ if rpc_response.error:
1181
+ parsed_event_obj = rpc_response.error
1182
+ elif rpc_response.result:
1183
+ parsed_event_obj = self._parse_a2a_event_from_rpc_result(
1184
+ rpc_response.result, task_id_from_topic
1185
+ )
1186
+
1187
+ if not parsed_event_obj:
1188
+ log.error(
1189
+ "%s Failed to parse or validate A2A event from RPC result for task %s. Result: %s",
1190
+ self.log_identifier,
1191
+ task_id_from_topic,
1192
+ rpc_response.result,
1193
+ )
1194
+ generic_error = JSONRPCError(
1195
+ code=-32000, message="Invalid event structure received from agent."
1196
+ )
1197
+ await self._send_error_to_external(external_request_context, generic_error)
1198
+ self.task_context_manager.remove_context(task_id_from_topic)
1199
+ self.task_context_manager.remove_context(
1200
+ f"{task_id_from_topic}_stream_buffer"
1201
+ )
1202
+ return False
1203
+
1204
+ try:
1205
+ await self._process_parsed_a2a_event(
1206
+ parsed_event_obj,
1207
+ external_request_context,
1208
+ task_id_from_topic,
1209
+ original_rpc_id,
1210
+ )
1211
+ return True
1212
+ except Exception as e:
1213
+ log.exception(
1214
+ "%s Error in _process_parsed_a2a_event for task %s: %s",
1215
+ self.log_identifier,
1216
+ task_id_from_topic,
1217
+ e,
1218
+ )
1219
+ error_obj = JSONRPCError(
1220
+ code=-32000, message=f"Gateway processing error: {e}"
1221
+ )
1222
+ await self._send_error_to_external(external_request_context, error_obj)
1223
+ self.task_context_manager.remove_context(task_id_from_topic)
1224
+ self.task_context_manager.remove_context(
1225
+ f"{task_id_from_topic}_stream_buffer"
1226
+ )
1227
+ return False
1228
+
1229
+ async def _message_processor_loop(self):
1230
+ log.info("%s Starting message processor loop...", self.log_identifier)
1231
+ loop = asyncio.get_running_loop()
1232
+
1233
+ while not self.stop_signal.is_set():
1234
+ original_broker_message: Optional[SolaceMessage] = None
1235
+ item = None
1236
+ processed_successfully = False
1237
+ topic = None
1238
+
1239
+ try:
1240
+ item = await loop.run_in_executor(None, self.internal_event_queue.get)
1241
+
1242
+ if item is None:
1243
+ log.info(
1244
+ "%s Received shutdown sentinel. Exiting message processor loop.",
1245
+ self.log_identifier,
1246
+ )
1247
+ break
1248
+
1249
+ topic = item.get("topic")
1250
+ payload = item.get("payload")
1251
+ original_broker_message = item.get("_original_broker_message")
1252
+
1253
+ if not topic or payload is None or not original_broker_message:
1254
+ log.warning(
1255
+ "%s Invalid item received from internal queue: %s",
1256
+ self.log_identifier,
1257
+ item,
1258
+ )
1259
+ processed_successfully = False
1260
+ continue
1261
+
1262
+ if _topic_matches_subscription(
1263
+ topic, get_discovery_topic(self.namespace)
1264
+ ):
1265
+ processed_successfully = await self._handle_discovery_message(
1266
+ payload
1267
+ )
1268
+ elif _topic_matches_subscription(
1269
+ topic,
1270
+ get_gateway_response_subscription_topic(
1271
+ self.namespace, self.gateway_id
1272
+ ),
1273
+ ) or _topic_matches_subscription(
1274
+ topic,
1275
+ get_gateway_status_subscription_topic(
1276
+ self.namespace, self.gateway_id
1277
+ ),
1278
+ ):
1279
+ task_id_from_topic: Optional[str] = None
1280
+ response_sub = get_gateway_response_subscription_topic(
1281
+ self.namespace, self.gateway_id
1282
+ )
1283
+ status_sub = get_gateway_status_subscription_topic(
1284
+ self.namespace, self.gateway_id
1285
+ )
1286
+
1287
+ if _topic_matches_subscription(topic, response_sub):
1288
+ task_id_from_topic = self._extract_task_id_from_topic(
1289
+ topic, response_sub
1290
+ )
1291
+ elif _topic_matches_subscription(topic, status_sub):
1292
+ task_id_from_topic = self._extract_task_id_from_topic(
1293
+ topic, status_sub
1294
+ )
1295
+
1296
+ if task_id_from_topic:
1297
+ processed_successfully = await self._handle_agent_event(
1298
+ topic, payload, task_id_from_topic
1299
+ )
1300
+ else:
1301
+ log.error(
1302
+ "%s Could not extract task_id from topic %s for _handle_agent_event. Ignoring.",
1303
+ self.log_identifier,
1304
+ topic,
1305
+ )
1306
+ processed_successfully = False
1307
+ else:
1308
+ log.warning(
1309
+ "%s Received message on unhandled topic: %s. Acknowledging.",
1310
+ self.log_identifier,
1311
+ topic,
1312
+ )
1313
+ processed_successfully = True
1314
+
1315
+ except queue.Empty:
1316
+ continue
1317
+ except asyncio.CancelledError:
1318
+ log.info("%s Message processor loop cancelled.", self.log_identifier)
1319
+ break
1320
+ except Exception as e:
1321
+ log.exception(
1322
+ "%s Unhandled error in message processor loop: %s",
1323
+ self.log_identifier,
1324
+ e,
1325
+ )
1326
+ processed_successfully = False
1327
+ await asyncio.sleep(1)
1328
+ finally:
1329
+ if original_broker_message:
1330
+ if processed_successfully:
1331
+ original_broker_message.call_acknowledgements()
1332
+ else:
1333
+ original_broker_message.call_negative_acknowledgements()
1334
+ log.warning(
1335
+ "%s NACKed SolaceMessage for topic: %s",
1336
+ self.log_identifier,
1337
+ topic or "unknown",
1338
+ )
1339
+
1340
+ if item and item is not None:
1341
+ self.internal_event_queue.task_done()
1342
+
1343
+ log.info("%s Message processor loop finished.", self.log_identifier)
1344
+
1345
+ def _run_async_operations(self):
1346
+ log.info(
1347
+ "%s Initializing asyncio event loop in dedicated thread...",
1348
+ self.log_identifier,
1349
+ )
1350
+ self.async_loop = asyncio.new_event_loop()
1351
+ asyncio.set_event_loop(self.async_loop)
1352
+
1353
+ processor_task = None
1354
+ try:
1355
+ log.info(
1356
+ "%s Starting _message_processor_loop as an asyncio task.",
1357
+ self.log_identifier,
1358
+ )
1359
+ processor_task = self.async_loop.create_task(self._message_processor_loop())
1360
+
1361
+ log.info(
1362
+ "%s Calling _start_listener() to initiate external platform connection.",
1363
+ self.log_identifier,
1364
+ )
1365
+ self._start_listener()
1366
+
1367
+ log.info(
1368
+ "%s Running asyncio event loop forever (or until stop_signal).",
1369
+ self.log_identifier,
1370
+ )
1371
+ self.async_loop.run_forever()
1372
+
1373
+ except Exception as e:
1374
+ log.exception(
1375
+ "%s Unhandled exception in _run_async_operations: %s",
1376
+ self.log_identifier,
1377
+ e,
1378
+ )
1379
+ self.stop_signal.set()
1380
+ finally:
1381
+ if processor_task and not processor_task.done():
1382
+ log.info(
1383
+ "%s Cancelling _message_processor_loop task.", self.log_identifier
1384
+ )
1385
+ processor_task.cancel()
1386
+ try:
1387
+ self.async_loop.run_until_complete(
1388
+ asyncio.gather(processor_task, return_exceptions=True)
1389
+ )
1390
+ except RuntimeError as loop_err:
1391
+ log.warning(
1392
+ "%s Error awaiting processor task during cleanup (loop closed?): %s",
1393
+ self.log_identifier,
1394
+ loop_err,
1395
+ )
1396
+
1397
+ if self.async_loop.is_running():
1398
+ log.info(
1399
+ "%s Stopping asyncio event loop from _run_async_operations finally block.",
1400
+ self.log_identifier,
1401
+ )
1402
+ self.async_loop.stop()
1403
+ log.info(
1404
+ "%s Async operations loop finished in dedicated thread.",
1405
+ self.log_identifier,
1406
+ )
1407
+
1408
+ def run(self):
1409
+ log.info("%s Starting BaseGatewayComponent run method.", self.log_identifier)
1410
+ if not self.async_thread or not self.async_thread.is_alive():
1411
+ self.async_thread = threading.Thread(
1412
+ target=self._run_async_operations,
1413
+ name=f"{self.name}_AsyncOpsThread",
1414
+ daemon=True,
1415
+ )
1416
+ self.async_thread.start()
1417
+ log.info("%s Async operations thread started.", self.log_identifier)
1418
+ else:
1419
+ log.warning(
1420
+ "%s Async operations thread already running.", self.log_identifier
1421
+ )
1422
+
1423
+ super().run()
1424
+ log.info("%s BaseGatewayComponent run method finished.", self.log_identifier)
1425
+
1426
+ def cleanup(self):
1427
+ log.info("%s Starting cleanup for BaseGatewayComponent...", self.log_identifier)
1428
+
1429
+ log.info("%s Calling _stop_listener()...", self.log_identifier)
1430
+ try:
1431
+ if (
1432
+ self.async_loop
1433
+ and not self.async_loop.is_running()
1434
+ and self.async_thread
1435
+ and self.async_thread.is_alive()
1436
+ ):
1437
+ log.warning(
1438
+ "%s Async loop not running during cleanup, _stop_listener might face issues if it needs the loop.",
1439
+ self.log_identifier,
1440
+ )
1441
+ self._stop_listener()
1442
+ except Exception as e:
1443
+ log.exception(
1444
+ "%s Error during _stop_listener(): %s", self.log_identifier, e
1445
+ )
1446
+
1447
+ if self.internal_event_queue:
1448
+ log.info(
1449
+ "%s Signaling _message_processor_loop to stop...", self.log_identifier
1450
+ )
1451
+ self.internal_event_queue.put(None)
1452
+
1453
+ if self.async_loop and self.async_loop.is_running():
1454
+ log.info("%s Requesting asyncio loop to stop...", self.log_identifier)
1455
+ self.async_loop.call_soon_threadsafe(self.async_loop.stop)
1456
+
1457
+ if self.async_thread and self.async_thread.is_alive():
1458
+ log.info(
1459
+ "%s Joining async operations thread (timeout 10s)...",
1460
+ self.log_identifier,
1461
+ )
1462
+ self.async_thread.join(timeout=10)
1463
+ if self.async_thread.is_alive():
1464
+ log.warning(
1465
+ "%s Async operations thread did not join cleanly.",
1466
+ self.log_identifier,
1467
+ )
1468
+
1469
+ if self.async_loop and not self.async_loop.is_closed():
1470
+ if self.async_loop.is_running():
1471
+ self.async_loop.call_soon_threadsafe(self.async_loop.stop)
1472
+ log.info(
1473
+ "%s Closing asyncio event loop (if not already closed by its thread).",
1474
+ self.log_identifier,
1475
+ )
1476
+ if not self.async_loop.is_running():
1477
+ self.async_loop.close()
1478
+ else:
1479
+ self.async_loop.call_soon_threadsafe(self.async_loop.close)
1480
+
1481
+ super().cleanup()
1482
+ log.info("%s BaseGatewayComponent cleanup finished.", self.log_identifier)
1483
+
1484
+ @abstractmethod
1485
+ async def _extract_initial_claims(
1486
+ self, external_event_data: Any
1487
+ ) -> Optional[Dict[str, Any]]:
1488
+ """
1489
+ Extracts the primary identity claims from a platform-specific event.
1490
+ This method MUST be implemented by derived gateway components.
1491
+
1492
+ Args:
1493
+ external_event_data: Raw event data from the external platform
1494
+ (e.g., FastAPIRequest, Slack event dictionary).
1495
+
1496
+ Returns:
1497
+ A dictionary of initial claims, which MUST include an 'id' key.
1498
+ Example: {"id": "user@example.com", "source": "slack_api"}
1499
+ Return None if authentication fails.
1500
+ """
1501
+ pass
1502
+
1503
+ @abstractmethod
1504
+ def _start_listener(self) -> None:
1505
+ pass
1506
+
1507
+ @abstractmethod
1508
+ def _stop_listener(self) -> None:
1509
+ pass
1510
+
1511
+ @abstractmethod
1512
+ def _translate_external_input(
1513
+ self, external_event: Any
1514
+ ) -> Tuple[str, List[A2APart], Dict[str, Any]]:
1515
+ pass
1516
+
1517
+ @abstractmethod
1518
+ async def _send_update_to_external(
1519
+ self,
1520
+ external_request_context: Dict[str, Any],
1521
+ event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
1522
+ is_final_chunk_of_update: bool,
1523
+ ) -> None:
1524
+ pass
1525
+
1526
+ @abstractmethod
1527
+ async def _send_final_response_to_external(
1528
+ self, external_request_context: Dict[str, Any], task_data: Task
1529
+ ) -> None:
1530
+ pass
1531
+
1532
+ @abstractmethod
1533
+ async def _send_error_to_external(
1534
+ self, external_request_context: Dict[str, Any], error_data: JSONRPCError
1535
+ ) -> None:
1536
+ pass
1537
+
1538
+ def invoke(self, message, data):
1539
+ if isinstance(message, SolaceMessage):
1540
+ message.call_acknowledgements()
1541
+ log.warning("%s Invoke method called unexpectedly.", self.log_identifier)
1542
+ return None