solace-agent-mesh 1.7.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 (447) 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/artifacts/filesystem_artifact_service.py +164 -0
  7. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +163 -0
  8. solace_agent_mesh/agent/adk/callbacks.py +752 -127
  9. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +99 -7
  10. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +52 -5
  11. solace_agent_mesh/agent/adk/mcp_content_processor.py +1 -1
  12. solace_agent_mesh/agent/adk/models/lite_llm.py +34 -16
  13. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +24 -137
  14. solace_agent_mesh/agent/adk/runner.py +66 -8
  15. solace_agent_mesh/agent/adk/schema_migration.py +88 -0
  16. solace_agent_mesh/agent/adk/services.py +41 -1
  17. solace_agent_mesh/agent/adk/setup.py +220 -32
  18. solace_agent_mesh/agent/adk/stream_parser.py +229 -40
  19. solace_agent_mesh/agent/protocol/event_handlers.py +219 -33
  20. solace_agent_mesh/agent/proxies/a2a/component.py +572 -75
  21. solace_agent_mesh/agent/proxies/a2a/config.py +80 -4
  22. solace_agent_mesh/agent/proxies/base/component.py +188 -22
  23. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +3 -1
  24. solace_agent_mesh/agent/sac/app.py +37 -12
  25. solace_agent_mesh/agent/sac/component.py +322 -52
  26. solace_agent_mesh/agent/sac/patch_adk.py +8 -16
  27. solace_agent_mesh/agent/sac/task_execution_context.py +90 -0
  28. solace_agent_mesh/agent/tools/__init__.py +3 -0
  29. solace_agent_mesh/agent/tools/audio_tools.py +3 -3
  30. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +698 -24
  31. solace_agent_mesh/agent/tools/deep_research_tools.py +2161 -0
  32. solace_agent_mesh/agent/tools/peer_agent_tool.py +82 -15
  33. solace_agent_mesh/agent/tools/time_tools.py +126 -0
  34. solace_agent_mesh/agent/tools/tool_config_types.py +54 -2
  35. solace_agent_mesh/agent/tools/web_search_tools.py +279 -0
  36. solace_agent_mesh/agent/tools/web_tools.py +125 -17
  37. solace_agent_mesh/agent/utils/artifact_helpers.py +243 -5
  38. solace_agent_mesh/agent/utils/context_helpers.py +17 -0
  39. solace_agent_mesh/assets/docs/404.html +6 -6
  40. solace_agent_mesh/assets/docs/assets/css/{styles.906a1503.css → styles.8162edfb.css} +1 -1
  41. solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
  42. solace_agent_mesh/assets/docs/assets/js/15ba94aa.e186750d.js +1 -0
  43. solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
  44. solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
  45. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
  46. solace_agent_mesh/assets/docs/assets/js/{17896441.a5e82f9b.js.LICENSE.txt → 2279.550aa580.js.LICENSE.txt} +6 -0
  47. solace_agent_mesh/assets/docs/assets/js/240a0364.83e37aa8.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.2f0db237.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/3a6c6137.7e61915d.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/3ac1795d.7f7ab1c1.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/3ff0015d.e53c9b78.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/41adc471.0e95b87c.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/4667dc50.bf2ad456.js +1 -0
  54. solace_agent_mesh/assets/docs/assets/js/49eed117.493d6f99.js +1 -0
  55. solace_agent_mesh/assets/docs/assets/js/{509e993c.4c7a1a6d.js → 509e993c.a1fbf45a.js} +1 -1
  56. solace_agent_mesh/assets/docs/assets/js/547e15cc.8e6da617.js +1 -0
  57. solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/5b8d9c11.d4eb37b8.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.1ee87753.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/64195356.09dbd087.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/66d4869e.30340bd3.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/729898df.7249e9fd.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/7e294c01.7c5f6906.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/8024126c.e3467286.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/81a99df0.7ed65d45.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/9bb13469.4523ae20.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/a7d42657.a956689d.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e563275.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/e04b235d.06d23db6.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.deb2b62e.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/e6f9706b.acc800d3.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/e92d0134.c147a429.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/ee0c2fe7.94d0a351.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/f284c35a.cc97854c.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/main.d634009f.js +2 -0
  87. solace_agent_mesh/assets/docs/assets/js/runtime~main.27bb82a7.js +1 -0
  88. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +68 -68
  89. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +50 -50
  90. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +42 -42
  91. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +55 -55
  92. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +75 -75
  93. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/image-tools/index.html +81 -0
  94. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +67 -50
  95. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/research-tools/index.html +136 -0
  96. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +178 -144
  97. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +43 -42
  98. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +20 -18
  99. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +23 -23
  100. solace_agent_mesh/assets/docs/docs/documentation/components/platform-service/index.html +33 -0
  101. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +45 -45
  102. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +98 -112
  103. solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
  104. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +208 -125
  105. solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
  106. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +28 -28
  107. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +29 -29
  108. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +14 -14
  109. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/index.html +47 -0
  110. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/kubernetes-deployment-guide/index.html +197 -0
  111. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +67 -53
  112. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +17 -17
  113. solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
  114. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +38 -38
  115. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +87 -87
  116. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +67 -49
  117. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +17 -17
  118. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +51 -51
  119. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +22 -22
  120. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +27 -27
  121. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +135 -135
  122. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +66 -66
  123. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +51 -51
  124. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +50 -38
  125. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +86 -86
  126. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +51 -51
  127. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +24 -24
  128. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +30 -30
  129. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +44 -44
  130. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
  131. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +50 -23
  132. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +29 -24
  133. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +21 -21
  134. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +40 -37
  135. solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +96 -66
  137. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +181 -181
  138. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +75 -75
  139. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +27 -27
  140. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +44 -44
  141. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +39 -38
  142. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +30 -30
  143. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +18 -18
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/vibe_coding/index.html +62 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +135 -114
  146. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +37 -37
  147. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +14 -14
  148. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +27 -25
  149. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +69 -69
  150. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +72 -72
  151. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +112 -112
  152. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +28 -28
  153. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +42 -42
  154. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +20 -20
  155. solace_agent_mesh/assets/docs/docs/documentation/migrations/platform-service-split/index.html +85 -0
  156. solace_agent_mesh/assets/docs/lunr-index-1768329217460.json +1 -0
  157. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  158. solace_agent_mesh/assets/docs/search-doc-1768329217460.json +1 -0
  159. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  160. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  161. solace_agent_mesh/cli/__init__.py +1 -1
  162. solace_agent_mesh/cli/commands/add_cmd/__init__.py +3 -1
  163. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -1
  164. solace_agent_mesh/cli/commands/add_cmd/proxy_cmd.py +100 -0
  165. solace_agent_mesh/cli/commands/eval_cmd.py +1 -1
  166. solace_agent_mesh/cli/commands/init_cmd/__init__.py +15 -0
  167. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +1 -1
  168. solace_agent_mesh/cli/commands/init_cmd/env_step.py +30 -3
  169. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +3 -4
  170. solace_agent_mesh/cli/commands/init_cmd/platform_service_step.py +85 -0
  171. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +16 -3
  172. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +2 -1
  173. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +1 -0
  174. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +3 -3
  175. solace_agent_mesh/cli/commands/run_cmd.py +64 -49
  176. solace_agent_mesh/cli/commands/tools_cmd.py +315 -0
  177. solace_agent_mesh/cli/main.py +15 -0
  178. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-tcIFZLis.js → authCallback-KnKMP_vb.js} +1 -1
  179. solace_agent_mesh/client/webui/frontend/static/assets/client-DpBL2stg.js +25 -0
  180. solace_agent_mesh/client/webui/frontend/static/assets/main-Cd498TV2.js +435 -0
  181. solace_agent_mesh/client/webui/frontend/static/assets/main-rSf8Vu29.css +1 -0
  182. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CINwxvwV.js → vendor-CGk8Suyh.js} +189 -94
  183. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  184. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  185. solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
  186. solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
  187. solace_agent_mesh/common/a2a/types.py +1 -1
  188. solace_agent_mesh/common/agent_registry.py +38 -11
  189. solace_agent_mesh/common/data_parts.py +124 -0
  190. solace_agent_mesh/common/error_handlers.py +83 -0
  191. solace_agent_mesh/common/exceptions.py +24 -0
  192. solace_agent_mesh/common/oauth/__init__.py +17 -0
  193. solace_agent_mesh/common/oauth/oauth_client.py +408 -0
  194. solace_agent_mesh/common/oauth/utils.py +50 -0
  195. solace_agent_mesh/common/rag_dto.py +156 -0
  196. solace_agent_mesh/common/sac/sam_component_base.py +73 -1
  197. solace_agent_mesh/common/sam_events/event_service.py +2 -2
  198. solace_agent_mesh/common/utils/embeds/converter.py +1 -8
  199. solace_agent_mesh/common/utils/embeds/modifiers.py +2 -27
  200. solace_agent_mesh/common/utils/embeds/resolver.py +94 -25
  201. solace_agent_mesh/common/utils/embeds/types.py +1 -0
  202. solace_agent_mesh/common/utils/log_formatters.py +20 -0
  203. solace_agent_mesh/common/utils/mime_helpers.py +12 -5
  204. solace_agent_mesh/common/utils/rbac_utils.py +69 -0
  205. solace_agent_mesh/common/utils/templates/__init__.py +8 -0
  206. solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
  207. solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
  208. solace_agent_mesh/config_portal/backend/common.py +12 -0
  209. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-CljP4_mv.js +103 -0
  210. solace_agent_mesh/config_portal/frontend/static/client/assets/{components-Rk0n-9cK.js → components-CaC6hG8d.js} +22 -22
  211. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-mvZjNKiz.js → entry.client-H_TM0YBt.js} +3 -3
  212. solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DzNKzXrc.js → index-CnFykb2v.js} +16 -16
  213. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-f8439d40.js +1 -0
  214. solace_agent_mesh/config_portal/frontend/static/client/assets/root-BIMqslJB.css +1 -0
  215. solace_agent_mesh/config_portal/frontend/static/client/assets/root-mJmTIdIk.js +10 -0
  216. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  217. solace_agent_mesh/core_a2a/service.py +3 -2
  218. solace_agent_mesh/gateway/adapter/base.py +28 -1
  219. solace_agent_mesh/gateway/adapter/types.py +9 -0
  220. solace_agent_mesh/gateway/base/app.py +10 -0
  221. solace_agent_mesh/gateway/base/auth_interface.py +103 -0
  222. solace_agent_mesh/gateway/base/component.py +451 -10
  223. solace_agent_mesh/gateway/generic/component.py +274 -30
  224. solace_agent_mesh/gateway/http_sse/alembic/env.py +0 -7
  225. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +2 -43
  226. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +2 -2
  227. solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
  228. solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
  229. solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
  230. solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
  231. solace_agent_mesh/gateway/http_sse/alembic.ini +0 -36
  232. solace_agent_mesh/gateway/http_sse/app.py +23 -6
  233. solace_agent_mesh/gateway/http_sse/component.py +158 -73
  234. solace_agent_mesh/gateway/http_sse/dependencies.py +50 -57
  235. solace_agent_mesh/gateway/http_sse/main.py +58 -482
  236. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +2 -2
  237. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +1 -1
  238. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +1 -1
  239. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -2
  240. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +7 -0
  241. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +2 -2
  242. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +2 -2
  243. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +5 -0
  244. solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
  245. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +1 -1
  246. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +8 -1
  247. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +1 -1
  248. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +1 -1
  249. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +12 -107
  250. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +86 -2
  251. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +38 -7
  252. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +113 -7
  253. solace_agent_mesh/gateway/http_sse/routers/auth.py +69 -132
  254. solace_agent_mesh/gateway/http_sse/routers/config.py +235 -10
  255. solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
  256. solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
  257. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +1 -1
  258. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +1 -1
  259. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +1 -0
  260. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +3 -2
  261. solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
  262. solace_agent_mesh/gateway/http_sse/routers/feedback.py +2 -2
  263. solace_agent_mesh/gateway/http_sse/routers/people.py +2 -2
  264. solace_agent_mesh/gateway/http_sse/routers/projects.py +250 -24
  265. solace_agent_mesh/gateway/http_sse/routers/prompts.py +1416 -0
  266. solace_agent_mesh/gateway/http_sse/routers/sessions.py +14 -5
  267. solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
  268. solace_agent_mesh/gateway/http_sse/routers/sse.py +117 -4
  269. solace_agent_mesh/gateway/http_sse/routers/tasks.py +509 -149
  270. solace_agent_mesh/gateway/http_sse/routers/users.py +1 -1
  271. solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
  272. solace_agent_mesh/gateway/http_sse/routers/visualization.py +2 -1
  273. solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
  274. solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
  275. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +1 -1
  276. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +1 -1
  277. solace_agent_mesh/gateway/http_sse/services/project_service.py +539 -12
  278. solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
  279. solace_agent_mesh/gateway/http_sse/services/session_service.py +198 -21
  280. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +354 -4
  281. solace_agent_mesh/gateway/http_sse/sse_manager.py +280 -169
  282. solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
  283. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +41 -1
  284. solace_agent_mesh/services/__init__.py +0 -0
  285. solace_agent_mesh/services/platform/__init__.py +29 -0
  286. solace_agent_mesh/services/platform/alembic/env.py +85 -0
  287. solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
  288. solace_agent_mesh/services/platform/alembic.ini +109 -0
  289. solace_agent_mesh/services/platform/api/__init__.py +3 -0
  290. solace_agent_mesh/services/platform/api/dependencies.py +154 -0
  291. solace_agent_mesh/services/platform/api/main.py +314 -0
  292. solace_agent_mesh/services/platform/api/middleware.py +51 -0
  293. solace_agent_mesh/services/platform/api/routers/__init__.py +33 -0
  294. solace_agent_mesh/services/platform/api/routers/health_router.py +31 -0
  295. solace_agent_mesh/services/platform/app.py +215 -0
  296. solace_agent_mesh/services/platform/component.py +777 -0
  297. solace_agent_mesh/shared/__init__.py +14 -0
  298. solace_agent_mesh/shared/api/__init__.py +42 -0
  299. solace_agent_mesh/shared/auth/__init__.py +26 -0
  300. solace_agent_mesh/shared/auth/dependencies.py +204 -0
  301. solace_agent_mesh/shared/auth/middleware.py +347 -0
  302. solace_agent_mesh/shared/database/__init__.py +20 -0
  303. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/base_repository.py +1 -1
  304. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_exceptions.py +1 -1
  305. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_helpers.py +1 -1
  306. solace_agent_mesh/shared/exceptions/__init__.py +36 -0
  307. solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exception_handlers.py +1 -1
  308. solace_agent_mesh/shared/utils/__init__.py +21 -0
  309. solace_agent_mesh/templates/logging_config_template.yaml +48 -0
  310. solace_agent_mesh/templates/main_orchestrator.yaml +12 -1
  311. solace_agent_mesh/templates/platform.yaml +49 -0
  312. solace_agent_mesh/templates/plugin_readme_template.md +3 -25
  313. solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
  314. solace_agent_mesh/templates/proxy_template.yaml +62 -0
  315. solace_agent_mesh/templates/webui.yaml +148 -6
  316. solace_agent_mesh/tools/web_search/__init__.py +18 -0
  317. solace_agent_mesh/tools/web_search/base.py +84 -0
  318. solace_agent_mesh/tools/web_search/google_search.py +247 -0
  319. solace_agent_mesh/tools/web_search/models.py +99 -0
  320. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/METADATA +29 -8
  321. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/RECORD +334 -313
  322. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/WHEEL +1 -1
  323. solace_agent_mesh/agent/adk/adk_llm.txt +0 -226
  324. solace_agent_mesh/agent/adk/adk_llm_detail.txt +0 -566
  325. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +0 -171
  326. solace_agent_mesh/agent/adk/models/models_llm.txt +0 -189
  327. solace_agent_mesh/agent/agent_llm.txt +0 -369
  328. solace_agent_mesh/agent/agent_llm_detail.txt +0 -1702
  329. solace_agent_mesh/agent/protocol/protocol_llm.txt +0 -81
  330. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +0 -92
  331. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +0 -190
  332. solace_agent_mesh/agent/proxies/base/base_llm.txt +0 -148
  333. solace_agent_mesh/agent/proxies/proxies_llm.txt +0 -283
  334. solace_agent_mesh/agent/sac/sac_llm.txt +0 -189
  335. solace_agent_mesh/agent/sac/sac_llm_detail.txt +0 -200
  336. solace_agent_mesh/agent/testing/testing_llm.txt +0 -58
  337. solace_agent_mesh/agent/testing/testing_llm_detail.txt +0 -68
  338. solace_agent_mesh/agent/tools/tools_llm.txt +0 -276
  339. solace_agent_mesh/agent/tools/tools_llm_detail.txt +0 -275
  340. solace_agent_mesh/agent/utils/utils_llm.txt +0 -152
  341. solace_agent_mesh/agent/utils/utils_llm_detail.txt +0 -149
  342. solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +0 -1
  343. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +0 -1
  344. solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +0 -1
  345. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +0 -2
  346. solace_agent_mesh/assets/docs/assets/js/240a0364.c39f8388.js +0 -1
  347. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +0 -1
  348. solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +0 -1
  349. solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +0 -1
  350. solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +0 -1
  351. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +0 -1
  352. solace_agent_mesh/assets/docs/assets/js/55b7b518.f2b1d1ba.js +0 -1
  353. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +0 -1
  354. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +0 -1
  355. solace_agent_mesh/assets/docs/assets/js/66d4869e.830d443f.js +0 -1
  356. solace_agent_mesh/assets/docs/assets/js/6d84eae0.4a5fbf39.js +0 -1
  357. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +0 -1
  358. solace_agent_mesh/assets/docs/assets/js/81a99df0.07034dd9.js +0 -1
  359. solace_agent_mesh/assets/docs/assets/js/82fbfb93.139a1a1f.js +0 -1
  360. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +0 -1
  361. solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +0 -1
  362. solace_agent_mesh/assets/docs/assets/js/9bb13469.dd1c9b54.js +0 -1
  363. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +0 -1
  364. solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +0 -1
  365. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.eaff365e.js +0 -1
  366. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +0 -1
  367. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +0 -1
  368. solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +0 -1
  369. solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +0 -1
  370. solace_agent_mesh/assets/docs/assets/js/e6f9706b.045d0fa1.js +0 -1
  371. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +0 -1
  372. solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +0 -1
  373. solace_agent_mesh/assets/docs/assets/js/main.f213fe0c.js +0 -2
  374. solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +0 -1
  375. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +0 -47
  376. solace_agent_mesh/assets/docs/lunr-index-1762283454666.json +0 -1
  377. solace_agent_mesh/assets/docs/search-doc-1762283454666.json +0 -1
  378. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +0 -250
  379. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +0 -365
  380. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +0 -305
  381. solace_agent_mesh/client/webui/frontend/static/assets/client-CRYdKo2Q.js +0 -25
  382. solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +0 -1
  383. solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +0 -353
  384. solace_agent_mesh/common/a2a/a2a_llm.txt +0 -175
  385. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +0 -193
  386. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +0 -445
  387. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +0 -736
  388. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +0 -330
  389. solace_agent_mesh/common/common_llm.txt +0 -230
  390. solace_agent_mesh/common/common_llm_detail.txt +0 -2562
  391. solace_agent_mesh/common/middleware/middleware_llm.txt +0 -174
  392. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +0 -185
  393. solace_agent_mesh/common/sac/sac_llm.txt +0 -71
  394. solace_agent_mesh/common/sac/sac_llm_detail.txt +0 -82
  395. solace_agent_mesh/common/sam_events/sam_events_llm.txt +0 -104
  396. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +0 -115
  397. solace_agent_mesh/common/services/providers/providers_llm.txt +0 -81
  398. solace_agent_mesh/common/services/services_llm.txt +0 -368
  399. solace_agent_mesh/common/services/services_llm_detail.txt +0 -459
  400. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +0 -220
  401. solace_agent_mesh/common/utils/utils_llm.txt +0 -335
  402. solace_agent_mesh/common/utils/utils_llm_detail.txt +0 -572
  403. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +0 -98
  404. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-61038fc6.js +0 -1
  405. solace_agent_mesh/config_portal/frontend/static/client/assets/root-BWvk5-gF.js +0 -10
  406. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +0 -1
  407. solace_agent_mesh/core_a2a/core_a2a_llm.txt +0 -90
  408. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +0 -101
  409. solace_agent_mesh/gateway/base/base_llm.txt +0 -226
  410. solace_agent_mesh/gateway/base/base_llm_detail.txt +0 -235
  411. solace_agent_mesh/gateway/gateway_llm.txt +0 -369
  412. solace_agent_mesh/gateway/gateway_llm_detail.txt +0 -3885
  413. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +0 -345
  414. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +0 -92
  415. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +0 -161
  416. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +0 -105
  417. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +0 -299
  418. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +0 -3278
  419. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +0 -221
  420. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +0 -257
  421. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +0 -308
  422. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +0 -450
  423. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +0 -133
  424. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +0 -123
  425. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +0 -312
  426. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +0 -303
  427. solace_agent_mesh/gateway/http_sse/shared/__init__.py +0 -146
  428. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +0 -319
  429. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +0 -47
  430. solace_agent_mesh/llm.txt +0 -228
  431. solace_agent_mesh/llm_detail.txt +0 -2835
  432. solace_agent_mesh/solace_agent_mesh_llm.txt +0 -362
  433. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +0 -8599
  434. solace_agent_mesh/templates/logging_config_template.ini +0 -45
  435. solace_agent_mesh/templates/templates_llm.txt +0 -147
  436. /solace_agent_mesh/assets/docs/assets/js/{main.f213fe0c.js.LICENSE.txt → main.d634009f.js.LICENSE.txt} +0 -0
  437. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/auth_utils.py +0 -0
  438. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/pagination.py +0 -0
  439. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/response_utils.py +0 -0
  440. /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/error_dto.py +0 -0
  441. /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exceptions.py +0 -0
  442. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/enums.py +0 -0
  443. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/timestamp_utils.py +0 -0
  444. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/types.py +0 -0
  445. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/utils.py +0 -0
  446. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/entry_points.txt +0 -0
  447. {solace_agent_mesh-1.7.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,6 +238,9 @@ 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")
173
245
  if filename == "unknown_artifact":
174
246
  log.warning(
@@ -201,13 +273,14 @@ async def process_artifact_blocks_callback(
201
273
  bytes_transferred=0,
202
274
  artifact_chunk=None,
203
275
  )
276
+
204
277
  await _publish_data_part_status_update(
205
278
  host_component, a2a_context, artifact_progress_data
206
279
  )
207
280
  params_str = " ".join(
208
281
  [f'{k}="{v}"' for k, v in event.params.items()]
209
282
  )
210
- original_text = f"«««save_artifact: {params_str}\n"
283
+ original_text = f"{ARTIFACT_BLOCK_DELIMITER_OPEN}save_artifact: {params_str}\n"
211
284
  session.state["artifact_block_original_text"] = original_text
212
285
 
213
286
  elif isinstance(event, BlockProgressedEvent):
@@ -224,19 +297,36 @@ async def process_artifact_blocks_callback(
224
297
  log_identifier,
225
298
  )
226
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
+
227
308
  progress_data = ArtifactCreationProgressData(
228
309
  filename=filename,
229
310
  description=params.get("description"),
230
311
  status="in-progress",
231
312
  bytes_transferred=event.buffered_size,
232
- artifact_chunk=event.chunk,
313
+ artifact_chunk=resolved_chunk, # Resolved chunk
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
233
320
  )
321
+ new_char_count = previous_char_count + len(event.chunk)
322
+ session.state["artifact_chars_sent"] = new_char_count
323
+
234
324
  await _publish_data_part_status_update(
235
325
  host_component, a2a_context, progress_data
236
326
  )
237
327
 
238
328
  elif isinstance(event, BlockCompletedEvent):
239
- log.info(
329
+ log.debug(
240
330
  "%s Event: BlockCompleted. Content length: %d",
241
331
  log_identifier,
242
332
  len(event.content),
@@ -333,6 +423,13 @@ async def process_artifact_blocks_callback(
333
423
  version_for_tool,
334
424
  logical_task_id,
335
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
+ )
336
433
  else:
337
434
  log.warning(
338
435
  "%s No logical_task_id, cannot register inline artifact.",
@@ -344,6 +441,39 @@ async def process_artifact_blocks_callback(
344
441
  log_identifier,
345
442
  e_track,
346
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
+
347
477
  # Publish completion status immediately via SSE
348
478
  if a2a_context:
349
479
  progress_data = ArtifactCreationProgressData(
@@ -377,10 +507,106 @@ async def process_artifact_blocks_callback(
377
507
  "filename": filename,
378
508
  "version": version_for_tool,
379
509
  "status": status_for_tool,
510
+ "description": params.get("description"),
511
+ "mime_type": params.get("mime_type"),
512
+ "bytes_transferred": len(event.content),
380
513
  "original_text": original_text,
381
514
  }
382
515
  )
383
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
+
384
610
  elif isinstance(event, BlockInvalidatedEvent):
385
611
  log.debug(
386
612
  "%s Event: BlockInvalidated. Rolled back: '%s'",
@@ -466,8 +692,12 @@ async def process_artifact_blocks_callback(
466
692
  len(completed_blocks_list),
467
693
  )
468
694
 
695
+ # Get a2a_context for sending signals
696
+ a2a_context = callback_context.state.get("a2a_context")
697
+
469
698
  tool_call_parts = []
470
699
  for block_info in completed_blocks_list:
700
+ function_call_id = f"host-notify-{uuid.uuid4()}"
471
701
  notify_tool_call = adk_types.FunctionCall(
472
702
  name="_notify_artifact_save",
473
703
  args={
@@ -475,10 +705,41 @@ async def process_artifact_blocks_callback(
475
705
  "version": block_info["version"],
476
706
  "status": block_info["status"],
477
707
  },
478
- id=f"host-notify-{uuid.uuid4()}",
708
+ id=function_call_id,
479
709
  )
480
710
  tool_call_parts.append(adk_types.Part(function_call=notify_tool_call))
481
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
+
482
743
  existing_parts = llm_response.content.parts if llm_response.content else []
483
744
  final_existing_parts = existing_parts
484
745
 
@@ -493,6 +754,7 @@ async def process_artifact_blocks_callback(
493
754
  session.state[parser_state_key] = None
494
755
  session.state["completed_artifact_blocks_list"] = None
495
756
  session.state["artifact_block_original_text"] = None
757
+ session.state["completed_template_blocks_list"] = None
496
758
  log.debug("%s Cleaned up parser session state.", log_identifier)
497
759
 
498
760
  return None
@@ -745,26 +1007,24 @@ async def manage_large_mcp_tool_responses_callback(
745
1007
  message_parts_for_llm: list[str] = []
746
1008
 
747
1009
  if needs_truncation_for_llm:
748
- truncation_suffix = "... [Response truncated due to size limit.]"
749
- adjusted_max_bytes = llm_max_bytes - len(truncation_suffix.encode("utf-8"))
750
- if adjusted_max_bytes < 0:
751
- adjusted_max_bytes = 0
752
-
753
- truncated_bytes = serialized_original_response_str.encode("utf-8")[
754
- :adjusted_max_bytes
755
- ]
756
- truncated_preview_str = (
757
- truncated_bytes.decode("utf-8", "ignore") + truncation_suffix
758
- )
759
-
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.
760
1014
  final_llm_response_dict["mcp_tool_output"] = {
761
- "type": "truncated_json_string",
762
- "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.",
763
1017
  }
764
1018
  message_parts_for_llm.append(
765
- 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,
766
1027
  )
767
- log.debug("%s MCP tool output truncated for LLM.", log_identifier)
768
1028
 
769
1029
  if needs_saving_as_artifact:
770
1030
  if save_result and save_result.status in [
@@ -781,19 +1041,27 @@ async def manage_large_mcp_tool_responses_callback(
781
1041
  filename = first_artifact.data_filename
782
1042
  version = first_artifact.data_version
783
1043
  if total_artifacts > 1:
784
- message_parts_for_llm.append(
785
- f"The full response has been saved as {total_artifacts} artifacts, starting with '{filename}' (version {version})."
786
- )
1044
+ artifact_msg = f"The full response has been saved as {total_artifacts} artifacts, starting with '{filename}' (version {version})."
787
1045
  else:
788
- message_parts_for_llm.append(
789
- 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}").'
790
1053
  )
1054
+ message_parts_for_llm.append(artifact_msg)
791
1055
  elif save_result.fallback_artifact:
792
1056
  filename = save_result.fallback_artifact.data_filename
793
1057
  version = save_result.fallback_artifact.data_version
794
- message_parts_for_llm.append(
795
- f"The full response has been saved as artifact '{filename}' (version {version})."
796
- )
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)
797
1065
 
798
1066
  log.debug(
799
1067
  "%s Added saved artifact details to LLM response.", log_identifier
@@ -818,16 +1086,18 @@ async def manage_large_mcp_tool_responses_callback(
818
1086
  and save_result.status in [McpSaveStatus.SUCCESS, McpSaveStatus.PARTIAL_SUCCESS]
819
1087
  ):
820
1088
  if needs_truncation_for_llm:
821
- 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"
822
1091
  else:
823
1092
  final_llm_response_dict["status"] = "processed_and_saved"
824
1093
  elif needs_saving_as_artifact:
825
1094
  if needs_truncation_for_llm:
826
- final_llm_response_dict["status"] = "processed_truncated_save_failed"
1095
+ final_llm_response_dict["status"] = "processed_artifact_only_save_failed"
827
1096
  else:
828
1097
  final_llm_response_dict["status"] = "processed_save_failed"
829
1098
  elif needs_truncation_for_llm:
830
- 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"
831
1101
  else:
832
1102
  final_llm_response_dict["status"] = "processed"
833
1103
 
@@ -843,60 +1113,113 @@ async def manage_large_mcp_tool_responses_callback(
843
1113
  return final_llm_response_dict
844
1114
 
845
1115
 
846
- def _generate_fenced_artifact_instruction() -> str:
847
- """Generates the instruction text for using fenced artifact blocks."""
1116
+ def _generate_fenced_block_syntax_rules() -> str:
1117
+ """Generates the shared syntax rules for all fenced blocks."""
848
1118
  open_delim = ARTIFACT_BLOCK_DELIMITER_OPEN
849
1119
  close_delim = ARTIFACT_BLOCK_DELIMITER_CLOSE
850
- return f"""\
851
- **Creating Text-Based Artifacts:**
852
-
853
- **When to Create Text-based Artifacts:**
854
- Create an artifact when the content provides value as a standalone file:
855
- - Content with special formatting (HTML, Markdown, CSS, structured markup) that requires proper rendering
856
- - Content explicitly intended for use outside this conversation (reports, emails, presentations, reference documents)
857
- - Structured reference content users will save or follow (schedules, guides, templates)
858
- - Content that will be edited, expanded, or reused
859
- - Substantial text documents
860
- - Technical documentation meant as reference material
861
-
862
- **When NOT to Create Text-based Artifacts:**
863
- - Simple answers, explanations, or conversational responses
864
- - Brief advice, opinions, or quick information
865
- - Short lists, summaries, or single paragraphs
866
- - Temporary content only relevant to the immediate conversation
867
- - Basic explanations that don't require reference material
868
-
869
- **Behaviour of created artifacts:**
870
- - they are sent back to the UI inline with the text and show up as an interactive file component
871
- - the user can easily see the content so there is no need to return or embed it again.
872
- - do not embed the same artifact again, since the user already has it to expand and view
873
-
874
- **How to create artifacts:**
875
- To create an artifact from content you generate (like code, a report, or a document), you MUST use a fenced artifact block with the EXACT syntax shown below. This is the only reliable way to ensure your content is saved correctly.
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.
876
1123
 
877
1124
  **EXACT SYNTAX (copy this pattern exactly):**
878
- {open_delim}save_artifact: filename="your_filename.ext" mime_type="text/plain" description="A brief description."
879
- The full content you want to save goes here.
1125
+ {open_delim}keyword: parameter="value" ...
1126
+ The content for the block goes here.
880
1127
  It can span multiple lines.
881
1128
  {close_delim}
882
1129
 
883
1130
  **CRITICAL FORMATTING RULES:**
884
- 1. The opening delimiter MUST be EXACTLY three angle brackets: `{open_delim}` (not `{open_delim[0:2]}` or `{open_delim[0:1]}`)
885
- 2. Immediately after the opening delimiter, write `save_artifact:` with a colon and NO space before the colon
886
- 3. Parameters (filename, mime_type, description) must be on the SAME line as the opening delimiter
887
- 4. All parameter values **MUST** be enclosed in double quotes: `filename="example.txt"`
888
- 5. You **MUST NOT** use double quotes `"` inside parameter values. Use single quotes or rephrase instead
889
- 6. After all parameters, press enter/newline, then write your content
890
- 7. Close the block with EXACTLY three angle brackets: `{close_delim}` on its own line
891
- 8. Do NOT surround the block with triple backticks (```). The delimiters `{open_delim}` and `{close_delim}` are sufficient
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.
892
1139
 
893
1140
  **COMMON ERRORS TO AVOID:**
1141
+ ❌ WRONG: `{open_delim[0:1]}template_liquid:` (only 1 angle brackets)
894
1142
  ❌ WRONG: `{open_delim[0:2]}save_artifact:` (only 2 angle brackets)
895
- ❌ WRONG: `{open_delim[0:1]}save_artifact:` (only 1 angle bracket)
896
1143
  ❌ WRONG: `{open_delim}save_artifact` (missing colon)
897
1144
  ✅ CORRECT: `{open_delim}save_artifact: filename="test.txt" mime_type="text/plain"`
1145
+ """
1146
+
1147
+
1148
+ def _generate_fenced_artifact_instruction() -> str:
1149
+ """Generates the instruction text for using fenced artifact blocks."""
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
1181
+ close_delim = ARTIFACT_BLOCK_DELIMITER_CLOSE
1182
+ return f"""\
1183
+ **Inline Liquid Templates (`{open_delim}template_liquid: ...`):**
1184
+
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 %}}
1209
+ {close_delim}
898
1210
 
899
- The system will automatically save the content and give you a confirmation in the next turn by way of an automatically injected _notify_artifact_save tool call.
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.
1215
+
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.
900
1223
  """
901
1224
 
902
1225
 
@@ -904,7 +1227,7 @@ def _generate_artifact_creation_instruction() -> str:
904
1227
  return """
905
1228
  **Creating Text-Based Artifacts:**
906
1229
 
907
- **When to Create Text-based Artifacts:**
1230
+ When to Create Text-based Artifacts:
908
1231
  Create an artifact when the content provides value as a standalone file:
909
1232
  - Content with special formatting (HTML, Markdown, CSS, structured markup) that requires proper rendering
910
1233
  - Content explicitly intended for use outside this conversation (reports, emails, presentations, reference documents)
@@ -913,15 +1236,123 @@ def _generate_artifact_creation_instruction() -> str:
913
1236
  - Substantial text documents
914
1237
  - Technical documentation meant as reference material
915
1238
 
916
- **When NOT to Create Text-based Artifacts:**
1239
+ When NOT to Create Text-based Artifacts:
917
1240
  - Simple answers, explanations, or conversational responses
918
1241
  - Brief advice, opinions, or quick information
919
- - Short lists, summaries, or single paragraphs
1242
+ - Short lists, summaries, or single paragraphs
920
1243
  - Temporary content only relevant to the immediate conversation
921
1244
  - Basic explanations that don't require reference material
922
1245
  """
923
1246
 
924
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
+
925
1356
  def _generate_embed_instruction(
926
1357
  include_artifact_content: bool,
927
1358
  log_identifier: str,
@@ -931,11 +1362,17 @@ def _generate_embed_instruction(
931
1362
  close_delim = EMBED_DELIMITER_CLOSE
932
1363
  chain_delim = EMBED_CHAIN_DELIMITER
933
1364
  early_types = "`math`, `datetime`, `uuid`, `artifact_meta`"
934
- modifier_list = ", ".join(
935
- [f"`{prefix}`" for prefix in MODIFIER_IMPLEMENTATIONS.keys()]
936
- )
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])
937
1372
 
938
1373
  base_instruction = f"""\
1374
+ **Using Dynamic Embeds in Responses:**
1375
+
939
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
940
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.
941
1378
  Use HTML entities to escape the delimiters.
@@ -951,7 +1388,31 @@ Examples:
951
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)
952
1389
 
953
1390
  The following embeds are resolved *late* (by the gateway before final display):
954
- - `{open_delim}artifact_return:filename[:version]{close_delim}`: **This is the primary way to return an artifact to the user.** It attaches the specified artifact to the message. The embed itself is removed from the text. Use this instead of describing a file and expecting the user to download it. Note: artifact_return is not necessary if the artifact was just created by you in this same response, since newly created artifacts are automatically attached to your message."""
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
+ """
955
1416
 
956
1417
  artifact_content_instruction = f"""
957
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).
@@ -960,23 +1421,18 @@ The following embeds are resolved *late* (by the gateway before final display):
960
1421
  - Available modifiers: {modifier_list}.
961
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.
962
1423
  - Use `artifact_meta` first to check size; embedding large files may fail.
963
- - **Using `apply_to_template` Modifier:**
964
- - This modifier renders a Mustache template artifact using the data from the previous step.
965
- - **Data Context:**
966
- - 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`.
967
- - If the input data is a **list** (e.g., from `jsonpath` or a JSON array), it's available under `items`.
968
- - If the input data is a **dictionary** (e.g., from a JSON object), its keys are directly available (e.g., `{{{{key1}}}}`).
969
- - If the input data is a **plain string** (and not auto-parsed as CSV), it's available under `text`.
970
- - The template filename can include a version (e.g., `template.mustache:2`). Defaults to latest.
971
- - 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.
972
1428
  - Examples:
973
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.)
974
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)
975
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)
976
- - `{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)
977
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)
978
- - `{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)
979
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)`
980
1436
  """
981
1437
 
982
1438
  final_instruction = base_instruction
@@ -989,6 +1445,64 @@ Ensure the syntax is exactly `{open_delim}type:expression{close_delim}` or `{ope
989
1445
  return final_instruction
990
1446
 
991
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
+
992
1506
  def _generate_tool_instructions_from_registry(
993
1507
  active_tools: List[BuiltinTool],
994
1508
  log_identifier: str,
@@ -1058,11 +1572,35 @@ def inject_dynamic_instructions_callback(
1058
1572
  Parallel Tool Calling:
1059
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.
1060
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
+
1061
1597
  Embeds in responses from agents:
1062
- 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
1063
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
1064
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.
1065
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
+
1066
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.
1067
1605
  Simple, direct requests like 'create an image of a dog' or 'write an email to thank my boss' do not require a plan.
1068
1606
 
@@ -1074,8 +1612,11 @@ If a plan is created:
1074
1612
 
1075
1613
  """
1076
1614
  injected_instructions.append(planning_instruction)
1077
- fenced_artifact_instruction = _generate_fenced_artifact_instruction()
1078
- 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())
1079
1620
 
1080
1621
  agent_instruction_str: Optional[str] = None
1081
1622
  if host_component._agent_system_instruction_callback:
@@ -1143,6 +1684,11 @@ If a plan is created:
1143
1684
  include_artifact_content_instr,
1144
1685
  )
1145
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
+
1146
1692
  if active_builtin_tools:
1147
1693
  instruction = _generate_tool_instructions_from_registry(
1148
1694
  active_builtin_tools, log_identifier
@@ -1212,6 +1758,8 @@ If a plan is created:
1212
1758
  e_last_call,
1213
1759
  )
1214
1760
 
1761
+ injected_instructions.append(_generate_examples_instruction())
1762
+
1215
1763
  if injected_instructions:
1216
1764
  combined_instructions = "\n\n---\n\n".join(injected_instructions)
1217
1765
  if llm_request.config is None:
@@ -1730,9 +2278,11 @@ def notify_tool_invocation_start_callback(
1730
2278
  tool_args=serializable_args,
1731
2279
  function_call_id=tool_context.function_call_id,
1732
2280
  )
1733
- asyncio.run_coroutine_threadsafe(
1734
- _publish_data_part_status_update(host_component, a2a_context, tool_data),
1735
- 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,
1736
2286
  )
1737
2287
  log.debug(
1738
2288
  "%s Scheduled tool_invocation_start notification.",
@@ -1803,9 +2353,11 @@ def notify_tool_execution_result_callback(
1803
2353
  result_data=serializable_response,
1804
2354
  function_call_id=tool_context.function_call_id,
1805
2355
  )
1806
- asyncio.run_coroutine_threadsafe(
1807
- _publish_data_part_status_update(host_component, a2a_context, tool_data),
1808
- 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,
1809
2361
  )
1810
2362
  log.debug(
1811
2363
  "%s Scheduled tool_result notification for function call ID %s.",
@@ -1903,3 +2455,76 @@ def auto_continue_on_max_tokens_callback(
1903
2455
  )
1904
2456
 
1905
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