solace-agent-mesh 1.6.1__py3-none-any.whl → 1.13.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 (481) hide show
  1. solace_agent_mesh/agent/adk/alembic/README +74 -0
  2. solace_agent_mesh/agent/adk/alembic/env.py +77 -0
  3. solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
  4. solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
  5. solace_agent_mesh/agent/adk/alembic.ini +112 -0
  6. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  7. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +165 -1
  8. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +163 -0
  9. solace_agent_mesh/agent/adk/callbacks.py +852 -109
  10. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +234 -36
  11. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +52 -5
  12. solace_agent_mesh/agent/adk/mcp_content_processor.py +1 -1
  13. solace_agent_mesh/agent/adk/models/lite_llm.py +77 -21
  14. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +24 -137
  15. solace_agent_mesh/agent/adk/runner.py +85 -20
  16. solace_agent_mesh/agent/adk/schema_migration.py +88 -0
  17. solace_agent_mesh/agent/adk/services.py +94 -18
  18. solace_agent_mesh/agent/adk/setup.py +281 -65
  19. solace_agent_mesh/agent/adk/stream_parser.py +231 -37
  20. solace_agent_mesh/agent/adk/tool_wrapper.py +3 -0
  21. solace_agent_mesh/agent/protocol/event_handlers.py +472 -137
  22. solace_agent_mesh/agent/proxies/a2a/app.py +3 -2
  23. solace_agent_mesh/agent/proxies/a2a/component.py +572 -75
  24. solace_agent_mesh/agent/proxies/a2a/config.py +80 -4
  25. solace_agent_mesh/agent/proxies/base/app.py +3 -2
  26. solace_agent_mesh/agent/proxies/base/component.py +188 -22
  27. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +3 -1
  28. solace_agent_mesh/agent/sac/app.py +91 -3
  29. solace_agent_mesh/agent/sac/component.py +591 -157
  30. solace_agent_mesh/agent/sac/patch_adk.py +8 -16
  31. solace_agent_mesh/agent/sac/task_execution_context.py +146 -4
  32. solace_agent_mesh/agent/tools/__init__.py +3 -0
  33. solace_agent_mesh/agent/tools/audio_tools.py +3 -3
  34. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +710 -171
  35. solace_agent_mesh/agent/tools/deep_research_tools.py +2161 -0
  36. solace_agent_mesh/agent/tools/dynamic_tool.py +2 -0
  37. solace_agent_mesh/agent/tools/peer_agent_tool.py +82 -15
  38. solace_agent_mesh/agent/tools/time_tools.py +126 -0
  39. solace_agent_mesh/agent/tools/tool_config_types.py +57 -2
  40. solace_agent_mesh/agent/tools/web_search_tools.py +279 -0
  41. solace_agent_mesh/agent/tools/web_tools.py +125 -17
  42. solace_agent_mesh/agent/utils/artifact_helpers.py +248 -6
  43. solace_agent_mesh/agent/utils/context_helpers.py +17 -0
  44. solace_agent_mesh/assets/docs/404.html +6 -6
  45. solace_agent_mesh/assets/docs/assets/css/{styles.906a1503.css → styles.8162edfb.css} +1 -1
  46. solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/15ba94aa.e186750d.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
  51. solace_agent_mesh/assets/docs/assets/js/{17896441.a5e82f9b.js.LICENSE.txt → 2279.550aa580.js.LICENSE.txt} +6 -0
  52. solace_agent_mesh/assets/docs/assets/js/240a0364.83e37aa8.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
  54. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.2f0db237.js +1 -0
  55. solace_agent_mesh/assets/docs/assets/js/3a6c6137.7e61915d.js +1 -0
  56. solace_agent_mesh/assets/docs/assets/js/3ac1795d.7f7ab1c1.js +1 -0
  57. solace_agent_mesh/assets/docs/assets/js/3ff0015d.e53c9b78.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/41adc471.0e95b87c.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/4667dc50.bf2ad456.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/49eed117.493d6f99.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/{509e993c.4c7a1a6d.js → 509e993c.a1fbf45a.js} +1 -1
  62. solace_agent_mesh/assets/docs/assets/js/547e15cc.8e6da617.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/5b8d9c11.d4eb37b8.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.1ee87753.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/64195356.09dbd087.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/66d4869e.30340bd3.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/729898df.7249e9fd.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/7e294c01.7c5f6906.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/8024126c.e3467286.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/81a99df0.7ed65d45.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/9bb13469.4523ae20.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/a7d42657.a956689d.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e563275.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
  95. solace_agent_mesh/assets/docs/assets/js/e04b235d.06d23db6.js +1 -0
  96. solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.deb2b62e.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/e6f9706b.acc800d3.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/e92d0134.c147a429.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/ee0c2fe7.94d0a351.js +1 -0
  101. solace_agent_mesh/assets/docs/assets/js/f284c35a.cc97854c.js +1 -0
  102. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/main.d634009f.js +2 -0
  104. solace_agent_mesh/assets/docs/assets/js/runtime~main.27bb82a7.js +1 -0
  105. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +68 -68
  106. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +50 -50
  107. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +42 -42
  108. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +55 -55
  109. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +82 -68
  110. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/image-tools/index.html +81 -0
  111. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +67 -50
  112. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/research-tools/index.html +136 -0
  113. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +178 -144
  114. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +43 -42
  115. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +20 -18
  116. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +23 -23
  117. solace_agent_mesh/assets/docs/docs/documentation/components/platform-service/index.html +33 -0
  118. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +45 -45
  119. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
  120. solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
  121. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +208 -125
  122. solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
  123. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +28 -49
  124. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +29 -30
  125. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +14 -14
  126. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/index.html +47 -0
  127. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/kubernetes-deployment-guide/index.html +197 -0
  128. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +90 -0
  129. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +17 -16
  130. solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
  131. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +38 -38
  132. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +162 -171
  133. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +67 -49
  134. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +17 -17
  135. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +51 -51
  136. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +22 -22
  137. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +27 -27
  138. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +135 -135
  139. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +66 -66
  140. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +51 -51
  141. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +50 -38
  142. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +86 -86
  143. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +51 -51
  144. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +24 -24
  145. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +30 -30
  146. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +44 -44
  147. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
  148. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
  149. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
  150. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +23 -19
  151. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +40 -37
  152. solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
  153. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +112 -87
  154. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
  155. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +87 -64
  156. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
  157. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +44 -44
  158. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +39 -37
  159. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +30 -30
  160. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +18 -18
  161. solace_agent_mesh/assets/docs/docs/documentation/getting-started/vibe_coding/index.html +62 -0
  162. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +311 -0
  163. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +39 -42
  164. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +14 -14
  165. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +27 -25
  166. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +69 -69
  167. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +72 -72
  168. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
  169. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
  170. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +42 -42
  171. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +20 -20
  172. solace_agent_mesh/assets/docs/docs/documentation/migrations/platform-service-split/index.html +85 -0
  173. solace_agent_mesh/assets/docs/lunr-index-1768329217460.json +1 -0
  174. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  175. solace_agent_mesh/assets/docs/search-doc-1768329217460.json +1 -0
  176. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  177. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  178. solace_agent_mesh/cli/__init__.py +1 -1
  179. solace_agent_mesh/cli/commands/add_cmd/__init__.py +3 -1
  180. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -1
  181. solace_agent_mesh/cli/commands/add_cmd/proxy_cmd.py +100 -0
  182. solace_agent_mesh/cli/commands/docs_cmd.py +4 -1
  183. solace_agent_mesh/cli/commands/eval_cmd.py +1 -1
  184. solace_agent_mesh/cli/commands/init_cmd/__init__.py +15 -0
  185. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +1 -1
  186. solace_agent_mesh/cli/commands/init_cmd/env_step.py +30 -3
  187. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +3 -4
  188. solace_agent_mesh/cli/commands/init_cmd/platform_service_step.py +85 -0
  189. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +16 -3
  190. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +2 -1
  191. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +1 -0
  192. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +3 -3
  193. solace_agent_mesh/cli/commands/run_cmd.py +64 -49
  194. solace_agent_mesh/cli/commands/tools_cmd.py +315 -0
  195. solace_agent_mesh/cli/main.py +15 -0
  196. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-BTf6dqwp.js → authCallback-KnKMP_vb.js} +1 -1
  197. solace_agent_mesh/client/webui/frontend/static/assets/client-DpBL2stg.js +25 -0
  198. solace_agent_mesh/client/webui/frontend/static/assets/main-Cd498TV2.js +435 -0
  199. solace_agent_mesh/client/webui/frontend/static/assets/main-rSf8Vu29.css +1 -0
  200. solace_agent_mesh/client/webui/frontend/static/assets/vendor-CGk8Suyh.js +565 -0
  201. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  202. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  203. solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
  204. solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
  205. solace_agent_mesh/common/a2a/events.py +2 -1
  206. solace_agent_mesh/common/a2a/protocol.py +5 -0
  207. solace_agent_mesh/common/a2a/types.py +2 -1
  208. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +23 -6
  209. solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
  210. solace_agent_mesh/common/agent_registry.py +38 -11
  211. solace_agent_mesh/common/data_parts.py +144 -4
  212. solace_agent_mesh/common/error_handlers.py +83 -0
  213. solace_agent_mesh/common/exceptions.py +24 -0
  214. solace_agent_mesh/common/oauth/__init__.py +17 -0
  215. solace_agent_mesh/common/oauth/oauth_client.py +408 -0
  216. solace_agent_mesh/common/oauth/utils.py +50 -0
  217. solace_agent_mesh/common/rag_dto.py +156 -0
  218. solace_agent_mesh/common/sac/sam_component_base.py +97 -19
  219. solace_agent_mesh/common/sam_events/event_service.py +2 -2
  220. solace_agent_mesh/common/services/employee_service.py +1 -1
  221. solace_agent_mesh/common/utils/embeds/constants.py +1 -0
  222. solace_agent_mesh/common/utils/embeds/converter.py +1 -8
  223. solace_agent_mesh/common/utils/embeds/modifiers.py +4 -28
  224. solace_agent_mesh/common/utils/embeds/resolver.py +152 -31
  225. solace_agent_mesh/common/utils/embeds/types.py +9 -0
  226. solace_agent_mesh/common/utils/log_formatters.py +20 -0
  227. solace_agent_mesh/common/utils/mime_helpers.py +12 -5
  228. solace_agent_mesh/common/utils/pydantic_utils.py +90 -3
  229. solace_agent_mesh/common/utils/rbac_utils.py +69 -0
  230. solace_agent_mesh/common/utils/templates/__init__.py +8 -0
  231. solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
  232. solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
  233. solace_agent_mesh/config_portal/backend/common.py +12 -0
  234. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-CljP4_mv.js +103 -0
  235. solace_agent_mesh/config_portal/frontend/static/client/assets/{components-Rk0n-9cK.js → components-CaC6hG8d.js} +22 -22
  236. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-mvZjNKiz.js → entry.client-H_TM0YBt.js} +3 -3
  237. solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DzNKzXrc.js → index-CnFykb2v.js} +16 -16
  238. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-f8439d40.js +1 -0
  239. solace_agent_mesh/config_portal/frontend/static/client/assets/root-BIMqslJB.css +1 -0
  240. solace_agent_mesh/config_portal/frontend/static/client/assets/root-mJmTIdIk.js +10 -0
  241. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  242. solace_agent_mesh/core_a2a/service.py +3 -2
  243. solace_agent_mesh/gateway/adapter/__init__.py +1 -0
  244. solace_agent_mesh/gateway/adapter/base.py +170 -0
  245. solace_agent_mesh/gateway/adapter/types.py +230 -0
  246. solace_agent_mesh/gateway/base/app.py +39 -2
  247. solace_agent_mesh/gateway/base/auth_interface.py +103 -0
  248. solace_agent_mesh/gateway/base/component.py +1027 -151
  249. solace_agent_mesh/gateway/generic/__init__.py +1 -0
  250. solace_agent_mesh/gateway/generic/app.py +50 -0
  251. solace_agent_mesh/gateway/generic/component.py +894 -0
  252. solace_agent_mesh/gateway/http_sse/alembic/env.py +0 -7
  253. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
  254. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
  255. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
  256. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
  257. solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
  258. solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
  259. solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
  260. solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
  261. solace_agent_mesh/gateway/http_sse/alembic.ini +0 -36
  262. solace_agent_mesh/gateway/http_sse/app.py +40 -11
  263. solace_agent_mesh/gateway/http_sse/component.py +285 -160
  264. solace_agent_mesh/gateway/http_sse/dependencies.py +149 -114
  265. solace_agent_mesh/gateway/http_sse/main.py +68 -450
  266. solace_agent_mesh/gateway/http_sse/repository/__init__.py +19 -1
  267. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +2 -2
  268. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
  269. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
  270. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +26 -3
  271. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +7 -0
  272. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +47 -0
  273. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +114 -6
  274. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +13 -0
  275. solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
  276. solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
  277. solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
  278. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +8 -2
  279. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +8 -1
  280. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
  281. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
  282. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +177 -11
  283. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +86 -2
  284. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +38 -7
  285. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +256 -58
  286. solace_agent_mesh/gateway/http_sse/routers/auth.py +168 -134
  287. solace_agent_mesh/gateway/http_sse/routers/config.py +302 -8
  288. solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
  289. solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
  290. solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
  291. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +14 -1
  292. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +1 -1
  293. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
  294. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +5 -2
  295. solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
  296. solace_agent_mesh/gateway/http_sse/routers/feedback.py +133 -2
  297. solace_agent_mesh/gateway/http_sse/routers/people.py +2 -2
  298. solace_agent_mesh/gateway/http_sse/routers/projects.py +768 -0
  299. solace_agent_mesh/gateway/http_sse/routers/prompts.py +1416 -0
  300. solace_agent_mesh/gateway/http_sse/routers/sessions.py +167 -7
  301. solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
  302. solace_agent_mesh/gateway/http_sse/routers/sse.py +131 -8
  303. solace_agent_mesh/gateway/http_sse/routers/tasks.py +670 -18
  304. solace_agent_mesh/gateway/http_sse/routers/users.py +1 -1
  305. solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
  306. solace_agent_mesh/gateway/http_sse/routers/visualization.py +92 -9
  307. solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
  308. solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
  309. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +1 -1
  310. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +1 -1
  311. solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
  312. solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
  313. solace_agent_mesh/gateway/http_sse/services/session_service.py +361 -12
  314. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +354 -4
  315. solace_agent_mesh/gateway/http_sse/session_manager.py +15 -15
  316. solace_agent_mesh/gateway/http_sse/sse_manager.py +286 -166
  317. solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
  318. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +41 -1
  319. solace_agent_mesh/services/__init__.py +0 -0
  320. solace_agent_mesh/services/platform/__init__.py +29 -0
  321. solace_agent_mesh/services/platform/alembic/env.py +85 -0
  322. solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
  323. solace_agent_mesh/services/platform/alembic.ini +109 -0
  324. solace_agent_mesh/services/platform/api/__init__.py +3 -0
  325. solace_agent_mesh/services/platform/api/dependencies.py +154 -0
  326. solace_agent_mesh/services/platform/api/main.py +314 -0
  327. solace_agent_mesh/services/platform/api/middleware.py +51 -0
  328. solace_agent_mesh/services/platform/api/routers/__init__.py +33 -0
  329. solace_agent_mesh/services/platform/api/routers/health_router.py +31 -0
  330. solace_agent_mesh/services/platform/app.py +215 -0
  331. solace_agent_mesh/services/platform/component.py +777 -0
  332. solace_agent_mesh/shared/__init__.py +14 -0
  333. solace_agent_mesh/shared/api/__init__.py +42 -0
  334. solace_agent_mesh/shared/auth/__init__.py +26 -0
  335. solace_agent_mesh/shared/auth/dependencies.py +204 -0
  336. solace_agent_mesh/shared/auth/middleware.py +347 -0
  337. solace_agent_mesh/shared/database/__init__.py +20 -0
  338. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/base_repository.py +1 -1
  339. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_exceptions.py +1 -1
  340. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_helpers.py +1 -1
  341. solace_agent_mesh/shared/exceptions/__init__.py +36 -0
  342. solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exception_handlers.py +19 -5
  343. solace_agent_mesh/shared/utils/__init__.py +21 -0
  344. solace_agent_mesh/templates/logging_config_template.yaml +48 -0
  345. solace_agent_mesh/templates/main_orchestrator.yaml +12 -1
  346. solace_agent_mesh/templates/platform.yaml +49 -0
  347. solace_agent_mesh/templates/plugin_readme_template.md +3 -25
  348. solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
  349. solace_agent_mesh/templates/proxy_template.yaml +62 -0
  350. solace_agent_mesh/templates/webui.yaml +148 -6
  351. solace_agent_mesh/tools/web_search/__init__.py +18 -0
  352. solace_agent_mesh/tools/web_search/base.py +84 -0
  353. solace_agent_mesh/tools/web_search/google_search.py +247 -0
  354. solace_agent_mesh/tools/web_search/models.py +99 -0
  355. {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/METADATA +31 -12
  356. solace_agent_mesh-1.13.2.dist-info/RECORD +591 -0
  357. {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/WHEEL +1 -1
  358. solace_agent_mesh/agent/adk/adk_llm.txt +0 -232
  359. solace_agent_mesh/agent/adk/adk_llm_detail.txt +0 -566
  360. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +0 -171
  361. solace_agent_mesh/agent/adk/models/models_llm.txt +0 -142
  362. solace_agent_mesh/agent/agent_llm.txt +0 -378
  363. solace_agent_mesh/agent/agent_llm_detail.txt +0 -1702
  364. solace_agent_mesh/agent/protocol/protocol_llm.txt +0 -81
  365. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +0 -92
  366. solace_agent_mesh/agent/sac/sac_llm.txt +0 -189
  367. solace_agent_mesh/agent/sac/sac_llm_detail.txt +0 -200
  368. solace_agent_mesh/agent/testing/testing_llm.txt +0 -57
  369. solace_agent_mesh/agent/testing/testing_llm_detail.txt +0 -68
  370. solace_agent_mesh/agent/tools/tools_llm.txt +0 -263
  371. solace_agent_mesh/agent/tools/tools_llm_detail.txt +0 -274
  372. solace_agent_mesh/agent/utils/utils_llm.txt +0 -138
  373. solace_agent_mesh/agent/utils/utils_llm_detail.txt +0 -149
  374. solace_agent_mesh/assets/docs/assets/js/15ba94aa.932dd2db.js +0 -1
  375. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +0 -2
  376. solace_agent_mesh/assets/docs/assets/js/240a0364.7eac6021.js +0 -1
  377. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +0 -1
  378. solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +0 -1
  379. solace_agent_mesh/assets/docs/assets/js/3ac1795d.76654dd9.js +0 -1
  380. solace_agent_mesh/assets/docs/assets/js/3ff0015d.2be20244.js +0 -1
  381. solace_agent_mesh/assets/docs/assets/js/547e15cc.2cbb060a.js +0 -1
  382. solace_agent_mesh/assets/docs/assets/js/55b7b518.f2b1d1ba.js +0 -1
  383. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +0 -1
  384. solace_agent_mesh/assets/docs/assets/js/631738c7.a8b1ef8b.js +0 -1
  385. solace_agent_mesh/assets/docs/assets/js/6a520c9d.ba015d81.js +0 -1
  386. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +0 -1
  387. solace_agent_mesh/assets/docs/assets/js/6d84eae0.4a5fbf39.js +0 -1
  388. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +0 -1
  389. solace_agent_mesh/assets/docs/assets/js/8024126c.56e59919.js +0 -1
  390. solace_agent_mesh/assets/docs/assets/js/81a99df0.07034dd9.js +0 -1
  391. solace_agent_mesh/assets/docs/assets/js/82fbfb93.139a1a1f.js +0 -1
  392. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +0 -1
  393. solace_agent_mesh/assets/docs/assets/js/94e8668d.b5ddb7a1.js +0 -1
  394. solace_agent_mesh/assets/docs/assets/js/9bb13469.dd1c9b54.js +0 -1
  395. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +0 -1
  396. solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e6dd091.js +0 -1
  397. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.eaff365e.js +0 -1
  398. solace_agent_mesh/assets/docs/assets/js/da0b5bad.d08a9466.js +0 -1
  399. solace_agent_mesh/assets/docs/assets/js/dd817ffc.0aa9630a.js +0 -1
  400. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.d590bc9e.js +0 -1
  401. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +0 -1
  402. solace_agent_mesh/assets/docs/assets/js/e3d9abda.6b9493d0.js +0 -1
  403. solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +0 -1
  404. solace_agent_mesh/assets/docs/assets/js/e92d0134.cf6d6522.js +0 -1
  405. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +0 -1
  406. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +0 -1
  407. solace_agent_mesh/assets/docs/assets/js/main.b12eac43.js +0 -2
  408. solace_agent_mesh/assets/docs/assets/js/runtime~main.e268214e.js +0 -1
  409. solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +0 -1
  410. solace_agent_mesh/assets/docs/search-doc-1761248203150.json +0 -1
  411. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +0 -250
  412. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +0 -365
  413. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +0 -305
  414. solace_agent_mesh/client/webui/frontend/static/assets/client-CaY59VuC.js +0 -25
  415. solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +0 -342
  416. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +0 -1
  417. solace_agent_mesh/client/webui/frontend/static/assets/vendor-BEmvJSYz.js +0 -405
  418. solace_agent_mesh/common/a2a/a2a_llm.txt +0 -182
  419. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +0 -193
  420. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +0 -407
  421. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +0 -736
  422. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +0 -313
  423. solace_agent_mesh/common/common_llm.txt +0 -251
  424. solace_agent_mesh/common/common_llm_detail.txt +0 -2562
  425. solace_agent_mesh/common/middleware/middleware_llm.txt +0 -174
  426. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +0 -185
  427. solace_agent_mesh/common/sac/sac_llm.txt +0 -71
  428. solace_agent_mesh/common/sac/sac_llm_detail.txt +0 -82
  429. solace_agent_mesh/common/sam_events/sam_events_llm.txt +0 -104
  430. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +0 -115
  431. solace_agent_mesh/common/services/providers/providers_llm.txt +0 -80
  432. solace_agent_mesh/common/services/services_llm.txt +0 -363
  433. solace_agent_mesh/common/services/services_llm_detail.txt +0 -459
  434. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +0 -220
  435. solace_agent_mesh/common/utils/utils_llm.txt +0 -336
  436. solace_agent_mesh/common/utils/utils_llm_detail.txt +0 -572
  437. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +0 -98
  438. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-61038fc6.js +0 -1
  439. solace_agent_mesh/config_portal/frontend/static/client/assets/root-BWvk5-gF.js +0 -10
  440. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +0 -1
  441. solace_agent_mesh/core_a2a/core_a2a_llm.txt +0 -90
  442. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +0 -101
  443. solace_agent_mesh/gateway/base/base_llm.txt +0 -224
  444. solace_agent_mesh/gateway/base/base_llm_detail.txt +0 -235
  445. solace_agent_mesh/gateway/gateway_llm.txt +0 -373
  446. solace_agent_mesh/gateway/gateway_llm_detail.txt +0 -3885
  447. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +0 -295
  448. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +0 -155
  449. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +0 -105
  450. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +0 -299
  451. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +0 -3278
  452. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +0 -263
  453. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +0 -266
  454. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +0 -340
  455. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +0 -346
  456. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +0 -83
  457. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +0 -107
  458. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +0 -314
  459. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +0 -297
  460. solace_agent_mesh/gateway/http_sse/shared/__init__.py +0 -146
  461. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +0 -285
  462. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +0 -47
  463. solace_agent_mesh/llm.txt +0 -228
  464. solace_agent_mesh/llm_detail.txt +0 -2835
  465. solace_agent_mesh/solace_agent_mesh_llm.txt +0 -362
  466. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +0 -8599
  467. solace_agent_mesh/templates/logging_config_template.ini +0 -45
  468. solace_agent_mesh/templates/templates_llm.txt +0 -147
  469. solace_agent_mesh-1.6.1.dist-info/RECORD +0 -525
  470. /solace_agent_mesh/assets/docs/assets/js/{main.b12eac43.js.LICENSE.txt → main.d634009f.js.LICENSE.txt} +0 -0
  471. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/auth_utils.py +0 -0
  472. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/pagination.py +0 -0
  473. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/response_utils.py +0 -0
  474. /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/error_dto.py +0 -0
  475. /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exceptions.py +0 -0
  476. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/enums.py +0 -0
  477. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/timestamp_utils.py +0 -0
  478. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/types.py +0 -0
  479. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/utils.py +0 -0
  480. {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/entry_points.txt +0 -0
  481. {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/licenses/LICENSE +0 -0
@@ -4,8 +4,9 @@ Base Component class for Gateway implementations in the Solace AI Connector.
4
4
 
5
5
  import logging
6
6
  import asyncio
7
- import queue
8
7
  import base64
8
+ import queue
9
+ import re
9
10
  import uuid
10
11
  from datetime import datetime, timezone
11
12
  from typing import Any, Dict, Optional, List, Tuple, Union
@@ -21,7 +22,9 @@ from ...common.services.identity_service import (
21
22
  create_identity_service,
22
23
  )
23
24
  from .task_context import TaskContextManager
25
+ from .auth_interface import AuthHandler
24
26
  from ...common.a2a.types import ContentPart
27
+ from ...common.utils.rbac_utils import validate_agent_access
25
28
  from a2a.types import (
26
29
  Message as A2AMessage,
27
30
  AgentCard,
@@ -31,20 +34,25 @@ from a2a.types import (
31
34
  TaskArtifactUpdateEvent,
32
35
  JSONRPCError,
33
36
  TextPart,
37
+ DataPart,
34
38
  FilePart,
35
39
  FileWithBytes,
36
40
  Artifact as A2AArtifact,
37
41
  )
38
42
  from ...common import a2a
39
- from ...common.utils import is_text_based_mime_type
40
43
  from ...common.utils.embeds import (
41
44
  resolve_embeds_in_string,
42
- resolve_embeds_recursively_in_string,
43
45
  evaluate_embed,
44
46
  LATE_EMBED_TYPES,
45
47
  EARLY_EMBED_TYPES,
46
- EMBED_DELIMITER_OPEN,
48
+ resolve_embeds_recursively_in_string,
47
49
  )
50
+ from ...common.utils.embeds.types import ResolutionMode
51
+ from ...agent.utils.artifact_helpers import (
52
+ load_artifact_content_or_metadata,
53
+ format_artifact_uri,
54
+ )
55
+ from ...common.utils.mime_helpers import is_text_based_mime_type
48
56
  from solace_ai_connector.common.message import (
49
57
  Message as SolaceMessage,
50
58
  )
@@ -97,9 +105,32 @@ class BaseGatewayComponent(SamComponentBase):
97
105
 
98
106
  return super().get_config(key, default)
99
107
 
100
- def __init__(self, resolve_artifact_uris_in_gateway: bool = True, **kwargs: Any):
108
+ def __init__(
109
+ self,
110
+ resolve_artifact_uris_in_gateway: bool = True,
111
+ supports_inline_artifact_resolution: bool = False,
112
+ filter_tool_data_parts: bool = True,
113
+ **kwargs: Any
114
+ ):
115
+ """
116
+ Initialize the BaseGatewayComponent.
117
+
118
+ Args:
119
+ resolve_artifact_uris_in_gateway: If True, resolves artifact URIs before sending to external.
120
+ supports_inline_artifact_resolution: If True, SIGNAL_ARTIFACT_RETURN embeds are converted
121
+ to FileParts during embed resolution. If False (default), signals are passed through
122
+ for the gateway to handle manually. Use False for legacy gateways (e.g., Slack),
123
+ True for modern gateways that support inline artifact rendering (e.g., HTTP SSE).
124
+ filter_tool_data_parts: If True (default), filters out tool-related DataParts (tool_call,
125
+ tool_result, etc.) from final Task messages before sending to gateway. Use True for
126
+ gateways that don't want to display internal tool execution details (e.g., Slack),
127
+ False for gateways that display all parts (e.g., HTTP SSE Web UI).
128
+ **kwargs: Additional arguments passed to parent class.
129
+ """
101
130
  super().__init__(info, **kwargs)
102
131
  self.resolve_artifact_uris_in_gateway = resolve_artifact_uris_in_gateway
132
+ self.supports_inline_artifact_resolution = supports_inline_artifact_resolution
133
+ self.filter_tool_data_parts = filter_tool_data_parts
103
134
  log.info("%s Initializing Base Gateway Component...", self.log_identifier)
104
135
 
105
136
  try:
@@ -139,7 +170,9 @@ class BaseGatewayComponent(SamComponentBase):
139
170
 
140
171
  self.agent_registry: AgentRegistry = AgentRegistry()
141
172
  self.core_a2a_service: CoreA2AService = CoreA2AService(
142
- agent_registry=self.agent_registry, namespace=self.namespace
173
+ agent_registry=self.agent_registry,
174
+ namespace=self.namespace,
175
+ component_id="WebUI"
143
176
  )
144
177
  self.shared_artifact_service: Optional[BaseArtifactService] = (
145
178
  initialize_artifact_service(self)
@@ -159,10 +192,65 @@ class BaseGatewayComponent(SamComponentBase):
159
192
  self.log_identifier,
160
193
  )
161
194
 
195
+ # Authentication handler (optional, enterprise feature)
196
+ self.auth_handler: Optional[AuthHandler] = None
197
+
198
+ # Setup authentication if enabled (subclasses override _setup_auth)
199
+ self._setup_auth()
200
+
162
201
  log.info(
163
- "%s Base Gateway Component initialized successfully.", self.log_identifier
202
+ "%s Initialized Base Gateway Component.", self.log_identifier
164
203
  )
165
204
 
205
+ def _setup_auth(self) -> None:
206
+ """
207
+ Setup authentication handler if enabled.
208
+
209
+ This method is called during initialization and can be overridden
210
+ by subclasses to customize auth setup. The default implementation
211
+ does nothing - subclasses should override to enable auth.
212
+
213
+ Example override in subclass:
214
+ def _setup_auth(self):
215
+ if self.get_config('enable_auth', False):
216
+ from enterprise.auth import SAMOAuth2Handler
217
+ self.auth_handler = SAMOAuth2Handler(self.config)
218
+ """
219
+ # Base implementation: no auth
220
+ # Subclasses (like GenericGateway) override to enable auth
221
+ pass
222
+
223
+ async def _inject_auth_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
224
+ """
225
+ Inject authentication headers if authenticated.
226
+
227
+ This helper method should be called before making outgoing HTTP requests
228
+ to add authentication headers (e.g., Bearer tokens) to the request.
229
+
230
+ Args:
231
+ headers: Existing headers dictionary
232
+
233
+ Returns:
234
+ Headers dictionary with auth headers added (if authenticated)
235
+
236
+ Example:
237
+ headers = {"Content-Type": "application/json"}
238
+ headers = await self._inject_auth_headers(headers)
239
+ # headers now includes Authorization if authenticated
240
+ """
241
+ if self.auth_handler:
242
+ try:
243
+ auth_headers = await self.auth_handler.get_auth_headers()
244
+ headers.update(auth_headers)
245
+ except Exception as e:
246
+ log.warning(
247
+ "%s Failed to get auth headers: %s",
248
+ self.log_identifier,
249
+ e
250
+ )
251
+
252
+ return headers
253
+
166
254
  async def authenticate_and_enrich_user(
167
255
  self, external_event_data: Any
168
256
  ) -> Optional[Dict[str, Any]]:
@@ -208,6 +296,8 @@ class BaseGatewayComponent(SamComponentBase):
208
296
  user_identity: Any,
209
297
  is_streaming: bool = True,
210
298
  api_version: str = "v2",
299
+ task_id_override: str | None = None,
300
+ metadata: dict[str, Any] | None = None,
211
301
  ) -> str:
212
302
  log_id_prefix = f"{self.log_identifier}[SubmitA2ATask]"
213
303
  log.info(
@@ -241,7 +331,7 @@ class BaseGatewayComponent(SamComponentBase):
241
331
  user_config = await config_resolver.resolve_user_config(
242
332
  user_identity, gateway_context, {}
243
333
  )
244
- log.info(
334
+ log.debug(
245
335
  "%s Resolved user configuration for user_identity '%s': %s",
246
336
  log_id_prefix,
247
337
  user_identity.get("id"),
@@ -258,13 +348,26 @@ class BaseGatewayComponent(SamComponentBase):
258
348
 
259
349
  user_config["user_profile"] = user_identity
260
350
 
351
+ # Validate user has permission to access this target agent
352
+ validate_agent_access(
353
+ user_config=user_config,
354
+ target_agent_name=target_agent_name,
355
+ validation_context={
356
+ "gateway_id": self.gateway_id,
357
+ "source": "gateway_request",
358
+ },
359
+ log_identifier=log_id_prefix,
360
+ )
361
+
261
362
  external_request_context["user_identity"] = user_identity
262
363
  external_request_context["a2a_user_config"] = user_config
263
364
  external_request_context["api_version"] = api_version
365
+ external_request_context["is_streaming"] = is_streaming
264
366
  log.debug(
265
- "%s Stored user_identity, configuration, and api_version (%s) in external_request_context.",
367
+ "%s Stored user_identity, configuration, api_version (%s), and is_streaming (%s) in external_request_context.",
266
368
  log_id_prefix,
267
369
  api_version,
370
+ is_streaming,
268
371
  )
269
372
 
270
373
  now = datetime.now(timezone.utc)
@@ -299,6 +402,15 @@ class BaseGatewayComponent(SamComponentBase):
299
402
  "system_purpose": system_purpose,
300
403
  "response_format": response_format,
301
404
  }
405
+
406
+ # Add session behavior if provided by adapter
407
+ session_behavior = external_request_context.get("session_behavior")
408
+ if session_behavior:
409
+ a2a_metadata["sessionBehavior"] = session_behavior
410
+ log.debug(
411
+ "%s Setting sessionBehavior to: %s", log_id_prefix, session_behavior
412
+ )
413
+
302
414
  invoked_artifacts = external_request_context.get("invoked_with_artifacts")
303
415
  if invoked_artifacts:
304
416
  a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
@@ -307,9 +419,15 @@ class BaseGatewayComponent(SamComponentBase):
307
419
  log_id_prefix,
308
420
  len(invoked_artifacts),
309
421
  )
422
+
423
+ if metadata:
424
+ a2a_metadata.update(metadata)
310
425
 
311
426
  # This correlation ID is used by the gateway to track the task
312
- task_id = f"gdk-task-{uuid.uuid4().hex}"
427
+ if task_id_override:
428
+ task_id = task_id_override
429
+ else:
430
+ task_id = f"gdk-task-{uuid.uuid4().hex}"
313
431
 
314
432
  prepared_a2a_parts = await self._prepare_parts_for_publishing(
315
433
  parts=a2a_parts,
@@ -450,7 +568,7 @@ class BaseGatewayComponent(SamComponentBase):
450
568
  async def _handle_resolved_signals(
451
569
  self,
452
570
  external_request_context: Dict,
453
- signals: List[Tuple[int, Any]],
571
+ signals: List[Tuple[None, str, Any]],
454
572
  original_rpc_id: Optional[str],
455
573
  is_finalizing_context: bool = False,
456
574
  ):
@@ -458,7 +576,7 @@ class BaseGatewayComponent(SamComponentBase):
458
576
  if not signals:
459
577
  return
460
578
 
461
- for _, signal_tuple in signals:
579
+ for signal_tuple in signals:
462
580
  if (
463
581
  isinstance(signal_tuple, tuple)
464
582
  and len(signal_tuple) == 3
@@ -518,6 +636,375 @@ class BaseGatewayComponent(SamComponentBase):
518
636
  log.exception(
519
637
  "%s Error sending status signal: %s", log_id_prefix, e
520
638
  )
639
+ elif signal_type == "SIGNAL_ARTIFACT_RETURN":
640
+ # Handle artifact return signal for legacy gateways
641
+ # During finalizing context (final Task), suppress this to avoid duplicates
642
+ # since the same signal might appear in both streaming and final responses
643
+ if is_finalizing_context:
644
+ log.debug(
645
+ "%s Suppressing SIGNAL_ARTIFACT_RETURN during finalizing context to avoid duplicate: %s",
646
+ log_id_prefix,
647
+ signal_data,
648
+ )
649
+ continue
650
+
651
+ log.info(
652
+ "%s Handling SIGNAL_ARTIFACT_RETURN for legacy gateway: %s",
653
+ log_id_prefix,
654
+ signal_data,
655
+ )
656
+ try:
657
+ filename = signal_data.get("filename")
658
+ version = signal_data.get("version")
659
+
660
+ if not filename:
661
+ log.error(
662
+ "%s SIGNAL_ARTIFACT_RETURN missing filename. Skipping.",
663
+ log_id_prefix,
664
+ )
665
+ continue
666
+
667
+ # Load artifact content (not just metadata) for legacy gateways
668
+ # Legacy gateways like Slack need the actual bytes to upload files
669
+ artifact_data = await load_artifact_content_or_metadata(
670
+ self.shared_artifact_service,
671
+ app_name=external_request_context.get(
672
+ "app_name_for_artifacts", self.gateway_id
673
+ ),
674
+ user_id=external_request_context.get("user_id_for_artifacts"),
675
+ session_id=external_request_context.get("a2a_session_id"),
676
+ filename=filename,
677
+ version=version,
678
+ load_metadata_only=False, # Load full content for legacy gateways
679
+ )
680
+
681
+ if artifact_data.get("status") != "success":
682
+ log.error(
683
+ "%s Failed to load artifact content for %s v%s",
684
+ log_id_prefix,
685
+ filename,
686
+ version,
687
+ )
688
+ continue
689
+
690
+ # Get content and ensure it's bytes
691
+ content = artifact_data.get("content")
692
+ if not content:
693
+ log.error(
694
+ "%s No content found in artifact %s v%s",
695
+ log_id_prefix,
696
+ filename,
697
+ version,
698
+ )
699
+ continue
700
+
701
+ # Convert to bytes if it's a string (text-based artifacts)
702
+ if isinstance(content, str):
703
+ content_bytes = content.encode("utf-8")
704
+ elif isinstance(content, bytes):
705
+ content_bytes = content
706
+ else:
707
+ log.error(
708
+ "%s Artifact content is neither string nor bytes: %s",
709
+ log_id_prefix,
710
+ type(content),
711
+ )
712
+ continue
713
+
714
+ # Resolve any late embeds inside the artifact content before returning.
715
+ content_bytes = await self._resolve_embeds_in_artifact_content(
716
+ content_bytes=content_bytes,
717
+ mime_type=artifact_data.get("metadata", {}).get(
718
+ "mime_type"
719
+ ),
720
+ filename=filename,
721
+ external_request_context=external_request_context,
722
+ log_id_prefix=log_id_prefix,
723
+ )
724
+
725
+ # Create FilePart with bytes for legacy gateway to upload
726
+ file_part = a2a.create_file_part_from_bytes(
727
+ content_bytes=content_bytes,
728
+ name=filename,
729
+ mime_type=artifact_data.get("metadata", {}).get(
730
+ "mime_type"
731
+ ),
732
+ )
733
+
734
+ # Create artifact with the file part
735
+ # Import Part type for wrapping
736
+ from a2a.types import Artifact, Part
737
+ artifact = Artifact(
738
+ artifact_id=str(uuid.uuid4().hex),
739
+ parts=[Part(root=file_part)],
740
+ name=filename,
741
+ description=f"Artifact: {filename}",
742
+ )
743
+
744
+ # Send as TaskArtifactUpdateEvent
745
+ a2a_task_id_for_signal = external_request_context.get(
746
+ "a2a_task_id_for_event", original_rpc_id
747
+ )
748
+
749
+ if not a2a_task_id_for_signal:
750
+ log.error(
751
+ "%s Cannot determine A2A task ID for artifact signal. Skipping.",
752
+ log_id_prefix,
753
+ )
754
+ continue
755
+
756
+ artifact_event = a2a.create_artifact_update(
757
+ task_id=a2a_task_id_for_signal,
758
+ context_id=external_request_context.get("a2a_session_id"),
759
+ artifact=artifact,
760
+ )
761
+
762
+ await self._send_update_to_external(
763
+ external_request_context=external_request_context,
764
+ event_data=artifact_event,
765
+ is_final_chunk_of_update=False,
766
+ )
767
+ log.info(
768
+ "%s Sent artifact signal as TaskArtifactUpdateEvent for %s",
769
+ log_id_prefix,
770
+ filename,
771
+ )
772
+ except Exception as e:
773
+ log.exception(
774
+ "%s Error sending artifact signal: %s", log_id_prefix, e
775
+ )
776
+ elif signal_type == "SIGNAL_ARTIFACT_CREATION_COMPLETE":
777
+ # Handle artifact creation completion for legacy gateways
778
+ # This is similar to SIGNAL_ARTIFACT_RETURN but for newly created artifacts
779
+ log.info(
780
+ "%s Handling SIGNAL_ARTIFACT_CREATION_COMPLETE for legacy gateway: %s",
781
+ log_id_prefix,
782
+ signal_data,
783
+ )
784
+ try:
785
+ filename = signal_data.get("filename")
786
+ version = signal_data.get("version")
787
+
788
+ if not filename:
789
+ log.error(
790
+ "%s SIGNAL_ARTIFACT_CREATION_COMPLETE missing filename. Skipping.",
791
+ log_id_prefix,
792
+ )
793
+ continue
794
+
795
+ # Load artifact content (not just metadata) for legacy gateways
796
+ # Legacy gateways like Slack need the actual bytes to upload files
797
+ artifact_data = await load_artifact_content_or_metadata(
798
+ self.shared_artifact_service,
799
+ app_name=external_request_context.get(
800
+ "app_name_for_artifacts", self.gateway_id
801
+ ),
802
+ user_id=external_request_context.get("user_id_for_artifacts"),
803
+ session_id=external_request_context.get("a2a_session_id"),
804
+ filename=filename,
805
+ version=version,
806
+ load_metadata_only=False, # Load full content for legacy gateways
807
+ )
808
+
809
+ if artifact_data.get("status") != "success":
810
+ log.error(
811
+ "%s Failed to load artifact content for %s v%s",
812
+ log_id_prefix,
813
+ filename,
814
+ version,
815
+ )
816
+ continue
817
+
818
+ # Get content and ensure it's bytes
819
+ content = artifact_data.get("content")
820
+ if not content:
821
+ log.error(
822
+ "%s No content found in artifact %s v%s",
823
+ log_id_prefix,
824
+ filename,
825
+ version,
826
+ )
827
+ continue
828
+
829
+ # Convert to bytes if it's a string (text-based artifacts)
830
+ if isinstance(content, str):
831
+ content_bytes = content.encode("utf-8")
832
+ elif isinstance(content, bytes):
833
+ content_bytes = content
834
+ else:
835
+ log.error(
836
+ "%s Artifact content is neither string nor bytes: %s",
837
+ log_id_prefix,
838
+ type(content),
839
+ )
840
+ continue
841
+
842
+ # Create FilePart with bytes for legacy gateway to upload
843
+ file_part = a2a.create_file_part_from_bytes(
844
+ content_bytes=content_bytes,
845
+ name=filename,
846
+ mime_type=signal_data.get("mime_type") or artifact_data.get("metadata", {}).get("mime_type"),
847
+ )
848
+
849
+ # Create artifact with the file part
850
+ # Import Part type for wrapping
851
+ from a2a.types import Artifact, Part
852
+ artifact = Artifact(
853
+ artifact_id=str(uuid.uuid4().hex),
854
+ parts=[Part(root=file_part)],
855
+ name=filename,
856
+ description=f"Artifact: {filename}",
857
+ )
858
+
859
+ # Send as TaskArtifactUpdateEvent
860
+ a2a_task_id_for_signal = external_request_context.get(
861
+ "a2a_task_id_for_event", original_rpc_id
862
+ )
863
+
864
+ if not a2a_task_id_for_signal:
865
+ log.error(
866
+ "%s Cannot determine A2A task ID for artifact creation signal. Skipping.",
867
+ log_id_prefix,
868
+ )
869
+ continue
870
+
871
+ artifact_event = a2a.create_artifact_update(
872
+ task_id=a2a_task_id_for_signal,
873
+ context_id=external_request_context.get("a2a_session_id"),
874
+ artifact=artifact,
875
+ )
876
+
877
+ await self._send_update_to_external(
878
+ external_request_context=external_request_context,
879
+ event_data=artifact_event,
880
+ is_final_chunk_of_update=False,
881
+ )
882
+ log.info(
883
+ "%s Sent artifact creation completion as TaskArtifactUpdateEvent for %s",
884
+ log_id_prefix,
885
+ filename,
886
+ )
887
+ except Exception as e:
888
+ log.exception(
889
+ "%s Error sending artifact creation completion signal: %s", log_id_prefix, e
890
+ )
891
+ elif signal_type == "SIGNAL_DEEP_RESEARCH_REPORT":
892
+ # Handle deep research report signal for legacy gateways
893
+ # For legacy gateways, we send the report as a file attachment
894
+ if is_finalizing_context:
895
+ log.debug(
896
+ "%s Suppressing SIGNAL_DEEP_RESEARCH_REPORT during finalizing context to avoid duplicate: %s",
897
+ log_id_prefix,
898
+ signal_data,
899
+ )
900
+ continue
901
+
902
+ try:
903
+ filename = signal_data.get("filename")
904
+ version = signal_data.get("version")
905
+
906
+ if not filename:
907
+ log.error(
908
+ "%s SIGNAL_DEEP_RESEARCH_REPORT missing filename. Skipping.",
909
+ log_id_prefix,
910
+ )
911
+ continue
912
+
913
+ # Load artifact content for legacy gateways
914
+ artifact_data = await load_artifact_content_or_metadata(
915
+ self.shared_artifact_service,
916
+ app_name=external_request_context.get(
917
+ "app_name_for_artifacts", self.gateway_id
918
+ ),
919
+ user_id=external_request_context.get("user_id_for_artifacts"),
920
+ session_id=external_request_context.get("a2a_session_id"),
921
+ filename=filename,
922
+ version=version,
923
+ load_metadata_only=False,
924
+ )
925
+
926
+ if artifact_data.get("status") != "success":
927
+ log.error(
928
+ "%s Failed to load deep research report content for %s v%s",
929
+ log_id_prefix,
930
+ filename,
931
+ version,
932
+ )
933
+ continue
934
+
935
+ content = artifact_data.get("content")
936
+ if not content:
937
+ log.error(
938
+ "%s No content found in deep research report %s v%s",
939
+ log_id_prefix,
940
+ filename,
941
+ version,
942
+ )
943
+ continue
944
+
945
+ # Convert to bytes if it's a string
946
+ if isinstance(content, str):
947
+ content_bytes = content.encode("utf-8")
948
+ elif isinstance(content, bytes):
949
+ content_bytes = content
950
+ else:
951
+ log.error(
952
+ "%s Deep research report content is neither string nor bytes: %s",
953
+ log_id_prefix,
954
+ type(content),
955
+ )
956
+ continue
957
+
958
+ # Create FilePart with bytes for legacy gateway to upload
959
+ file_part = a2a.create_file_part_from_bytes(
960
+ content_bytes=content_bytes,
961
+ name=filename,
962
+ mime_type=artifact_data.get("metadata", {}).get(
963
+ "mime_type", "text/markdown"
964
+ ),
965
+ )
966
+
967
+ # Create artifact with the file part
968
+ from a2a.types import Artifact, Part
969
+ artifact = Artifact(
970
+ artifact_id=str(uuid.uuid4().hex),
971
+ parts=[Part(root=file_part)],
972
+ name=filename,
973
+ description=f"Deep Research Report: {filename}",
974
+ )
975
+
976
+ # Send as TaskArtifactUpdateEvent
977
+ a2a_task_id_for_signal = external_request_context.get(
978
+ "a2a_task_id_for_event", original_rpc_id
979
+ )
980
+
981
+ if not a2a_task_id_for_signal:
982
+ log.error(
983
+ "%s Cannot determine A2A task ID for deep research report signal. Skipping.",
984
+ log_id_prefix,
985
+ )
986
+ continue
987
+
988
+ artifact_event = a2a.create_artifact_update(
989
+ task_id=a2a_task_id_for_signal,
990
+ context_id=external_request_context.get("a2a_session_id"),
991
+ artifact=artifact,
992
+ )
993
+
994
+ await self._send_update_to_external(
995
+ external_request_context=external_request_context,
996
+ event_data=artifact_event,
997
+ is_final_chunk_of_update=False,
998
+ )
999
+ log.info(
1000
+ "%s Sent deep research report as TaskArtifactUpdateEvent for %s",
1001
+ log_id_prefix,
1002
+ filename,
1003
+ )
1004
+ except Exception as e:
1005
+ log.exception(
1006
+ "%s Error sending deep research report signal: %s", log_id_prefix, e
1007
+ )
521
1008
  else:
522
1009
  log.warning(
523
1010
  "%s Received unhandled signal type during embed resolution: %s",
@@ -525,10 +1012,80 @@ class BaseGatewayComponent(SamComponentBase):
525
1012
  signal_type,
526
1013
  )
527
1014
 
528
- async def _resolve_uri_in_file_part(self, file_part: FilePart):
1015
+ async def _resolve_embeds_in_artifact_content(
1016
+ self,
1017
+ content_bytes: bytes,
1018
+ mime_type: Optional[str],
1019
+ filename: str,
1020
+ external_request_context: Dict[str, Any],
1021
+ log_id_prefix: str,
1022
+ ) -> bytes:
1023
+ """
1024
+ Checks if content is text-based and, if so, resolves late embeds within it.
1025
+ Returns the (potentially modified) content as bytes.
1026
+ """
1027
+ if is_text_based_mime_type(mime_type):
1028
+ log.info(
1029
+ "%s Artifact '%s' is text-based (%s). Resolving late embeds.",
1030
+ log_id_prefix,
1031
+ filename,
1032
+ mime_type,
1033
+ )
1034
+ try:
1035
+ decoded_content = content_bytes.decode("utf-8")
1036
+
1037
+ # Construct context and config for the resolver
1038
+ embed_eval_context = {
1039
+ "artifact_service": self.shared_artifact_service,
1040
+ "session_context": {
1041
+ "app_name": external_request_context.get(
1042
+ "app_name_for_artifacts", self.gateway_id
1043
+ ),
1044
+ "user_id": external_request_context.get(
1045
+ "user_id_for_artifacts"
1046
+ ),
1047
+ "session_id": external_request_context.get("a2a_session_id"),
1048
+ },
1049
+ }
1050
+ embed_eval_config = {
1051
+ "gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
1052
+ "gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
1053
+ }
1054
+
1055
+ resolved_string = await resolve_embeds_recursively_in_string(
1056
+ text=decoded_content,
1057
+ context=embed_eval_context,
1058
+ resolver_func=evaluate_embed,
1059
+ types_to_resolve=LATE_EMBED_TYPES,
1060
+ resolution_mode=ResolutionMode.RECURSIVE_ARTIFACT_CONTENT,
1061
+ log_identifier=f"{log_id_prefix}[RecursiveResolve]",
1062
+ config=embed_eval_config,
1063
+ max_depth=self.gateway_recursive_embed_depth,
1064
+ )
1065
+ resolved_bytes = resolved_string.encode("utf-8")
1066
+ log.info(
1067
+ "%s Successfully resolved embeds in '%s'. New size: %d bytes.",
1068
+ log_id_prefix,
1069
+ filename,
1070
+ len(resolved_bytes),
1071
+ )
1072
+ return resolved_bytes
1073
+ except Exception as resolve_err:
1074
+ log.error(
1075
+ "%s Failed to resolve embeds within artifact '%s': %s. Returning raw content.",
1076
+ log_id_prefix,
1077
+ filename,
1078
+ resolve_err,
1079
+ )
1080
+ return content_bytes
1081
+
1082
+ async def _resolve_uri_in_file_part(
1083
+ self, file_part: FilePart, external_request_context: Dict[str, Any]
1084
+ ):
529
1085
  """
530
1086
  Checks if a FilePart has a resolvable URI and, if so,
531
1087
  resolves it and mutates the part in-place by calling the common utility.
1088
+ After resolving the URI, it also resolves any late embeds within the content.
532
1089
  """
533
1090
  await a2a.resolve_file_part_uri(
534
1091
  part=file_part,
@@ -536,15 +1093,43 @@ class BaseGatewayComponent(SamComponentBase):
536
1093
  log_identifier=self.log_identifier,
537
1094
  )
538
1095
 
539
- async def _resolve_uris_in_parts_list(self, parts: List[ContentPart]):
1096
+ # After resolving the URI to get the content, resolve any late embeds inside it.
1097
+ if file_part.file and isinstance(file_part.file, FileWithBytes):
1098
+ # The content is a base64 encoded string in the `bytes` attribute.
1099
+ # We need to decode it to raw bytes for processing.
1100
+ try:
1101
+ content_bytes = base64.b64decode(file_part.file.bytes)
1102
+ except Exception as e:
1103
+ log.error(
1104
+ "%s Failed to base64 decode file content for embed resolution: %s",
1105
+ f"{self.log_identifier}[UriResolve]",
1106
+ e,
1107
+ )
1108
+ return
1109
+
1110
+ resolved_bytes = await self._resolve_embeds_in_artifact_content(
1111
+ content_bytes=content_bytes,
1112
+ mime_type=file_part.file.mime_type,
1113
+ filename=file_part.file.name,
1114
+ external_request_context=external_request_context,
1115
+ log_id_prefix=f"{self.log_identifier}[UriResolve]",
1116
+ )
1117
+ # Re-encode the resolved content back to a base64 string for the FileWithBytes model.
1118
+ file_part.file.bytes = base64.b64encode(resolved_bytes).decode("utf-8")
1119
+
1120
+ async def _resolve_uris_in_parts_list(
1121
+ self, parts: List[ContentPart], external_request_context: Dict[str, Any]
1122
+ ):
540
1123
  """Iterates over a list of part objects and resolves any FilePart URIs."""
541
1124
  if not parts:
542
1125
  return
543
1126
  for part in parts:
544
1127
  if isinstance(part, FilePart):
545
- await self._resolve_uri_in_file_part(part)
1128
+ await self._resolve_uri_in_file_part(part, external_request_context)
546
1129
 
547
- async def _resolve_uris_in_payload(self, parsed_event: Any):
1130
+ async def _resolve_uris_in_payload(
1131
+ self, parsed_event: Any, external_request_context: Dict[str, Any]
1132
+ ):
548
1133
  """
549
1134
  Dispatcher that calls the appropriate targeted URI resolver based on the
550
1135
  Pydantic model type of the event.
@@ -568,7 +1153,9 @@ class BaseGatewayComponent(SamComponentBase):
568
1153
  parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
569
1154
 
570
1155
  if parts_to_resolve:
571
- await self._resolve_uris_in_parts_list(parts_to_resolve)
1156
+ await self._resolve_uris_in_parts_list(
1157
+ parts_to_resolve, external_request_context
1158
+ )
572
1159
  else:
573
1160
  log.debug(
574
1161
  "%s Payload type '%s' did not yield any parts for URI resolution. Skipping.",
@@ -620,6 +1207,64 @@ class BaseGatewayComponent(SamComponentBase):
620
1207
  processed_parts.append(part)
621
1208
  return processed_parts
622
1209
 
1210
+ def _should_include_data_part_in_final_output(self, part: Any) -> bool:
1211
+ """
1212
+ Determines if a DataPart should be included in the final output sent to the gateway.
1213
+
1214
+ This filters out internal/tool-related DataParts that shouldn't be shown to end users.
1215
+ Gateways can override this method for custom filtering logic.
1216
+
1217
+ Args:
1218
+ part: The part to check (expected to be a DataPart)
1219
+
1220
+ Returns:
1221
+ True if the part should be included, False if it should be filtered out
1222
+ """
1223
+ from a2a.types import DataPart
1224
+
1225
+ if not isinstance(part, DataPart):
1226
+ return True
1227
+
1228
+ # Check if this is a tool result by looking at metadata
1229
+ # Tool results have metadata.tool_name set
1230
+ if part.metadata and part.metadata.get("tool_name"):
1231
+ # This is a tool result - filter it out
1232
+ return False
1233
+
1234
+ # Get the type of the data part
1235
+ data_type = part.data.get("type") if part.data else None
1236
+
1237
+ # Filter out tool-related data parts that are internal
1238
+ tool_related_types = {
1239
+ "tool_call",
1240
+ "tool_result",
1241
+ "tool_error",
1242
+ "tool_execution",
1243
+ }
1244
+
1245
+ if data_type in tool_related_types:
1246
+ return False
1247
+
1248
+ # Handle artifact_creation_progress based on gateway capabilities
1249
+ if data_type == "artifact_creation_progress":
1250
+ # For modern gateways (HTTP SSE), keep these to display progress bubbles
1251
+ # For legacy gateways (Slack), filter them out as they'll be converted to FileParts
1252
+ if self.supports_inline_artifact_resolution:
1253
+ return True # Keep for HTTP SSE
1254
+ else:
1255
+ return False # Filter for Slack (will be converted to FileParts instead)
1256
+
1257
+ # Keep user-facing data parts like general progress updates
1258
+ user_facing_types = {
1259
+ "agent_progress_update",
1260
+ }
1261
+
1262
+ if data_type in user_facing_types:
1263
+ return True
1264
+
1265
+ # Default: include unknown types (to avoid hiding potentially useful info)
1266
+ return True
1267
+
623
1268
  async def _resolve_embeds_and_handle_signals(
624
1269
  self,
625
1270
  event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
@@ -628,17 +1273,11 @@ class BaseGatewayComponent(SamComponentBase):
628
1273
  original_rpc_id: Optional[str],
629
1274
  is_finalizing_context: bool = False,
630
1275
  ) -> bool:
631
- """
632
- Resolves embeds and handles signals for an event containing parts.
633
- Modifies event_with_parts in place if text content changes.
634
- Manages stream buffer for TaskStatusUpdateEvent.
635
- Returns True if the event content was modified or signals were handled, False otherwise.
636
- """
637
1276
  if not self.enable_embed_resolution:
638
1277
  return False
639
1278
 
640
1279
  log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
641
- content_modified_or_signal_handled = False
1280
+ content_modified = False
642
1281
 
643
1282
  embed_eval_context = {
644
1283
  "artifact_service": self.shared_artifact_service,
@@ -656,8 +1295,6 @@ class BaseGatewayComponent(SamComponentBase):
656
1295
  }
657
1296
 
658
1297
  parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
659
- is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
660
-
661
1298
  if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
662
1299
  if event_with_parts.status and event_with_parts.status.message:
663
1300
  parts_owner = event_with_parts.status.message
@@ -665,146 +1302,359 @@ class BaseGatewayComponent(SamComponentBase):
665
1302
  if event_with_parts.artifact:
666
1303
  parts_owner = event_with_parts.artifact
667
1304
 
668
- if parts_owner and parts_owner.parts:
669
- new_parts: List[ContentPart] = []
670
- stream_buffer_key = f"{a2a_task_id}_stream_buffer"
671
- current_buffer = ""
1305
+ if not (parts_owner and parts_owner.parts):
1306
+ return False
1307
+
1308
+ is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
1309
+ stream_buffer_key = f"{a2a_task_id}_stream_buffer"
1310
+ current_buffer = ""
1311
+ if is_streaming_status_update:
1312
+ current_buffer = (
1313
+ self.task_context_manager.get_context(stream_buffer_key) or ""
1314
+ )
1315
+
1316
+ original_parts: List[ContentPart] = (
1317
+ a2a.get_parts_from_message(parts_owner)
1318
+ if isinstance(parts_owner, A2AMessage)
1319
+ else a2a.get_parts_from_artifact(parts_owner)
1320
+ )
672
1321
 
673
- if is_streaming_status_update:
674
- current_buffer = (
675
- self.task_context_manager.get_context(stream_buffer_key) or ""
1322
+ new_parts: List[ContentPart] = []
1323
+ other_signals = []
1324
+
1325
+ for part in original_parts:
1326
+ if isinstance(part, TextPart) and part.text:
1327
+ text_to_resolve = current_buffer + part.text
1328
+ current_buffer = "" # Buffer is now being processed
1329
+
1330
+ (
1331
+ resolved_text,
1332
+ processed_idx,
1333
+ signals_with_placeholders,
1334
+ ) = await resolve_embeds_in_string(
1335
+ text=text_to_resolve,
1336
+ context=embed_eval_context,
1337
+ resolver_func=evaluate_embed,
1338
+ types_to_resolve=LATE_EMBED_TYPES.union({"status_update"}),
1339
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1340
+ log_identifier=log_id_prefix,
1341
+ config=embed_eval_config,
676
1342
  )
677
1343
 
678
- parts: List[ContentPart] = []
679
- if isinstance(parts_owner, A2AMessage):
680
- parts = a2a.get_parts_from_message(parts_owner)
681
- elif isinstance(parts_owner, A2AArtifact):
682
- parts = a2a.get_parts_from_artifact(parts_owner)
1344
+ if not signals_with_placeholders:
1345
+ new_parts.append(a2a.create_text_part(text=resolved_text))
1346
+ else:
1347
+ placeholder_map = {p: s for _, s, p in signals_with_placeholders}
1348
+ split_pattern = (
1349
+ f"({'|'.join(re.escape(p) for p in placeholder_map.keys())})"
1350
+ )
1351
+ text_fragments = re.split(split_pattern, resolved_text)
1352
+
1353
+ for i, fragment in enumerate(text_fragments):
1354
+ if not fragment:
1355
+ continue
1356
+ if fragment in placeholder_map:
1357
+ signal_tuple = placeholder_map[fragment]
1358
+ signal_type, signal_data = signal_tuple[1], signal_tuple[2]
1359
+ if signal_type == "SIGNAL_ARTIFACT_RETURN":
1360
+ # Only convert to FilePart if gateway supports inline artifact resolution
1361
+ if self.supports_inline_artifact_resolution:
1362
+ try:
1363
+ filename, version = (
1364
+ signal_data["filename"],
1365
+ signal_data["version"],
1366
+ )
1367
+ artifact_data = (
1368
+ await load_artifact_content_or_metadata(
1369
+ self.shared_artifact_service,
1370
+ **embed_eval_context["session_context"],
1371
+ filename=filename,
1372
+ version=version,
1373
+ load_metadata_only=True,
1374
+ )
1375
+ )
1376
+ if artifact_data.get("status") == "success":
1377
+ uri = format_artifact_uri(
1378
+ **embed_eval_context["session_context"],
1379
+ filename=filename,
1380
+ version=artifact_data.get("version"),
1381
+ )
1382
+ new_parts.append(
1383
+ a2a.create_file_part_from_uri(
1384
+ uri,
1385
+ filename,
1386
+ artifact_data.get("metadata", {}).get(
1387
+ "mime_type"
1388
+ ),
1389
+ )
1390
+ )
1391
+ else:
1392
+ new_parts.append(
1393
+ a2a.create_text_part(
1394
+ f"[Error: Artifact '{filename}' v{version} not found.]"
1395
+ )
1396
+ )
1397
+ except Exception as e:
1398
+ log.exception(
1399
+ "%s Error handling SIGNAL_ARTIFACT_RETURN: %s",
1400
+ log_id_prefix,
1401
+ e,
1402
+ )
1403
+ new_parts.append(
1404
+ a2a.create_text_part(
1405
+ f"[Error: Could not retrieve artifact '{signal_data.get('filename')}'.]"
1406
+ )
1407
+ )
1408
+ else:
1409
+ # Legacy gateway mode: pass signal through for gateway to handle
1410
+ other_signals.append(signal_tuple)
1411
+ elif signal_type == "SIGNAL_DEEP_RESEARCH_REPORT":
1412
+ # Deep research reports should be rendered by the frontend component
1413
+ # For modern gateways (HTTP SSE), create a DataPart with artifact reference
1414
+ # For legacy gateways, pass through as signal
1415
+ if self.supports_inline_artifact_resolution:
1416
+ try:
1417
+ filename = signal_data["filename"]
1418
+ version = signal_data["version"]
1419
+ log.info(
1420
+ "%s Converting SIGNAL_DEEP_RESEARCH_REPORT to DataPart for frontend rendering: %s v%s",
1421
+ log_id_prefix,
1422
+ filename,
1423
+ version,
1424
+ )
1425
+ # Create a DataPart that the frontend can use to render DeepResearchReportBubble
1426
+ # The frontend will fetch the artifact content separately
1427
+ artifact_data = (
1428
+ await load_artifact_content_or_metadata(
1429
+ self.shared_artifact_service,
1430
+ **embed_eval_context["session_context"],
1431
+ filename=filename,
1432
+ version=version,
1433
+ load_metadata_only=True,
1434
+ )
1435
+ )
1436
+ if artifact_data.get("status") == "success":
1437
+ uri = format_artifact_uri(
1438
+ **embed_eval_context["session_context"],
1439
+ filename=filename,
1440
+ version=artifact_data.get("version"),
1441
+ )
1442
+ # Create a DataPart with deep_research_report type
1443
+ # This will be rendered by DeepResearchReportBubble in the frontend
1444
+ data_part = a2a.create_data_part(
1445
+ data={
1446
+ "type": "deep_research_report",
1447
+ "filename": filename,
1448
+ "version": artifact_data.get("version"),
1449
+ "uri": uri,
1450
+ },
1451
+ metadata={"source": "deep_research_tool"},
1452
+ )
1453
+ new_parts.append(data_part)
1454
+ else:
1455
+ new_parts.append(
1456
+ a2a.create_text_part(
1457
+ f"[Error: Deep research report '{filename}' v{version} not found.]"
1458
+ )
1459
+ )
1460
+ except Exception as e:
1461
+ log.exception(
1462
+ "%s Error handling SIGNAL_DEEP_RESEARCH_REPORT: %s",
1463
+ log_id_prefix,
1464
+ e,
1465
+ )
1466
+ new_parts.append(
1467
+ a2a.create_text_part(
1468
+ f"[Error: Could not retrieve deep research report '{signal_data.get('filename')}'.]"
1469
+ )
1470
+ )
1471
+ else:
1472
+ # Legacy gateway mode: pass signal through for gateway to handle
1473
+ other_signals.append(signal_tuple)
1474
+ elif signal_type == "SIGNAL_INLINE_BINARY_CONTENT":
1475
+ signal_data["content_bytes"] = signal_data.get("bytes")
1476
+ del signal_data["bytes"]
1477
+ new_parts.append(
1478
+ a2a.create_file_part_from_bytes(**signal_data)
1479
+ )
1480
+ else:
1481
+ other_signals.append(signal_tuple)
1482
+ else:
1483
+ # Check if the non-placeholder fragment is just whitespace
1484
+ # and is between two placeholders. If so, drop it.
1485
+ is_just_whitespace = not fragment.strip()
1486
+ prev_fragment_was_placeholder = (
1487
+ i > 0 and text_fragments[i - 1] in placeholder_map
1488
+ )
1489
+ next_fragment_is_placeholder = (
1490
+ i < len(text_fragments) - 1
1491
+ and text_fragments[i + 1] in placeholder_map
1492
+ )
683
1493
 
684
- for part in parts:
685
- if isinstance(part, TextPart) and part.text is not None:
686
- text_to_resolve = part.text
687
- original_part_text = part.text
1494
+ if (
1495
+ is_just_whitespace
1496
+ and prev_fragment_was_placeholder
1497
+ and next_fragment_is_placeholder
1498
+ ):
1499
+ log.debug(
1500
+ "%s Dropping whitespace fragment between two file signals.",
1501
+ log_id_prefix,
1502
+ )
1503
+ continue
688
1504
 
689
- if is_streaming_status_update:
690
- current_buffer += part.text
691
- text_to_resolve = current_buffer
1505
+ new_parts.append(a2a.create_text_part(text=fragment))
692
1506
 
693
- resolved_text, processed_idx, signals = (
694
- await resolve_embeds_in_string(
695
- text=text_to_resolve,
696
- context=embed_eval_context,
697
- resolver_func=evaluate_embed,
698
- types_to_resolve=LATE_EMBED_TYPES.copy(),
699
- log_identifier=log_id_prefix,
700
- config=embed_eval_config,
1507
+ if is_streaming_status_update:
1508
+ current_buffer = text_to_resolve[processed_idx:]
1509
+
1510
+ elif isinstance(part, FilePart) and part.file:
1511
+ # Handle recursive embeds in text-based FileParts
1512
+ new_parts.append(part) # Placeholder for now
1513
+ elif isinstance(part, DataPart):
1514
+ # Handle special DataPart types
1515
+ data_type = part.data.get("type") if part.data else None
1516
+
1517
+ if data_type == "template_block":
1518
+ # Resolve template block and replace with resolved text
1519
+ try:
1520
+ from ...common.utils.templates import resolve_template_blocks_in_string
1521
+
1522
+ # Reconstruct the template block syntax
1523
+ data_artifact = part.data.get("data_artifact", "")
1524
+ jsonpath = part.data.get("jsonpath")
1525
+ limit = part.data.get("limit")
1526
+ template_content = part.data.get("template_content", "")
1527
+
1528
+ # Build params string
1529
+ params_parts = [f'data="{data_artifact}"']
1530
+ if jsonpath:
1531
+ params_parts.append(f'jsonpath="{jsonpath}"')
1532
+ if limit is not None:
1533
+ params_parts.append(f'limit="{limit}"')
1534
+ params_str = " ".join(params_parts)
1535
+
1536
+ # Reconstruct full template block
1537
+ template_block = f"«««template: {params_str}\n{template_content}\n»»»"
1538
+
1539
+ log.debug(
1540
+ "%s Resolving template block inline: data=%s",
1541
+ log_id_prefix,
1542
+ data_artifact,
1543
+ )
1544
+
1545
+ # Resolve the template
1546
+ resolved_text = await resolve_template_blocks_in_string(
1547
+ text=template_block,
1548
+ artifact_service=self.shared_artifact_service,
1549
+ session_context={
1550
+ "app_name": external_request_context.get(
1551
+ "app_name_for_artifacts", self.gateway_id
1552
+ ),
1553
+ "user_id": external_request_context.get("user_id_for_artifacts"),
1554
+ "session_id": external_request_context.get("a2a_session_id"),
1555
+ },
1556
+ log_identifier=f"{log_id_prefix}[TemplateResolve]",
701
1557
  )
702
- )
703
1558
 
704
- if signals:
705
- await self._handle_resolved_signals(
706
- external_request_context,
707
- signals,
708
- original_rpc_id,
709
- is_finalizing_context,
1559
+ log.info(
1560
+ "%s Template resolved successfully. Output length: %d",
1561
+ log_id_prefix,
1562
+ len(resolved_text),
710
1563
  )
711
- content_modified_or_signal_handled = True
712
1564
 
713
- if resolved_text is not None:
1565
+ # Replace the DataPart with a TextPart containing the resolved content
714
1566
  new_parts.append(a2a.create_text_part(text=resolved_text))
715
- if is_streaming_status_update:
716
- if resolved_text != text_to_resolve[:processed_idx]:
717
- content_modified_or_signal_handled = True
718
- elif resolved_text != original_part_text:
719
- content_modified_or_signal_handled = True
720
-
721
- if is_streaming_status_update:
722
- current_buffer = text_to_resolve[processed_idx:]
723
- elif (
724
- processed_idx < len(text_to_resolve)
725
- and not content_modified_or_signal_handled
726
- ):
727
- log.warning(
728
- "%s Unclosed embed in non-streaming TextPart. Remainder: '%s'",
1567
+
1568
+ except Exception as e:
1569
+ log.error(
1570
+ "%s Failed to resolve template block: %s",
729
1571
  log_id_prefix,
730
- text_to_resolve[processed_idx:],
1572
+ e,
1573
+ exc_info=True,
731
1574
  )
732
- content_modified_or_signal_handled = True
733
-
734
- elif isinstance(part, FilePart) and part.file:
735
- if isinstance(part.file, FileWithBytes) and part.file.bytes:
736
- mime_type = part.file.mime_type or ""
737
- is_container = is_text_based_mime_type(mime_type)
738
- try:
739
- decoded_content_for_check = base64.b64decode(
740
- part.file.bytes
741
- ).decode("utf-8", errors="ignore")
742
- if (
743
- is_container
744
- and EMBED_DELIMITER_OPEN in decoded_content_for_check
745
- ):
746
- original_content = decoded_content_for_check
747
- resolved_content = (
748
- await resolve_embeds_recursively_in_string(
749
- text=original_content,
750
- context=embed_eval_context,
751
- resolver_func=evaluate_embed,
752
- types_to_resolve=LATE_EMBED_TYPES,
753
- log_identifier=log_id_prefix,
754
- config=embed_eval_config,
755
- max_depth=self.gateway_recursive_embed_depth,
756
- )
757
- )
758
- if resolved_content != original_content:
759
- new_file_content = part.file.model_copy()
760
- new_file_content.bytes = base64.b64encode(
761
- resolved_content.encode("utf-8")
762
- ).decode("utf-8")
763
- new_parts.append(
764
- FilePart(
765
- file=new_file_content,
766
- metadata=part.metadata,
767
- )
768
- )
769
- content_modified_or_signal_handled = True
770
- else:
771
- new_parts.append(part)
772
- else:
773
- new_parts.append(part)
774
- except Exception as e:
775
- log.warning(
776
- "%s Error during recursive FilePart resolution for %s: %s. Using original.",
1575
+ # Send error message as TextPart
1576
+ error_text = f"[Template rendering error: {str(e)}]"
1577
+ new_parts.append(a2a.create_text_part(text=error_text))
1578
+
1579
+ elif (
1580
+ data_type == "artifact_creation_progress"
1581
+ and not self.supports_inline_artifact_resolution
1582
+ ):
1583
+ # Legacy gateway mode: convert completed artifact creation to FilePart
1584
+ status = part.data.get("status")
1585
+ if status == "completed" and not is_finalizing_context:
1586
+ # Extract artifact info from the DataPart
1587
+ filename = part.data.get("filename")
1588
+ version = part.data.get("version")
1589
+ mime_type = part.data.get("mime_type")
1590
+
1591
+ if filename and version is not None:
1592
+ log.info(
1593
+ "%s Converting artifact creation completion to FilePart for legacy gateway: %s v%s",
777
1594
  log_id_prefix,
778
- part.file.name,
779
- e,
1595
+ filename,
1596
+ version,
780
1597
  )
1598
+ # This will be sent as an artifact signal, so don't add to new_parts
1599
+ # Instead, add to other_signals for processing
1600
+ signal_tuple = (
1601
+ None,
1602
+ "SIGNAL_ARTIFACT_CREATION_COMPLETE",
1603
+ {
1604
+ "filename": filename,
1605
+ "version": version,
1606
+ "mime_type": mime_type,
1607
+ },
1608
+ )
1609
+ other_signals.append(signal_tuple)
1610
+ else:
1611
+ # Missing required info, keep the DataPart as-is
781
1612
  new_parts.append(part)
1613
+ elif status == "completed" and is_finalizing_context:
1614
+ # Suppress during finalizing to avoid duplicates
1615
+ log.debug(
1616
+ "%s Suppressing artifact creation completion during finalizing context for %s",
1617
+ log_id_prefix,
1618
+ part.data.get("filename"),
1619
+ )
1620
+ continue
782
1621
  else:
783
- # This is a FileWithUri or empty FileWithBytes, which we don't process for embeds here.
1622
+ # Keep in-progress or failed status DataParts
784
1623
  new_parts.append(part)
785
1624
  else:
1625
+ # Not an artifact creation DataPart, or modern gateway - keep as-is
786
1626
  new_parts.append(part)
1627
+ else:
1628
+ new_parts.append(part)
787
1629
 
1630
+ if other_signals:
1631
+ await self._handle_resolved_signals(
1632
+ external_request_context,
1633
+ other_signals,
1634
+ original_rpc_id,
1635
+ is_finalizing_context,
1636
+ )
1637
+
1638
+ if new_parts != original_parts:
1639
+ content_modified = True
788
1640
  if isinstance(parts_owner, A2AMessage):
789
1641
  if isinstance(event_with_parts, TaskStatusUpdateEvent):
790
1642
  event_with_parts.status.message = a2a.update_message_parts(
791
- message=parts_owner, new_parts=new_parts
1643
+ parts_owner, new_parts
792
1644
  )
793
1645
  elif isinstance(event_with_parts, Task):
794
1646
  event_with_parts.status.message = a2a.update_message_parts(
795
- message=parts_owner, new_parts=new_parts
1647
+ parts_owner, new_parts
796
1648
  )
797
1649
  elif isinstance(parts_owner, A2AArtifact):
798
1650
  event_with_parts.artifact = a2a.update_artifact_parts(
799
- artifact=parts_owner, new_parts=new_parts
1651
+ parts_owner, new_parts
800
1652
  )
801
1653
 
802
- if is_streaming_status_update:
803
- self.task_context_manager.store_context(
804
- stream_buffer_key, current_buffer
805
- )
1654
+ if is_streaming_status_update:
1655
+ self.task_context_manager.store_context(stream_buffer_key, current_buffer)
806
1656
 
807
- return content_modified_or_signal_handled
1657
+ return content_modified or bool(other_signals)
808
1658
 
809
1659
  async def _process_parsed_a2a_event(
810
1660
  self,
@@ -837,13 +1687,6 @@ class BaseGatewayComponent(SamComponentBase):
837
1687
  elif isinstance(parsed_event, Task):
838
1688
  is_finalizing_context_for_embeds = True
839
1689
 
840
- if self.resolve_artifact_uris_in_gateway:
841
- log.debug(
842
- "%s Resolving artifact URIs before sending to external...",
843
- log_id_prefix,
844
- )
845
- await self._resolve_uris_in_payload(parsed_event)
846
-
847
1690
  if not isinstance(parsed_event, JSONRPCError):
848
1691
  content_was_modified_or_signals_handled = (
849
1692
  await self._resolve_embeds_and_handle_signals(
@@ -855,6 +1698,15 @@ class BaseGatewayComponent(SamComponentBase):
855
1698
  )
856
1699
  )
857
1700
 
1701
+ if self.resolve_artifact_uris_in_gateway:
1702
+ log.debug(
1703
+ "%s Resolving artifact URIs before sending to external...",
1704
+ log_id_prefix,
1705
+ )
1706
+ await self._resolve_uris_in_payload(
1707
+ parsed_event, external_request_context
1708
+ )
1709
+
858
1710
  send_this_event_to_external = True
859
1711
  is_final_chunk_of_status_update = False
860
1712
 
@@ -925,6 +1777,7 @@ class BaseGatewayComponent(SamComponentBase):
925
1777
  context=embed_eval_context,
926
1778
  resolver_func=evaluate_embed,
927
1779
  types_to_resolve=all_embed_types,
1780
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
928
1781
  log_identifier=log_id_prefix,
929
1782
  config=embed_eval_config,
930
1783
  )
@@ -986,12 +1839,13 @@ class BaseGatewayComponent(SamComponentBase):
986
1839
  }
987
1840
  resolved_remaining_text, _, signals = (
988
1841
  await resolve_embeds_in_string(
989
- remaining_buffer,
990
- embed_eval_context,
991
- evaluate_embed,
992
- LATE_EMBED_TYPES.copy(),
993
- log_id_prefix,
994
- embed_eval_config,
1842
+ text=remaining_buffer,
1843
+ context=embed_eval_context,
1844
+ resolver_func=evaluate_embed,
1845
+ types_to_resolve=LATE_EMBED_TYPES.copy(),
1846
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1847
+ log_identifier=log_id_prefix,
1848
+ config=embed_eval_config,
995
1849
  )
996
1850
  )
997
1851
  await self._handle_resolved_signals(
@@ -1017,6 +1871,32 @@ class BaseGatewayComponent(SamComponentBase):
1017
1871
 
1018
1872
  if send_this_event_to_external:
1019
1873
  if isinstance(parsed_event, Task):
1874
+ # Filter DataParts from final Task if gateway has filtering enabled
1875
+ # This prevents tool results and other internal data from appearing in user-facing output
1876
+ if (
1877
+ self.filter_tool_data_parts
1878
+ and parsed_event.status
1879
+ and parsed_event.status.message
1880
+ and parsed_event.status.message.parts
1881
+ ):
1882
+ original_parts = a2a.get_parts_from_message(
1883
+ parsed_event.status.message
1884
+ )
1885
+ filtered_parts = [
1886
+ part
1887
+ for part in original_parts
1888
+ if self._should_include_data_part_in_final_output(part)
1889
+ ]
1890
+ if len(filtered_parts) != len(original_parts):
1891
+ log.debug(
1892
+ "%s Filtered %d DataParts from final Task message",
1893
+ log_id_prefix,
1894
+ len(original_parts) - len(filtered_parts),
1895
+ )
1896
+ parsed_event.status.message = a2a.update_message_parts(
1897
+ parsed_event.status.message, filtered_parts
1898
+ )
1899
+
1020
1900
  await self._send_final_response_to_external(
1021
1901
  external_request_context, parsed_event
1022
1902
  )
@@ -1166,10 +2046,6 @@ class BaseGatewayComponent(SamComponentBase):
1166
2046
  )
1167
2047
  self._start_listener()
1168
2048
 
1169
- log.info(
1170
- "%s Starting _message_processor_loop as an asyncio task.",
1171
- self.log_identifier,
1172
- )
1173
2049
  await self._message_processor_loop()
1174
2050
 
1175
2051
  def _pre_async_cleanup(self) -> None:
@@ -1197,7 +2073,7 @@ class BaseGatewayComponent(SamComponentBase):
1197
2073
  self.internal_event_queue.put(None)
1198
2074
 
1199
2075
  async def _message_processor_loop(self):
1200
- log.info("%s Starting message processor loop...", self.log_identifier)
2076
+ log.debug("%s Starting message processor loop as an asyncio task...", self.log_identifier)
1201
2077
  loop = self.get_async_loop()
1202
2078
 
1203
2079
  while not self.stop_signal.is_set():