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
@@ -10,7 +10,6 @@ import asyncio
10
10
  import uuid
11
11
  from typing import Any, Dict, Optional, TYPE_CHECKING, List
12
12
  from collections import defaultdict
13
- from datetime import datetime, timezone
14
13
 
15
14
  from google.adk.tools import BaseTool, ToolContext
16
15
  from google.adk.artifacts import BaseArtifactService
@@ -34,32 +33,31 @@ from ...agent.utils.context_helpers import (
34
33
  get_session_from_callback_context,
35
34
  )
36
35
  from ..tools.tool_definition import BuiltinTool
36
+ from ..tools.peer_agent_tool import PEER_TOOL_PREFIX
37
37
 
38
38
  from ...common.utils.embeds import (
39
39
  EMBED_DELIMITER_OPEN,
40
40
  EMBED_DELIMITER_CLOSE,
41
- )
42
-
43
- from ...common.utils.embeds import (
44
41
  EMBED_CHAIN_DELIMITER,
42
+ EARLY_EMBED_TYPES,
43
+ evaluate_embed,
44
+ resolve_embeds_in_string,
45
45
  )
46
+ from ...common.utils.embeds.types import ResolutionMode
46
47
 
47
48
  from ...common.utils.embeds.modifiers import MODIFIER_IMPLEMENTATIONS
48
49
 
49
50
  from ...common import a2a
50
- from ...common.a2a.types import ContentPart
51
+ from ...common.a2a.types import ArtifactInfo
51
52
  from ...common.data_parts import (
52
53
  AgentProgressUpdateData,
53
54
  ArtifactCreationProgressData,
54
55
  LlmInvocationData,
55
56
  ToolInvocationStartData,
56
57
  ToolResultData,
58
+ TemplateBlockData,
57
59
  )
58
60
 
59
- from ...agent.utils.artifact_helpers import (
60
- save_artifact_with_metadata,
61
- DEFAULT_SCHEMA_MAX_KEYS,
62
- )
63
61
 
64
62
  METADATA_RESPONSE_KEY = "appended_artifact_metadata"
65
63
  from ..tools.builtin_artifact_tools import _internal_create_artifact
@@ -73,10 +71,14 @@ from ...agent.adk.stream_parser import (
73
71
  BlockProgressedEvent,
74
72
  BlockCompletedEvent,
75
73
  BlockInvalidatedEvent,
74
+ TemplateBlockStartedEvent,
75
+ TemplateBlockCompletedEvent,
76
76
  ARTIFACT_BLOCK_DELIMITER_OPEN,
77
77
  ARTIFACT_BLOCK_DELIMITER_CLOSE,
78
+ TEMPLATE_LIQUID_START_SEQUENCE,
78
79
  )
79
80
 
81
+
80
82
  log = logging.getLogger(__name__)
81
83
 
82
84
  A2A_LLM_STREAM_CHUNKS_PROCESSED_KEY = "temp:llm_stream_chunks_processed"
@@ -90,32 +92,95 @@ async def _publish_data_part_status_update(
90
92
  a2a_context: Dict[str, Any],
91
93
  data_part_model: BaseModel,
92
94
  ):
93
- """Helper to construct and publish a TaskStatusUpdateEvent with a DataPart."""
94
- logical_task_id = a2a_context.get("logical_task_id")
95
- context_id = a2a_context.get("contextId")
95
+ """Helper to construct and publish a TaskStatusUpdateEvent with a DataPart.
96
96
 
97
- status_update_event = a2a.create_data_signal_event(
98
- task_id=logical_task_id,
99
- context_id=context_id,
97
+ This function delegates to the host component's publish_data_signal_from_thread method,
98
+ which handles the async loop check and scheduling internally.
99
+ """
100
+ host_component.publish_data_signal_from_thread(
101
+ a2a_context=a2a_context,
100
102
  signal_data=data_part_model,
101
- agent_name=host_component.agent_name,
103
+ skip_buffer_flush=False,
104
+ log_identifier=host_component.log_identifier,
102
105
  )
103
106
 
