solace-agent-mesh 1.6.3__py3-none-any.whl → 1.7.1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (244) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +12 -18
  2. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
  3. solace_agent_mesh/agent/adk/callbacks.py +138 -20
  4. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +2 -0
  5. solace_agent_mesh/agent/adk/models/lite_llm.py +38 -5
  6. solace_agent_mesh/agent/adk/models/models_llm.txt +82 -35
  7. solace_agent_mesh/agent/adk/runner.py +9 -0
  8. solace_agent_mesh/agent/adk/stream_parser.py +6 -1
  9. solace_agent_mesh/agent/adk/tool_wrapper.py +3 -0
  10. solace_agent_mesh/agent/agent_llm.txt +61 -70
  11. solace_agent_mesh/agent/protocol/event_handlers.py +29 -1
  12. solace_agent_mesh/agent/protocol/protocol_llm.txt +1 -1
  13. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
  14. solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
  15. solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
  16. solace_agent_mesh/agent/sac/app.py +22 -0
  17. solace_agent_mesh/agent/sac/component.py +76 -40
  18. solace_agent_mesh/agent/sac/sac_llm.txt +1 -1
  19. solace_agent_mesh/agent/sac/task_execution_context.py +21 -0
  20. solace_agent_mesh/agent/testing/testing_llm.txt +2 -1
  21. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +13 -148
  22. solace_agent_mesh/agent/tools/dynamic_tool.py +2 -0
  23. solace_agent_mesh/agent/tools/tools_llm.txt +93 -80
  24. solace_agent_mesh/agent/tools/tools_llm_detail.txt +3 -2
  25. solace_agent_mesh/agent/utils/artifact_helpers.py +4 -0
  26. solace_agent_mesh/agent/utils/utils_llm.txt +16 -2
  27. solace_agent_mesh/assets/docs/404.html +3 -3
  28. solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +1 -0
  29. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
  30. solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +1 -0
  31. solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
  37. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
  38. solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
  39. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
  40. solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
  41. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
  42. solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
  43. solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +1 -0
  44. solace_agent_mesh/assets/docs/assets/js/{ab9708a8.3e6dd091.js → ab9708a8.245ae0ef.js} +1 -1
  45. solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
  46. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +1 -0
  54. solace_agent_mesh/assets/docs/assets/js/{e6f9706b.e74a984d.js → e6f9706b.045d0fa1.js} +1 -1
  55. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
  56. solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +1 -0
  57. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/main.f213fe0c.js +2 -0
  59. solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +1 -0
  60. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  64. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +18 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  66. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +5 -5
  68. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  71. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +196 -0
  72. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +4 -4
  73. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +5 -5
  74. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +6 -7
  75. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +4 -4
  76. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
  77. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +4 -4
  78. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +4 -4
  79. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  80. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +160 -169
  81. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  82. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  83. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +4 -4
  84. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +4 -4
  85. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  86. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  87. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +5 -5
  88. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +4 -4
  89. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  90. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  91. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  92. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  93. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  94. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  95. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +59 -0
  96. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +62 -0
  97. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +10 -6
  98. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +4 -4
  99. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +4 -4
  100. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
  101. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +27 -4
  102. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
  103. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +4 -4
  104. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +5 -4
  105. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  106. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  107. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
  108. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +9 -9
  109. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +4 -4
  110. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +4 -4
  111. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +4 -4
  112. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +4 -4
  113. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
  114. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
  115. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  116. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  117. solace_agent_mesh/assets/docs/lunr-index-1762283454666.json +1 -0
  118. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  119. solace_agent_mesh/assets/docs/search-doc-1762283454666.json +1 -0
  120. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  121. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  122. solace_agent_mesh/cli/__init__.py +1 -1
  123. solace_agent_mesh/cli/commands/docs_cmd.py +4 -1
  124. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +1 -1
  125. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-D4_RMYRh.js → authCallback-tcIFZLis.js} +1 -1
  126. solace_agent_mesh/client/webui/frontend/static/assets/{client-UZ3qU6Bq.js → client-CRYdKo2Q.js} +3 -3
  127. solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +1 -0
  128. solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +353 -0
  129. solace_agent_mesh/client/webui/frontend/static/assets/vendor-CINwxvwV.js +470 -0
  130. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  131. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  132. solace_agent_mesh/common/a2a/a2a_llm.txt +13 -20
  133. solace_agent_mesh/common/a2a/protocol.py +5 -0
  134. solace_agent_mesh/common/a2a/types.py +1 -0
  135. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +49 -11
  136. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +23 -6
  137. solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
  138. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +26 -9
  139. solace_agent_mesh/common/common_llm.txt +13 -34
  140. solace_agent_mesh/common/data_parts.py +20 -4
  141. solace_agent_mesh/common/middleware/middleware_llm.txt +1 -1
  142. solace_agent_mesh/common/sac/sac_llm.txt +1 -1
  143. solace_agent_mesh/common/sam_events/sam_events_llm.txt +1 -1
  144. solace_agent_mesh/common/services/employee_service.py +1 -1
  145. solace_agent_mesh/common/services/providers/providers_llm.txt +3 -2
  146. solace_agent_mesh/common/services/services_llm.txt +9 -4
  147. solace_agent_mesh/common/utils/embeds/constants.py +1 -0
  148. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
  149. solace_agent_mesh/common/utils/embeds/modifiers.py +2 -1
  150. solace_agent_mesh/common/utils/embeds/resolver.py +58 -6
  151. solace_agent_mesh/common/utils/embeds/types.py +8 -0
  152. solace_agent_mesh/common/utils/utils_llm.txt +5 -6
  153. solace_agent_mesh/core_a2a/core_a2a_llm.txt +1 -1
  154. solace_agent_mesh/gateway/adapter/__init__.py +1 -0
  155. solace_agent_mesh/gateway/adapter/base.py +143 -0
  156. solace_agent_mesh/gateway/adapter/types.py +221 -0
  157. solace_agent_mesh/gateway/base/app.py +29 -2
  158. solace_agent_mesh/gateway/base/base_llm.txt +10 -8
  159. solace_agent_mesh/gateway/base/component.py +573 -142
  160. solace_agent_mesh/gateway/gateway_llm.txt +55 -59
  161. solace_agent_mesh/gateway/generic/__init__.py +1 -0
  162. solace_agent_mesh/gateway/generic/app.py +50 -0
  163. solace_agent_mesh/gateway/generic/component.py +650 -0
  164. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +99 -49
  165. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +92 -0
  166. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
  167. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +150 -0
  168. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
  169. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
  170. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +26 -20
  171. solace_agent_mesh/gateway/http_sse/app.py +0 -14
  172. solace_agent_mesh/gateway/http_sse/component.py +17 -56
  173. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +1 -1
  174. solace_agent_mesh/gateway/http_sse/dependencies.py +21 -3
  175. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +8 -8
  176. solace_agent_mesh/gateway/http_sse/main.py +23 -5
  177. solace_agent_mesh/gateway/http_sse/repository/__init__.py +19 -1
  178. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +56 -98
  179. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
  180. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
  181. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +23 -1
  182. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +47 -0
  183. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +112 -4
  184. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -1
  185. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +51 -60
  186. solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
  187. solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
  188. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +7 -1
  189. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
  190. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
  191. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +125 -157
  192. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +269 -8
  193. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +143 -51
  194. solace_agent_mesh/gateway/http_sse/routers/config.py +69 -0
  195. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +198 -94
  196. solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
  197. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +68 -18
  198. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +13 -0
  199. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +30 -0
  200. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +51 -35
  201. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +2 -0
  202. solace_agent_mesh/gateway/http_sse/routers/feedback.py +133 -2
  203. solace_agent_mesh/gateway/http_sse/routers/projects.py +542 -0
  204. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +9 -11
  205. solace_agent_mesh/gateway/http_sse/routers/sessions.py +154 -3
  206. solace_agent_mesh/gateway/http_sse/routers/tasks.py +296 -4
  207. solace_agent_mesh/gateway/http_sse/services/project_service.py +403 -0
  208. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +16 -10
  209. solace_agent_mesh/gateway/http_sse/services/session_service.py +178 -6
  210. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +2 -3
  211. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +48 -14
  212. solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
  213. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.1.dist-info}/METADATA +3 -5
  214. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.1.dist-info}/RECORD +218 -175
  215. solace_agent_mesh/assets/docs/assets/js/15ba94aa.932dd2db.js +0 -1
  216. solace_agent_mesh/assets/docs/assets/js/3ac1795d.76654dd9.js +0 -1
  217. solace_agent_mesh/assets/docs/assets/js/3ff0015d.2be20244.js +0 -1
  218. solace_agent_mesh/assets/docs/assets/js/547e15cc.2cbb060a.js +0 -1
  219. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +0 -1
  220. solace_agent_mesh/assets/docs/assets/js/631738c7.7c4594c9.js +0 -1
  221. solace_agent_mesh/assets/docs/assets/js/6a520c9d.ba015d81.js +0 -1
  222. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +0 -1
  223. solace_agent_mesh/assets/docs/assets/js/71da7b71.ddbdfbe2.js +0 -1
  224. solace_agent_mesh/assets/docs/assets/js/8024126c.56e59919.js +0 -1
  225. solace_agent_mesh/assets/docs/assets/js/94e8668d.3b883666.js +0 -1
  226. solace_agent_mesh/assets/docs/assets/js/da0b5bad.d08a9466.js +0 -1
  227. solace_agent_mesh/assets/docs/assets/js/dd817ffc.0aa9630a.js +0 -1
  228. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.d590bc9e.js +0 -1
  229. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +0 -1
  230. solace_agent_mesh/assets/docs/assets/js/e3d9abda.6b9493d0.js +0 -1
  231. solace_agent_mesh/assets/docs/assets/js/e92d0134.4f395c6b.js +0 -1
  232. solace_agent_mesh/assets/docs/assets/js/f284c35a.720d2ef2.js +0 -1
  233. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +0 -1
  234. solace_agent_mesh/assets/docs/assets/js/main.ed05b14d.js +0 -2
  235. solace_agent_mesh/assets/docs/assets/js/runtime~main.a8a75e0b.js +0 -1
  236. solace_agent_mesh/assets/docs/lunr-index-1761744323675.json +0 -1
  237. solace_agent_mesh/assets/docs/search-doc-1761744323675.json +0 -1
  238. solace_agent_mesh/client/webui/frontend/static/assets/main--3yJYl7S.css +0 -1
  239. solace_agent_mesh/client/webui/frontend/static/assets/main-DojKHS49.js +0 -342
  240. solace_agent_mesh/client/webui/frontend/static/assets/vendor-DSqhjwq_.js +0 -405
  241. /solace_agent_mesh/assets/docs/assets/js/{main.ed05b14d.js.LICENSE.txt → main.f213fe0c.js.LICENSE.txt} +0 -0
  242. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.1.dist-info}/WHEEL +0 -0
  243. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.1.dist-info}/entry_points.txt +0 -0
  244. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ Base Component class for Gateway implementations in the Solace AI Connector.
5
5
  import logging
6
6
  import asyncio
7
7
  import queue
8
- import base64
8
+ import re
9
9
  import uuid
10
10
  from datetime import datetime, timezone
11
11
  from typing import Any, Dict, Optional, List, Tuple, Union
@@ -31,19 +31,21 @@ from a2a.types import (
31
31
  TaskArtifactUpdateEvent,
32
32
  JSONRPCError,
33
33
  TextPart,
34
+ DataPart,
34
35
  FilePart,
35
- FileWithBytes,
36
36
  Artifact as A2AArtifact,
37
37
  )
38
38
  from ...common import a2a
39
- from ...common.utils import is_text_based_mime_type
40
39
  from ...common.utils.embeds import (
41
40
  resolve_embeds_in_string,
42
- resolve_embeds_recursively_in_string,
43
41
  evaluate_embed,
44
42
  LATE_EMBED_TYPES,
45
43
  EARLY_EMBED_TYPES,
46
- EMBED_DELIMITER_OPEN,
44
+ )
45
+ from ...common.utils.embeds.types import ResolutionMode
46
+ from ...agent.utils.artifact_helpers import (
47
+ load_artifact_content_or_metadata,
48
+ format_artifact_uri,
47
49
  )
48
50
  from solace_ai_connector.common.message import (
49
51
  Message as SolaceMessage,
@@ -97,9 +99,32 @@ class BaseGatewayComponent(SamComponentBase):
97
99
 
98
100
  return super().get_config(key, default)
99
101
 
100
- def __init__(self, resolve_artifact_uris_in_gateway: bool = True, **kwargs: Any):
102
+ def __init__(
103
+ self,
104
+ resolve_artifact_uris_in_gateway: bool = True,
105
+ supports_inline_artifact_resolution: bool = False,
106
+ filter_tool_data_parts: bool = True,
107
+ **kwargs: Any
108
+ ):
109
+ """
110
+ Initialize the BaseGatewayComponent.
111
+
112
+ Args:
113
+ resolve_artifact_uris_in_gateway: If True, resolves artifact URIs before sending to external.
114
+ supports_inline_artifact_resolution: If True, SIGNAL_ARTIFACT_RETURN embeds are converted
115
+ to FileParts during embed resolution. If False (default), signals are passed through
116
+ for the gateway to handle manually. Use False for legacy gateways (e.g., Slack),
117
+ True for modern gateways that support inline artifact rendering (e.g., HTTP SSE).
118
+ filter_tool_data_parts: If True (default), filters out tool-related DataParts (tool_call,
119
+ tool_result, etc.) from final Task messages before sending to gateway. Use True for
120
+ gateways that don't want to display internal tool execution details (e.g., Slack),
121
+ False for gateways that display all parts (e.g., HTTP SSE Web UI).
122
+ **kwargs: Additional arguments passed to parent class.
123
+ """
101
124
  super().__init__(info, **kwargs)
102
125
  self.resolve_artifact_uris_in_gateway = resolve_artifact_uris_in_gateway
126
+ self.supports_inline_artifact_resolution = supports_inline_artifact_resolution
127
+ self.filter_tool_data_parts = filter_tool_data_parts
103
128
  log.info("%s Initializing Base Gateway Component...", self.log_identifier)
104
129
 
105
130
  try:
@@ -263,10 +288,12 @@ class BaseGatewayComponent(SamComponentBase):
263
288
  external_request_context["user_identity"] = user_identity
264
289
  external_request_context["a2a_user_config"] = user_config
265
290
  external_request_context["api_version"] = api_version
291
+ external_request_context["is_streaming"] = is_streaming
266
292
  log.debug(
267
- "%s Stored user_identity, configuration, and api_version (%s) in external_request_context.",
293
+ "%s Stored user_identity, configuration, api_version (%s), and is_streaming (%s) in external_request_context.",
268
294
  log_id_prefix,
269
295
  api_version,
296
+ is_streaming,
270
297
  )
271
298
 
272
299
  now = datetime.now(timezone.utc)
@@ -458,7 +485,7 @@ class BaseGatewayComponent(SamComponentBase):
458
485
  async def _handle_resolved_signals(
459
486
  self,
460
487
  external_request_context: Dict,
461
- signals: List[Tuple[int, Any]],
488
+ signals: List[Tuple[None, str, Any]],
462
489
  original_rpc_id: Optional[str],
463
490
  is_finalizing_context: bool = False,
464
491
  ):
@@ -466,7 +493,7 @@ class BaseGatewayComponent(SamComponentBase):
466
493
  if not signals:
467
494
  return
468
495
 
469
- for _, signal_tuple in signals:
496
+ for signal_tuple in signals:
470
497
  if (
471
498
  isinstance(signal_tuple, tuple)
472
499
  and len(signal_tuple) == 3
@@ -526,6 +553,245 @@ class BaseGatewayComponent(SamComponentBase):
526
553
  log.exception(
527
554
  "%s Error sending status signal: %s", log_id_prefix, e
528
555
  )
556
+ elif signal_type == "SIGNAL_ARTIFACT_RETURN":
557
+ # Handle artifact return signal for legacy gateways
558
+ # During finalizing context (final Task), suppress this to avoid duplicates
559
+ # since the same signal might appear in both streaming and final responses
560
+ if is_finalizing_context:
561
+ log.debug(
562
+ "%s Suppressing SIGNAL_ARTIFACT_RETURN during finalizing context to avoid duplicate: %s",
563
+ log_id_prefix,
564
+ signal_data,
565
+ )
566
+ continue
567
+
568
+ log.info(
569
+ "%s Handling SIGNAL_ARTIFACT_RETURN for legacy gateway: %s",
570
+ log_id_prefix,
571
+ signal_data,
572
+ )
573
+ try:
574
+ filename = signal_data.get("filename")
575
+ version = signal_data.get("version")
576
+
577
+ if not filename:
578
+ log.error(
579
+ "%s SIGNAL_ARTIFACT_RETURN missing filename. Skipping.",
580
+ log_id_prefix,
581
+ )
582
+ continue
583
+
584
+ # Load artifact content (not just metadata) for legacy gateways
585
+ # Legacy gateways like Slack need the actual bytes to upload files
586
+ artifact_data = await load_artifact_content_or_metadata(
587
+ self.shared_artifact_service,
588
+ app_name=external_request_context.get(
589
+ "app_name_for_artifacts", self.gateway_id
590
+ ),
591
+ user_id=external_request_context.get("user_id_for_artifacts"),
592
+ session_id=external_request_context.get("a2a_session_id"),
593
+ filename=filename,
594
+ version=version,
595
+ load_metadata_only=False, # Load full content for legacy gateways
596
+ )
597
+
598
+ if artifact_data.get("status") != "success":
599
+ log.error(
600
+ "%s Failed to load artifact content for %s v%s",
601
+ log_id_prefix,
602
+ filename,
603
+ version,
604
+ )
605
+ continue
606
+
607
+ # Get content and ensure it's bytes
608
+ content = artifact_data.get("content")
609
+ if not content:
610
+ log.error(
611
+ "%s No content found in artifact %s v%s",
612
+ log_id_prefix,
613
+ filename,
614
+ version,
615
+ )
616
+ continue
617
+
618
+ # Convert to bytes if it's a string (text-based artifacts)
619
+ if isinstance(content, str):
620
+ content_bytes = content.encode("utf-8")
621
+ elif isinstance(content, bytes):
622
+ content_bytes = content
623
+ else:
624
+ log.error(
625
+ "%s Artifact content is neither string nor bytes: %s",
626
+ log_id_prefix,
627
+ type(content),
628
+ )
629
+ continue
630
+
631
+ # Create FilePart with bytes for legacy gateway to upload
632
+ file_part = a2a.create_file_part_from_bytes(
633
+ content_bytes=content_bytes,
634
+ name=filename,
635
+ mime_type=artifact_data.get("metadata", {}).get("mime_type"),
636
+ )
637
+
638
+ # Create artifact with the file part
639
+ # Import Part type for wrapping
640
+ from a2a.types import Artifact, Part
641
+ artifact = Artifact(
642
+ artifact_id=str(uuid.uuid4().hex),
643
+ parts=[Part(root=file_part)],
644
+ name=filename,
645
+ description=f"Artifact: {filename}",
646
+ )
647
+
648
+ # Send as TaskArtifactUpdateEvent
649
+ a2a_task_id_for_signal = external_request_context.get(
650
+ "a2a_task_id_for_event", original_rpc_id
651
+ )
652
+
653
+ if not a2a_task_id_for_signal:
654
+ log.error(
655
+ "%s Cannot determine A2A task ID for artifact signal. Skipping.",
656
+ log_id_prefix,
657
+ )
658
+ continue
659
+
660
+ artifact_event = a2a.create_artifact_update(
661
+ task_id=a2a_task_id_for_signal,
662
+ context_id=external_request_context.get("a2a_session_id"),
663
+ artifact=artifact,
664
+ )
665
+
666
+ await self._send_update_to_external(
667
+ external_request_context=external_request_context,
668
+ event_data=artifact_event,
669
+ is_final_chunk_of_update=False,
670
+ )
671
+ log.info(
672
+ "%s Sent artifact signal as TaskArtifactUpdateEvent for %s",
673
+ log_id_prefix,
674
+ filename,
675
+ )
676
+ except Exception as e:
677
+ log.exception(
678
+ "%s Error sending artifact signal: %s", log_id_prefix, e
679
+ )
680
+ elif signal_type == "SIGNAL_ARTIFACT_CREATION_COMPLETE":
681
+ # Handle artifact creation completion for legacy gateways
682
+ # This is similar to SIGNAL_ARTIFACT_RETURN but for newly created artifacts
683
+ log.info(
684
+ "%s Handling SIGNAL_ARTIFACT_CREATION_COMPLETE for legacy gateway: %s",
685
+ log_id_prefix,
686
+ signal_data,
687
+ )
688
+ try:
689
+ filename = signal_data.get("filename")
690
+ version = signal_data.get("version")
691
+
692
+ if not filename:
693
+ log.error(
694
+ "%s SIGNAL_ARTIFACT_CREATION_COMPLETE missing filename. Skipping.",
695
+ log_id_prefix,
696
+ )
697
+ continue
698
+
699
+ # Load artifact content (not just metadata) for legacy gateways
700
+ # Legacy gateways like Slack need the actual bytes to upload files
701
+ artifact_data = await load_artifact_content_or_metadata(
702
+ self.shared_artifact_service,
703
+ app_name=external_request_context.get(
704
+ "app_name_for_artifacts", self.gateway_id
705
+ ),
706
+ user_id=external_request_context.get("user_id_for_artifacts"),
707
+ session_id=external_request_context.get("a2a_session_id"),
708
+ filename=filename,
709
+ version=version,
710
+ load_metadata_only=False, # Load full content for legacy gateways
711
+ )
712
+
713
+ if artifact_data.get("status") != "success":
714
+ log.error(
715
+ "%s Failed to load artifact content for %s v%s",
716
+ log_id_prefix,
717
+ filename,
718
+ version,
719
+ )
720
+ continue
721
+
722
+ # Get content and ensure it's bytes
723
+ content = artifact_data.get("content")
724
+ if not content:
725
+ log.error(
726
+ "%s No content found in artifact %s v%s",
727
+ log_id_prefix,
728
+ filename,
729
+ version,
730
+ )
731
+ continue
732
+
733
+ # Convert to bytes if it's a string (text-based artifacts)
734
+ if isinstance(content, str):
735
+ content_bytes = content.encode("utf-8")
736
+ elif isinstance(content, bytes):
737
+ content_bytes = content
738
+ else:
739
+ log.error(
740
+ "%s Artifact content is neither string nor bytes: %s",
741
+ log_id_prefix,
742
+ type(content),
743
+ )
744
+ continue
745
+
746
+ # Create FilePart with bytes for legacy gateway to upload
747
+ file_part = a2a.create_file_part_from_bytes(
748
+ content_bytes=content_bytes,
749
+ name=filename,
750
+ mime_type=signal_data.get("mime_type") or artifact_data.get("metadata", {}).get("mime_type"),
751
+ )
752
+
753
+ # Create artifact with the file part
754
+ # Import Part type for wrapping
755
+ from a2a.types import Artifact, Part
756
+ artifact = Artifact(
757
+ artifact_id=str(uuid.uuid4().hex),
758
+ parts=[Part(root=file_part)],
759
+ name=filename,
760
+ description=f"Artifact: {filename}",
761
+ )
762
+
763
+ # Send as TaskArtifactUpdateEvent
764
+ a2a_task_id_for_signal = external_request_context.get(
765
+ "a2a_task_id_for_event", original_rpc_id
766
+ )
767
+
768
+ if not a2a_task_id_for_signal:
769
+ log.error(
770
+ "%s Cannot determine A2A task ID for artifact creation signal. Skipping.",
771
+ log_id_prefix,
772
+ )
773
+ continue
774
+
775
+ artifact_event = a2a.create_artifact_update(
776
+ task_id=a2a_task_id_for_signal,
777
+ context_id=external_request_context.get("a2a_session_id"),
778
+ artifact=artifact,
779
+ )
780
+
781
+ await self._send_update_to_external(
782
+ external_request_context=external_request_context,
783
+ event_data=artifact_event,
784
+ is_final_chunk_of_update=False,
785
+ )
786
+ log.info(
787
+ "%s Sent artifact creation completion as TaskArtifactUpdateEvent for %s",
788
+ log_id_prefix,
789
+ filename,
790
+ )
791
+ except Exception as e:
792
+ log.exception(
793
+ "%s Error sending artifact creation completion signal: %s", log_id_prefix, e
794
+ )
529
795
  else:
530
796
  log.warning(
531
797
  "%s Received unhandled signal type during embed resolution: %s",
@@ -628,6 +894,64 @@ class BaseGatewayComponent(SamComponentBase):
628
894
  processed_parts.append(part)
629
895
  return processed_parts
630
896
 
897
+ def _should_include_data_part_in_final_output(self, part: Any) -> bool:
898
+ """
899
+ Determines if a DataPart should be included in the final output sent to the gateway.
900
+
901
+ This filters out internal/tool-related DataParts that shouldn't be shown to end users.
902
+ Gateways can override this method for custom filtering logic.
903
+
904
+ Args:
905
+ part: The part to check (expected to be a DataPart)
906
+
907
+ Returns:
908
+ True if the part should be included, False if it should be filtered out
909
+ """
910
+ from a2a.types import DataPart
911
+
912
+ if not isinstance(part, DataPart):
913
+ return True
914
+
915
+ # Check if this is a tool result by looking at metadata
916
+ # Tool results have metadata.tool_name set
917
+ if part.metadata and part.metadata.get("tool_name"):
918
+ # This is a tool result - filter it out
919
+ return False
920
+
921
+ # Get the type of the data part
922
+ data_type = part.data.get("type") if part.data else None
923
+
924
+ # Filter out tool-related data parts that are internal
925
+ tool_related_types = {
926
+ "tool_call",
927
+ "tool_result",
928
+ "tool_error",
929
+ "tool_execution",
930
+ }
931
+
932
+ if data_type in tool_related_types:
933
+ return False
934
+
935
+ # Handle artifact_creation_progress based on gateway capabilities
936
+ if data_type == "artifact_creation_progress":
937
+ # For modern gateways (HTTP SSE), keep these to display progress bubbles
938
+ # For legacy gateways (Slack), filter them out as they'll be converted to FileParts
939
+ if self.supports_inline_artifact_resolution:
940
+ return True # Keep for HTTP SSE
941
+ else:
942
+ return False # Filter for Slack (will be converted to FileParts instead)
943
+
944
+ # Keep user-facing data parts like general progress updates
945
+ user_facing_types = {
946
+ "agent_progress_update",
947
+ }
948
+
949
+ if data_type in user_facing_types:
950
+ return True
951
+
952
+ # Default: include unknown types (to avoid hiding potentially useful info)
953
+ return True
954
+
631
955
  async def _resolve_embeds_and_handle_signals(
632
956
  self,
633
957
  event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
@@ -636,17 +960,11 @@ class BaseGatewayComponent(SamComponentBase):
636
960
  original_rpc_id: Optional[str],
637
961
  is_finalizing_context: bool = False,
638
962
  ) -> bool:
639
- """
640
- Resolves embeds and handles signals for an event containing parts.
641
- Modifies event_with_parts in place if text content changes.
642
- Manages stream buffer for TaskStatusUpdateEvent.
643
- Returns True if the event content was modified or signals were handled, False otherwise.
644
- """
645
963
  if not self.enable_embed_resolution:
646
964
  return False
647
965
 
648
966
  log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
649
- content_modified_or_signal_handled = False
967
+ content_modified = False
650
968
 
651
969
  embed_eval_context = {
652
970
  "artifact_service": self.shared_artifact_service,
@@ -664,8 +982,6 @@ class BaseGatewayComponent(SamComponentBase):
664
982
  }
665
983
 
666
984
  parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
667
- is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
668
-
669
985
  if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
670
986
  if event_with_parts.status and event_with_parts.status.message:
671
987
  parts_owner = event_with_parts.status.message
@@ -673,146 +989,233 @@ class BaseGatewayComponent(SamComponentBase):
673
989
  if event_with_parts.artifact:
674
990
  parts_owner = event_with_parts.artifact
675
991
 
676
- if parts_owner and parts_owner.parts:
677
- new_parts: List[ContentPart] = []
678
- stream_buffer_key = f"{a2a_task_id}_stream_buffer"
679
- current_buffer = ""
680
-
681
- if is_streaming_status_update:
682
- current_buffer = (
683
- self.task_context_manager.get_context(stream_buffer_key) or ""
684
- )
992
+ if not (parts_owner and parts_owner.parts):
993
+ return False
685
994
 
686
- parts: List[ContentPart] = []
687
- if isinstance(parts_owner, A2AMessage):
688
- parts = a2a.get_parts_from_message(parts_owner)
689
- elif isinstance(parts_owner, A2AArtifact):
690
- parts = a2a.get_parts_from_artifact(parts_owner)
995
+ is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
996
+ stream_buffer_key = f"{a2a_task_id}_stream_buffer"
997
+ current_buffer = ""
998
+ if is_streaming_status_update:
999
+ current_buffer = (
1000
+ self.task_context_manager.get_context(stream_buffer_key) or ""
1001
+ )
691
1002
 
692
- for part in parts:
693
- if isinstance(part, TextPart) and part.text is not None:
694
- text_to_resolve = part.text
695
- original_part_text = part.text
1003
+ original_parts: List[ContentPart] = (
1004
+ a2a.get_parts_from_message(parts_owner)
1005
+ if isinstance(parts_owner, A2AMessage)
1006
+ else a2a.get_parts_from_artifact(parts_owner)
1007
+ )
696
1008
 
697
- if is_streaming_status_update:
698
- current_buffer += part.text
699
- text_to_resolve = current_buffer
1009
+ new_parts: List[ContentPart] = []
1010
+ other_signals = []
1011
+
1012
+ for part in original_parts:
1013
+ if isinstance(part, TextPart) and part.text:
1014
+ text_to_resolve = current_buffer + part.text
1015
+ current_buffer = "" # Buffer is now being processed
1016
+
1017
+ (
1018
+ resolved_text,
1019
+ processed_idx,
1020
+ signals_with_placeholders,
1021
+ ) = await resolve_embeds_in_string(
1022
+ text=text_to_resolve,
1023
+ context=embed_eval_context,
1024
+ resolver_func=evaluate_embed,
1025
+ types_to_resolve=LATE_EMBED_TYPES.union({"status_update"}),
1026
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1027
+ log_identifier=log_id_prefix,
1028
+ config=embed_eval_config,
1029
+ )
700
1030
 
701
- resolved_text, processed_idx, signals = (
702
- await resolve_embeds_in_string(
703
- text=text_to_resolve,
704
- context=embed_eval_context,
705
- resolver_func=evaluate_embed,
706
- types_to_resolve=LATE_EMBED_TYPES.copy(),
707
- log_identifier=log_id_prefix,
708
- config=embed_eval_config,
709
- )
1031
+ if not signals_with_placeholders:
1032
+ new_parts.append(a2a.create_text_part(text=resolved_text))
1033
+ else:
1034
+ placeholder_map = {p: s for _, s, p in signals_with_placeholders}
1035
+ split_pattern = (
1036
+ f"({'|'.join(re.escape(p) for p in placeholder_map.keys())})"
710
1037
  )
1038
+ text_fragments = re.split(split_pattern, resolved_text)
711
1039
 
712
- if signals:
713
- await self._handle_resolved_signals(
714
- external_request_context,
715
- signals,
716
- original_rpc_id,
717
- is_finalizing_context,
718
- )
719
- content_modified_or_signal_handled = True
720
-
721
- if resolved_text is not None:
722
- new_parts.append(a2a.create_text_part(text=resolved_text))
723
- if is_streaming_status_update:
724
- if resolved_text != text_to_resolve[:processed_idx]:
725
- content_modified_or_signal_handled = True
726
- elif resolved_text != original_part_text:
727
- content_modified_or_signal_handled = True
728
-
729
- if is_streaming_status_update:
730
- current_buffer = text_to_resolve[processed_idx:]
731
- elif (
732
- processed_idx < len(text_to_resolve)
733
- and not content_modified_or_signal_handled
734
- ):
735
- log.warning(
736
- "%s Unclosed embed in non-streaming TextPart. Remainder: '%s'",
737
- log_id_prefix,
738
- text_to_resolve[processed_idx:],
739
- )
740
- content_modified_or_signal_handled = True
741
-
742
- elif isinstance(part, FilePart) and part.file:
743
- if isinstance(part.file, FileWithBytes) and part.file.bytes:
744
- mime_type = part.file.mime_type or ""
745
- is_container = is_text_based_mime_type(mime_type)
746
- try:
747
- decoded_content_for_check = base64.b64decode(
748
- part.file.bytes
749
- ).decode("utf-8", errors="ignore")
750
- if (
751
- is_container
752
- and EMBED_DELIMITER_OPEN in decoded_content_for_check
753
- ):
754
- original_content = decoded_content_for_check
755
- resolved_content = (
756
- await resolve_embeds_recursively_in_string(
757
- text=original_content,
758
- context=embed_eval_context,
759
- resolver_func=evaluate_embed,
760
- types_to_resolve=LATE_EMBED_TYPES,
761
- log_identifier=log_id_prefix,
762
- config=embed_eval_config,
763
- max_depth=self.gateway_recursive_embed_depth,
764
- )
765
- )
766
- if resolved_content != original_content:
767
- new_file_content = part.file.model_copy()
768
- new_file_content.bytes = base64.b64encode(
769
- resolved_content.encode("utf-8")
770
- ).decode("utf-8")
771
- new_parts.append(
772
- FilePart(
773
- file=new_file_content,
774
- metadata=part.metadata,
1040
+ for i, fragment in enumerate(text_fragments):
1041
+ if not fragment:
1042
+ continue
1043
+ if fragment in placeholder_map:
1044
+ signal_tuple = placeholder_map[fragment]
1045
+ signal_type, signal_data = signal_tuple[1], signal_tuple[2]
1046
+ if signal_type == "SIGNAL_ARTIFACT_RETURN":
1047
+ # Only convert to FilePart if gateway supports inline artifact resolution
1048
+ if self.supports_inline_artifact_resolution:
1049
+ try:
1050
+ filename, version = (
1051
+ signal_data["filename"],
1052
+ signal_data["version"],
1053
+ )
1054
+ artifact_data = (
1055
+ await load_artifact_content_or_metadata(
1056
+ self.shared_artifact_service,
1057
+ **embed_eval_context["session_context"],
1058
+ filename=filename,
1059
+ version=version,
1060
+ load_metadata_only=True,
1061
+ )
1062
+ )
1063
+ if artifact_data.get("status") == "success":
1064
+ uri = format_artifact_uri(
1065
+ **embed_eval_context["session_context"],
1066
+ filename=filename,
1067
+ version=artifact_data.get("version"),
1068
+ )
1069
+ new_parts.append(
1070
+ a2a.create_file_part_from_uri(
1071
+ uri,
1072
+ filename,
1073
+ artifact_data.get("metadata", {}).get(
1074
+ "mime_type"
1075
+ ),
1076
+ )
1077
+ )
1078
+ else:
1079
+ new_parts.append(
1080
+ a2a.create_text_part(
1081
+ f"[Error: Artifact '{filename}' v{version} not found.]"
1082
+ )
1083
+ )
1084
+ except Exception as e:
1085
+ log.exception(
1086
+ "%s Error handling SIGNAL_ARTIFACT_RETURN: %s",
1087
+ log_id_prefix,
1088
+ e,
1089
+ )
1090
+ new_parts.append(
1091
+ a2a.create_text_part(
1092
+ f"[Error: Could not retrieve artifact '{signal_data.get('filename')}'.]"
1093
+ )
775
1094
  )
776
- )
777
- content_modified_or_signal_handled = True
778
1095
  else:
779
- new_parts.append(part)
1096
+ # Legacy gateway mode: pass signal through for gateway to handle
1097
+ other_signals.append(signal_tuple)
1098
+ elif signal_type == "SIGNAL_INLINE_BINARY_CONTENT":
1099
+ signal_data["content_bytes"] = signal_data.get("bytes")
1100
+ del signal_data["bytes"]
1101
+ new_parts.append(
1102
+ a2a.create_file_part_from_bytes(**signal_data)
1103
+ )
780
1104
  else:
781
- new_parts.append(part)
782
- except Exception as e:
783
- log.warning(
784
- "%s Error during recursive FilePart resolution for %s: %s. Using original.",
1105
+ other_signals.append(signal_tuple)
1106
+ else:
1107
+ # Check if the non-placeholder fragment is just whitespace
1108
+ # and is between two placeholders. If so, drop it.
1109
+ is_just_whitespace = not fragment.strip()
1110
+ prev_fragment_was_placeholder = (
1111
+ i > 0 and text_fragments[i - 1] in placeholder_map
1112
+ )
1113
+ next_fragment_is_placeholder = (
1114
+ i < len(text_fragments) - 1
1115
+ and text_fragments[i + 1] in placeholder_map
1116
+ )
1117
+
1118
+ if (
1119
+ is_just_whitespace
1120
+ and prev_fragment_was_placeholder
1121
+ and next_fragment_is_placeholder
1122
+ ):
1123
+ log.debug(
1124
+ "%s Dropping whitespace fragment between two file signals.",
1125
+ log_id_prefix,
1126
+ )
1127
+ continue
1128
+
1129
+ new_parts.append(a2a.create_text_part(text=fragment))
1130
+
1131
+ if is_streaming_status_update:
1132
+ current_buffer = text_to_resolve[processed_idx:]
1133
+
1134
+ elif isinstance(part, FilePart) and part.file:
1135
+ # Handle recursive embeds in text-based FileParts
1136
+ new_parts.append(part) # Placeholder for now
1137
+ elif isinstance(part, DataPart):
1138
+ # Handle artifact creation progress DataParts for legacy gateways
1139
+ data_type = part.data.get("type") if part.data else None
1140
+ if (
1141
+ data_type == "artifact_creation_progress"
1142
+ and not self.supports_inline_artifact_resolution
1143
+ ):
1144
+ # Legacy gateway mode: convert completed artifact creation to FilePart
1145
+ status = part.data.get("status")
1146
+ if status == "completed" and not is_finalizing_context:
1147
+ # Extract artifact info from the DataPart
1148
+ filename = part.data.get("filename")
1149
+ version = part.data.get("version")
1150
+ mime_type = part.data.get("mime_type")
1151
+
1152
+ if filename and version is not None:
1153
+ log.info(
1154
+ "%s Converting artifact creation completion to FilePart for legacy gateway: %s v%s",
785
1155
  log_id_prefix,
786
- part.file.name,
787
- e,
1156
+ filename,
1157
+ version,
1158
+ )
1159
+ # This will be sent as an artifact signal, so don't add to new_parts
1160
+ # Instead, add to other_signals for processing
1161
+ signal_tuple = (
1162
+ None,
1163
+ "SIGNAL_ARTIFACT_CREATION_COMPLETE",
1164
+ {
1165
+ "filename": filename,
1166
+ "version": version,
1167
+ "mime_type": mime_type,
1168
+ },
788
1169
  )
1170
+ other_signals.append(signal_tuple)
1171
+ else:
1172
+ # Missing required info, keep the DataPart as-is
789
1173
  new_parts.append(part)
1174
+ elif status == "completed" and is_finalizing_context:
1175
+ # Suppress during finalizing to avoid duplicates
1176
+ log.debug(
1177
+ "%s Suppressing artifact creation completion during finalizing context for %s",
1178
+ log_id_prefix,
1179
+ part.data.get("filename"),
1180
+ )
1181
+ continue
790
1182
  else:
791
- # This is a FileWithUri or empty FileWithBytes, which we don't process for embeds here.
1183
+ # Keep in-progress or failed status DataParts
792
1184
  new_parts.append(part)
793
1185
  else:
1186
+ # Not an artifact creation DataPart, or modern gateway - keep as-is
794
1187
  new_parts.append(part)
1188
+ else:
1189
+ new_parts.append(part)
795
1190
 
1191
+ if other_signals:
1192
+ await self._handle_resolved_signals(
1193
+ external_request_context,
1194
+ other_signals,
1195
+ original_rpc_id,
1196
+ is_finalizing_context,
1197
+ )
1198
+
1199
+ if new_parts != original_parts:
1200
+ content_modified = True
796
1201
  if isinstance(parts_owner, A2AMessage):
797
1202
  if isinstance(event_with_parts, TaskStatusUpdateEvent):
798
1203
  event_with_parts.status.message = a2a.update_message_parts(
799
- message=parts_owner, new_parts=new_parts
1204
+ parts_owner, new_parts
800
1205
  )
801
1206
  elif isinstance(event_with_parts, Task):
802
1207
  event_with_parts.status.message = a2a.update_message_parts(
803
- message=parts_owner, new_parts=new_parts
1208
+ parts_owner, new_parts
804
1209
  )
805
1210
  elif isinstance(parts_owner, A2AArtifact):
806
1211
  event_with_parts.artifact = a2a.update_artifact_parts(
807
- artifact=parts_owner, new_parts=new_parts
1212
+ parts_owner, new_parts
808
1213
  )
809
1214
 
810
- if is_streaming_status_update:
811
- self.task_context_manager.store_context(
812
- stream_buffer_key, current_buffer
813
- )
1215
+ if is_streaming_status_update:
1216
+ self.task_context_manager.store_context(stream_buffer_key, current_buffer)
814
1217
 
815
- return content_modified_or_signal_handled
1218
+ return content_modified or bool(other_signals)
816
1219
 
817
1220
  async def _process_parsed_a2a_event(
818
1221
  self,
@@ -845,13 +1248,6 @@ class BaseGatewayComponent(SamComponentBase):
845
1248
  elif isinstance(parsed_event, Task):
846
1249
  is_finalizing_context_for_embeds = True
847
1250
 
848
- if self.resolve_artifact_uris_in_gateway:
849
- log.debug(
850
- "%s Resolving artifact URIs before sending to external...",
851
- log_id_prefix,
852
- )
853
- await self._resolve_uris_in_payload(parsed_event)
854
-
855
1251
  if not isinstance(parsed_event, JSONRPCError):
856
1252
  content_was_modified_or_signals_handled = (
857
1253
  await self._resolve_embeds_and_handle_signals(
@@ -863,6 +1259,13 @@ class BaseGatewayComponent(SamComponentBase):
863
1259
  )
864
1260
  )
865
1261
 
1262
+ if self.resolve_artifact_uris_in_gateway:
1263
+ log.debug(
1264
+ "%s Resolving artifact URIs before sending to external...",
1265
+ log_id_prefix,
1266
+ )
1267
+ await self._resolve_uris_in_payload(parsed_event)
1268
+
866
1269
  send_this_event_to_external = True
867
1270
  is_final_chunk_of_status_update = False
868
1271
 
@@ -933,6 +1336,7 @@ class BaseGatewayComponent(SamComponentBase):
933
1336
  context=embed_eval_context,
934
1337
  resolver_func=evaluate_embed,
935
1338
  types_to_resolve=all_embed_types,
1339
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
936
1340
  log_identifier=log_id_prefix,
937
1341
  config=embed_eval_config,
938
1342
  )
@@ -994,12 +1398,13 @@ class BaseGatewayComponent(SamComponentBase):
994
1398
  }
995
1399
  resolved_remaining_text, _, signals = (
996
1400
  await resolve_embeds_in_string(
997
- remaining_buffer,
998
- embed_eval_context,
999
- evaluate_embed,
1000
- LATE_EMBED_TYPES.copy(),
1001
- log_id_prefix,
1002
- embed_eval_config,
1401
+ text=remaining_buffer,
1402
+ context=embed_eval_context,
1403
+ resolver_func=evaluate_embed,
1404
+ types_to_resolve=LATE_EMBED_TYPES.copy(),
1405
+ resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
1406
+ log_identifier=log_id_prefix,
1407
+ config=embed_eval_config,
1003
1408
  )
1004
1409
  )
1005
1410
  await self._handle_resolved_signals(
@@ -1025,6 +1430,32 @@ class BaseGatewayComponent(SamComponentBase):
1025
1430
 
1026
1431
  if send_this_event_to_external:
1027
1432
  if isinstance(parsed_event, Task):
1433
+ # Filter DataParts from final Task if gateway has filtering enabled
1434
+ # This prevents tool results and other internal data from appearing in user-facing output
1435
+ if (
1436
+ self.filter_tool_data_parts
1437
+ and parsed_event.status
1438
+ and parsed_event.status.message
1439
+ and parsed_event.status.message.parts
1440
+ ):
1441
+ original_parts = a2a.get_parts_from_message(
1442
+ parsed_event.status.message
1443
+ )
1444
+ filtered_parts = [
1445
+ part
1446
+ for part in original_parts
1447
+ if self._should_include_data_part_in_final_output(part)
1448
+ ]
1449
+ if len(filtered_parts) != len(original_parts):
1450
+ log.debug(
1451
+ "%s Filtered %d DataParts from final Task message",
1452
+ log_id_prefix,
1453
+ len(original_parts) - len(filtered_parts),
1454
+ )
1455
+ parsed_event.status.message = a2a.update_message_parts(
1456
+ parsed_event.status.message, filtered_parts
1457
+ )
1458
+
1028
1459
  await self._send_final_response_to_external(
1029
1460
  external_request_context, parsed_event
1030
1461
  )