104
- loop = host_component.get_async_loop()
105
- if loop and loop.is_running():
106
- asyncio.run_coroutine_threadsafe(
107
- host_component._publish_status_update_with_buffer_flush(
108
- status_update_event,
109
- a2a_context,
110
- skip_buffer_flush=False,
111
- ),
112
- loop,
107
+
108
+ async def _resolve_early_embeds_in_chunk(
109
+ chunk: str,
110
+ callback_context: CallbackContext,
111
+ host_component: "SamAgentComponent",
112
+ log_identifier: str,
113
+ ) -> str:
114
+ """
115
+ Resolves early embeds in an artifact chunk before streaming to the browser.
116
+
117
+ Args:
118
+ chunk: The text chunk containing potential embeds
119
+ callback_context: The ADK callback context with services
120
+ host_component: The host component instance
121
+ log_identifier: Identifier for logging
122
+
123
+ Returns:
124
+ The chunk with early embeds resolved
125
+ """
126
+ if not chunk or EMBED_DELIMITER_OPEN not in chunk:
127
+ return chunk
128
+
129
+ try:
130
+ # Build resolution context from callback_context (pattern from EmbedResolvingMCPToolset)
131
+ invocation_context = callback_context._invocation_context
132
+ if not invocation_context:
133
+ log.warning(
134
+ "%s No invocation context available for embed resolution",
135
+ log_identifier,
136
+ )
137
+ return chunk
138
+
139
+ session_context = invocation_context.session
140
+ if not session_context:
141
+ log.warning(
142
+ "%s No session context available for embed resolution", log_identifier
143
+ )
144
+ return chunk
145
+
146
+ resolution_context = {
147
+ "artifact_service": invocation_context.artifact_service,
148
+ "session_context": {
149
+ "session_id": get_original_session_id(invocation_context),
150
+ "user_id": session_context.user_id,
151
+ "app_name": session_context.app_name,
152
+ },
153
+ }
154
+
155
+ # Resolve only early embeds (math, datetime, uuid, artifact_meta)
156
+ resolved_text, processed_until, _ = await resolve_embeds_in_string(
157
+ text=chunk,
158
+ context=resolution_context,
159
+ resolver_func=evaluate_embed,
160
+ types_to_resolve=EARLY_EMBED_TYPES, # Only resolve early embeds
161
+ resolution_mode=ResolutionMode.ARTIFACT_STREAMING, # New mode
162
+ log_identifier=log_identifier,
163
+ config=None, # Could pass host_component config if needed
113
164
  )
114
- else:
165
+
166
+ # SAFETY CHECK: If resolver buffered something, parser has a bug
167
+ if processed_until < len(chunk):
168
+ log.error(
169
+ "%s PARSER BUG DETECTED: Resolver buffered partial embed. "
170
+ "Chunk ends with: %r. Returning unresolved chunk to avoid corruption.",
171
+ log_identifier,
172
+ chunk[-50:] if len(chunk) > 50 else chunk,
173
+ )
174
+ # Fallback: return original unresolved chunk (degraded but not corrupted)
175
+ return chunk
176
+
177
+ return resolved_text
178
+
179
+ except Exception as e:
115
180
  log.error(
116
- "%s Async loop not available. Cannot publish status update.",
117
- host_component.log_identifier,
181
+ "%s Error resolving embeds in chunk: %s", log_identifier, e, exc_info=True
118
182
  )
183
+ return chunk # Return original chunk on error
119
184
 
120
185
 
121
186
  async def process_artifact_blocks_callback(
@@ -135,9 +200,13 @@ async def process_artifact_blocks_callback(
135
200
  parser: FencedBlockStreamParser = session.state.get(parser_state_key)
136
201
  if parser is None:
137
202
  log.debug("%s New turn. Creating new FencedBlockStreamParser.", log_identifier)
138
- parser = FencedBlockStreamParser(progress_update_interval_bytes=250)
203
+ parser = FencedBlockStreamParser(progress_update_interval_bytes=50)
139
204
  session.state[parser_state_key] = parser
140
205
  session.state["completed_artifact_blocks_list"] = []
206
+ session.state["completed_template_blocks_list"] = []
207
+ session.state["artifact_chars_sent"] = (
208
+ 0 # Reset character tracking for new turn
209
+ )
141
210
 
142
211
  stream_chunks_were_processed = callback_context.state.get(
143
212
  A2A_LLM_STREAM_CHUNKS_PROCESSED_KEY, False
@@ -169,18 +238,49 @@ async def process_artifact_blocks_callback(
169
238
  log_identifier,
170
239
  event.params,
171
240
  )
241
+ # Reset character tracking for this new artifact block
242
+ session.state["artifact_chars_sent"] = 0
243
+
172
244
  filename = event.params.get("filename", "unknown_artifact")
245
+ if filename == "unknown_artifact":
246
+ log.warning(
247
+ "%s Fenced artifact block started without a 'filename' parameter.",
248
+ log_identifier,
249
+ )
250
+ description = event.params.get("description")
251
+ if filename == "unknown_artifact":
252
+ log.warning(
253
+ "%s Fenced artifact block started without a 'filename' parameter.",
254
+ log_identifier,
255
+ )
173
256
  if a2a_context:
257
+ status_text = f"Receiving artifact `{filename}`..."
258
+ if description:
259
+ status_text = (
260
+ f"Receiving artifact `{filename}`: {description}"
261
+ )
174
262
  progress_data = AgentProgressUpdateData(
175
- status_text=f"Receiving artifact `{filename}`..."
263
+ status_text=status_text
176
264
  )
177
265
  await _publish_data_part_status_update(
178
266
  host_component, a2a_context, progress_data
179
267
  )
268
+ # Also send an initial in-progress event to create the UI bubble
269
+ artifact_progress_data = ArtifactCreationProgressData(
270
+ filename=filename,
271
+ description=description,
272
+ status="in-progress",
273
+ bytes_transferred=0,
274
+ artifact_chunk=None,
275
+ )
276
+
277
+ await _publish_data_part_status_update(
278
+ host_component, a2a_context, artifact_progress_data
279
+ )
180
280
  params_str = " ".join(
181
281
  [f'{k}="{v}"' for k, v in event.params.items()]
182
282
  )
183
- original_text = f"«««save_artifact: {params_str}\n"
283
+ original_text = f"{ARTIFACT_BLOCK_DELIMITER_OPEN}save_artifact: {params_str}\n"
184
284
  session.state["artifact_block_original_text"] = original_text
185
285
 
186
286
  elif isinstance(event, BlockProgressedEvent):
@@ -189,20 +289,44 @@ async def process_artifact_blocks_callback(
189
289
  log_identifier,
190
290
  event.buffered_size,
191
291
  )
192
- params = parser._block_params
292
+ params = event.params
193
293
  filename = params.get("filename", "unknown_artifact")
294
+ if filename == "unknown_artifact":
295
+ log.warning(
296
+ "%s Fenced artifact block progressed without a 'filename' parameter.",
297
+ log_identifier,
298
+ )
194
299
  if a2a_context:
300
+ # Resolve early embeds in the chunk before streaming
301
+ resolved_chunk = await _resolve_early_embeds_in_chunk(
302
+ chunk=event.chunk,
303
+ callback_context=callback_context,
304
+ host_component=host_component,
305
+ log_identifier=f"{log_identifier}[ResolveChunk]",
306
+ )
307
+
195
308
  progress_data = ArtifactCreationProgressData(
196
309
  filename=filename,
197
- bytes_saved=event.buffered_size,
198
- artifact_chunk=event.chunk,
310
+ description=params.get("description"),
311
+ status="in-progress",
312
+ bytes_transferred=event.buffered_size,
313
+ artifact_chunk=resolved_chunk, # Resolved chunk
199
314
  )
315
+
316
+ # Track the cumulative character count of what we've sent
317
+ # We need character count (not bytes) to slice correctly later
318
+ previous_char_count = session.state.get(
319
+ "artifact_chars_sent", 0
320
+ )
321
+ new_char_count = previous_char_count + len(event.chunk)
322
+ session.state["artifact_chars_sent"] = new_char_count
323
+
200
324
  await _publish_data_part_status_update(
201
325
  host_component, a2a_context, progress_data
202
326
  )
203
327
 
204
328
  elif isinstance(event, BlockCompletedEvent):
205
- log.info(
329
+ log.debug(
206
330
  "%s Event: BlockCompleted. Content length: %d",
207
331
  log_identifier,
208
332
  len(event.content),
@@ -236,6 +360,18 @@ async def process_artifact_blocks_callback(
236
360
  "original_text": original_text,
237
361
  }
238
362
  )
363
+ if a2a_context:
364
+ if not filename or not filename.strip():
365
+ filename = "unknown_artifact"
366
+ progress_data = ArtifactCreationProgressData(
367
+ filename=filename or "unknown_artifact",
368
+ description=params.get("description"),
369
+ status="failed",
370
+ bytes_transferred=0,
371
+ )
372
+ await _publish_data_part_status_update(
373
+ host_component, a2a_context, progress_data
374
+ )
239
375
  continue
240
376
 
241
377
  kwargs_for_call = {
@@ -257,7 +393,6 @@ async def process_artifact_blocks_callback(
257
393
  log_identifier,
258
394
  params["schema_max_keys"],
259
395
  )
260
-
261
396
  wrapped_creator = ADKToolWrapper(
262
397
  original_func=_internal_create_artifact,
263
398
  tool_config=None, # No specific config for this internal tool
@@ -288,6 +423,13 @@ async def process_artifact_blocks_callback(
288
423
  version_for_tool,
289
424
  logical_task_id,
290
425
  )
426
+ else:
427
+ log.warning(
428
+ "%s TaskExecutionContext not found for task %s, cannot register inline artifact '%s'.",
429
+ log_identifier,
430
+ logical_task_id,
431
+ filename,
432
+ )
291
433
  else:
292
434
  log.warning(
293
435
  "%s No logical_task_id, cannot register inline artifact.",
@@ -299,19 +441,172 @@ async def process_artifact_blocks_callback(
299
441
  log_identifier,
300
442
  e_track,
301
443
  )
444
+
445
+ # Send final progress update with any remaining content not yet sent
446
+ if a2a_context:
447
+ # Check if there's unsent content (content after last progress event)
448
+ total_bytes = len(event.content.encode("utf-8"))
449
+ chars_already_sent = session.state.get(
450
+ "artifact_chars_sent", 0
451
+ )
452
+
453
+ if chars_already_sent < len(event.content):
454
+ # There's unsent content - send it as a final progress update
455
+ final_chunk = event.content[chars_already_sent:]
456
+
457
+ # Resolve embeds in final chunk
458
+ resolved_final_chunk = await _resolve_early_embeds_in_chunk(
459
+ chunk=final_chunk,
460
+ callback_context=callback_context,
461
+ host_component=host_component,
462
+ log_identifier=f"{log_identifier}[ResolveFinalChunk]",
463
+ )
464
+
465
+ final_progress_data = ArtifactCreationProgressData(
466
+ filename=filename,
467
+ description=params.get("description"),
468
+ status="in-progress",
469
+ bytes_transferred=total_bytes,
470
+ artifact_chunk=resolved_final_chunk, # Resolved final chunk
471
+ )
472
+
473
+ await _publish_data_part_status_update(
474
+ host_component, a2a_context, final_progress_data
475
+ )
476
+
477
+ # Publish completion status immediately via SSE
478
+ if a2a_context:
479
+ progress_data = ArtifactCreationProgressData(
480
+ filename=filename,
481
+ description=params.get("description"),
482
+ status="completed",
483
+ bytes_transferred=len(event.content),
484
+ mime_type=params.get("mime_type"),
485
+ version=version_for_tool,
486
+ )
487
+ await _publish_data_part_status_update(
488
+ host_component, a2a_context, progress_data
489
+ )
302
490
  else:
303
491
  status_for_tool = "error"
304
492
  version_for_tool = 0
493
+ # Publish failure status immediately via SSE
494
+ if a2a_context:
495
+ progress_data = ArtifactCreationProgressData(
496
+ filename=filename,
497
+ description=params.get("description"),
498
+ status="failed",
499
+ bytes_transferred=len(event.content),
500
+ )
501
+ await _publish_data_part_status_update(
502
+ host_component, a2a_context, progress_data
503
+ )
305
504
 
306
505
  session.state["completed_artifact_blocks_list"].append(
307
506
  {
308
507
  "filename": filename,
309
508
  "version": version_for_tool,
310
509
  "status": status_for_tool,
510
+ "description": params.get("description"),
511
+ "mime_type": params.get("mime_type"),
512
+ "bytes_transferred": len(event.content),
311
513
  "original_text": original_text,
312
514
  }
313
515
  )
314
516
 
517
+ elif isinstance(event, TemplateBlockStartedEvent):
518
+ log.debug(
519
+ "%s Event: TemplateBlockStarted. Params: %s",
520
+ log_identifier,
521
+ event.params,
522
+ )
523
+
524
+ elif isinstance(event, TemplateBlockCompletedEvent):
525
+ log.debug(
526
+ "%s Event: TemplateBlockCompleted. Template length: %d",
527
+ log_identifier,
528
+ len(event.template_content),
529
+ )
530
+
531
+ # Create a TemplateBlockData message to send to the gateway
532
+ template_id = str(uuid.uuid4())
533
+ params = event.params
534
+
535
+ data_artifact = params.get("data")
536
+ if not data_artifact:
537
+ log.warning(
538
+ "%s Template block is missing 'data' parameter. Skipping.",
539
+ log_identifier,
540
+ )
541
+ continue
542
+
543
+ template_data = TemplateBlockData(
544
+ template_id=template_id,
545
+ data_artifact=data_artifact,
546
+ jsonpath=params.get("jsonpath"),
547
+ limit=(
548
+ int(params.get("limit"))
549
+ if params.get("limit")
550
+ else None
551
+ ),
552
+ template_content=event.template_content,
553
+ )
554
+
555
+ # Publish A2A status update with template metadata
556
+ if a2a_context:
557
+ await _publish_data_part_status_update(
558
+ host_component, a2a_context, template_data
559
+ )
560
+ log.info(
561
+ "%s Published TemplateBlockData with ID: %s",
562
+ log_identifier,
563
+ template_id,
564
+ )
565
+
566
+ # Reconstruct the original template block text for peer-to-peer responses
567
+ # Peer agents don't receive TemplateBlockData signals, so they need
568
+ # the original block text to pass templates through to the gateway
569
+ params_str = " ".join([f'{k}="{v}"' for k, v in params.items()])
570
+ original_template_text = (
571
+ f"{TEMPLATE_LIQUID_START_SEQUENCE} {params_str}\n"
572
+ f"{event.template_content}"
573
+ f"{ARTIFACT_BLOCK_DELIMITER_CLOSE}"
574
+ )
575
+
576
+ # For RUN_BASED sessions (peer-to-peer agent requests), preserve the
577
+ # template block in the response text at its original position.
578
+ # This allows the calling agent to forward it to the gateway.
579
+ # Gateway requests use streaming sessions and receive TemplateBlockData
580
+ # signals instead.
581
+ is_run_based = a2a_context and a2a_context.get(
582
+ "is_run_based_session", False
583
+ )
584
+ if is_run_based and llm_response.partial:
585
+ processed_parts.append(
586
+ adk_types.Part(text=original_template_text)
587
+ )
588
+ log.debug(
589
+ "%s Preserved template block in RUN_BASED peer response. Template ID: %s",
590
+ log_identifier,
591
+ template_id,
592
+ )
593
+
594
+ # Store template_id and original text in session for potential future use
595
+ # (Gateway will handle the actual resolution via signals,
596
+ # but peer agents need the original text in their responses)
597
+ if (
598
+ "completed_template_blocks_list" not in session.state
599
+ or session.state["completed_template_blocks_list"] is None
600
+ ):
601
+ session.state["completed_template_blocks_list"] = []
602
+ session.state["completed_template_blocks_list"].append(
603
+ {
604
+ "template_id": template_id,
605
+ "data_artifact": data_artifact,
606
+ "original_text": original_template_text,
607
+ }
608
+ )
609
+
315
610
  elif isinstance(event, BlockInvalidatedEvent):
316
611
  log.debug(
317
612
  "%s Event: BlockInvalidated. Rolled back: '%s'",
@@ -347,6 +642,11 @@ async def process_artifact_blocks_callback(
347
642
  )
348
643
  params = event.params
349
644
  filename = params.get("filename", "unknown_artifact")
645
+ if filename == "unknown_artifact":
646
+ log.warning(
647
+ "%s Unterminated fenced artifact block is missing a valid 'filename'. Failing operation.",
648
+ log_identifier,
649
+ )
350
650
  if (
351
651
  "completed_artifact_blocks_list" not in session.state
352
652
  or session.state["completed_artifact_blocks_list"] is None
@@ -392,8 +692,12 @@ async def process_artifact_blocks_callback(
392
692
  len(completed_blocks_list),
393
693
  )
394
694
 
695
+ # Get a2a_context for sending signals
696
+ a2a_context = callback_context.state.get("a2a_context")
697
+
395
698
  tool_call_parts = []
396
699
  for block_info in completed_blocks_list:
700
+ function_call_id = f"host-notify-{uuid.uuid4()}"
397
701
  notify_tool_call = adk_types.FunctionCall(
398
702
  name="_notify_artifact_save",
399
703
  args={
@@ -401,10 +705,41 @@ async def process_artifact_blocks_callback(
401
705
  "version": block_info["version"],
402
706
  "status": block_info["status"],
403
707
  },
404
- id=f"host-notify-{uuid.uuid4()}",
708
+ id=function_call_id,
405
709
  )
406
710
  tool_call_parts.append(adk_types.Part(function_call=notify_tool_call))
407
711
 
712
+ # Send artifact saved notification now that we have the function_call_id
713
+ # This ensures the signal and tool call arrive together
714
+ if block_info["status"] == "success" and a2a_context:
715
+ try:
716
+ artifact_info = ArtifactInfo(
717
+ filename=block_info["filename"],
718
+ version=block_info["version"],
719
+ mime_type=block_info.get("mime_type")
720
+ or "application/octet-stream",
721
+ size=block_info.get("bytes_transferred", 0),
722
+ description=block_info.get("description"),
723
+ version_count=None, # Count not available in save context
724
+ )
725
+ await host_component.notify_artifact_saved(
726
+ artifact_info=artifact_info,
727
+ a2a_context=a2a_context,
728
+ function_call_id=function_call_id,
729
+ )
730
+ log.debug(
731
+ "%s Published artifact saved notification for fenced block: %s (function_call_id=%s)",
732
+ log_identifier,
733
+ block_info["filename"],
734
+ function_call_id,
735
+ )
736
+ except Exception as signal_err:
737
+ log.warning(
738
+ "%s Failed to publish artifact saved notification: %s",
739
+ log_identifier,
740
+ signal_err,
741
+ )
742
+
408
743
  existing_parts = llm_response.content.parts if llm_response.content else []
409
744
  final_existing_parts = existing_parts
410
745
 
@@ -419,6 +754,7 @@ async def process_artifact_blocks_callback(
419
754
  session.state[parser_state_key] = None
420
755
  session.state["completed_artifact_blocks_list"] = None
421
756
  session.state["artifact_block_original_text"] = None
757
+ session.state["completed_template_blocks_list"] = None
422
758
  log.debug("%s Cleaned up parser session state.", log_identifier)
423
759
 
424
760
  return None
@@ -671,26 +1007,24 @@ async def manage_large_mcp_tool_responses_callback(
671
1007
  message_parts_for_llm: list[str] = []
672
1008
 
673
1009
  if needs_truncation_for_llm:
674
- truncation_suffix = "... [Response truncated due to size limit.]"
675
- adjusted_max_bytes = llm_max_bytes - len(truncation_suffix.encode("utf-8"))
676
- if adjusted_max_bytes < 0:
677
- adjusted_max_bytes = 0
678
-
679
- truncated_bytes = serialized_original_response_str.encode("utf-8")[
680
- :adjusted_max_bytes
681
- ]
682
- truncated_preview_str = (
683
- truncated_bytes.decode("utf-8", "ignore") + truncation_suffix
684
- )
685
-
1010
+ # ALL-OR-NOTHING APPROACH: Do not include truncated data to prevent LLM hallucination.
1011
+ # When LLMs receive partial data, they tend to confidently fill in gaps with
1012
+ # hallucinated information. By withholding partial data entirely, we force the LLM
1013
+ # to use reliable mechanisms (template_liquid, load_artifact) to access the full data.
686
1014
  final_llm_response_dict["mcp_tool_output"] = {
687
- "type": "truncated_json_string",
688
- "content": truncated_preview_str,
1015
+ "type": "data_in_artifact_only",
1016
+ "message": "Data exceeds size limit. Full data saved as artifact - use template_liquid, load_artifact or other artifact analysis tools to process and access.",
689
1017
  }
690
1018
  message_parts_for_llm.append(
691
- f"The response from tool '{tool.name}' was too large ({original_response_bytes} bytes) for direct display and has been truncated."
1019
+ f"The response from tool '{tool.name}' was too large ({original_response_bytes} bytes) to display directly. "
1020
+ "The data has NOT been included here to prevent incomplete information. "
1021
+ "You MUST use template_liquid (for displaying to users) or load_artifact or other "
1022
+ "artifact analysis tools (for processing) to access the full data."
1023
+ )
1024
+ log.debug(
1025
+ "%s MCP tool output withheld from LLM (all-or-nothing approach).",
1026
+ log_identifier,
692
1027
  )
693
- log.debug("%s MCP tool output truncated for LLM.", log_identifier)
694
1028
 
695
1029
  if needs_saving_as_artifact:
696
1030
  if save_result and save_result.status in [
@@ -707,19 +1041,27 @@ async def manage_large_mcp_tool_responses_callback(
707
1041
  filename = first_artifact.data_filename
708
1042
  version = first_artifact.data_version
709
1043
  if total_artifacts > 1:
710
- message_parts_for_llm.append(
711
- f"The full response has been saved as {total_artifacts} artifacts, starting with '{filename}' (version {version})."
712
- )
1044
+ artifact_msg = f"The full response has been saved as {total_artifacts} artifacts, starting with '{filename}' (version {version})."
713
1045
  else:
714
- message_parts_for_llm.append(
715
- f"The full response has been saved as artifact '{filename}' (version {version})."
1046
+ artifact_msg = f"The full response has been saved as artifact '{filename}' (version {version})."
1047
+
1048
+ # When data was too large and truncated, provide explicit guidance
1049
+ if needs_truncation_for_llm:
1050
+ artifact_msg += (
1051
+ f' To display this data to the user, use template_liquid with data="{filename}". '
1052
+ f'To process the data yourself, use load_artifact("{filename}").'
716
1053
  )
1054
+ message_parts_for_llm.append(artifact_msg)
717
1055
  elif save_result.fallback_artifact:
718
1056
  filename = save_result.fallback_artifact.data_filename
719
1057
  version = save_result.fallback_artifact.data_version
720
- message_parts_for_llm.append(
721
- f"The full response has been saved as artifact '{filename}' (version {version})."
722
- )
1058
+ artifact_msg = f"The full response has been saved as artifact '{filename}' (version {version})."
1059
+ if needs_truncation_for_llm:
1060
+ artifact_msg += (
1061
+ f' To display this data to the user, use template_liquid with data="{filename}". '
1062
+ f'To process the data yourself, use load_artifact("{filename}").'
1063
+ )
1064
+ message_parts_for_llm.append(artifact_msg)
723
1065
 
724
1066
  log.debug(
725
1067
  "%s Added saved artifact details to LLM response.", log_identifier
@@ -744,16 +1086,18 @@ async def manage_large_mcp_tool_responses_callback(
744
1086
  and save_result.status in [McpSaveStatus.SUCCESS, McpSaveStatus.PARTIAL_SUCCESS]
745
1087
  ):
746
1088
  if needs_truncation_for_llm:
747
- final_llm_response_dict["status"] = "processed_saved_and_truncated"
1089
+ # Data was too large - withheld from LLM, only available via artifact
1090
+ final_llm_response_dict["status"] = "processed_saved_artifact_only"
748
1091
  else:
749
1092
  final_llm_response_dict["status"] = "processed_and_saved"
750
1093
  elif needs_saving_as_artifact:
751
1094
  if needs_truncation_for_llm:
752
- final_llm_response_dict["status"] = "processed_truncated_save_failed"
1095
+ final_llm_response_dict["status"] = "processed_artifact_only_save_failed"
753
1096
  else:
754
1097
  final_llm_response_dict["status"] = "processed_save_failed"
755
1098
  elif needs_truncation_for_llm:
756
- final_llm_response_dict["status"] = "processed_truncated"
1099
+ # This case shouldn't happen (truncation implies saving), but handle it
1100
+ final_llm_response_dict["status"] = "processed_data_withheld"
757
1101
  else:
758
1102
  final_llm_response_dict["status"] = "processed"
759
1103
 
@@ -769,34 +1113,121 @@ async def manage_large_mcp_tool_responses_callback(
769
1113
  return final_llm_response_dict
770
1114
 
771
1115
 
1116
+ def _generate_fenced_block_syntax_rules() -> str:
1117
+ """Generates the shared syntax rules for all fenced blocks."""
1118
+ open_delim = ARTIFACT_BLOCK_DELIMITER_OPEN
1119
+ close_delim = ARTIFACT_BLOCK_DELIMITER_CLOSE
1120
+ return f"""
1121
+ **Fenced Block Syntax Rules (Applies to `save_artifact` and `template_liquid`):**
1122
+ To create content blocks, you MUST use the EXACT syntax shown below.
1123
+
1124
+ **EXACT SYNTAX (copy this pattern exactly):**
1125
+ {open_delim}keyword: parameter="value" ...
1126
+ The content for the block goes here.
1127
+ It can span multiple lines.
1128
+ {close_delim}
1129
+
1130
+ **CRITICAL FORMATTING RULES:**
1131
+ 1. The opening delimiter MUST be EXACTLY `{open_delim}`.
1132
+ 2. Immediately after the delimiter, write the keyword (`save_artifact` or `template_liquid`) followed by a colon, with NO space before the colon (e.g., `{open_delim}save_artifact:`).
1133
+ 3. All parameters (like `filename`, `data`, `mime_type`) must be on the SAME line as the opening delimiter.
1134
+ 4. All parameter values **MUST** be enclosed in double quotes (e.g., `filename="example.txt"`).
1135
+ 5. You **MUST NOT** use double quotes `"` inside parameter values. Use single quotes or rephrase instead.
1136
+ 6. The block's content begins on the line immediately following the parameters.
1137
+ 7. Close the block with EXACTLY `{close_delim}` (three angle brackets) on its own line.
1138
+ 8. Do NOT surround the block with triple backticks (```). The `{open_delim}` and `{close_delim}` delimiters are sufficient.
1139
+
1140
+ **COMMON ERRORS TO AVOID:**
1141
+ ❌ WRONG: `{open_delim[0:1]}template_liquid:` (only 1 angle brackets)
1142
+ ❌ WRONG: `{open_delim[0:2]}save_artifact:` (only 2 angle brackets)
1143
+ ❌ WRONG: `{open_delim}save_artifact` (missing colon)
1144
+ ✅ CORRECT: `{open_delim}save_artifact: filename="test.txt" mime_type="text/plain"`
1145
+ """
1146
+
1147
+
772
1148
  def _generate_fenced_artifact_instruction() -> str:
773
1149
  """Generates the instruction text for using fenced artifact blocks."""
774
1150
  open_delim = ARTIFACT_BLOCK_DELIMITER_OPEN
1151
+ return f"""\
1152
+ **Creating Text-Based Artifacts (`{open_delim}save_artifact: ...`):**
1153
+
1154
+ When to Create Artifacts:
1155
+ Create an artifact when the content provides value as a standalone file, such as:
1156
+ - Content with special formatting (HTML, Markdown, CSS).
1157
+ - Documents intended for use outside the conversation (reports, emails).
1158
+ - Structured reference content (schedules, guides, templates).
1159
+ - Substantial text documents or technical documentation.
1160
+
1161
+ When NOT to Create Artifacts:
1162
+ - Simple answers, explanations, or conversational responses.
1163
+ - Brief advice, opinions, or short lists.
1164
+
1165
+ Behavior of Created Artifacts:
1166
+ - They are sent to the user as an interactive file component.
1167
+ - The user can see the content, so there is no need to return or embed it again.
1168
+
1169
+ Parameters for `{open_delim}save_artifact: ...`:
1170
+ - `filename="your_filename.ext"` (REQUIRED)
1171
+ - `mime_type="text/plain"` (optional, defaults to text/plain)
1172
+ - `description="A brief description."` (optional)
1173
+
1174
+ The system will automatically save the content and confirm it in the next turn.
1175
+ """
1176
+
1177
+
1178
+ def _generate_inline_template_instruction() -> str:
1179
+ """Generates the instruction text for using inline Liquid templates."""
1180
+ open_delim = ARTIFACT_BLOCK_DELIMITER_OPEN
775
1181
  close_delim = ARTIFACT_BLOCK_DELIMITER_CLOSE
776
1182
  return f"""\
777
- **Creating Text-Based Artifacts:**
778
- To create an artifact from content you generate (like code, a report, or a document), you MUST use a special `save_artifact` block. This is the only reliable way to ensure your content is saved correctly.
1183
+ **Inline Liquid Templates (`{open_delim}template_liquid: ...`):**
779
1184
 
780
- **Syntax:**
781
- {open_delim}save_artifact: filename="your_filename.ext" mime_type="text/plain" description="A brief description."
782
- The full content you want to save goes here.
783
- It can span multiple lines.
1185
+ Use inline Liquid templates to dynamically render data from artifacts for user-friendly display. This is faster and more accurate than reading the artifact and reformatting it yourself.
1186
+
1187
+ IMPORTANT: Template Format
1188
+ - Templates use Liquid template syntax (same as Shopify templates - NOTE that Jekyll extensions are NOT supported).
1189
+
1190
+ When to Use Inline Templates:
1191
+ - Formatting CSV, JSON, or YAML data into tables or lists.
1192
+ - Applying simple transformations (filtering, limiting rows).
1193
+
1194
+ Parameters for `{open_delim}template_liquid: ...`:
1195
+ - `data="filename.ext"` (REQUIRED): The data artifact to render. Can include version: `data="file.csv:2"`.
1196
+ - `jsonpath="$.expression"` (optional): JSONPath to extract a subset of JSON/YAML data.
1197
+ - `limit="N"` (optional): Limit to the first N rows (CSV) or items (JSON/YAML arrays).
1198
+
1199
+ Data Context for Liquid Templates:
1200
+ - CSV data: Available as `headers` (array of column names) and `data_rows` (array of row arrays).
1201
+ - JSON/YAML arrays: Available as `items`.
1202
+ - JSON/YAML objects: Keys are directly available (e.g., `name`, `email`).
1203
+
1204
+ Example - CSV Table:
1205
+ {open_delim}template_liquid: data="sales_data.csv" limit="5"
1206
+ | {{% for h in headers %}}{{{{ h }}}} | {{% endfor %}}
1207
+ |{{% for h in headers %}}---|{{% endfor %}}
1208
+ {{% for row in data_rows %}}| {{% for cell in row %}}{{{{ cell }}}} | {{% endfor %}}{{% endfor %}}
784
1209
  {close_delim}
785
1210
 
786
- - **Rules:**
787
- - The parameters `filename` and `mime_type` are required. `description` is optional but recommended.
788
- - All parameter values **MUST** be enclosed in double quotes.
789
- - You **MUST NOT** use double quotes `"` inside the parameter values (e.g., within the description string). Use single quotes or rephrase instead.
790
- - Do not surround a save_artifact block with '```' (triple backticks). This will create rendering issues.
1211
+ **IMPORTANT - Pipe Characters in Markdown Tables:**
1212
+ Text data may contain "|" characters which will break markdown table rendering by pushing data into wrong columns. For text fields that might contain pipes, use the `replace` filter to escape them:
1213
+ `{{{{ item.summary | replace: "|", "&#124;" }}}}`
1214
+ Only apply this to text fields that might contain pipes - numerical columns don't need it.
791
1215
 
792
- The system will automatically save the content and give you a confirmation in the next turn."""
1216
+ Negative Examples
1217
+ Use {{ issues.size }} instead of {{ issues|length }}
1218
+ Use {{ forloop.index }} instead of {{ loop.index }} (Liquid uses forloop not loop)
1219
+ Use {{ issue.fields.description | truncate: 200 }} instead of slicing with [:200]
1220
+ Do not use Jekyll-specific tags or filters (e.g., `{{% assign %}}`, `{{% capture %}}`, `where`, `sort`, `where_exp`, etc.)
1221
+
1222
+ The rendered output will appear inline in your response automatically.
1223
+ """
793
1224
 
794
1225
 
795
1226
  def _generate_artifact_creation_instruction() -> str:
796
1227
  return """
797
1228
  **Creating Text-Based Artifacts:**
798
1229
 
799
- **When to Create Text-based Artifacts:**
1230
+ When to Create Text-based Artifacts:
800
1231
  Create an artifact when the content provides value as a standalone file:
801
1232
  - Content with special formatting (HTML, Markdown, CSS, structured markup) that requires proper rendering
802
1233
  - Content explicitly intended for use outside this conversation (reports, emails, presentations, reference documents)
@@ -805,15 +1236,123 @@ def _generate_artifact_creation_instruction() -> str:
805
1236
  - Substantial text documents
806
1237
  - Technical documentation meant as reference material
807
1238
 
808
- **When NOT to Create Text-based Artifacts:**
1239
+ When NOT to Create Text-based Artifacts:
809
1240
  - Simple answers, explanations, or conversational responses
810
1241
  - Brief advice, opinions, or quick information
811
- - Short lists, summaries, or single paragraphs
1242
+ - Short lists, summaries, or single paragraphs
812
1243
  - Temporary content only relevant to the immediate conversation
813
1244
  - Basic explanations that don't require reference material
814
1245
  """
815
1246
 
816
1247
 
1248
+ def _generate_examples_instruction() -> str:
1249
+ open_delim = ARTIFACT_BLOCK_DELIMITER_OPEN
1250
+ close_delim = ARTIFACT_BLOCK_DELIMITER_CLOSE
1251
+ embed_open_delim = EMBED_DELIMITER_OPEN
1252
+ embed_close_delim = EMBED_DELIMITER_CLOSE
1253
+
1254
+ return (
1255
+ f"""\
1256
+ Example 1:
1257
+ - User: "Create a markdown file with your two csv files as tables."
1258
+ <note>There are two csv files already uploaded: data1.csv and data2.csv</note>
1259
+ - OrchestratorAgent:
1260
+ {embed_open_delim}status_update:Creating Markdown tables from CSV files...{embed_close_delim}
1261
+ {open_delim}save_artifact: filename="data_tables.md" mime_type="text/markdown" description="Markdown tables from CSV files"
1262
+ # Data Tables
1263
+ ## Data 1
1264
+ {open_delim}template_liquid: data="data1.csv"
1265
+ """
1266
+ + """| {% for h in headers %}{{ h }} | {% endfor %}
1267
+ |{% for h in headers %}---|{% endfor %}
1268
+ {% for row in data_rows %}| {% for cell in row %}{{ cell }} | {% endfor %}{% endfor %}
1269
+ """
1270
+ + f"""{close_delim}
1271
+ ## Data 2
1272
+ {open_delim}template_liquid: data="data2.csv"
1273
+ """
1274
+ + """| {% for h in headers %}{{ h }} | {% endfor %}
1275
+ |{% for h in headers %}---|{% endfor %}
1276
+ {% for row in data_rows %}| {% for cell in row %}{{ cell }} | {% endfor %}{% endfor %}
1277
+ """
1278
+ + f"""{close_delim}
1279
+ {close_delim}
1280
+ Example 2:
1281
+ - User: "Create a text file with the result of sqrt(12345) + sqrt(67890) + sqrt(13579) + sqrt(24680)."
1282
+ - OrchestratorAgent:
1283
+ {embed_open_delim}status_update:Calculating and creating text file...{embed_close_delim}
1284
+ {open_delim}save_artifact: filename="math.txt" mime_type="text/plain" description="Result of sqrt(12345) + sqrt(67890) + sqrt(13579) + sqrt(24680)"
1285
+ result = {embed_open_delim}math: sqrt(12345) + sqrt(67890) + sqrt(13579) + sqrt(24680) | .2f{embed_close_delim}
1286
+ {close_delim}
1287
+
1288
+ Example 3:
1289
+ - User: "Show me the first 10 entries from data1.csv"
1290
+ - OrchestratorAgent:
1291
+ {embed_open_delim}status_update:Loading CSV data...{embed_close_delim}
1292
+ {open_delim}template_liquid: data="data1.csv" limit="10"
1293
+ """
1294
+ + """| {% for h in headers %}{{ h }} | {% endfor %}
1295
+ |{% for h in headers %}---|{% endfor %}
1296
+ {% for row in data_rows %}| {% for cell in row %}{{ cell }} | {% endfor %}{% endfor %}
1297
+ """
1298
+ + f"""{close_delim}
1299
+
1300
+ Example 4:
1301
+ - User: "Show me the Jira issues as a table"
1302
+ <note>There is a JSON artifact jira_issues.json with items containing key, summary, status, type, assignee, updated fields</note>
1303
+ - OrchestratorAgent:
1304
+ {embed_open_delim}status_update:Rendering Jira issues table...{embed_close_delim}
1305
+ {open_delim}template_liquid: data="jira_issues.json" limit="10"
1306
+ """
1307
+ + """| Key | Summary | Status | Type | Assignee | Updated |
1308
+ |-----|---------|--------|------|----------|---------|
1309
+ {% for item in items %}| [{{ item.key }}](https://jira.example.com/browse/{{ item.key }}) | {{ item.summary | replace: "|", "&#124;" }} | {{ item.status }} | {{ item.type }} | {{ item.assignee }} | {{ item.updated }} |
1310
+ {% endfor %}"""
1311
+ + f"""
1312
+ {close_delim}
1313
+ <note>The replace filter on item.summary escapes any pipe characters that would break the markdown table. Only apply to text fields that might contain pipes.</note>
1314
+
1315
+ Example 5:
1316
+ - User: "Search the database for all orders from last month"
1317
+ - OrchestratorAgent:
1318
+ {embed_open_delim}status_update:Querying order database...{embed_close_delim}
1319
+ [calls search_database tool with no visible text]
1320
+ [After getting results:]
1321
+ Found 247 orders from last month totaling $45,231.
1322
+
1323
+ Example 6:
1324
+ - User: "Create an HTML with the chart image you just generated with the customer data."
1325
+ - OrchestratorAgent:
1326
+ {embed_open_delim}status_update:Generating HTML report with chart...{embed_close_delim}
1327
+ {open_delim}save_artifact: filename="customer_analysis.html" mime_type="text/html" description="Interactive customer analysis dashboard"
1328
+ <!DOCTYPE html>
1329
+ <html>
1330
+ <head>
1331
+ <title>Customer Chart - {embed_open_delim}datetime:%Y-%m-%d{embed_close_delim}</title>
1332
+ """
1333
+ + """
1334
+ <style>
1335
+ body { font-family: Arial, sans-serif; margin: 20px; }
1336
+ .metric { background: #f0f0f0; padding: 10px; margin: 10px 0; }
1337
+ img { max-width: 100%; height: auto; }
1338
+ """
1339
+ + f""" </style>
1340
+ </head>
1341
+ <body>
1342
+ <h1>Customer Analysis Report</h1>
1343
+ <p>Generated: {embed_open_delim}datetime:iso{embed_close_delim}</p>
1344
+
1345
+ <h2>Customer Distribution Chart</h2>
1346
+ <img src="{embed_open_delim}artifact_content:customer_chart.png >>> format:datauri{embed_close_delim}" alt="Customer Distribution">
1347
+
1348
+ </body>
1349
+ </html>
1350
+ {close_delim}
1351
+
1352
+ """
1353
+ )
1354
+
1355
+
817
1356
  def _generate_embed_instruction(
818
1357
  include_artifact_content: bool,
819
1358
  log_identifier: str,
@@ -823,12 +1362,18 @@ def _generate_embed_instruction(
823
1362
  close_delim = EMBED_DELIMITER_CLOSE
824
1363
  chain_delim = EMBED_CHAIN_DELIMITER
825
1364
  early_types = "`math`, `datetime`, `uuid`, `artifact_meta`"
826
- modifier_list = ", ".join(
827
- [f"`{prefix}`" for prefix in MODIFIER_IMPLEMENTATIONS.keys()]
828
- )
1365
+
1366
+ modifier_list = MODIFIER_IMPLEMENTATIONS.keys()
1367
+ # Remove apply_to_template from the modifier list as it's been deprecated
1368
+ if "apply_to_template" in modifier_list:
1369
+ modifier_list = list(modifier_list)
1370
+ modifier_list.remove("apply_to_template")
1371
+ modifier_list = ", ".join([f"`{prefix}`" for prefix in modifier_list])
829
1372
 
830
1373
  base_instruction = f"""\
831
- You can use dynamic embeds in your text responses and tool parameters using the syntax {open_delim}type:expression {chain_delim} format{close_delim}. This allows you to
1374
+ **Using Dynamic Embeds in Responses:**
1375
+
1376
+ You can use dynamic embeds in your text responses and tool parameters using the syntax {open_delim}type:expression {chain_delim} format{close_delim}. NOTE that this differs from 'save_artifact', which has different delimiters. This allows you to
832
1377
  always have correct information in your output. Specifically, make sure you always use embeds for math, even if it is simple. You will make mistakes if you try to do math yourself.
833
1378
  Use HTML entities to escape the delimiters.
834
1379
  This host resolves the following embed types *early* (before sending to the LLM or tool): {early_types}. This means the embed is replaced with its resolved value.
@@ -836,31 +1381,59 @@ This host resolves the following embed types *early* (before sending to the LLM
836
1381
  - `{open_delim}datetime:format_or_keyword{close_delim}`: Inserts current date/time. Use Python strftime format (e.g., `%Y-%m-%d`) or keywords (`iso`, `timestamp`, `date`, `time`, `now`).
837
1382
  - `{open_delim}uuid:{close_delim}`: Inserts a random UUID.
838
1383
  - `{open_delim}artifact_meta:filename[:version]{close_delim}`: Inserts a summary of the artifact's metadata (latest version if unspecified).
839
- - `{open_delim}status_update:Your message here{close_delim}`: Generates an immediate, distinct status message event that is displayed to the user (e.g., 'Thinking...', 'Searching database...'). This message appears in a status area, not as part of the main chat conversation. Use this to provide interim feedback during processing."""
1384
+ - `{open_delim}status_update:Your message here{close_delim}`: Generates an immediate, distinct status message event that is displayed to the user (e.g., 'Thinking...', 'Searching database...'). This message appears in a status area, not as part of the main chat conversation. Use this to provide interim feedback during processing.
1385
+
1386
+ Examples:
1387
+ - `{open_delim}status_update:Analyzing data...{close_delim}` (Shows 'Analyzing data...' as a status update)
1388
+ - `The result of 23.5 * 4.2 is {open_delim}math:23.5 * 4.2 | .2f{close_delim}` (Embeds calculated result with 2 decimal places)
1389
+
1390
+ The following embeds are resolved *late* (by the gateway before final display):
1391
+ - `{open_delim}artifact_return:filename[:version]{close_delim}`: Attaches an artifact to your message so the user receives the file. The embed itself is removed from the text.
1392
+
1393
+ **CRITICAL - Returning Artifacts to Users:**
1394
+ Only artifacts created with the `{open_delim}save_artifact:...{close_delim}` fenced block syntax are automatically sent to the user.
1395
+
1396
+ **You MUST use artifact_return for:**
1397
+ - Artifacts created by tools (e.g., image generation, chart creation, file conversion)
1398
+ - Artifacts created by other agents you called
1399
+ - Artifacts from MCP servers
1400
+
1401
+ **When deciding whether to return an artifact:**
1402
+ - Return artifacts the user explicitly requested or that answer their question
1403
+ - Return final outputs (charts, reports, images, documents)
1404
+ - Do NOT return intermediate/temporary artifacts (e.g., temp files, internal data)
1405
+
1406
+ **Example - Tool creates an image:**
1407
+ User: "Create a chart of sales data"
1408
+ [You call a charting tool that creates sales_chart.png]
1409
+ Your response: "Here's the sales chart. {open_delim}artifact_return:sales_chart.png{close_delim}"
1410
+
1411
+ **Example - Agent creates a report:**
1412
+ User: "Generate a quarterly report"
1413
+ [You call ReportAgent which creates quarterly_report.pdf]
1414
+ Your response: "The quarterly report is ready. {open_delim}artifact_return:quarterly_report.pdf{close_delim}"
1415
+ """
840
1416
 
841
1417
  artifact_content_instruction = f"""
842
1418
  - `{open_delim}artifact_content:filename[:version] {chain_delim} modifier1:value1 {chain_delim} ... {chain_delim} format:output_format{close_delim}`: Embeds artifact content after applying a chain of modifiers. This is resolved *late* (typically by a gateway before final display).
1419
+ - If this embed resolves to binary content (like an image), it will be automatically converted into an attached file, similar to `artifact_return`.
843
1420
  - Use `{chain_delim}` to separate the artifact identifier from the modifier steps and the final format step.
844
1421
  - Available modifiers: {modifier_list}.
845
1422
  - The `format:output_format` step *must* be the last step in the chain. Supported formats include `text`, `datauri`, `json`, `json_pretty`, `csv`. Formatting as datauri, will include the data URI prefix, so do not add it yourself.
846
1423
  - Use `artifact_meta` first to check size; embedding large files may fail.
847
- - **Using `apply_to_template` Modifier:**
848
- - This modifier renders a Mustache template artifact using the data from the previous step.
849
- - **Data Context:**
850
- - If the input data's original MIME type was `text/csv` or `application/csv`, it's automatically parsed into an object with two keys: `headers` (a list of column name strings) and `data_rows` (a list of lists, where each inner list contains the string values for a row). Example template usage: `<thead><tr>{{{{#headers}}}}<th>{{{{.}}}}</th>{{{{/headers}}}}</tr></thead><tbody>{{{{#data_rows}}}}<tr>{{{{#.}}}}<td>{{{{.}}}}</td>{{{{/.}}}}</tr>{{{{/data_rows}}}}</tbody>`. If CSV parsing fails, the raw string content is available under `text`.
851
- - If the input data is a **list** (e.g., from `jsonpath` or a JSON array), it's available under `items`.
852
- - If the input data is a **dictionary** (e.g., from a JSON object), its keys are directly available (e.g., `{{{{key1}}}}`).
853
- - If the input data is a **plain string** (and not auto-parsed as CSV), it's available under `text`.
854
- - The template filename can include a version (e.g., `template.mustache:2`). Defaults to latest.
855
- - The template itself can contain `«artifact_content:...»` embeds, which will be resolved before rendering.
1424
+ - Efficient workflows for large artifacts:
1425
+ - To extract specific line ranges: `load_artifact(filename, version, include_line_numbers=True)` to identify lines, then use `slice_lines:start:end` modifier to extract that range.
1426
+ - To fill templates with many placeholders: use `artifact_search_and_replace_regex` with `replacements` array (single atomic operation instead of multiple calls).
1427
+ - Line numbers are display-only; `slice_lines` always operates on original content.
856
1428
  - Examples:
857
1429
  - `<img src="{open_delim}artifact_content:image.png {chain_delim} format:datauri{close_delim}`"> (Embed image as data URI - NOTE that this includes the datauri prefix. Do not add it yourself.)
858
1430
  - `{open_delim}artifact_content:data.json {chain_delim} jsonpath:$.items[*] {chain_delim} select_fields:name,status {chain_delim} format:json_pretty{close_delim}` (Extract and format JSON fields)
859
1431
  - `{open_delim}artifact_content:logs.txt {chain_delim} grep:ERROR {chain_delim} head:10 {chain_delim} format:text{close_delim}` (Get first 10 error lines)
860
- - `{open_delim}artifact_content:products.csv {chain_delim} apply_to_template:product_table.html.mustache {chain_delim} format:text{close_delim}` (CSV is auto-parsed to `headers` and `data_rows` for the HTML template)
861
1432
  - `{open_delim}artifact_content:config.json {chain_delim} jsonpath:$.userPreferences.theme {chain_delim} format:text{close_delim}` (Extract a single value from a JSON artifact)
862
- - `{open_delim}artifact_content:sensor_readings.csv {chain_delim} filter_rows_eq:status:critical {chain_delim} select_cols:timestamp,sensor_id,value {chain_delim} format:csv{close_delim}` (Filter critical sensor readings and select specific columns, output as CSV)
863
- - `{open_delim}artifact_content:server.log {chain_delim} tail:100 {chain_delim} grep:WARN {chain_delim} format:text{close_delim}` (Get warning lines from the last 100 lines of a log file)"""
1433
+ - `{open_delim}artifact_content:server.log {chain_delim} tail:100 {chain_delim} grep:WARN {chain_delim} format:text{close_delim}` (Get warning lines from the last 100 lines of a log file)
1434
+ - `{open_delim}artifact_content:template.html {chain_delim} slice_lines:10:50 {chain_delim} format:text{close_delim}` (Extract lines 10-50 from a large file)
1435
+ - `<img src="{open_delim}artifact_content:diagram.png {chain_delim} format:datauri{close_delim}`"> (Embed an PNG diagram as a data URI)`
1436
+ """
864
1437
 
865
1438
  final_instruction = base_instruction
866
1439
  if include_artifact_content:
@@ -872,6 +1445,64 @@ Ensure the syntax is exactly `{open_delim}type:expression{close_delim}` or `{ope
872
1445
  return final_instruction
873
1446
 
874
1447
 
1448
+ def _generate_conversation_flow_instruction() -> str:
1449
+ """Generates instruction text for conversation flow and response formatting."""
1450
+ open_delim = EMBED_DELIMITER_OPEN
1451
+ close_delim = EMBED_DELIMITER_CLOSE
1452
+ return f"""\
1453
+ **Conversation Flow and Response Formatting:**
1454
+
1455
+ **CRITICAL: Minimize Narration - Maximize Results**
1456
+
1457
+ You do NOT need to produce visible text on every turn. Many turns should contain ONLY status updates and tool calls, with NO visible text at all.
1458
+ Only produce visible text when you have actual results, answers, or insights to share with the user.
1459
+
1460
+ Response Content Rules:
1461
+ 1. Visible responses should contain ONLY:
1462
+ - Direct answers to the user's question
1463
+ - Analysis and insights derived from tool results
1464
+ - Final results and data
1465
+ - Follow-up questions when needed
1466
+ - Plans for complex multi-step tasks
1467
+
1468
+ 2. DO NOT include visible text for:
1469
+ - Process narration ("Let me...", "I'll...", "Now I will...")
1470
+ - Acknowledgments of tool calls ("I'm calling...", "Searching...")
1471
+ - Descriptions of what you're about to do
1472
+ - Play-by-play commentary on your actions
1473
+ - Transitional phrases between tool calls
1474
+
1475
+ 3. Use invisible status_update embeds for ALL process updates:
1476
+ - "Searching for..."
1477
+ - "Analyzing..."
1478
+ - "Creating..."
1479
+ - "Querying..."
1480
+ - "Calling agent X..."
1481
+
1482
+ 4. NEVER mix process narration with status updates - if you use a status_update embed, do NOT repeat that information in visible text.
1483
+
1484
+ Examples:
1485
+
1486
+ **Excellent (no visible text, just status and tools):**
1487
+ "{open_delim}status_update:Retrieving sales data...{close_delim}" [then calls tool, no visible text]
1488
+
1489
+ **Good (visible text only contains results):**
1490
+ "{open_delim}status_update:Analyzing Q4 sales...{close_delim}" [calls tool]
1491
+ "Sales increased 23% in Q4, driven primarily by enterprise accounts."
1492
+
1493
+ **Bad (unnecessary narration):**
1494
+ "Let me retrieve the sales data for you." [then calls tool]
1495
+
1496
+ **Bad (narration mixed with results):**
1497
+ "I've analyzed the data and found that sales increased 23% in Q4."
1498
+
1499
+ **Bad (play-by-play commentary):**
1500
+ "Now I'll search for the information. After that I'll analyze it."
1501
+
1502
+ Remember: The user can see status updates and tool calls. You don't need to announce them in visible text.
1503
+ """
1504
+
1505
+
875
1506
  def _generate_tool_instructions_from_registry(
876
1507
  active_tools: List[BuiltinTool],
877
1508
  log_identifier: str,
@@ -882,6 +1513,10 @@ def _generate_tool_instructions_from_registry(
882
1513
 
883
1514
  instructions_by_category = defaultdict(list)
884
1515
  for tool in sorted(active_tools, key=lambda t: (t.category, t.name)):
1516
+ # Skip internal tools (those starting with underscore)
1517
+ if tool.name.startswith("_"):
1518
+ continue
1519
+
885
1520
  param_parts = []
886
1521
  if tool.parameters and tool.parameters.properties:
887
1522
  for name, schema in tool.parameters.properties.items():
@@ -937,27 +1572,51 @@ def inject_dynamic_instructions_callback(
937
1572
  Parallel Tool Calling:
938
1573
  The system is capable of calling multiple tools in parallel to speed up processing. Please try to run tools in parallel when they don't depend on each other. This saves money and time, providing faster results to the user.
939
1574
 
1575
+ **Response Formatting - CRITICAL**:
1576
+ In most cases when calling tools, you should produce NO visible text at all - only status_update embeds and the tool calls themselves.
1577
+ The user can see your tool calls and status updates, so narrating your actions is redundant and creates noise.
1578
+
1579
+ If you do include visible text:
1580
+ - It must contain actual results, insights, or answers - NOT process narration
1581
+ - Do NOT end with a colon (":") before tool calls, as this leaves it hanging
1582
+ - Prefer ending with a period (".") if you must include visible text
1583
+
1584
+ Examples:
1585
+ - BEST: "{open_delim}status_update:Searching database...{close_delim}" [then calls tool, NO visible text]
1586
+ - BAD: "Let me search for that information." [then calls tool]
1587
+ - BAD: "Searching for information..." [then calls tool]
1588
+
1589
+ **CRITICAL - No Links From Training Data**:
1590
+ - DO NOT include URLs, links, or markdown links from your training data in responses
1591
+ - NEVER include markdown links like [text](url) or raw URLs like https://example.com unless they came from a tool result
1592
+ - If a delegated agent's response contains [[cite:searchN]] citations, those are properly formatted - preserve them exactly
1593
+ - If a delegated agent's response has no links, do NOT add any links yourself
1594
+ - The ONLY acceptable links are those returned by tools (web search, deep research, etc.) with proper citation format
1595
+ - Your role is to coordinate and present results, not to augment them with links from your training data
1596
+
940
1597
  Embeds in responses from agents:
941
- To be efficient, agents may response with artifact_content embeds in their responses. These will not be resolved until they are sent back to a gateway. If it makes
1598
+ To be efficient, peer agents may respond with artifact_content in their responses. These will not be resolved until they are sent back to a gateway. If it makes
942
1599
  sense, just carry that embed forward to your response to the user. For example, if you ask for an org chart from another agent and its response contains an embed like
943
1600
  `{open_delim}artifact_content:org_chart.md{close_delim}`, you can just include that embed in your response to the user. The gateway will resolve it and display the org chart.
944
1601
 
1602
+ Similarly, template_liquid blocks in peer agent responses can be carried forward to your response to the user for resolution by the gateway.
1603
+
945
1604
  When faced with a complex goal or request that involves multiple steps, data retrieval, or artifact summarization to produce a new report or document, you MUST first create a plan.
946
1605
  Simple, direct requests like 'create an image of a dog' or 'write an email to thank my boss' do not require a plan.
947
1606
 
948
1607
  If a plan is created:
949
1608
  1. It should be a terse, hierarchical list describing the steps needed, with each checkbox item on its own line.
950
- 2. Use '' (empty checkbox emoji) for pending items and '' (checked checkbox emoji) for completed items.
1609
+ 2. Use '' for pending items, '✅' for completed items, and '' for cancelled items.
951
1610
  3. If the plan changes significantly during execution, restate the updated plan.
952
1611
  4. As items are completed, update the plan to check them off.
953
1612
 
954
1613
  """
955
1614
  injected_instructions.append(planning_instruction)
956
- log.debug("%s Added hardcoded planning instructions.", log_identifier)
957
- artifact_creation_instruction = _generate_artifact_creation_instruction()
958
- injected_instructions.append(artifact_creation_instruction)
959
- fenced_artifact_instruction = _generate_fenced_artifact_instruction()
960
- injected_instructions.append(fenced_artifact_instruction)
1615
+
1616
+ # Add the consolidated block instructions
1617
+ injected_instructions.append(_generate_fenced_artifact_instruction())
1618
+ injected_instructions.append(_generate_inline_template_instruction())
1619
+ injected_instructions.append(_generate_fenced_block_syntax_rules())
961
1620
 
962
1621
  agent_instruction_str: Optional[str] = None
963
1622
  if host_component._agent_system_instruction_callback:
@@ -1025,6 +1684,11 @@ If a plan is created:
1025
1684
  include_artifact_content_instr,
1026
1685
  )
1027
1686
 
1687
+ instruction = _generate_conversation_flow_instruction()
1688
+ if instruction:
1689
+ injected_instructions.append(instruction)
1690
+ log.debug("%s Prepared conversation flow instructions.", log_identifier)
1691
+
1028
1692
  if active_builtin_tools:
1029
1693
  instruction = _generate_tool_instructions_from_registry(
1030
1694
  active_builtin_tools, log_identifier
@@ -1094,6 +1758,8 @@ If a plan is created:
1094
1758
  e_last_call,
1095
1759
  )
1096
1760
 
1761
+ injected_instructions.append(_generate_examples_instruction())
1762
+
1097
1763
  if injected_instructions:
1098
1764
  combined_instructions = "\n\n---\n\n".join(injected_instructions)
1099
1765
  if llm_request.config is None:
@@ -1612,9 +2278,11 @@ def notify_tool_invocation_start_callback(
1612
2278
  tool_args=serializable_args,
1613
2279
  function_call_id=tool_context.function_call_id,
1614
2280
  )
1615
- asyncio.run_coroutine_threadsafe(
1616
- _publish_data_part_status_update(host_component, a2a_context, tool_data),
1617
- host_component.get_async_loop(),
2281
+ host_component.publish_data_signal_from_thread(
2282
+ a2a_context=a2a_context,
2283
+ signal_data=tool_data,
2284
+ skip_buffer_flush=False,
2285
+ log_identifier=log_identifier,
1618
2286
  )
1619
2287
  log.debug(
1620
2288
  "%s Scheduled tool_invocation_start notification.",
@@ -1685,9 +2353,11 @@ def notify_tool_execution_result_callback(
1685
2353
  result_data=serializable_response,
1686
2354
  function_call_id=tool_context.function_call_id,
1687
2355
  )
1688
- asyncio.run_coroutine_threadsafe(
1689
- _publish_data_part_status_update(host_component, a2a_context, tool_data),
1690
- host_component.get_async_loop(),
2356
+ host_component.publish_data_signal_from_thread(
2357
+ a2a_context=a2a_context,
2358
+ signal_data=tool_data,
2359
+ skip_buffer_flush=False,
2360
+ log_identifier=log_identifier,
1691
2361
  )
1692
2362
  log.debug(
1693
2363
  "%s Scheduled tool_result notification for function call ID %s.",
@@ -1785,3 +2455,76 @@ def auto_continue_on_max_tokens_callback(
1785
2455
  )
1786
2456
 
1787
2457
  return hijacked_response
2458
+
2459
+
2460
+ def preregister_long_running_tools_callback(
2461
+ callback_context: CallbackContext,
2462
+ llm_response: LlmResponse,
2463
+ host_component: "SamAgentComponent",
2464
+ ) -> Optional[LlmResponse]:
2465
+ """
2466
+ ADK after_model_callback to pre-register all long-running tool calls
2467
+ before any tool execution begins. This prevents race conditions where
2468
+ one tool completes before another has registered.
2469
+
2470
+ The race condition occurs because tools are executed via asyncio.gather
2471
+ (non-deterministic order) and each tool calls register_parallel_call_sent()
2472
+ inside its run_async(). If Tool A completes before Tool B even registers,
2473
+ the system thinks all calls are done (completed=1, total=1).
2474
+
2475
+ By pre-registering all long-running tools in this callback (which runs
2476
+ BEFORE tool execution), we ensure the total count is set correctly upfront.
2477
+ """
2478
+ log_identifier = "[Callback:PreregisterLongRunning]"
2479
+
2480
+ # Only process non-partial responses with function calls
2481
+ if llm_response.partial:
2482
+ return None
2483
+
2484
+ if not llm_response.content or not llm_response.content.parts:
2485
+ return None
2486
+
2487
+ # Find all long-running tool calls (identified by peer_ prefix)
2488
+ long_running_calls = []
2489
+ for part in llm_response.content.parts:
2490
+ if part.function_call:
2491
+ tool_name = part.function_call.name
2492
+ if tool_name.startswith(PEER_TOOL_PREFIX):
2493
+ long_running_calls.append(part.function_call)
2494
+
2495
+ if not long_running_calls:
2496
+ return None
2497
+
2498
+ # Get task context
2499
+ a2a_context = callback_context.state.get("a2a_context")
2500
+ if not a2a_context:
2501
+ log.warning("%s No a2a_context, cannot pre-register tools", log_identifier)
2502
+ return None
2503
+
2504
+ logical_task_id = a2a_context.get("logical_task_id")
2505
+ invocation_id = callback_context._invocation_context.invocation_id
2506
+
2507
+ with host_component.active_tasks_lock:
2508
+ task_context = host_component.active_tasks.get(logical_task_id)
2509
+
2510
+ if not task_context:
2511
+ log.warning(
2512
+ "%s TaskContext not found for %s, cannot pre-register",
2513
+ log_identifier,
2514
+ logical_task_id,
2515
+ )
2516
+ return None
2517
+
2518
+ # Pre-register ALL long-running calls atomically
2519
+ for fc in long_running_calls:
2520
+ task_context.register_parallel_call_sent(invocation_id)
2521
+
2522
+ log.info(
2523
+ "%s Pre-registered %d long-running tool call(s) for invocation %s (task %s)",
2524
+ log_identifier,
2525
+ len(long_running_calls),
2526
+ invocation_id,
2527
+ logical_task_id,
2528
+ )
2529
+
2530
+ return None # Don't alter the response