solace-agent-mesh 1.11.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.
Files changed (624) hide show
  1. solace_agent_mesh/__init__.py +0 -0
  2. solace_agent_mesh/agent/__init__.py +0 -0
  3. solace_agent_mesh/agent/adk/__init__.py +0 -0
  4. solace_agent_mesh/agent/adk/adk_llm.txt +226 -0
  5. solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
  6. solace_agent_mesh/agent/adk/alembic/README +74 -0
  7. solace_agent_mesh/agent/adk/alembic/env.py +77 -0
  8. solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
  9. solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
  10. solace_agent_mesh/agent/adk/alembic.ini +112 -0
  11. solace_agent_mesh/agent/adk/app_llm_agent.py +52 -0
  12. solace_agent_mesh/agent/adk/artifacts/__init__.py +1 -0
  13. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
  14. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +545 -0
  15. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +609 -0
  16. solace_agent_mesh/agent/adk/callbacks.py +2318 -0
  17. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +406 -0
  18. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +415 -0
  19. solace_agent_mesh/agent/adk/mcp_content_processor.py +666 -0
  20. solace_agent_mesh/agent/adk/models/lite_llm.py +1026 -0
  21. solace_agent_mesh/agent/adk/models/models_llm.txt +189 -0
  22. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +132 -0
  23. solace_agent_mesh/agent/adk/runner.py +390 -0
  24. solace_agent_mesh/agent/adk/schema_migration.py +88 -0
  25. solace_agent_mesh/agent/adk/services.py +468 -0
  26. solace_agent_mesh/agent/adk/setup.py +1325 -0
  27. solace_agent_mesh/agent/adk/stream_parser.py +415 -0
  28. solace_agent_mesh/agent/adk/tool_wrapper.py +165 -0
  29. solace_agent_mesh/agent/agent_llm.txt +369 -0
  30. solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
  31. solace_agent_mesh/agent/protocol/__init__.py +0 -0
  32. solace_agent_mesh/agent/protocol/event_handlers.py +2041 -0
  33. solace_agent_mesh/agent/protocol/protocol_llm.txt +81 -0
  34. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
  35. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  36. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  37. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
  38. solace_agent_mesh/agent/proxies/a2a/app.py +56 -0
  39. solace_agent_mesh/agent/proxies/a2a/component.py +1585 -0
  40. solace_agent_mesh/agent/proxies/a2a/config.py +216 -0
  41. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  42. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  43. solace_agent_mesh/agent/proxies/base/app.py +100 -0
  44. solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
  45. solace_agent_mesh/agent/proxies/base/component.py +816 -0
  46. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  47. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +19 -0
  48. solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
  49. solace_agent_mesh/agent/sac/__init__.py +0 -0
  50. solace_agent_mesh/agent/sac/app.py +595 -0
  51. solace_agent_mesh/agent/sac/component.py +3668 -0
  52. solace_agent_mesh/agent/sac/patch_adk.py +103 -0
  53. solace_agent_mesh/agent/sac/sac_llm.txt +189 -0
  54. solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
  55. solace_agent_mesh/agent/sac/task_execution_context.py +415 -0
  56. solace_agent_mesh/agent/testing/__init__.py +3 -0
  57. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  58. solace_agent_mesh/agent/testing/testing_llm.txt +58 -0
  59. solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
  60. solace_agent_mesh/agent/tools/__init__.py +16 -0
  61. solace_agent_mesh/agent/tools/audio_tools.py +1740 -0
  62. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +2500 -0
  63. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +244 -0
  64. solace_agent_mesh/agent/tools/dynamic_tool.py +396 -0
  65. solace_agent_mesh/agent/tools/general_agent_tools.py +572 -0
  66. solace_agent_mesh/agent/tools/image_tools.py +1185 -0
  67. solace_agent_mesh/agent/tools/peer_agent_tool.py +363 -0
  68. solace_agent_mesh/agent/tools/registry.py +38 -0
  69. solace_agent_mesh/agent/tools/test_tools.py +136 -0
  70. solace_agent_mesh/agent/tools/time_tools.py +126 -0
  71. solace_agent_mesh/agent/tools/tool_config_types.py +93 -0
  72. solace_agent_mesh/agent/tools/tool_definition.py +53 -0
  73. solace_agent_mesh/agent/tools/tools_llm.txt +276 -0
  74. solace_agent_mesh/agent/tools/tools_llm_detail.txt +275 -0
  75. solace_agent_mesh/agent/tools/web_tools.py +392 -0
  76. solace_agent_mesh/agent/utils/__init__.py +0 -0
  77. solace_agent_mesh/agent/utils/artifact_helpers.py +1353 -0
  78. solace_agent_mesh/agent/utils/config_parser.py +49 -0
  79. solace_agent_mesh/agent/utils/context_helpers.py +77 -0
  80. solace_agent_mesh/agent/utils/utils_llm.txt +152 -0
  81. solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
  82. solace_agent_mesh/assets/docs/404.html +16 -0
  83. solace_agent_mesh/assets/docs/assets/css/styles.8162edfb.css +1 -0
  84. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  85. solace_agent_mesh/assets/docs/assets/images/sam-enterprise-credentials-b269f095349473118b2b33bdfcc40122.png +0 -0
  86. solace_agent_mesh/assets/docs/assets/js/032c2d61.f3d37824.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/0bcf40b7.c019ad46.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  95. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  96. solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/2131ec11.5c7a1f6e.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
  101. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js.LICENSE.txt +13 -0
  102. solace_agent_mesh/assets/docs/assets/js/2334.1cf50a20.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/240a0364.9ad94d1b.js +1 -0
  104. solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
  105. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/341393d4.0fac2613.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/3624.0eaa1fd0.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/3ac1795d.28b7c67b.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/3ff0015d.2ddc75c0.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/41adc471.48b12a4e.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/509e993c.a1fbf45a.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.90a87880.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  131. solace_agent_mesh/assets/docs/assets/js/6063ff4c.ef84f702.js +1 -0
  132. solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
  133. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  134. solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
  135. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  136. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
  137. solace_agent_mesh/assets/docs/assets/js/66d4869e.b77431fc.js +1 -0
  138. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  139. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  140. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  141. solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
  142. solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
  143. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
  144. solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
  145. solace_agent_mesh/assets/docs/assets/js/6fdfefc7.99de744e.js +1 -0
  146. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  147. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  148. solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
  149. solace_agent_mesh/assets/docs/assets/js/722f809d.965da774.js +1 -0
  150. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  151. solace_agent_mesh/assets/docs/assets/js/742f027b.46c07808.js +1 -0
  152. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  153. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  154. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  155. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
  156. solace_agent_mesh/assets/docs/assets/js/81a99df0.2484b8d9.js +1 -0
  157. solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
  158. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  159. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  160. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  161. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  162. solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js +2 -0
  163. solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js.LICENSE.txt +61 -0
  164. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  165. solace_agent_mesh/assets/docs/assets/js/8731.6c1dbf0c.js +1 -0
  166. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  167. solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
  168. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  169. solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
  170. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  171. solace_agent_mesh/assets/docs/assets/js/945fb41e.6f4cdffd.js +1 -0
  172. solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
  173. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  174. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  175. solace_agent_mesh/assets/docs/assets/js/9bb13469.b2333011.js +1 -0
  176. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  177. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  178. solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
  179. solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +1 -0
  180. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  181. solace_agent_mesh/assets/docs/assets/js/ad71b5ed.af3ecfd1.js +1 -0
  182. solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
  183. solace_agent_mesh/assets/docs/assets/js/c198a0dc.8f31f867.js +1 -0
  184. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
  185. solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
  186. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
  187. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  188. solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
  189. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
  190. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  191. solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
  192. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
  193. solace_agent_mesh/assets/docs/assets/js/de5f4c65.e8241890.js +1 -0
  194. solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
  195. solace_agent_mesh/assets/docs/assets/js/e04b235d.52cb25ed.js +1 -0
  196. solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.b1068f9b.js +1 -0
  197. solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
  198. solace_agent_mesh/assets/docs/assets/js/e6f9706b.4488e34c.js +1 -0
  199. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
  200. solace_agent_mesh/assets/docs/assets/js/f284c35a.250993bf.js +1 -0
  201. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
  202. solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js +2 -0
  203. solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js.LICENSE.txt +81 -0
  204. solace_agent_mesh/assets/docs/assets/js/runtime~main.9e0813a2.js +1 -0
  205. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +154 -0
  206. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +99 -0
  207. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +90 -0
  208. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +107 -0
  209. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +166 -0
  210. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +101 -0
  211. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +219 -0
  212. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +92 -0
  213. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +29 -0
  214. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +55 -0
  215. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +110 -0
  216. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
  217. solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
  218. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +345 -0
  219. solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
  220. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +83 -0
  221. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +84 -0
  222. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +25 -0
  223. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
  224. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +85 -0
  225. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +60 -0
  226. solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
  227. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +144 -0
  228. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +191 -0
  229. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +128 -0
  230. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +54 -0
  231. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  232. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +34 -0
  233. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +55 -0
  234. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +267 -0
  235. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +142 -0
  236. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +116 -0
  237. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +86 -0
  238. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +164 -0
  239. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +140 -0
  240. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +57 -0
  241. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +72 -0
  242. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +102 -0
  243. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
  244. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
  245. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
  246. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +37 -0
  247. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +86 -0
  248. solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
  249. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +247 -0
  250. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
  251. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +184 -0
  252. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
  253. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +75 -0
  254. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +54 -0
  255. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +85 -0
  256. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +41 -0
  257. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
  258. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +78 -0
  259. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +25 -0
  260. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +78 -0
  261. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +160 -0
  262. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +142 -0
  263. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
  264. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
  265. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +100 -0
  266. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +52 -0
  267. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  268. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  269. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  270. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  271. solace_agent_mesh/assets/docs/img/sam-enterprise-credentials.png +0 -0
  272. solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
  273. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  274. solace_agent_mesh/assets/docs/lunr-index-1765810064709.json +1 -0
  275. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  276. solace_agent_mesh/assets/docs/search-doc-1765810064709.json +1 -0
  277. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  278. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  279. solace_agent_mesh/cli/__init__.py +1 -0
  280. solace_agent_mesh/cli/commands/__init__.py +0 -0
  281. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  282. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  283. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +729 -0
  284. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  285. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +102 -0
  286. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +114 -0
  287. solace_agent_mesh/cli/commands/docs_cmd.py +60 -0
  288. solace_agent_mesh/cli/commands/eval_cmd.py +46 -0
  289. solace_agent_mesh/cli/commands/init_cmd/__init__.py +439 -0
  290. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  291. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  292. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  293. solace_agent_mesh/cli/commands/init_cmd/env_step.py +238 -0
  294. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  295. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +464 -0
  296. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  297. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +119 -0
  298. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +215 -0
  299. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +20 -0
  300. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +137 -0
  301. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  302. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +144 -0
  303. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +306 -0
  304. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +283 -0
  305. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
  306. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  307. solace_agent_mesh/cli/commands/run_cmd.py +215 -0
  308. solace_agent_mesh/cli/main.py +52 -0
  309. solace_agent_mesh/cli/utils.py +262 -0
  310. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-Dj3JtK42.js +1 -0
  311. solace_agent_mesh/client/webui/frontend/static/assets/client-ZKk9kEJ5.js +25 -0
  312. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  313. solace_agent_mesh/client/webui/frontend/static/assets/main-BcUaNZ-Q.css +1 -0
  314. solace_agent_mesh/client/webui/frontend/static/assets/main-vjch4RYc.js +435 -0
  315. solace_agent_mesh/client/webui/frontend/static/assets/vendor-BNV4kZN0.js +535 -0
  316. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +15 -0
  317. solace_agent_mesh/client/webui/frontend/static/index.html +16 -0
  318. solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
  319. solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
  320. solace_agent_mesh/common/__init__.py +1 -0
  321. solace_agent_mesh/common/a2a/__init__.py +241 -0
  322. solace_agent_mesh/common/a2a/a2a_llm.txt +175 -0
  323. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
  324. solace_agent_mesh/common/a2a/artifact.py +368 -0
  325. solace_agent_mesh/common/a2a/events.py +213 -0
  326. solace_agent_mesh/common/a2a/message.py +375 -0
  327. solace_agent_mesh/common/a2a/protocol.py +689 -0
  328. solace_agent_mesh/common/a2a/task.py +127 -0
  329. solace_agent_mesh/common/a2a/translation.py +655 -0
  330. solace_agent_mesh/common/a2a/types.py +55 -0
  331. solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
  332. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +445 -0
  333. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
  334. solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
  335. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +48 -0
  336. solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
  337. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +41 -0
  338. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +330 -0
  339. solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
  340. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +48 -0
  341. solace_agent_mesh/common/agent_registry.py +122 -0
  342. solace_agent_mesh/common/common_llm.txt +230 -0
  343. solace_agent_mesh/common/common_llm_detail.txt +2562 -0
  344. solace_agent_mesh/common/constants.py +6 -0
  345. solace_agent_mesh/common/data_parts.py +150 -0
  346. solace_agent_mesh/common/exceptions.py +49 -0
  347. solace_agent_mesh/common/middleware/__init__.py +12 -0
  348. solace_agent_mesh/common/middleware/config_resolver.py +132 -0
  349. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  350. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
  351. solace_agent_mesh/common/middleware/registry.py +127 -0
  352. solace_agent_mesh/common/oauth/__init__.py +17 -0
  353. solace_agent_mesh/common/oauth/oauth_client.py +408 -0
  354. solace_agent_mesh/common/oauth/utils.py +50 -0
  355. solace_agent_mesh/common/sac/__init__.py +0 -0
  356. solace_agent_mesh/common/sac/sac_llm.txt +71 -0
  357. solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
  358. solace_agent_mesh/common/sac/sam_component_base.py +730 -0
  359. solace_agent_mesh/common/sam_events/__init__.py +9 -0
  360. solace_agent_mesh/common/sam_events/event_service.py +208 -0
  361. solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
  362. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
  363. solace_agent_mesh/common/services/__init__.py +4 -0
  364. solace_agent_mesh/common/services/employee_service.py +164 -0
  365. solace_agent_mesh/common/services/identity_service.py +134 -0
  366. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  367. solace_agent_mesh/common/services/providers/local_file_identity_service.py +151 -0
  368. solace_agent_mesh/common/services/providers/providers_llm.txt +81 -0
  369. solace_agent_mesh/common/services/services_llm.txt +368 -0
  370. solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
  371. solace_agent_mesh/common/utils/__init__.py +7 -0
  372. solace_agent_mesh/common/utils/artifact_utils.py +31 -0
  373. solace_agent_mesh/common/utils/asyncio_macos_fix.py +88 -0
  374. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  375. solace_agent_mesh/common/utils/embeds/constants.py +56 -0
  376. solace_agent_mesh/common/utils/embeds/converter.py +447 -0
  377. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +220 -0
  378. solace_agent_mesh/common/utils/embeds/evaluators.py +395 -0
  379. solace_agent_mesh/common/utils/embeds/modifiers.py +793 -0
  380. solace_agent_mesh/common/utils/embeds/resolver.py +967 -0
  381. solace_agent_mesh/common/utils/embeds/types.py +23 -0
  382. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  383. solace_agent_mesh/common/utils/initializer.py +52 -0
  384. solace_agent_mesh/common/utils/log_formatters.py +64 -0
  385. solace_agent_mesh/common/utils/message_utils.py +80 -0
  386. solace_agent_mesh/common/utils/mime_helpers.py +172 -0
  387. solace_agent_mesh/common/utils/push_notification_auth.py +135 -0
  388. solace_agent_mesh/common/utils/pydantic_utils.py +159 -0
  389. solace_agent_mesh/common/utils/rbac_utils.py +69 -0
  390. solace_agent_mesh/common/utils/templates/__init__.py +8 -0
  391. solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
  392. solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
  393. solace_agent_mesh/common/utils/type_utils.py +28 -0
  394. solace_agent_mesh/common/utils/utils_llm.txt +335 -0
  395. solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
  396. solace_agent_mesh/config_portal/__init__.py +0 -0
  397. solace_agent_mesh/config_portal/backend/__init__.py +0 -0
  398. solace_agent_mesh/config_portal/backend/common.py +77 -0
  399. solace_agent_mesh/config_portal/backend/plugin_catalog/__init__.py +0 -0
  400. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
  401. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  402. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +166 -0
  403. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
  404. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
  405. solace_agent_mesh/config_portal/backend/server.py +644 -0
  406. solace_agent_mesh/config_portal/frontend/static/client/Solace_community_logo.png +0 -0
  407. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DiOiAjzL.js +103 -0
  408. solace_agent_mesh/config_portal/frontend/static/client/assets/components-Rk0n-9cK.js +140 -0
  409. solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-mvZjNKiz.js +19 -0
  410. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DzNKzXrc.js +68 -0
  411. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-ba77705e.js +1 -0
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/root-B17tZKK7.css +1 -0
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/root-V2BeTIUc.js +10 -0
  414. solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
  415. solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
  416. solace_agent_mesh/core_a2a/__init__.py +1 -0
  417. solace_agent_mesh/core_a2a/core_a2a_llm.txt +90 -0
  418. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
  419. solace_agent_mesh/core_a2a/service.py +307 -0
  420. solace_agent_mesh/evaluation/__init__.py +0 -0
  421. solace_agent_mesh/evaluation/evaluator.py +691 -0
  422. solace_agent_mesh/evaluation/message_organizer.py +553 -0
  423. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  424. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  425. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  426. solace_agent_mesh/evaluation/report/modal.html +59 -0
  427. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  428. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  429. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  430. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  431. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  432. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  433. solace_agent_mesh/evaluation/report_data_processor.py +970 -0
  434. solace_agent_mesh/evaluation/report_generator.py +607 -0
  435. solace_agent_mesh/evaluation/run.py +954 -0
  436. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  437. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  438. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  439. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  440. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  441. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  442. solace_agent_mesh/evaluation/subscriber.py +776 -0
  443. solace_agent_mesh/evaluation/summary_builder.py +880 -0
  444. solace_agent_mesh/gateway/__init__.py +0 -0
  445. solace_agent_mesh/gateway/adapter/__init__.py +1 -0
  446. solace_agent_mesh/gateway/adapter/base.py +143 -0
  447. solace_agent_mesh/gateway/adapter/types.py +221 -0
  448. solace_agent_mesh/gateway/base/__init__.py +1 -0
  449. solace_agent_mesh/gateway/base/app.py +345 -0
  450. solace_agent_mesh/gateway/base/base_llm.txt +226 -0
  451. solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
  452. solace_agent_mesh/gateway/base/component.py +2030 -0
  453. solace_agent_mesh/gateway/base/task_context.py +75 -0
  454. solace_agent_mesh/gateway/gateway_llm.txt +369 -0
  455. solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
  456. solace_agent_mesh/gateway/generic/__init__.py +1 -0
  457. solace_agent_mesh/gateway/generic/app.py +50 -0
  458. solace_agent_mesh/gateway/generic/component.py +727 -0
  459. solace_agent_mesh/gateway/http_sse/__init__.py +0 -0
  460. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +345 -0
  461. solace_agent_mesh/gateway/http_sse/alembic/env.py +87 -0
  462. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  463. solace_agent_mesh/gateway/http_sse/alembic/versions/20250910_d5b3f8f2e9a0_create_initial_database.py +58 -0
  464. solace_agent_mesh/gateway/http_sse/alembic/versions/20250911_b1c2d3e4f5g6_add_database_indexes.py +83 -0
  465. solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +412 -0
  466. solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
  467. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  468. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
  469. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
  470. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
  471. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
  472. solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
  473. solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
  474. solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
  475. solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
  476. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +161 -0
  477. solace_agent_mesh/gateway/http_sse/alembic.ini +109 -0
  478. solace_agent_mesh/gateway/http_sse/app.py +351 -0
  479. solace_agent_mesh/gateway/http_sse/component.py +2360 -0
  480. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  481. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +105 -0
  482. solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +109 -0
  483. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +110 -0
  484. solace_agent_mesh/gateway/http_sse/dependencies.py +653 -0
  485. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +299 -0
  486. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
  487. solace_agent_mesh/gateway/http_sse/main.py +789 -0
  488. solace_agent_mesh/gateway/http_sse/repository/__init__.py +46 -0
  489. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +102 -0
  490. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +11 -0
  491. solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
  492. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +221 -0
  493. solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
  494. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
  495. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
  496. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +66 -0
  497. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -0
  498. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +32 -0
  499. solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
  500. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +125 -0
  501. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +239 -0
  502. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +34 -0
  503. solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
  504. solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
  505. solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
  506. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +257 -0
  507. solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
  508. solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
  509. solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
  510. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +53 -0
  511. solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
  512. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +39 -0
  513. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
  514. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
  515. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +308 -0
  516. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +268 -0
  517. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +248 -0
  518. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  519. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +74 -0
  520. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1137 -0
  521. solace_agent_mesh/gateway/http_sse/routers/auth.py +311 -0
  522. solace_agent_mesh/gateway/http_sse/routers/config.py +371 -0
  523. solace_agent_mesh/gateway/http_sse/routers/dto/__init__.py +10 -0
  524. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +450 -0
  525. solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
  526. solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
  527. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +15 -0
  528. solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
  529. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +133 -0
  530. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +33 -0
  531. solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
  532. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +18 -0
  533. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
  534. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
  535. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +123 -0
  536. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +33 -0
  537. solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
  538. solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
  539. solace_agent_mesh/gateway/http_sse/routers/feedback.py +168 -0
  540. solace_agent_mesh/gateway/http_sse/routers/people.py +38 -0
  541. solace_agent_mesh/gateway/http_sse/routers/projects.py +767 -0
  542. solace_agent_mesh/gateway/http_sse/routers/prompts.py +1415 -0
  543. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +312 -0
  544. solace_agent_mesh/gateway/http_sse/routers/sessions.py +634 -0
  545. solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
  546. solace_agent_mesh/gateway/http_sse/routers/sse.py +230 -0
  547. solace_agent_mesh/gateway/http_sse/routers/tasks.py +1089 -0
  548. solace_agent_mesh/gateway/http_sse/routers/users.py +83 -0
  549. solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
  550. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1220 -0
  551. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  552. solace_agent_mesh/gateway/http_sse/services/agent_card_service.py +71 -0
  553. solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
  554. solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
  555. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +273 -0
  556. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +250 -0
  557. solace_agent_mesh/gateway/http_sse/services/people_service.py +78 -0
  558. solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
  559. solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
  560. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +303 -0
  561. solace_agent_mesh/gateway/http_sse/services/session_service.py +702 -0
  562. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +593 -0
  563. solace_agent_mesh/gateway/http_sse/services/task_service.py +119 -0
  564. solace_agent_mesh/gateway/http_sse/session_manager.py +219 -0
  565. solace_agent_mesh/gateway/http_sse/shared/__init__.py +146 -0
  566. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  567. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +252 -0
  568. solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
  569. solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
  570. solace_agent_mesh/gateway/http_sse/shared/enums.py +40 -0
  571. solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
  572. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +217 -0
  573. solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
  574. solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
  575. solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
  576. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +319 -0
  577. solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
  578. solace_agent_mesh/gateway/http_sse/shared/types.py +50 -0
  579. solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
  580. solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +88 -0
  581. solace_agent_mesh/gateway/http_sse/sse_manager.py +491 -0
  582. solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
  583. solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
  584. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +72 -0
  585. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
  586. solace_agent_mesh/llm.txt +228 -0
  587. solace_agent_mesh/llm_detail.txt +2835 -0
  588. solace_agent_mesh/services/__init__.py +0 -0
  589. solace_agent_mesh/services/platform/__init__.py +18 -0
  590. solace_agent_mesh/services/platform/alembic/env.py +85 -0
  591. solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
  592. solace_agent_mesh/services/platform/alembic.ini +109 -0
  593. solace_agent_mesh/services/platform/api/__init__.py +3 -0
  594. solace_agent_mesh/services/platform/api/dependencies.py +147 -0
  595. solace_agent_mesh/services/platform/api/main.py +280 -0
  596. solace_agent_mesh/services/platform/api/middleware.py +51 -0
  597. solace_agent_mesh/services/platform/api/routers/__init__.py +24 -0
  598. solace_agent_mesh/services/platform/app.py +114 -0
  599. solace_agent_mesh/services/platform/component.py +235 -0
  600. solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
  601. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
  602. solace_agent_mesh/templates/agent_template.yaml +53 -0
  603. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  604. solace_agent_mesh/templates/gateway_app_template.py +75 -0
  605. solace_agent_mesh/templates/gateway_component_template.py +484 -0
  606. solace_agent_mesh/templates/gateway_config_template.yaml +38 -0
  607. solace_agent_mesh/templates/logging_config_template.yaml +48 -0
  608. solace_agent_mesh/templates/main_orchestrator.yaml +66 -0
  609. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  610. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  611. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  612. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +60 -0
  613. solace_agent_mesh/templates/plugin_pyproject_template.toml +32 -0
  614. solace_agent_mesh/templates/plugin_readme_template.md +12 -0
  615. solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
  616. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  617. solace_agent_mesh/templates/shared_config.yaml +112 -0
  618. solace_agent_mesh/templates/templates_llm.txt +147 -0
  619. solace_agent_mesh/templates/webui.yaml +177 -0
  620. solace_agent_mesh-1.11.2.dist-info/METADATA +504 -0
  621. solace_agent_mesh-1.11.2.dist-info/RECORD +624 -0
  622. solace_agent_mesh-1.11.2.dist-info/WHEEL +4 -0
  623. solace_agent_mesh-1.11.2.dist-info/entry_points.txt +3 -0
  624. solace_agent_mesh-1.11.2.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,2500 @@
1
+ """
2
+ Built-in ADK Tools for Artifact Management within the A2A Host.
3
+ These tools interact with the ADK ArtifactService via the ToolContext and
4
+ use state_delta for signaling artifact return requests to the host component.
5
+ Metadata handling is integrated via artifact_helpers.
6
+ """
7
+
8
+ import logging
9
+ import uuid
10
+ import json
11
+ import re
12
+ import fnmatch
13
+ from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
14
+ from datetime import datetime, timezone
15
+ from google.adk.tools import ToolContext
16
+
17
+ if TYPE_CHECKING:
18
+ from google.adk.agents.invocation_context import InvocationContext
19
+ from google.genai import types as adk_types
20
+ from .tool_definition import BuiltinTool
21
+ from .registry import tool_registry
22
+ from ...agent.utils.artifact_helpers import (
23
+ save_artifact_with_metadata,
24
+ decode_and_get_bytes,
25
+ load_artifact_content_or_metadata,
26
+ is_filename_safe,
27
+ METADATA_SUFFIX,
28
+ DEFAULT_SCHEMA_MAX_KEYS,
29
+ )
30
+ from ...common.utils.embeds import (
31
+ evaluate_embed,
32
+ EMBED_REGEX,
33
+ EMBED_CHAIN_DELIMITER,
34
+ )
35
+ from ...common.utils.embeds.types import ResolutionMode
36
+ from ...agent.utils.context_helpers import get_original_session_id
37
+ from ...agent.adk.models.lite_llm import LiteLlm
38
+ from google.adk.models import LlmRequest
39
+ from google.adk.models.registry import LLMRegistry
40
+ from ...common.utils.mime_helpers import is_text_based_file
41
+
42
+ log = logging.getLogger(__name__)
43
+
44
+ CATEGORY_NAME = "Artifact Management"
45
+ CATEGORY_DESCRIPTION = "List, read, create, update, and delete artifacts."
46
+
47
+
48
+ async def _internal_create_artifact(
49
+ filename: str,
50
+ content: str,
51
+ mime_type: str,
52
+ tool_context: ToolContext = None,
53
+ description: Optional[str] = None,
54
+ metadata_json: Optional[str] = None,
55
+ schema_max_keys: Optional[int] = None,
56
+ ) -> Dict[str, Any]:
57
+ """
58
+ Internal helper to create an artifact with its first chunk of content and metadata.
59
+ This function is not intended to be called directly by the LLM.
60
+ It is used by callbacks that process fenced artifact blocks.
61
+
62
+ Args:
63
+ filename: The desired name for the artifact.
64
+ content: The first chunk of the artifact content, as a string.
65
+ If the mime_type suggests binary data, this string is expected
66
+ to be base64 encoded.
67
+ mime_type: The MIME type of the content.
68
+ tool_context: The ADK ToolContext, required for accessing services.
69
+ description (str, optional): A description for the artifact.
70
+ metadata_json (str, optional): A JSON string of additional metadata.
71
+ schema_max_keys (int, optional): Max keys for schema inference.
72
+
73
+
74
+ Returns:
75
+ A dictionary indicating the result, returned by save_artifact_with_metadata.
76
+ """
77
+ if not tool_context:
78
+ return {
79
+ "status": "error",
80
+ "filename": filename,
81
+ "message": "ToolContext is missing, cannot save artifact.",
82
+ }
83
+
84
+ if not is_filename_safe(filename):
85
+ return {
86
+ "status": "error",
87
+ "filename": filename,
88
+ "message": "Filename is invalid or contains disallowed characters (e.g., '/', '..').",
89
+ }
90
+
91
+ log_identifier = f"[BuiltinArtifactTool:_internal_create_artifact:{filename}]"
92
+
93
+ final_metadata = {}
94
+ if description:
95
+ final_metadata["description"] = description
96
+ if metadata_json:
97
+ try:
98
+ final_metadata.update(json.loads(metadata_json))
99
+ except (json.JSONDecodeError, TypeError):
100
+ log.warning(
101
+ "%s Invalid JSON in metadata_json attribute: %s",
102
+ log_identifier,
103
+ metadata_json,
104
+ )
105
+
106
+ final_metadata["metadata_parsing_error"] = (
107
+ f"Invalid JSON provided: {metadata_json}"
108
+ )
109
+
110
+ log.debug("%s Processing request with metadata: %s", log_identifier, final_metadata)
111
+
112
+ try:
113
+ inv_context = tool_context._invocation_context
114
+ artifact_bytes, final_mime_type = decode_and_get_bytes(
115
+ content, mime_type, log_identifier
116
+ )
117
+ max_keys_to_use = (
118
+ schema_max_keys if schema_max_keys is not None else DEFAULT_SCHEMA_MAX_KEYS
119
+ )
120
+ if schema_max_keys is not None:
121
+ log.debug(
122
+ "%s Using schema_max_keys provided by LLM: %d",
123
+ log_identifier,
124
+ schema_max_keys,
125
+ )
126
+ else:
127
+ log.debug(
128
+ "%s Using default schema_max_keys: %d",
129
+ log_identifier,
130
+ DEFAULT_SCHEMA_MAX_KEYS,
131
+ )
132
+
133
+ artifact_service = inv_context.artifact_service
134
+ if not artifact_service:
135
+ raise ValueError("ArtifactService is not available in the context.")
136
+ session_last_update_time = inv_context.session.last_update_time
137
+ timestamp_for_artifact: datetime
138
+ if isinstance(session_last_update_time, datetime):
139
+ timestamp_for_artifact = session_last_update_time
140
+ elif isinstance(session_last_update_time, (int, float)):
141
+ log.debug(
142
+ "%s Converting numeric session.last_update_time (%s) to datetime.",
143
+ log_identifier,
144
+ session_last_update_time,
145
+ )
146
+ try:
147
+ timestamp_for_artifact = datetime.fromtimestamp(
148
+ session_last_update_time, timezone.utc
149
+ )
150
+ except Exception as e:
151
+ log.warning(
152
+ "%s Failed to convert numeric timestamp %s to datetime: %s. Using current time.",
153
+ log_identifier,
154
+ session_last_update_time,
155
+ e,
156
+ )
157
+ timestamp_for_artifact = datetime.now(timezone.utc)
158
+ else:
159
+ if session_last_update_time is not None:
160
+ log.warning(
161
+ "%s Unexpected type for session.last_update_time: %s. Using current time.",
162
+ log_identifier,
163
+ type(session_last_update_time),
164
+ )
165
+ timestamp_for_artifact = datetime.now(timezone.utc)
166
+ result = await save_artifact_with_metadata(
167
+ artifact_service=artifact_service,
168
+ app_name=inv_context.app_name,
169
+ user_id=inv_context.user_id,
170
+ session_id=get_original_session_id(inv_context),
171
+ filename=filename,
172
+ content_bytes=artifact_bytes,
173
+ mime_type=final_mime_type,
174
+ metadata_dict=final_metadata,
175
+ timestamp=timestamp_for_artifact,
176
+ schema_max_keys=max_keys_to_use,
177
+ tool_context=tool_context,
178
+ )
179
+ log.info(
180
+ "%s Result from save_artifact_with_metadata: %s", log_identifier, result
181
+ )
182
+ return result
183
+ except Exception as e:
184
+ log.exception(
185
+ "%s Error creating artifact '%s': %s", log_identifier, filename, e
186
+ )
187
+ return {
188
+ "status": "error",
189
+ "filename": filename,
190
+ "message": f"Failed to create artifact: {e}",
191
+ }
192
+
193
+
194
+ async def list_artifacts(tool_context: ToolContext = None) -> Dict[str, Any]:
195
+ """
196
+ Lists all available data artifact filenames and their versions for the current session.
197
+ Includes a summary of the latest version's metadata for each artifact.
198
+
199
+ Args:
200
+ tool_context: The context provided by the ADK framework.
201
+
202
+ Returns:
203
+ A dictionary containing the list of artifacts with metadata summaries or an error.
204
+ """
205
+ if not tool_context:
206
+ return {"status": "error", "message": "ToolContext is missing."}
207
+ log_identifier = "[BuiltinArtifactTool:list_artifacts]"
208
+ log.debug("%s Processing request.", log_identifier)
209
+ try:
210
+ artifact_service = tool_context._invocation_context.artifact_service
211
+ if not artifact_service:
212
+ raise ValueError("ArtifactService is not available in the context.")
213
+ app_name = tool_context._invocation_context.app_name
214
+ user_id = tool_context._invocation_context.user_id
215
+ session_id = get_original_session_id(tool_context._invocation_context)
216
+ list_keys_method = getattr(artifact_service, "list_artifact_keys")
217
+ all_keys = await list_keys_method(
218
+ app_name=app_name, user_id=user_id, session_id=session_id
219
+ )
220
+ response_files = []
221
+ processed_data_files = set()
222
+ for key in all_keys:
223
+ if key.endswith(METADATA_SUFFIX):
224
+ continue # Skip metadata files initially
225
+
226
+ if key in processed_data_files:
227
+ continue # Already processed this data file
228
+
229
+ filename = key
230
+ metadata_summary = None
231
+ versions = []
232
+ try:
233
+ versions = await artifact_service.list_versions(
234
+ app_name=app_name,
235
+ user_id=user_id,
236
+ session_id=session_id,
237
+ filename=filename,
238
+ )
239
+ if not versions:
240
+ log.warning(
241
+ "%s Found artifact key '%s' but no versions listed. Skipping.",
242
+ log_identifier,
243
+ filename,
244
+ )
245
+ continue
246
+ latest_version = max(versions)
247
+ metadata_filename = f"{filename}{METADATA_SUFFIX}"
248
+ if metadata_filename in all_keys:
249
+ try:
250
+ metadata_part = await artifact_service.load_artifact(
251
+ app_name=app_name,
252
+ user_id=user_id,
253
+ session_id=session_id,
254
+ filename=metadata_filename,
255
+ version=latest_version,
256
+ )
257
+ if metadata_part and metadata_part.inline_data:
258
+ try:
259
+ metadata_dict = json.loads(
260
+ metadata_part.inline_data.data.decode("utf-8")
261
+ )
262
+ schema = metadata_dict.get("schema", {})
263
+ metadata_summary = {
264
+ "description": metadata_dict.get("description"),
265
+ "source": metadata_dict.get("source"),
266
+ "type": metadata_dict.get("mime_type"),
267
+ "size": metadata_dict.get("size_bytes"),
268
+ "schema_type": schema.get(
269
+ "type", metadata_dict.get("mime_type")
270
+ ),
271
+ "schema_inferred": schema.get("inferred"),
272
+ }
273
+ metadata_summary = {
274
+ k: v
275
+ for k, v in metadata_summary.items()
276
+ if v is not None
277
+ }
278
+ log.debug(
279
+ "%s Loaded metadata summary for '%s' v%d.",
280
+ log_identifier,
281
+ filename,
282
+ latest_version,
283
+ )
284
+ except json.JSONDecodeError as json_err:
285
+ log.warning(
286
+ "%s Failed to parse metadata JSON for '%s' v%d: %s",
287
+ log_identifier,
288
+ metadata_filename,
289
+ latest_version,
290
+ json_err,
291
+ )
292
+ metadata_summary = {"error": "Failed to parse metadata"}
293
+ except Exception as fmt_err:
294
+ log.warning(
295
+ "%s Failed to format metadata summary for '%s' v%d: %s",
296
+ log_identifier,
297
+ metadata_filename,
298
+ latest_version,
299
+ fmt_err,
300
+ )
301
+ metadata_summary = {
302
+ "error": "Failed to format metadata"
303
+ }
304
+ else:
305
+ log.warning(
306
+ "%s Metadata file '%s' v%d found but empty or unreadable.",
307
+ log_identifier,
308
+ metadata_filename,
309
+ latest_version,
310
+ )
311
+ metadata_summary = {
312
+ "error": "Metadata file empty or unreadable"
313
+ }
314
+ except Exception as load_err:
315
+ log.warning(
316
+ "%s Failed to load metadata file '%s' v%d: %s",
317
+ log_identifier,
318
+ metadata_filename,
319
+ latest_version,
320
+ load_err,
321
+ )
322
+ metadata_summary = {
323
+ "error": f"Failed to load metadata: {load_err}"
324
+ }
325
+ else:
326
+ log.debug(
327
+ "%s No companion metadata file found for '%s'.",
328
+ log_identifier,
329
+ filename,
330
+ )
331
+ metadata_summary = {"info": "No metadata file found"}
332
+ except Exception as version_err:
333
+ log.warning(
334
+ "%s Failed to list versions or process metadata for file '%s': %s. Skipping file.",
335
+ log_identifier,
336
+ filename,
337
+ version_err,
338
+ )
339
+ continue
340
+ response_files.append(
341
+ {
342
+ "filename": filename,
343
+ "versions": versions,
344
+ "metadata_summary": metadata_summary,
345
+ }
346
+ )
347
+ processed_data_files.add(filename)
348
+ log.info(
349
+ "%s Found %d data artifacts for session %s.",
350
+ log_identifier,
351
+ len(response_files),
352
+ session_id,
353
+ )
354
+ return {"status": "success", "artifacts": response_files}
355
+ except Exception as e:
356
+ log.exception("%s Error listing artifacts: %s", log_identifier, e)
357
+ return {"status": "error", "message": f"Failed to list artifacts: {e}"}
358
+
359
+
360
+ async def load_artifact(
361
+ filename: str,
362
+ version: int,
363
+ load_metadata_only: bool = False,
364
+ max_content_length: Optional[int] = None,
365
+ include_line_numbers: bool = False,
366
+ tool_context: ToolContext = None,
367
+ ) -> Dict[str, Any]:
368
+ """
369
+ Loads the content or metadata of a specific artifact version.
370
+ Early-stage embeds in the filename argument are resolved.
371
+
372
+ If load_metadata_only is True, loads the full metadata dictionary.
373
+ Otherwise, loads text content (potentially truncated) or binary metadata summary.
374
+
375
+ Args:
376
+ filename: The name of the artifact to load. May contain embeds.
377
+ version: The specific version number to load. Must be explicitly provided. Versions are 0-indexed.
378
+ load_metadata_only (bool): If True, load only the metadata JSON. Default False.
379
+ max_content_length (Optional[int]): Maximum character length for text content.
380
+ If None, uses app configuration. Range: 100-100,000.
381
+ include_line_numbers (bool): If True, prefix each line with its 1-based line number
382
+ followed by a TAB character for LLM viewing. Line numbers
383
+ are not stored in the artifact. Default False.
384
+ tool_context: The context provided by the ADK framework.
385
+
386
+ Returns:
387
+ A dictionary containing the artifact details and content/metadata or an error.
388
+ """
389
+ if not tool_context:
390
+ return {
391
+ "status": "error",
392
+ "filename": filename,
393
+ "version": version,
394
+ "message": "ToolContext is missing.",
395
+ }
396
+ log_identifier = f"[BuiltinArtifactTool:load_artifact:{filename}:{version}]"
397
+ log.debug(
398
+ "%s Processing request (load_metadata_only=%s).",
399
+ log_identifier,
400
+ load_metadata_only,
401
+ )
402
+ if version is None:
403
+ version = "latest"
404
+ try:
405
+ artifact_service = tool_context._invocation_context.artifact_service
406
+ if not artifact_service:
407
+ raise ValueError("ArtifactService is not available in the context.")
408
+ app_name = tool_context._invocation_context.app_name
409
+ user_id = tool_context._invocation_context.user_id
410
+ session_id = get_original_session_id(tool_context._invocation_context)
411
+ agent = getattr(tool_context._invocation_context, "agent", None)
412
+ host_component = getattr(agent, "host_component", None) if agent else None
413
+ result = await load_artifact_content_or_metadata(
414
+ artifact_service=artifact_service,
415
+ app_name=app_name,
416
+ user_id=user_id,
417
+ session_id=session_id,
418
+ filename=filename,
419
+ version=version,
420
+ load_metadata_only=load_metadata_only,
421
+ max_content_length=max_content_length,
422
+ include_line_numbers=include_line_numbers,
423
+ component=host_component,
424
+ log_identifier_prefix="[BuiltinArtifactTool:load_artifact]",
425
+ )
426
+ return result
427
+ except FileNotFoundError as fnf_err:
428
+ log.warning(
429
+ "%s Artifact not found (reported by helper): %s", log_identifier, fnf_err
430
+ )
431
+ return {
432
+ "status": "error",
433
+ "filename": filename,
434
+ "version": version,
435
+ "message": str(fnf_err),
436
+ }
437
+ except ValueError as val_err:
438
+ log.warning(
439
+ "%s Value error during load (reported by helper): %s",
440
+ log_identifier,
441
+ val_err,
442
+ )
443
+ return {
444
+ "status": "error",
445
+ "filename": filename,
446
+ "version": version,
447
+ "message": str(val_err),
448
+ }
449
+ except Exception as e:
450
+ log.exception(
451
+ "%s Unexpected error in load_artifact tool: %s", log_identifier, e
452
+ )
453
+ return {
454
+ "status": "error",
455
+ "filename": filename,
456
+ "version": version,
457
+ "message": f"Unexpected error processing load request: {e}",
458
+ }
459
+
460
+
461
+ async def apply_embed_and_create_artifact(
462
+ output_filename: str,
463
+ embed_directive: str,
464
+ output_metadata: Optional[Dict[str, Any]] = None,
465
+ tool_context: ToolContext = None,
466
+ ) -> Dict[str, Any]:
467
+ """
468
+ Resolves an 'artifact_content' embed directive (including modifiers and formatting)
469
+ and saves the resulting content as a new artifact. The entire embed directive
470
+ must be provided as a string as the embed_directive argument.
471
+
472
+ Args:
473
+ output_filename: The desired name for the new artifact.
474
+ embed_directive: The full '«artifact_content:...>>>...>>>format:...»' string.
475
+ output_metadata (dict, optional): Metadata for the new artifact.
476
+ tool_context: The context provided by the ADK framework.
477
+
478
+ Returns:
479
+ A dictionary indicating the result, including the new filename and version.
480
+ """
481
+ if not tool_context:
482
+ return {"status": "error", "message": "ToolContext is missing."}
483
+
484
+ log_identifier = f"[BuiltinArtifactTool:apply_embed:{output_filename}]"
485
+ log.info(
486
+ "%s Processing request with directive: %s", log_identifier, embed_directive
487
+ )
488
+
489
+ match = EMBED_REGEX.fullmatch(embed_directive)
490
+ if not match:
491
+ return {
492
+ "status": "error",
493
+ "message": f"Invalid embed directive format: {embed_directive}",
494
+ }
495
+
496
+ embed_type = match.group(1)
497
+ expression = match.group(2)
498
+ format_spec = match.group(3)
499
+
500
+ if embed_type != "artifact_content":
501
+ return {
502
+ "status": "error",
503
+ "message": f"This tool only supports 'artifact_content' embeds, got '{embed_type}'.",
504
+ }
505
+
506
+ try:
507
+ inv_context = tool_context._invocation_context
508
+ artifact_service = inv_context.artifact_service
509
+ if not artifact_service:
510
+ raise ValueError("ArtifactService not available.")
511
+
512
+ host_component = getattr(inv_context.agent, "host_component", None)
513
+ if not host_component:
514
+ log.warning(
515
+ "%s Could not access host component config for limits. Proceeding without them.",
516
+ log_identifier,
517
+ )
518
+ embed_config = {}
519
+ else:
520
+ embed_config = {
521
+ "gateway_artifact_content_limit_bytes": host_component.get_config(
522
+ "gateway_artifact_content_limit_bytes", -1
523
+ ),
524
+ "gateway_recursive_embed_depth": host_component.get_config(
525
+ "gateway_recursive_embed_depth", 3
526
+ ),
527
+ }
528
+
529
+ gateway_context = {
530
+ "artifact_service": artifact_service,
531
+ "session_context": {
532
+ "app_name": inv_context.app_name,
533
+ "user_id": inv_context.user_id,
534
+ "session_id": get_original_session_id(inv_context),
535
+ },
536
+ }
537
+ except Exception as ctx_err:
538
+ log.error(
539
+ "%s Failed to prepare context/config for embed evaluation: %s",
540
+ log_identifier,
541
+ ctx_err,
542
+ )
543
+ return {
544
+ "status": "error",
545
+ "message": f"Internal error preparing context: {ctx_err}",
546
+ }
547
+
548
+ resolved_content_str, error_msg_from_eval, _ = await evaluate_embed(
549
+ embed_type=embed_type,
550
+ expression=expression,
551
+ format_spec=format_spec,
552
+ context=gateway_context,
553
+ log_identifier=log_identifier,
554
+ resolution_mode=ResolutionMode.TOOL_PARAMETER,
555
+ config=embed_config,
556
+ )
557
+
558
+ if error_msg_from_eval or (
559
+ resolved_content_str and resolved_content_str.startswith("[Error:")
560
+ ):
561
+ error_to_report = error_msg_from_eval or resolved_content_str
562
+ log.error("%s Embed resolution failed: %s", log_identifier, error_to_report)
563
+ return {
564
+ "status": "error",
565
+ "message": f"Embed resolution failed: {error_to_report}",
566
+ }
567
+
568
+ output_mime_type = "text/plain"
569
+ final_format = None
570
+ chain_parts = expression.split(EMBED_CHAIN_DELIMITER)
571
+ if len(chain_parts) > 1:
572
+ last_part = chain_parts[-1].strip()
573
+ format_match = re.match(r"format:(.*)", last_part, re.DOTALL)
574
+ if format_match:
575
+ final_format = format_match.group(1).strip().lower()
576
+ elif format_spec:
577
+ final_format = format_spec.strip().lower()
578
+
579
+ if final_format:
580
+ if final_format == "html":
581
+ output_mime_type = "text/html"
582
+ elif final_format == "json" or final_format == "json_pretty":
583
+ output_mime_type = "application/json"
584
+ elif final_format == "csv":
585
+ output_mime_type = "text/csv"
586
+ elif final_format == "datauri":
587
+ output_mime_type = "text/plain"
588
+ log.warning(
589
+ "%s Embed resolved to data URI; saving new artifact as text/plain.",
590
+ log_identifier,
591
+ )
592
+
593
+ log.debug("%s Determined output MIME type as: %s", log_identifier, output_mime_type)
594
+
595
+ try:
596
+ resolved_bytes = resolved_content_str.encode("utf-8")
597
+ inv_context = tool_context._invocation_context
598
+ artifact_service = inv_context.artifact_service
599
+ if not artifact_service:
600
+ raise ValueError("ArtifactService is not available in the context.")
601
+
602
+ save_result = await save_artifact_with_metadata(
603
+ artifact_service=artifact_service,
604
+ app_name=inv_context.app_name,
605
+ user_id=inv_context.user_id,
606
+ session_id=get_original_session_id(inv_context),
607
+ filename=output_filename,
608
+ content_bytes=resolved_bytes,
609
+ mime_type=output_mime_type,
610
+ metadata_dict=(
611
+ lambda base_meta, user_meta: (
612
+ base_meta.update(user_meta or {}),
613
+ base_meta,
614
+ )[1]
615
+ )({"source_directive": embed_directive}, output_metadata),
616
+ timestamp=inv_context.session.last_update_time
617
+ or datetime.now(timezone.utc),
618
+ schema_max_keys=(
619
+ host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
620
+ if host_component
621
+ else DEFAULT_SCHEMA_MAX_KEYS
622
+ ),
623
+ tool_context=tool_context,
624
+ )
625
+
626
+ log.info(
627
+ "%s Successfully applied embed and saved new artifact '%s' (v%s).",
628
+ log_identifier,
629
+ output_filename,
630
+ save_result.get("data_version"),
631
+ )
632
+ return {
633
+ "status": "success",
634
+ "output_filename": output_filename,
635
+ "output_version": save_result.get("data_version"),
636
+ "output_mime_type": output_mime_type,
637
+ "message": f"Successfully created artifact '{output_filename}' v{save_result.get('data_version')} from embed directive.",
638
+ }
639
+
640
+ except Exception as save_err:
641
+ log.exception(
642
+ "%s Failed to save resolved content as artifact '%s': %s",
643
+ log_identifier,
644
+ output_filename,
645
+ save_err,
646
+ )
647
+ return {
648
+ "status": "error",
649
+ "message": f"Failed to save new artifact: {save_err}",
650
+ }
651
+
652
+
653
+ async def extract_content_from_artifact(
654
+ filename: str,
655
+ extraction_goal: str,
656
+ version: Optional[str] = "latest",
657
+ output_filename_base: Optional[str] = None,
658
+ tool_context: ToolContext = None,
659
+ ) -> Dict[str, Any]:
660
+ """
661
+ Loads an existing artifact, uses an internal LLM to process its content
662
+ based on an "extraction_goal," and manages the output by returning it
663
+ or saving it as a new artifact.
664
+
665
+ The tool's description for the LLM might dynamically update based on
666
+ the 'supported_binary_mime_types' configuration of the agent, indicating
667
+ which binary types it can attempt to process.
668
+
669
+ Args:
670
+ filename (str): Name of the source artifact. May contain embeds.
671
+ extraction_goal (str): Natural language instruction for the LLM on what
672
+ to extract or how to transform the content.
673
+ May contain embeds.
674
+ version (Optional[Union[int, str]]): Version of the source artifact.
675
+ Can be an integer or "latest".
676
+ Defaults to "latest". May contain embeds.
677
+ output_filename_base (Optional[str]): Optional base name for the new
678
+ artifact if the extracted content
679
+ is saved. May contain embeds.
680
+ tool_context (ToolContext): Provided by the ADK framework.
681
+
682
+ Returns:
683
+ Dict[str, Any]: A dictionary containing the status of the operation,
684
+ a message for the LLM, and potentially the extracted
685
+ data or details of a newly saved artifact.
686
+ Refer to the design document for specific response structures.
687
+ """
688
+ log_identifier = f"[BuiltinArtifactTool:extract_content:{filename}:{version}]"
689
+ log.debug(
690
+ "%s Processing request. Goal: '%s', Output base: '%s'",
691
+ log_identifier,
692
+ extraction_goal,
693
+ output_filename_base,
694
+ )
695
+
696
+ if not tool_context:
697
+ return {
698
+ "status": "error_tool_context_missing",
699
+ "message_to_llm": "Tool execution failed: ToolContext is missing.",
700
+ "filename": filename,
701
+ "version_requested": str(version),
702
+ }
703
+ if not filename:
704
+ return {
705
+ "status": "error_missing_filename",
706
+ "message_to_llm": "Tool execution failed: 'filename' parameter is required.",
707
+ "version_requested": str(version),
708
+ }
709
+ if not extraction_goal:
710
+ return {
711
+ "status": "error_missing_extraction_goal",
712
+ "message_to_llm": "Tool execution failed: 'extraction_goal' parameter is required.",
713
+ "filename": filename,
714
+ "version_requested": str(version),
715
+ }
716
+
717
+ inv_context = tool_context._invocation_context
718
+ host_component = getattr(inv_context.agent, "host_component", None)
719
+ if not host_component:
720
+ log.error(
721
+ "%s Host component not found on agent. Cannot retrieve config.",
722
+ log_identifier,
723
+ )
724
+ return {
725
+ "status": "error_internal_configuration",
726
+ "message_to_llm": "Tool configuration error: Host component not accessible.",
727
+ "filename": filename,
728
+ "version_requested": str(version),
729
+ }
730
+
731
+ try:
732
+ save_threshold = host_component.get_config(
733
+ "tool_output_save_threshold_bytes", 2048
734
+ )
735
+ llm_max_bytes = host_component.get_config(
736
+ "tool_output_llm_return_max_bytes", 4096
737
+ )
738
+ extraction_config = host_component.get_config(
739
+ "extract_content_from_artifact_config", {}
740
+ )
741
+ supported_binary_mime_types = extraction_config.get(
742
+ "supported_binary_mime_types", []
743
+ )
744
+ model_config_for_extraction = extraction_config.get("model")
745
+ except Exception as e:
746
+ log.exception("%s Error retrieving tool configuration: %s", log_identifier, e)
747
+ return {
748
+ "status": "error_internal_configuration",
749
+ "message_to_llm": f"Tool configuration error: {e}",
750
+ "filename": filename,
751
+ "version_requested": str(version),
752
+ }
753
+
754
+ source_artifact_data = None
755
+ processed_version: Union[int, str]
756
+
757
+ if version is None or (
758
+ isinstance(version, str) and version.strip().lower() == "latest"
759
+ ):
760
+ processed_version = "latest"
761
+ else:
762
+ try:
763
+ processed_version = int(version)
764
+ except ValueError:
765
+ log.warning(
766
+ "%s Invalid version string: '%s'. Must be an integer or 'latest'.",
767
+ log_identifier,
768
+ version,
769
+ )
770
+ return {
771
+ "status": "error_invalid_version_format",
772
+ "message_to_llm": f"Invalid version format '{version}'. Version must be an integer or 'latest'.",
773
+ "filename": filename,
774
+ "version_requested": str(version),
775
+ }
776
+ try:
777
+ log.debug(
778
+ "%s Loading source artifact '%s' version '%s' (processed as: %s)",
779
+ log_identifier,
780
+ filename,
781
+ version,
782
+ processed_version,
783
+ )
784
+ source_artifact_data = await load_artifact_content_or_metadata(
785
+ artifact_service=inv_context.artifact_service,
786
+ app_name=inv_context.app_name,
787
+ user_id=inv_context.user_id,
788
+ session_id=get_original_session_id(inv_context),
789
+ filename=filename,
790
+ version=processed_version,
791
+ return_raw_bytes=True,
792
+ log_identifier_prefix=log_identifier,
793
+ )
794
+ if source_artifact_data.get("status") != "success":
795
+ raise FileNotFoundError(
796
+ source_artifact_data.get("message", "Failed to load artifact")
797
+ )
798
+ log.info(
799
+ "%s Successfully loaded source artifact '%s' version %s (actual: v%s)",
800
+ log_identifier,
801
+ filename,
802
+ version,
803
+ source_artifact_data.get("version"),
804
+ )
805
+ except FileNotFoundError as e:
806
+ log.warning("%s Source artifact not found: %s", log_identifier, e)
807
+ return {
808
+ "status": "error_artifact_not_found",
809
+ "message_to_llm": f"Could not extract content. Source artifact '{filename}' (version {version}) was not found: {e}",
810
+ "filename": filename,
811
+ "version_requested": str(version),
812
+ }
813
+ except Exception as e:
814
+ log.exception("%s Error loading source artifact: %s", log_identifier, e)
815
+ return {
816
+ "status": "error_loading_artifact",
817
+ "message_to_llm": f"Error loading source artifact '{filename}': {e}",
818
+ "filename": filename,
819
+ "version_requested": str(version),
820
+ }
821
+
822
+ source_artifact_content_bytes = source_artifact_data.get("raw_bytes")
823
+ source_mime_type = source_artifact_data.get("mime_type", "application/octet-stream")
824
+ actual_source_version = source_artifact_data.get("version", "unknown")
825
+
826
+ chosen_llm = None
827
+ try:
828
+ if model_config_for_extraction:
829
+ if isinstance(model_config_for_extraction, str):
830
+ chosen_llm = LLMRegistry.new_llm(model_config_for_extraction)
831
+ log.info(
832
+ "%s Using tool-specific LLM (string): %s",
833
+ log_identifier,
834
+ model_config_for_extraction,
835
+ )
836
+ elif isinstance(model_config_for_extraction, dict):
837
+ chosen_llm = LiteLlm(**model_config_for_extraction)
838
+ log.info(
839
+ "%s Using tool-specific LLM (dict): %s",
840
+ log_identifier,
841
+ model_config_for_extraction.get("model"),
842
+ )
843
+ else:
844
+ log.warning(
845
+ "%s Invalid 'model' config for extraction tool. Falling back to agent default.",
846
+ log_identifier,
847
+ )
848
+ chosen_llm = inv_context.agent.canonical_model
849
+ else:
850
+ chosen_llm = inv_context.agent.canonical_model
851
+ log.info(
852
+ "%s Using agent's default LLM: %s", log_identifier, chosen_llm.model
853
+ )
854
+ except Exception as e:
855
+ log.exception("%s Error initializing LLM for extraction: %s", log_identifier, e)
856
+ return {
857
+ "status": "error_internal_llm_setup",
858
+ "message_to_llm": f"Failed to set up LLM for extraction: {e}",
859
+ "filename": filename,
860
+ "version_requested": str(version),
861
+ }
862
+
863
+ llm_parts = []
864
+ is_binary_supported = False
865
+
866
+ normalized_source_mime_type = source_mime_type.lower() if source_mime_type else ""
867
+
868
+ is_text_based = is_text_based_file(
869
+ mime_type=normalized_source_mime_type,
870
+ content_bytes=source_artifact_content_bytes,
871
+ )
872
+
873
+ if is_text_based:
874
+ try:
875
+ artifact_text_content = source_artifact_content_bytes.decode("utf-8")
876
+ llm_parts.append(
877
+ adk_types.Part(
878
+ text=f"Artifact Content (MIME type: {source_mime_type}):\n```\n{artifact_text_content}\n```"
879
+ )
880
+ )
881
+ log.debug("%s Prepared text content for LLM.", log_identifier)
882
+ except UnicodeDecodeError as e:
883
+ log.warning(
884
+ "%s Failed to decode text artifact as UTF-8: %s. Treating as opaque binary.",
885
+ log_identifier,
886
+ e,
887
+ )
888
+ llm_parts.append(
889
+ adk_types.Part(
890
+ text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}' and could not be decoded as text."
891
+ )
892
+ )
893
+ else: # Binary
894
+ for supported_pattern in supported_binary_mime_types:
895
+ if fnmatch.fnmatch(source_mime_type, supported_pattern):
896
+ is_binary_supported = True
897
+ break
898
+ if is_binary_supported:
899
+ llm_parts.append(
900
+ adk_types.Part(
901
+ inline_data=adk_types.Blob(
902
+ mime_type=source_mime_type, data=source_artifact_content_bytes
903
+ )
904
+ )
905
+ )
906
+ llm_parts.append(
907
+ adk_types.Part(
908
+ text=f"The above is the content of artifact '{filename}' (MIME type: {source_mime_type})."
909
+ )
910
+ )
911
+ log.debug(
912
+ "%s Prepared supported binary content (MIME: %s) for LLM.",
913
+ log_identifier,
914
+ source_mime_type,
915
+ )
916
+ else:
917
+ llm_parts.append(
918
+ adk_types.Part(
919
+ text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}'. Direct content processing is not supported by this tool's current configuration. Perform the extraction goal based on its filename and type if possible, or state that the content cannot be analyzed."
920
+ )
921
+ )
922
+ log.debug(
923
+ "%s Prepared message for unsupported binary content (MIME: %s) for LLM.",
924
+ log_identifier,
925
+ source_mime_type,
926
+ )
927
+
928
+ internal_llm_contents = [
929
+ adk_types.Content(
930
+ role="user", parts=[adk_types.Part(text=extraction_goal)] + llm_parts
931
+ )
932
+ ]
933
+ internal_llm_request = LlmRequest(
934
+ model=chosen_llm.model,
935
+ contents=internal_llm_contents,
936
+ config=adk_types.GenerateContentConfig(
937
+ temperature=0.1,
938
+ ),
939
+ )
940
+
941
+ extracted_content_str = ""
942
+ try:
943
+ log.info(
944
+ "%s Executing internal LLM call for extraction. Goal: %s",
945
+ log_identifier,
946
+ extraction_goal,
947
+ )
948
+ if hasattr(chosen_llm, "generate_content") and not hasattr(
949
+ chosen_llm, "generate_content_async"
950
+ ):
951
+ llm_response = chosen_llm.generate_content(request=internal_llm_request)
952
+ if llm_response.parts:
953
+ extracted_content_str = llm_response.parts[0].text or ""
954
+ else:
955
+ extracted_content_str = ""
956
+ elif hasattr(chosen_llm, "generate_content_async"):
957
+ log.debug(
958
+ "%s Calling LLM's generate_content_async (non-streaming) for extraction.",
959
+ log_identifier,
960
+ )
961
+ try:
962
+ llm_response_obj = None
963
+ async for response_event in chosen_llm.generate_content_async(
964
+ internal_llm_request
965
+ ):
966
+ llm_response_obj = response_event
967
+ break
968
+ if (
969
+ llm_response_obj
970
+ and hasattr(llm_response_obj, "text")
971
+ and llm_response_obj.text
972
+ ):
973
+ extracted_content_str = llm_response_obj.text
974
+ elif (
975
+ llm_response_obj
976
+ and hasattr(llm_response_obj, "parts")
977
+ and llm_response_obj.parts
978
+ ):
979
+ extracted_content_str = "".join(
980
+ [
981
+ part.text
982
+ for part in llm_response_obj.parts
983
+ if hasattr(part, "text") and part.text
984
+ ]
985
+ )
986
+ elif (
987
+ llm_response_obj
988
+ and hasattr(llm_response_obj, "content")
989
+ and hasattr(llm_response_obj.content, "parts")
990
+ and llm_response_obj.content.parts
991
+ ):
992
+ extracted_content_str = "".join(
993
+ [
994
+ part.text
995
+ for part in llm_response_obj.content.parts
996
+ if hasattr(part, "text") and part.text
997
+ ]
998
+ )
999
+ else:
1000
+ extracted_content_str = ""
1001
+ log.warning(
1002
+ "%s LLM response object or its text/parts were not found or empty after non-streaming call.",
1003
+ log_identifier,
1004
+ )
1005
+
1006
+ except Exception as llm_async_err:
1007
+ log.exception(
1008
+ "%s Asynchronous LLM call for extraction failed: %s",
1009
+ log_identifier,
1010
+ llm_async_err,
1011
+ )
1012
+ extracted_content_str = (
1013
+ f"[ERROR: Asynchronous LLM call failed: {llm_async_err}]"
1014
+ )
1015
+ else:
1016
+ log.error(
1017
+ "%s LLM does not have a known generate_content or generate_content_async method. Extraction will be empty.",
1018
+ log_identifier,
1019
+ )
1020
+ extracted_content_str = "[ERROR: LLM method not found]"
1021
+
1022
+ log.info(
1023
+ "%s Internal LLM call completed. Extracted content length: %d chars",
1024
+ log_identifier,
1025
+ len(extracted_content_str),
1026
+ )
1027
+ if not extracted_content_str.strip():
1028
+ log.warning(
1029
+ "%s Internal LLM produced empty or whitespace-only content for extraction goal.",
1030
+ log_identifier,
1031
+ )
1032
+
1033
+ except Exception as e:
1034
+ log.exception(
1035
+ "%s Internal LLM call for extraction failed: %s", log_identifier, e
1036
+ )
1037
+ return {
1038
+ "status": "error_extraction_failed",
1039
+ "message_to_llm": f"The LLM failed to process the artifact content for your goal '{extraction_goal}'. Error: {e}",
1040
+ "filename": filename,
1041
+ "version_requested": str(version),
1042
+ }
1043
+
1044
+ extracted_content_bytes = extracted_content_str.encode("utf-8")
1045
+ extracted_content_size_bytes = len(extracted_content_bytes)
1046
+ output_mime_type = "text/plain"
1047
+ try:
1048
+ json.loads(extracted_content_str)
1049
+ output_mime_type = "application/json"
1050
+ log.debug(
1051
+ "%s Extracted content appears to be valid JSON. Setting output MIME to application/json.",
1052
+ log_identifier,
1053
+ )
1054
+ except json.JSONDecodeError:
1055
+ log.debug(
1056
+ "%s Extracted content is not JSON. Using output MIME text/plain.",
1057
+ log_identifier,
1058
+ )
1059
+
1060
+ response_for_llm_str = extracted_content_str
1061
+ saved_extracted_artifact_details = None
1062
+ final_status = "success"
1063
+ message_to_llm_parts = [
1064
+ f"Successfully extracted content from '{filename}' (v{actual_source_version}) based on your goal: '{extraction_goal}'."
1065
+ ]
1066
+ was_saved = False
1067
+ was_truncated = False
1068
+
1069
+ if extracted_content_size_bytes > save_threshold:
1070
+ log.info(
1071
+ "%s Extracted content size (%d bytes) exceeds save threshold (%d bytes). Saving as new artifact.",
1072
+ log_identifier,
1073
+ extracted_content_size_bytes,
1074
+ save_threshold,
1075
+ )
1076
+ saved_extracted_artifact_details = await _save_extracted_artifact(
1077
+ tool_context,
1078
+ host_component,
1079
+ extracted_content_bytes,
1080
+ filename,
1081
+ actual_source_version,
1082
+ extraction_goal,
1083
+ output_filename_base,
1084
+ output_mime_type,
1085
+ )
1086
+ if saved_extracted_artifact_details.get("status") == "success":
1087
+ was_saved = True
1088
+ message_to_llm_parts.append(
1089
+ f"The full extracted content was saved as artifact '{saved_extracted_artifact_details.get('data_filename')}' "
1090
+ f"(version {saved_extracted_artifact_details.get('data_version')}). "
1091
+ f"You can retrieve it using 'load_artifact' or perform further extractions on it using 'extract_content_from_artifact' "
1092
+ f"with this new filename and version."
1093
+ )
1094
+ else:
1095
+ message_to_llm_parts.append(
1096
+ f"Attempted to save the large extracted content, but failed: {saved_extracted_artifact_details.get('message')}"
1097
+ )
1098
+
1099
+ if len(extracted_content_str.encode("utf-8")) > llm_max_bytes:
1100
+ was_truncated = True
1101
+ log.info(
1102
+ "%s Original extracted content (%d bytes) exceeds LLM return max bytes (%d bytes). Truncating for LLM response.",
1103
+ log_identifier,
1104
+ len(extracted_content_str.encode("utf-8")),
1105
+ llm_max_bytes,
1106
+ )
1107
+
1108
+ if not was_saved:
1109
+ log.info(
1110
+ "%s Saving extracted content now because it needs truncation for LLM response and wasn't saved previously.",
1111
+ log_identifier,
1112
+ )
1113
+ saved_extracted_artifact_details = await _save_extracted_artifact(
1114
+ tool_context,
1115
+ host_component,
1116
+ extracted_content_bytes,
1117
+ filename,
1118
+ actual_source_version,
1119
+ extraction_goal,
1120
+ output_filename_base,
1121
+ output_mime_type,
1122
+ )
1123
+ if saved_extracted_artifact_details.get("status") == "success":
1124
+ was_saved = True
1125
+ message_to_llm_parts.append(
1126
+ f"The full extracted content (which is being truncated for this response) was saved as artifact "
1127
+ f"'{saved_extracted_artifact_details.get('data_filename')}' (version {saved_extracted_artifact_details.get('data_version')}). "
1128
+ f"You can retrieve the full content using 'load_artifact' or perform further extractions on it."
1129
+ )
1130
+ else:
1131
+ message_to_llm_parts.append(
1132
+ f"Attempted to save the extracted content before truncation, but failed: {saved_extracted_artifact_details.get('message')}"
1133
+ )
1134
+
1135
+ truncation_suffix = "... [Content truncated]"
1136
+ adjusted_max_bytes = llm_max_bytes - len(truncation_suffix.encode("utf-8"))
1137
+ if adjusted_max_bytes < 0:
1138
+ adjusted_max_bytes = 0
1139
+
1140
+ temp_response_bytes = extracted_content_str.encode("utf-8")
1141
+ truncated_bytes = temp_response_bytes[:adjusted_max_bytes]
1142
+ response_for_llm_str = (
1143
+ truncated_bytes.decode("utf-8", "ignore") + truncation_suffix
1144
+ )
1145
+
1146
+ message_to_llm_parts.append(
1147
+ "The extracted content provided in 'extracted_data_preview' has been truncated due to size limits. "
1148
+ "If saved, the full version is available in the specified artifact."
1149
+ )
1150
+
1151
+ if was_saved and was_truncated:
1152
+ final_status = "success_full_content_saved_preview_returned"
1153
+ elif was_saved:
1154
+ final_status = "success_full_content_saved_and_returned"
1155
+ elif was_truncated:
1156
+ final_status = "success_content_returned_truncated_and_saved"
1157
+ else:
1158
+ final_status = "success_content_returned"
1159
+
1160
+ final_response_dict = {
1161
+ "status": final_status,
1162
+ "message_to_llm": " ".join(list(dict.fromkeys(message_to_llm_parts))),
1163
+ "source_filename": filename,
1164
+ "source_version_processed": actual_source_version,
1165
+ "extraction_goal_used": extraction_goal,
1166
+ }
1167
+
1168
+ if was_truncated:
1169
+ final_response_dict["extracted_data_preview"] = response_for_llm_str
1170
+ else:
1171
+ final_response_dict["extracted_data"] = response_for_llm_str
1172
+
1173
+ if (
1174
+ saved_extracted_artifact_details
1175
+ and saved_extracted_artifact_details.get("status") == "success"
1176
+ ):
1177
+ final_response_dict["saved_extracted_artifact_details"] = (
1178
+ saved_extracted_artifact_details
1179
+ )
1180
+ elif saved_extracted_artifact_details:
1181
+ final_response_dict["saved_extracted_artifact_attempt_details"] = (
1182
+ saved_extracted_artifact_details
1183
+ )
1184
+
1185
+ log.info(
1186
+ "%s Tool execution finished. Final status: %s. Response preview: %s",
1187
+ log_identifier,
1188
+ final_status,
1189
+ final_response_dict,
1190
+ )
1191
+ return final_response_dict
1192
+
1193
+
1194
+ async def append_to_artifact(
1195
+ filename: str,
1196
+ content_chunk: str,
1197
+ mime_type: str,
1198
+ tool_context: ToolContext = None,
1199
+ ) -> Dict[str, Any]:
1200
+ """
1201
+ Appends a chunk of content to an existing artifact. This operation will
1202
+ create a new version of the artifact. The content_chunk should be a string,
1203
+ potentially base64 encoded if it represents binary data (indicated by mime_type).
1204
+ The chunk size should be limited (e.g., max 3KB) by the LLM.
1205
+
1206
+ Args:
1207
+ filename: The name of the artifact to append to. May contain embeds.
1208
+ content_chunk: The chunk of content to append (max approx. 3KB).
1209
+ If mime_type suggests binary, this should be base64 encoded.
1210
+ May contain embeds.
1211
+ mime_type: The MIME type of the content_chunk. This helps determine if
1212
+ base64 decoding is needed for the chunk. The overall artifact's
1213
+ MIME type will be preserved from its latest version.
1214
+ May contain embeds.
1215
+ tool_context: The context provided by the ADK framework.
1216
+
1217
+ Returns:
1218
+ A dictionary indicating the result, including the new version of the artifact.
1219
+ """
1220
+ if not tool_context:
1221
+ return {
1222
+ "status": "error",
1223
+ "filename": filename,
1224
+ "message": "ToolContext is missing, cannot append to artifact.",
1225
+ }
1226
+
1227
+ log_identifier = f"[BuiltinArtifactTool:append_to_artifact:{filename}]"
1228
+ log.debug("%s Processing request to append chunk.", log_identifier)
1229
+
1230
+ try:
1231
+ inv_context = tool_context._invocation_context
1232
+ artifact_service = inv_context.artifact_service
1233
+ if not artifact_service:
1234
+ raise ValueError("ArtifactService is not available in the context.")
1235
+
1236
+ app_name = inv_context.app_name
1237
+ user_id = inv_context.user_id
1238
+ session_id = get_original_session_id(inv_context)
1239
+ host_component = getattr(inv_context.agent, "host_component", None)
1240
+
1241
+ log.debug(
1242
+ "%s Loading latest version of artifact '%s' content to append to.",
1243
+ log_identifier,
1244
+ filename,
1245
+ )
1246
+ content_load_result = await load_artifact_content_or_metadata(
1247
+ artifact_service=artifact_service,
1248
+ app_name=app_name,
1249
+ user_id=user_id,
1250
+ session_id=session_id,
1251
+ filename=filename,
1252
+ version="latest",
1253
+ load_metadata_only=False,
1254
+ return_raw_bytes=True,
1255
+ component=host_component,
1256
+ log_identifier_prefix=f"{log_identifier}[LoadOriginalContent]",
1257
+ )
1258
+
1259
+ if content_load_result.get("status") != "success":
1260
+ log.error(
1261
+ "%s Failed to load original artifact content '%s': %s",
1262
+ log_identifier,
1263
+ filename,
1264
+ content_load_result.get("message"),
1265
+ )
1266
+ return {
1267
+ "status": "error",
1268
+ "filename": filename,
1269
+ "message": f"Failed to load original artifact content to append to: {content_load_result.get('message')}",
1270
+ }
1271
+
1272
+ original_artifact_bytes = content_load_result.get("raw_bytes", b"")
1273
+ original_mime_type = content_load_result.get(
1274
+ "mime_type", "application/octet-stream"
1275
+ )
1276
+ original_version_loaded = content_load_result.get("version", "unknown")
1277
+ log.info(
1278
+ "%s Loaded original artifact content '%s' v%s, type: %s, size: %d bytes.",
1279
+ log_identifier,
1280
+ filename,
1281
+ original_version_loaded,
1282
+ original_mime_type,
1283
+ len(original_artifact_bytes),
1284
+ )
1285
+
1286
+ log.debug(
1287
+ "%s Loading latest version of artifact '%s' metadata.",
1288
+ log_identifier,
1289
+ filename,
1290
+ )
1291
+ metadata_load_result = await load_artifact_content_or_metadata(
1292
+ artifact_service=artifact_service,
1293
+ app_name=app_name,
1294
+ user_id=user_id,
1295
+ session_id=session_id,
1296
+ filename=filename,
1297
+ version="latest",
1298
+ load_metadata_only=True,
1299
+ component=host_component,
1300
+ log_identifier_prefix=f"{log_identifier}[LoadOriginalMetadata]",
1301
+ )
1302
+ original_metadata_dict = {}
1303
+ if metadata_load_result.get("status") == "success":
1304
+ original_metadata_dict = metadata_load_result.get("metadata", {})
1305
+ log.info(
1306
+ "%s Loaded original artifact metadata for '%s' v%s.",
1307
+ log_identifier,
1308
+ filename,
1309
+ metadata_load_result.get("version", "unknown"),
1310
+ )
1311
+ else:
1312
+ log.warning(
1313
+ "%s Failed to load original artifact metadata for '%s': %s. Proceeding with minimal metadata.",
1314
+ log_identifier,
1315
+ filename,
1316
+ metadata_load_result.get("message"),
1317
+ )
1318
+
1319
+ chunk_bytes, _ = decode_and_get_bytes(
1320
+ content_chunk, mime_type, f"{log_identifier}[DecodeChunk]"
1321
+ )
1322
+ log.debug(
1323
+ "%s Decoded content_chunk (declared type: %s) to %d bytes.",
1324
+ log_identifier,
1325
+ mime_type,
1326
+ len(chunk_bytes),
1327
+ )
1328
+
1329
+ combined_bytes = original_artifact_bytes + chunk_bytes
1330
+ log.debug(
1331
+ "%s Appended chunk. New total size: %d bytes.",
1332
+ log_identifier,
1333
+ len(combined_bytes),
1334
+ )
1335
+
1336
+ new_metadata_for_save = {
1337
+ key: value
1338
+ for key, value in original_metadata_dict.items()
1339
+ if key
1340
+ not in [
1341
+ "filename",
1342
+ "mime_type",
1343
+ "size_bytes",
1344
+ "timestamp_utc",
1345
+ "schema",
1346
+ "version",
1347
+ ]
1348
+ }
1349
+ new_metadata_for_save["appended_from_version"] = original_version_loaded
1350
+ new_metadata_for_save["appended_chunk_declared_mime_type"] = mime_type
1351
+
1352
+ schema_max_keys = (
1353
+ host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
1354
+ if host_component
1355
+ else DEFAULT_SCHEMA_MAX_KEYS
1356
+ )
1357
+
1358
+ save_result = await save_artifact_with_metadata(
1359
+ artifact_service=artifact_service,
1360
+ app_name=app_name,
1361
+ user_id=user_id,
1362
+ session_id=session_id,
1363
+ filename=filename,
1364
+ content_bytes=combined_bytes,
1365
+ mime_type=original_mime_type,
1366
+ metadata_dict=new_metadata_for_save,
1367
+ timestamp=datetime.now(timezone.utc),
1368
+ schema_max_keys=schema_max_keys,
1369
+ tool_context=tool_context,
1370
+ )
1371
+
1372
+ log.info(
1373
+ "%s Result from save_artifact_with_metadata after append: %s",
1374
+ log_identifier,
1375
+ save_result,
1376
+ )
1377
+
1378
+ if save_result.get("status") == "error":
1379
+ raise IOError(
1380
+ f"Failed to save appended artifact: {save_result.get('message', 'Unknown error')}"
1381
+ )
1382
+
1383
+ return {
1384
+ "status": "success",
1385
+ "filename": filename,
1386
+ "new_version": save_result.get("data_version"),
1387
+ "total_size_bytes": len(combined_bytes),
1388
+ "message": f"Chunk appended to '{filename}'. New version is {save_result.get('data_version')} with total size {len(combined_bytes)} bytes.",
1389
+ }
1390
+
1391
+ except FileNotFoundError as e:
1392
+ log.warning("%s Original artifact not found for append: %s", log_identifier, e)
1393
+ return {
1394
+ "status": "error",
1395
+ "filename": filename,
1396
+ "message": f"Original artifact '{filename}' not found: {e}",
1397
+ }
1398
+ except ValueError as e:
1399
+ log.warning("%s Value error during append: %s", log_identifier, e)
1400
+ return {"status": "error", "filename": filename, "message": str(e)}
1401
+ except IOError as e:
1402
+ log.warning("%s IO error during append: %s", log_identifier, e)
1403
+ return {"status": "error", "filename": filename, "message": str(e)}
1404
+ except Exception as e:
1405
+ log.exception(
1406
+ "%s Unexpected error appending to artifact '%s': %s",
1407
+ log_identifier,
1408
+ filename,
1409
+ e,
1410
+ )
1411
+ return {
1412
+ "status": "error",
1413
+ "filename": filename,
1414
+ "message": f"Failed to append to artifact: {e}",
1415
+ }
1416
+
1417
+
1418
+ async def _save_extracted_artifact(
1419
+ tool_context: ToolContext,
1420
+ host_component: Any,
1421
+ extracted_content_bytes: bytes,
1422
+ source_artifact_filename: str,
1423
+ source_artifact_version: Union[int, str],
1424
+ extraction_goal: str,
1425
+ output_filename_base: Optional[str],
1426
+ output_mime_type: str,
1427
+ ) -> Dict[str, Any]:
1428
+ """
1429
+ Saves the extracted content as a new artifact with comprehensive metadata.
1430
+
1431
+ Args:
1432
+ tool_context: The ADK ToolContext.
1433
+ host_component: The A2A_ADK_HostComponent instance for accessing config and services.
1434
+ extracted_content_bytes: The raw byte content of the extracted data.
1435
+ source_artifact_filename: The filename of the original artifact.
1436
+ source_artifact_version: The version of the original artifact.
1437
+ extraction_goal: The natural language goal used for extraction.
1438
+ output_filename_base: Optional base for the new artifact's filename.
1439
+ output_mime_type: The MIME type of the extracted content.
1440
+
1441
+ Returns:
1442
+ A dictionary containing details of the saved artifact, as returned by
1443
+ `save_artifact_with_metadata`.
1444
+ """
1445
+ log_identifier = f"[BuiltinArtifactTool:_save_extracted_artifact]"
1446
+ log.debug("%s Saving extracted content...", log_identifier)
1447
+
1448
+ try:
1449
+ base_name = output_filename_base or f"{source_artifact_filename}_extracted"
1450
+ base_name_sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", base_name)
1451
+ base_name_sanitized = base_name_sanitized.strip("_")
1452
+
1453
+ suffix = uuid.uuid4().hex[:8]
1454
+ extension_map = {
1455
+ "text/plain": ".txt",
1456
+ "application/json": ".json",
1457
+ "text/csv": ".csv",
1458
+ "text/html": ".html",
1459
+ "image/png": ".png",
1460
+ "image/jpeg": ".jpg",
1461
+ "application/pdf": ".pdf",
1462
+ }
1463
+ ext = extension_map.get(output_mime_type.lower(), ".dat")
1464
+ filename = f"{base_name_sanitized}_{suffix}{ext}"
1465
+ log.debug("%s Generated output filename: %s", log_identifier, filename)
1466
+
1467
+ timestamp = datetime.now(timezone.utc)
1468
+ metadata_for_saving = {
1469
+ "description": f"Content extracted/transformed from artifact '{source_artifact_filename}' (version {source_artifact_version}) using goal: '{extraction_goal}'.",
1470
+ "source_artifact_filename": source_artifact_filename,
1471
+ "source_artifact_version": source_artifact_version,
1472
+ "extraction_goal_used": extraction_goal,
1473
+ }
1474
+ log.debug(
1475
+ "%s Prepared metadata for saving: %s", log_identifier, metadata_for_saving
1476
+ )
1477
+
1478
+ inv_context = tool_context._invocation_context
1479
+ artifact_service = inv_context.artifact_service
1480
+ if not artifact_service:
1481
+ raise ValueError("ArtifactService is not available in the context.")
1482
+
1483
+ app_name = inv_context.app_name
1484
+ user_id = inv_context.user_id
1485
+ session_id = get_original_session_id(inv_context)
1486
+ schema_max_keys = host_component.get_config(
1487
+ "schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS
1488
+ )
1489
+
1490
+ log.debug(
1491
+ "%s Calling save_artifact_with_metadata for '%s' (app: %s, user: %s, session: %s, schema_keys: %d)",
1492
+ log_identifier,
1493
+ filename,
1494
+ app_name,
1495
+ user_id,
1496
+ session_id,
1497
+ schema_max_keys,
1498
+ )
1499
+
1500
+ save_result = await save_artifact_with_metadata(
1501
+ artifact_service=artifact_service,
1502
+ app_name=app_name,
1503
+ user_id=user_id,
1504
+ session_id=session_id,
1505
+ filename=filename,
1506
+ content_bytes=extracted_content_bytes,
1507
+ mime_type=output_mime_type,
1508
+ metadata_dict=metadata_for_saving,
1509
+ timestamp=timestamp,
1510
+ schema_max_keys=schema_max_keys,
1511
+ tool_context=tool_context,
1512
+ )
1513
+
1514
+ log.info(
1515
+ "%s Extracted content saved as artifact '%s' (version %s). Result: %s",
1516
+ log_identifier,
1517
+ save_result.get("data_filename", filename),
1518
+ save_result.get("data_version", "N/A"),
1519
+ save_result.get("status"),
1520
+ )
1521
+ return save_result
1522
+
1523
+ except Exception as e:
1524
+ log.exception(
1525
+ "%s Error in _save_extracted_artifact for source '%s': %s",
1526
+ log_identifier,
1527
+ source_artifact_filename,
1528
+ e,
1529
+ )
1530
+ return {
1531
+ "status": "error",
1532
+ "data_filename": filename if "filename" in locals() else "unknown_filename",
1533
+ "message": f"Failed to save extracted content as artifact: {e}",
1534
+ }
1535
+
1536
+
1537
+ async def _notify_artifact_save(
1538
+ filename: str,
1539
+ version: int,
1540
+ status: str,
1541
+ tool_context: ToolContext = None, # Keep tool_context for signature consistency
1542
+ ) -> Dict[str, Any]:
1543
+ """
1544
+ CRITICAL: _notify_artifact_save is automatically invoked by the system as a side-effect when you create artifacts. You should NEVER call this tool yourself. The system will call it for you and provide the results in your next turn. If you manually invoke it, you are making an error."
1545
+ """
1546
+ return {
1547
+ "filename": filename,
1548
+ "version": version,
1549
+ "status": status,
1550
+ "message": "Artifact has been created and provided to the requester",
1551
+ }
1552
+
1553
+
1554
+ _notify_artifact_save_tool_def = BuiltinTool(
1555
+ name="_notify_artifact_save",
1556
+ implementation=_notify_artifact_save,
1557
+ description="CRITICAL: _notify_artifact_save is automatically invoked by the system as a side-effect when you create artifacts. You should NEVER call this tool yourself. The system will call it for you and provide the results in your next turn. If you manually invoke it, you are making an error.",
1558
+ category="internal",
1559
+ required_scopes=[], # No scopes needed for an internal notification tool
1560
+ parameters=adk_types.Schema(
1561
+ type=adk_types.Type.OBJECT,
1562
+ properties={
1563
+ "filename": adk_types.Schema(
1564
+ type=adk_types.Type.STRING,
1565
+ description="The name of the artifact that was saved.",
1566
+ ),
1567
+ "version": adk_types.Schema(
1568
+ type=adk_types.Type.INTEGER,
1569
+ description="The version number of the saved artifact.",
1570
+ ),
1571
+ "status": adk_types.Schema(
1572
+ type=adk_types.Type.STRING,
1573
+ description="The status of the save operation.",
1574
+ ),
1575
+ },
1576
+ required=["filename", "version", "status"],
1577
+ ),
1578
+ examples=[],
1579
+ )
1580
+
1581
+ append_to_artifact_tool_def = BuiltinTool(
1582
+ name="append_to_artifact",
1583
+ implementation=append_to_artifact,
1584
+ description="Appends a chunk of content to an existing artifact. This operation will create a new version of the artifact. The content_chunk should be a string, potentially base64 encoded if it represents binary data (indicated by mime_type). The chunk size should be limited (e.g., max 3KB) by the LLM.",
1585
+ category="artifact_management",
1586
+ category_name=CATEGORY_NAME,
1587
+ category_description=CATEGORY_DESCRIPTION,
1588
+ required_scopes=["tool:artifact:append"],
1589
+ parameters=adk_types.Schema(
1590
+ type=adk_types.Type.OBJECT,
1591
+ properties={
1592
+ "filename": adk_types.Schema(
1593
+ type=adk_types.Type.STRING,
1594
+ description="The name of the artifact to append to. May contain embeds.",
1595
+ ),
1596
+ "content_chunk": adk_types.Schema(
1597
+ type=adk_types.Type.STRING,
1598
+ description="The chunk of content to append (max approx. 3KB). If mime_type suggests binary, this should be base64 encoded. May contain embeds.",
1599
+ ),
1600
+ "mime_type": adk_types.Schema(
1601
+ type=adk_types.Type.STRING,
1602
+ description="The MIME type of the content_chunk. This helps determine if base64 decoding is needed for the chunk. The overall artifact's MIME type will be preserved from its latest version. May contain embeds.",
1603
+ ),
1604
+ },
1605
+ required=["filename", "content_chunk", "mime_type"],
1606
+ ),
1607
+ examples=[],
1608
+ )
1609
+
1610
+ list_artifacts_tool_def = BuiltinTool(
1611
+ name="list_artifacts",
1612
+ implementation=list_artifacts,
1613
+ description="Lists all available data artifact filenames and their versions for the current session. Includes a summary of the latest version's metadata for each artifact.",
1614
+ category="artifact_management",
1615
+ category_name=CATEGORY_NAME,
1616
+ category_description=CATEGORY_DESCRIPTION,
1617
+ required_scopes=["tool:artifact:list"],
1618
+ parameters=adk_types.Schema(
1619
+ type=adk_types.Type.OBJECT,
1620
+ properties={},
1621
+ required=[],
1622
+ ),
1623
+ examples=[],
1624
+ )
1625
+
1626
+ load_artifact_tool_def = BuiltinTool(
1627
+ name="load_artifact",
1628
+ implementation=load_artifact,
1629
+ description="Loads the content or metadata of a specific artifact version. If load_metadata_only is True, loads the full metadata dictionary. Otherwise, loads text content (potentially truncated) or a summary for binary types. Line numbers can be optionally included for precise line range identification.",
1630
+ category="artifact_management",
1631
+ category_name=CATEGORY_NAME,
1632
+ category_description=CATEGORY_DESCRIPTION,
1633
+ required_scopes=["tool:artifact:load"],
1634
+ parameters=adk_types.Schema(
1635
+ type=adk_types.Type.OBJECT,
1636
+ properties={
1637
+ "filename": adk_types.Schema(
1638
+ type=adk_types.Type.STRING,
1639
+ description="The name of the artifact to load. May contain embeds.",
1640
+ ),
1641
+ "version": adk_types.Schema(
1642
+ type=adk_types.Type.INTEGER,
1643
+ description="The specific version number to load. Must be explicitly provided.",
1644
+ ),
1645
+ "load_metadata_only": adk_types.Schema(
1646
+ type=adk_types.Type.BOOLEAN,
1647
+ description="If True, load only the metadata JSON. Default False.",
1648
+ nullable=True,
1649
+ ),
1650
+ "max_content_length": adk_types.Schema(
1651
+ type=adk_types.Type.INTEGER,
1652
+ description="Optional. Maximum character length for text content. If None, uses app configuration. Range: 100-100,000.",
1653
+ nullable=True,
1654
+ ),
1655
+ "include_line_numbers": adk_types.Schema(
1656
+ type=adk_types.Type.BOOLEAN,
1657
+ description="If True, prefix each line with its 1-based line number followed by a TAB character. Line numbers are for LLM viewing only and are not stored in the artifact. Default False.",
1658
+ nullable=True,
1659
+ ),
1660
+ },
1661
+ required=["filename", "version"],
1662
+ ),
1663
+ examples=[],
1664
+ )
1665
+
1666
+ apply_embed_and_create_artifact_tool_def = BuiltinTool(
1667
+ name="apply_embed_and_create_artifact",
1668
+ implementation=apply_embed_and_create_artifact,
1669
+ description="Resolves an 'artifact_content' embed directive (including modifiers and formatting) and saves the resulting content as a new artifact. The entire embed directive must be provided as a string.",
1670
+ category="artifact_management",
1671
+ category_name=CATEGORY_NAME,
1672
+ category_description=CATEGORY_DESCRIPTION,
1673
+ required_scopes=["tool:artifact:create", "tool:artifact:load"],
1674
+ parameters=adk_types.Schema(
1675
+ type=adk_types.Type.OBJECT,
1676
+ properties={
1677
+ "output_filename": adk_types.Schema(
1678
+ type=adk_types.Type.STRING,
1679
+ description="The desired name for the new artifact.",
1680
+ ),
1681
+ "embed_directive": adk_types.Schema(
1682
+ type=adk_types.Type.STRING,
1683
+ description="The full '«artifact_content:...>>>...>>>format:...»' string.",
1684
+ ),
1685
+ "output_metadata": adk_types.Schema(
1686
+ type=adk_types.Type.OBJECT,
1687
+ description="Optional metadata for the new artifact.",
1688
+ nullable=True,
1689
+ ),
1690
+ },
1691
+ required=["output_filename", "embed_directive"],
1692
+ ),
1693
+ raw_string_args=["embed_directive"],
1694
+ examples=[],
1695
+ )
1696
+
1697
+ extract_content_from_artifact_tool_def = BuiltinTool(
1698
+ name="extract_content_from_artifact",
1699
+ implementation=extract_content_from_artifact,
1700
+ description="Loads an existing artifact, uses an internal LLM to process its content based on an 'extraction_goal,' and manages the output by returning it or saving it as a new artifact.",
1701
+ category="artifact_management",
1702
+ category_name=CATEGORY_NAME,
1703
+ category_description=CATEGORY_DESCRIPTION,
1704
+ required_scopes=["tool:artifact:load", "tool:artifact:create"],
1705
+ parameters=adk_types.Schema(
1706
+ type=adk_types.Type.OBJECT,
1707
+ properties={
1708
+ "filename": adk_types.Schema(
1709
+ type=adk_types.Type.STRING,
1710
+ description="Name of the source artifact. May contain embeds.",
1711
+ ),
1712
+ "extraction_goal": adk_types.Schema(
1713
+ type=adk_types.Type.STRING,
1714
+ description="Natural language instruction for the LLM on what to extract or how to transform the content. May contain embeds.",
1715
+ ),
1716
+ "version": adk_types.Schema(
1717
+ type=adk_types.Type.STRING,
1718
+ description="Version of the source artifact. Can be an integer or 'latest'. Defaults to 'latest'. May contain embeds.",
1719
+ nullable=True,
1720
+ ),
1721
+ "output_filename_base": adk_types.Schema(
1722
+ type=adk_types.Type.STRING,
1723
+ description="Optional base name for the new artifact if the extracted content is saved. May contain embeds.",
1724
+ nullable=True,
1725
+ ),
1726
+ },
1727
+ required=["filename", "extraction_goal"],
1728
+ ),
1729
+ examples=[],
1730
+ )
1731
+
1732
+ tool_registry.register(_notify_artifact_save_tool_def)
1733
+ tool_registry.register(append_to_artifact_tool_def)
1734
+ tool_registry.register(list_artifacts_tool_def)
1735
+ tool_registry.register(load_artifact_tool_def)
1736
+ tool_registry.register(apply_embed_and_create_artifact_tool_def)
1737
+ tool_registry.register(extract_content_from_artifact_tool_def)
1738
+
1739
+
1740
+ async def delete_artifact(
1741
+ filename: str,
1742
+ version: Optional[int] = None,
1743
+ tool_context: ToolContext = None,
1744
+ ) -> Dict[str, Any]:
1745
+ """
1746
+ Deletes a specific version of an artifact, or all versions if no version is specified.
1747
+
1748
+ Args:
1749
+ filename: The name of the artifact to delete.
1750
+ version: The specific version number to delete. If not provided, all versions will be deleted.
1751
+ tool_context: The context provided by the ADK framework.
1752
+
1753
+ Returns:
1754
+ A dictionary indicating the result of the deletion.
1755
+ """
1756
+ if not tool_context:
1757
+ return {
1758
+ "status": "error",
1759
+ "filename": filename,
1760
+ "message": "ToolContext is missing, cannot delete artifact.",
1761
+ }
1762
+
1763
+ log_identifier = (
1764
+ f"[BuiltinArtifactTool:delete_artifact:{filename}:{version or 'all'}]"
1765
+ )
1766
+ log.debug("%s Processing request.", log_identifier)
1767
+
1768
+ try:
1769
+ inv_context = tool_context._invocation_context
1770
+ artifact_service = inv_context.artifact_service
1771
+ if not artifact_service:
1772
+ raise ValueError("ArtifactService is not available in the context.")
1773
+
1774
+ app_name = inv_context.app_name
1775
+ user_id = inv_context.user_id
1776
+ session_id = get_original_session_id(inv_context)
1777
+
1778
+ if not hasattr(artifact_service, "delete_artifact"):
1779
+ raise NotImplementedError(
1780
+ "ArtifactService does not support deleting artifacts."
1781
+ )
1782
+
1783
+ if version is not None:
1784
+ log.warning(
1785
+ "%s Deleting a specific version (%s) is not supported by the current artifact service interface. "
1786
+ "All versions of the artifact will be deleted.",
1787
+ log_identifier,
1788
+ version,
1789
+ )
1790
+
1791
+ await artifact_service.delete_artifact(
1792
+ app_name=app_name,
1793
+ user_id=user_id,
1794
+ session_id=session_id,
1795
+ filename=filename,
1796
+ )
1797
+
1798
+ log.info(
1799
+ "%s Successfully deleted artifact '%s' version '%s'.",
1800
+ log_identifier,
1801
+ filename,
1802
+ version or "all",
1803
+ )
1804
+ return {
1805
+ "status": "success",
1806
+ "filename": filename,
1807
+ "version": version or "all",
1808
+ "message": f"Artifact '{filename}' version '{version or 'all'}' deleted successfully.",
1809
+ }
1810
+
1811
+ except FileNotFoundError as e:
1812
+ log.warning("%s Artifact not found for deletion: %s", log_identifier, e)
1813
+ return {
1814
+ "status": "error",
1815
+ "filename": filename,
1816
+ "message": f"Artifact '{filename}' not found.",
1817
+ }
1818
+ except Exception as e:
1819
+ log.exception(
1820
+ "%s Error deleting artifact '%s': %s", log_identifier, filename, e
1821
+ )
1822
+ return {
1823
+ "status": "error",
1824
+ "filename": filename,
1825
+ "message": f"Failed to delete artifact: {e}",
1826
+ }
1827
+
1828
+
1829
+ delete_artifact_tool_def = BuiltinTool(
1830
+ name="delete_artifact",
1831
+ implementation=delete_artifact,
1832
+ description="Deletes a specific version of an artifact, or all versions if no version is specified.",
1833
+ category="artifact_management",
1834
+ category_name=CATEGORY_NAME,
1835
+ category_description=CATEGORY_DESCRIPTION,
1836
+ required_scopes=["tool:artifact:delete"],
1837
+ parameters=adk_types.Schema(
1838
+ type=adk_types.Type.OBJECT,
1839
+ properties={
1840
+ "filename": adk_types.Schema(
1841
+ type=adk_types.Type.STRING,
1842
+ description="The name of the artifact to delete.",
1843
+ ),
1844
+ "version": adk_types.Schema(
1845
+ type=adk_types.Type.INTEGER,
1846
+ description="The specific version number to delete. If not provided, all versions will be deleted.",
1847
+ nullable=True,
1848
+ ),
1849
+ },
1850
+ required=["filename"],
1851
+ ),
1852
+ examples=[],
1853
+ )
1854
+
1855
+ tool_registry.register(delete_artifact_tool_def)
1856
+
1857
+
1858
+ def _perform_single_replacement(
1859
+ content: str,
1860
+ search_expr: str,
1861
+ replace_expr: str,
1862
+ is_regex: bool,
1863
+ regex_flags: str,
1864
+ log_identifier: str,
1865
+ strict_match_validation: bool = False,
1866
+ ) -> Tuple[str, int, Optional[str]]:
1867
+ """
1868
+ Performs a single search-and-replace operation.
1869
+
1870
+ Args:
1871
+ content: The text content to search/replace in
1872
+ search_expr: The search pattern (literal or regex)
1873
+ replace_expr: The replacement text
1874
+ is_regex: If True, search_expr is treated as regex
1875
+ regex_flags: Flags for regex behavior ('g', 'i', 'm', 's')
1876
+ log_identifier: Logging prefix
1877
+ strict_match_validation: If True, error on multiple matches without 'g' flag (for batch mode)
1878
+
1879
+ Returns:
1880
+ tuple: (new_content, match_count, error_message)
1881
+ error_message is None on success
1882
+ """
1883
+ match_count = 0
1884
+ new_content = content
1885
+
1886
+ if is_regex:
1887
+ # Parse regex flags
1888
+ flags_value = 0
1889
+ global_replace = False
1890
+
1891
+ if regex_flags:
1892
+ for flag_char in regex_flags.lower():
1893
+ if flag_char == "g":
1894
+ global_replace = True
1895
+ elif flag_char == "i":
1896
+ flags_value |= re.IGNORECASE
1897
+ elif flag_char == "m":
1898
+ flags_value |= re.MULTILINE
1899
+ elif flag_char == "s":
1900
+ flags_value |= re.DOTALL
1901
+ else:
1902
+ log.warning(
1903
+ "%s Ignoring unrecognized regexp flag: '%s'",
1904
+ log_identifier,
1905
+ flag_char,
1906
+ )
1907
+
1908
+ # Convert JavaScript-style capture groups ($1, $2) to Python style (\1, \2)
1909
+ # Also handle escaped dollar signs ($$) -> literal $
1910
+ python_replace_expr = replace_expr
1911
+ # First, protect escaped dollars: $$ -> a placeholder
1912
+ python_replace_expr = python_replace_expr.replace("$$", "\x00DOLLAR\x00")
1913
+ # Convert capture groups: $1 -> \1
1914
+ python_replace_expr = re.sub(r"\$(\d+)", r"\\\1", python_replace_expr)
1915
+ # Restore escaped dollars: placeholder -> $
1916
+ python_replace_expr = python_replace_expr.replace("\x00DOLLAR\x00", "$")
1917
+
1918
+ try:
1919
+ # Compile the regex pattern
1920
+ pattern = re.compile(search_expr, flags_value)
1921
+
1922
+ # Count matches first
1923
+ match_count = len(pattern.findall(content))
1924
+
1925
+ if match_count == 0:
1926
+ return content, 0, f"No matches found"
1927
+
1928
+ # Check for multiple matches without global flag (only in strict mode for batch operations)
1929
+ if strict_match_validation and match_count > 1 and not global_replace:
1930
+ return (
1931
+ content,
1932
+ match_count,
1933
+ f"Multiple matches found ({match_count}) but global flag 'g' not set",
1934
+ )
1935
+
1936
+ # Perform replacement
1937
+ count_limit = 0 if global_replace else 1
1938
+ new_content = pattern.sub(python_replace_expr, content, count=count_limit)
1939
+
1940
+ return new_content, match_count, None
1941
+
1942
+ except re.error as regex_err:
1943
+ return content, 0, f"Invalid regular expression: {regex_err}"
1944
+
1945
+ else:
1946
+ # Literal string replacement
1947
+ match_count = content.count(search_expr)
1948
+
1949
+ if match_count == 0:
1950
+ return content, 0, f"No matches found"
1951
+
1952
+ # Replace all occurrences for literal mode
1953
+ new_content = content.replace(search_expr, replace_expr)
1954
+ return new_content, match_count, None
1955
+
1956
+
1957
+ async def artifact_search_and_replace_regex(
1958
+ filename: str,
1959
+ search_expression: Optional[str] = None,
1960
+ replace_expression: Optional[str] = None,
1961
+ is_regexp: bool = False,
1962
+ version: Optional[str] = "latest",
1963
+ regexp_flags: Optional[str] = "",
1964
+ new_filename: Optional[str] = None,
1965
+ new_description: Optional[str] = None,
1966
+ replacements: Optional[List[Dict[str, Any]]] = None,
1967
+ tool_context: ToolContext = None,
1968
+ ) -> Dict[str, Any]:
1969
+ """
1970
+ Performs search and replace on an artifact's text content using either
1971
+ literal string matching or regular expressions. Note that this is run once across the entire artifact.
1972
+ If multiple replacements are needed, then set the 'g' flag in regexp_flags.
1973
+
1974
+ Handling Multi-line Search and Replace:
1975
+
1976
+ When searching for or replacing text that spans multiple lines:
1977
+
1978
+ - In literal mode (is_regexp=false): Include actual newline characters directly in your search_expression
1979
+ and replace_expression parameters. Do NOT use escape sequences like \n - the tool will search for those
1980
+ literal characters. Multi-line parameter values are fully supported in the XML parameter format.
1981
+
1982
+ - In regex mode (is_regexp=true): Use the regex pattern \n to match newline characters in your pattern.
1983
+
1984
+ For multiple independent replacements:
1985
+
1986
+ Use the replacements array parameter to perform all replacements atomically in a single tool call, which is more efficient than multiple sequential calls.
1987
+
1988
+ Args:
1989
+ filename: The name of the artifact to search/replace in.
1990
+ search_expression: The pattern to search for (regex if is_regexp=true, literal otherwise).
1991
+ replace_expression: The replacement text. For regex mode, supports capture groups ($1, $2, etc.). Use $$ to insert a literal dollar sign
1992
+ is_regexp: If True, treat search_expression as a regular expression. If False, treat as literal string.
1993
+ version: The version of the artifact to operate on. Can be an integer version number as a string or 'latest'. Defaults to 'latest'.
1994
+ regexp_flags: Flags for regex behavior (only used when is_regexp=true).
1995
+ String of letters: 'g' (global/replace-all), 'i' (case-insensitive), 'm' (multiline), 's' (dotall).
1996
+ Defaults to empty string (no flags).
1997
+ new_filename: Optional. If provided, saves the result as a new artifact with this name.
1998
+ new_description: Optional. Description for the new/updated artifact.
1999
+
2000
+ Returns:
2001
+ A dictionary containing the result status, filename, version, match count, and any error messages.
2002
+ """
2003
+ if not tool_context:
2004
+ return {
2005
+ "status": "error",
2006
+ "filename": filename,
2007
+ "message": "ToolContext is missing, cannot perform search and replace.",
2008
+ }
2009
+
2010
+ log_identifier = (
2011
+ f"[BuiltinArtifactTool:artifact_search_and_replace_regex:{filename}:{version}]"
2012
+ )
2013
+ log.debug("%s Processing request.", log_identifier)
2014
+
2015
+ # Validate parameter combinations
2016
+ if replacements is not None and (
2017
+ search_expression is not None or replace_expression is not None
2018
+ ):
2019
+ return {
2020
+ "status": "error",
2021
+ "filename": filename,
2022
+ "message": "Cannot provide both 'replacements' array and individual 'search_expression'/'replace_expression'. Use one or the other.",
2023
+ }
2024
+
2025
+ if replacements is None and (
2026
+ search_expression is None or replace_expression is None
2027
+ ):
2028
+ return {
2029
+ "status": "error",
2030
+ "filename": filename,
2031
+ "message": "Must provide either 'replacements' array or both 'search_expression' and 'replace_expression'.",
2032
+ }
2033
+
2034
+ if replacements is not None:
2035
+ if not isinstance(replacements, list) or len(replacements) == 0:
2036
+ return {
2037
+ "status": "error",
2038
+ "filename": filename,
2039
+ "message": "replacements must be a non-empty array.",
2040
+ }
2041
+
2042
+ # Validate each replacement entry
2043
+ for idx, repl in enumerate(replacements):
2044
+ if not isinstance(repl, dict):
2045
+ return {
2046
+ "status": "error",
2047
+ "filename": filename,
2048
+ "message": f"Replacement at index {idx} must be a dictionary.",
2049
+ }
2050
+ if "search" not in repl or "replace" not in repl or "is_regexp" not in repl:
2051
+ return {
2052
+ "status": "error",
2053
+ "filename": filename,
2054
+ "message": f"Replacement at index {idx} missing required fields: 'search', 'replace', 'is_regexp'.",
2055
+ }
2056
+
2057
+ # Validate inputs for single replacement mode
2058
+ if replacements is None and not search_expression:
2059
+ return {
2060
+ "status": "error",
2061
+ "filename": filename,
2062
+ "message": "search_expression cannot be empty.",
2063
+ }
2064
+
2065
+ # Determine output filename
2066
+ output_filename = new_filename if new_filename else filename
2067
+
2068
+ if new_filename and not is_filename_safe(new_filename):
2069
+ return {
2070
+ "status": "error",
2071
+ "filename": filename,
2072
+ "message": f"Invalid new_filename: '{new_filename}'. Filename must not contain path separators or traversal sequences.",
2073
+ }
2074
+
2075
+ try:
2076
+ inv_context = tool_context._invocation_context
2077
+ artifact_service = inv_context.artifact_service
2078
+ if not artifact_service:
2079
+ raise ValueError("ArtifactService is not available in the context.")
2080
+
2081
+ app_name = inv_context.app_name
2082
+ user_id = inv_context.user_id
2083
+ session_id = get_original_session_id(inv_context)
2084
+ host_component = getattr(inv_context.agent, "host_component", None)
2085
+
2086
+ # Load the source artifact
2087
+ log.debug(
2088
+ "%s Loading artifact '%s' version '%s'.", log_identifier, filename, version
2089
+ )
2090
+ load_result = await load_artifact_content_or_metadata(
2091
+ artifact_service=artifact_service,
2092
+ app_name=app_name,
2093
+ user_id=user_id,
2094
+ session_id=session_id,
2095
+ filename=filename,
2096
+ version=version,
2097
+ return_raw_bytes=True,
2098
+ component=host_component,
2099
+ log_identifier_prefix=log_identifier,
2100
+ )
2101
+
2102
+ if load_result.get("status") != "success":
2103
+ return {
2104
+ "status": "error",
2105
+ "filename": filename,
2106
+ "version": version,
2107
+ "message": f"Failed to load artifact: {load_result.get('message', 'Unknown error')}",
2108
+ }
2109
+
2110
+ source_bytes = load_result.get("raw_bytes")
2111
+ source_mime_type = load_result.get("mime_type", "application/octet-stream")
2112
+ actual_version = load_result.get("version", version)
2113
+
2114
+ # Verify it's a text-based artifact
2115
+ if not is_text_based_file(source_mime_type, source_bytes):
2116
+ return {
2117
+ "status": "error",
2118
+ "filename": filename,
2119
+ "version": actual_version,
2120
+ "message": f"Cannot perform search and replace on binary artifact of type '{source_mime_type}'. This tool only works with text-based content.",
2121
+ }
2122
+
2123
+ # Decode the content
2124
+ try:
2125
+ original_content = source_bytes.decode("utf-8")
2126
+ except UnicodeDecodeError as decode_err:
2127
+ log.error(
2128
+ "%s Failed to decode artifact content as UTF-8: %s",
2129
+ log_identifier,
2130
+ decode_err,
2131
+ )
2132
+ return {
2133
+ "status": "error",
2134
+ "filename": filename,
2135
+ "version": actual_version,
2136
+ "message": f"Failed to decode artifact content as UTF-8: {decode_err}",
2137
+ }
2138
+
2139
+ # Perform the search and replace
2140
+ if replacements:
2141
+ # Batch mode
2142
+ log.info(
2143
+ "%s Processing batch of %d replacements.",
2144
+ log_identifier,
2145
+ len(replacements),
2146
+ )
2147
+
2148
+ current_content = original_content
2149
+ replacement_results = []
2150
+ total_matches = 0
2151
+
2152
+ for idx, repl in enumerate(replacements):
2153
+ search_expr = repl["search"]
2154
+ replace_expr = repl["replace"]
2155
+ is_regex = repl["is_regexp"]
2156
+ regex_flags = repl.get("regexp_flags", "")
2157
+
2158
+ # Perform replacement on current state (with strict validation for batch mode)
2159
+ new_content, match_count, error_msg = _perform_single_replacement(
2160
+ current_content,
2161
+ search_expr,
2162
+ replace_expr,
2163
+ is_regex,
2164
+ regex_flags,
2165
+ log_identifier,
2166
+ strict_match_validation=True,
2167
+ )
2168
+
2169
+ if error_msg:
2170
+ # Rollback - return error with details
2171
+ log.warning(
2172
+ "%s Batch replacement failed at index %d: %s",
2173
+ log_identifier,
2174
+ idx,
2175
+ error_msg,
2176
+ )
2177
+
2178
+ # Mark all as skipped
2179
+ all_results = replacement_results + [
2180
+ {
2181
+ "search": repl["search"],
2182
+ "match_count": match_count,
2183
+ "status": "error",
2184
+ "error": error_msg,
2185
+ }
2186
+ ]
2187
+ # Add remaining as skipped
2188
+ for i in range(idx + 1, len(replacements)):
2189
+ all_results.append(
2190
+ {
2191
+ "search": replacements[i]["search"],
2192
+ "match_count": 0,
2193
+ "status": "skipped",
2194
+ }
2195
+ )
2196
+
2197
+ return {
2198
+ "status": "error",
2199
+ "filename": filename,
2200
+ "version": actual_version,
2201
+ "message": f"Batch replacement failed: No changes applied due to error in replacement {idx + 1}",
2202
+ "replacement_results": all_results,
2203
+ "failed_replacement": {
2204
+ "index": idx,
2205
+ "search": search_expr,
2206
+ "error": error_msg,
2207
+ },
2208
+ }
2209
+
2210
+ # Success - update state and continue
2211
+ current_content = new_content
2212
+ total_matches += match_count
2213
+ replacement_results.append(
2214
+ {
2215
+ "search": search_expr,
2216
+ "match_count": match_count,
2217
+ "status": "success",
2218
+ }
2219
+ )
2220
+
2221
+ log.debug(
2222
+ "%s Replacement %d/%d succeeded: %d matches",
2223
+ log_identifier,
2224
+ idx + 1,
2225
+ len(replacements),
2226
+ match_count,
2227
+ )
2228
+
2229
+ # All replacements succeeded
2230
+ final_content = current_content
2231
+ total_replacements = len(replacements)
2232
+
2233
+ log.info(
2234
+ "%s Batch replacement succeeded: %d operations, %d total matches",
2235
+ log_identifier,
2236
+ total_replacements,
2237
+ total_matches,
2238
+ )
2239
+
2240
+ else:
2241
+ # Single replacement mode (backward compatible)
2242
+ final_content, match_count, error_msg = _perform_single_replacement(
2243
+ original_content,
2244
+ search_expression,
2245
+ replace_expression,
2246
+ is_regexp,
2247
+ regexp_flags,
2248
+ log_identifier,
2249
+ )
2250
+
2251
+ if error_msg:
2252
+ # Check if it's a "no matches" error specifically
2253
+ if match_count == 0 and "No matches found" in error_msg:
2254
+ return {
2255
+ "status": "no_matches",
2256
+ "filename": filename,
2257
+ "version": actual_version,
2258
+ "match_count": 0,
2259
+ "message": f"No matches found for pattern '{search_expression}'. Artifact not modified.",
2260
+ }
2261
+ else:
2262
+ return {
2263
+ "status": "error",
2264
+ "filename": filename,
2265
+ "version": actual_version,
2266
+ "message": error_msg,
2267
+ }
2268
+
2269
+ total_replacements = 1
2270
+ total_matches = match_count
2271
+ replacement_results = None
2272
+
2273
+ # Prepare metadata for the new/updated artifact
2274
+ if replacements:
2275
+ new_metadata = {
2276
+ "source": f"artifact_search_and_replace_regex (batch) from '{filename}' v{actual_version}",
2277
+ "total_replacements": total_replacements,
2278
+ "total_matches": total_matches,
2279
+ }
2280
+ else:
2281
+ new_metadata = {
2282
+ "source": f"artifact_search_and_replace_regex from '{filename}' v{actual_version}",
2283
+ "search_expression": search_expression,
2284
+ "replace_expression": replace_expression,
2285
+ "is_regexp": is_regexp,
2286
+ "match_count": match_count,
2287
+ }
2288
+
2289
+ if regexp_flags and is_regexp:
2290
+ new_metadata["regexp_flags"] = regexp_flags
2291
+
2292
+ if new_description:
2293
+ new_metadata["description"] = new_description
2294
+ elif not new_filename:
2295
+ # If updating the same artifact, preserve original description if available
2296
+ try:
2297
+ metadata_load_result = await load_artifact_content_or_metadata(
2298
+ artifact_service=artifact_service,
2299
+ app_name=app_name,
2300
+ user_id=user_id,
2301
+ session_id=session_id,
2302
+ filename=filename,
2303
+ version=actual_version,
2304
+ load_metadata_only=True,
2305
+ component=host_component,
2306
+ log_identifier_prefix=log_identifier,
2307
+ )
2308
+ if metadata_load_result.get("status") == "success":
2309
+ original_metadata = metadata_load_result.get("metadata", {})
2310
+ if "description" in original_metadata:
2311
+ new_metadata["description"] = original_metadata["description"]
2312
+ except Exception as meta_err:
2313
+ log.warning(
2314
+ "%s Could not load original metadata to preserve description: %s",
2315
+ log_identifier,
2316
+ meta_err,
2317
+ )
2318
+
2319
+ # Save the result
2320
+ new_content_bytes = final_content.encode("utf-8")
2321
+ schema_max_keys = (
2322
+ host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
2323
+ if host_component
2324
+ else DEFAULT_SCHEMA_MAX_KEYS
2325
+ )
2326
+
2327
+ save_result = await save_artifact_with_metadata(
2328
+ artifact_service=artifact_service,
2329
+ app_name=app_name,
2330
+ user_id=user_id,
2331
+ session_id=session_id,
2332
+ filename=output_filename,
2333
+ content_bytes=new_content_bytes,
2334
+ mime_type=source_mime_type,
2335
+ metadata_dict=new_metadata,
2336
+ timestamp=datetime.now(timezone.utc),
2337
+ schema_max_keys=schema_max_keys,
2338
+ tool_context=tool_context,
2339
+ )
2340
+
2341
+ if save_result.get("status") not in ["success", "partial_success"]:
2342
+ log.error(
2343
+ "%s Failed to save modified artifact: %s",
2344
+ log_identifier,
2345
+ save_result.get("message"),
2346
+ )
2347
+ return {
2348
+ "status": "error",
2349
+ "filename": filename,
2350
+ "version": actual_version,
2351
+ "message": f"Search and replace succeeded, but failed to save result: {save_result.get('message')}",
2352
+ }
2353
+
2354
+ result_version = save_result.get("data_version")
2355
+ log.info(
2356
+ "%s Successfully saved modified artifact '%s' as version %s.",
2357
+ log_identifier,
2358
+ output_filename,
2359
+ result_version,
2360
+ )
2361
+
2362
+ # Return appropriate response based on mode
2363
+ if replacements:
2364
+ return {
2365
+ "status": "success",
2366
+ "source_filename": filename,
2367
+ "source_version": actual_version,
2368
+ "output_filename": output_filename,
2369
+ "output_version": result_version,
2370
+ "total_replacements": total_replacements,
2371
+ "replacement_results": replacement_results,
2372
+ "total_matches": total_matches,
2373
+ "message": f"Batch replacement completed: {total_replacements} operations, {total_matches} total matches",
2374
+ }
2375
+ else:
2376
+ # Compute replacements_made for backward compatibility
2377
+ # For literal replacements, all matches are replaced
2378
+ # For regex without 'g' flag, only first match is replaced
2379
+ global_replace = "g" in (regexp_flags or "")
2380
+ replacements_made = (
2381
+ match_count if not is_regexp or global_replace else min(match_count, 1)
2382
+ )
2383
+
2384
+ return {
2385
+ "status": "success",
2386
+ "source_filename": filename,
2387
+ "source_version": actual_version,
2388
+ "output_filename": output_filename,
2389
+ "output_version": result_version,
2390
+ "match_count": match_count,
2391
+ "replacements_made": replacements_made,
2392
+ "message": f"Successfully performed {'regex' if is_regexp else 'literal'} search and replace. "
2393
+ f"Found {match_count} match(es), saved result as '{output_filename}' v{result_version}.",
2394
+ }
2395
+
2396
+ except FileNotFoundError as fnf_err:
2397
+ log.warning("%s Artifact not found: %s", log_identifier, fnf_err)
2398
+ return {
2399
+ "status": "error",
2400
+ "filename": filename,
2401
+ "version": version,
2402
+ "message": f"Artifact not found: {fnf_err}",
2403
+ }
2404
+ except Exception as e:
2405
+ log.exception(
2406
+ "%s Unexpected error during search and replace: %s", log_identifier, e
2407
+ )
2408
+ return {
2409
+ "status": "error",
2410
+ "filename": filename,
2411
+ "version": version,
2412
+ "message": f"Unexpected error: {e}",
2413
+ }
2414
+
2415
+
2416
+ artifact_search_and_replace_regex_tool_def = BuiltinTool(
2417
+ name="artifact_search_and_replace_regex",
2418
+ implementation=artifact_search_and_replace_regex,
2419
+ description="Performs search and replace on an artifact's text content using either literal string matching or regular expressions. Supports both single replacements and atomic batch replacements for efficiency.",
2420
+ category="artifact_management",
2421
+ category_name=CATEGORY_NAME,
2422
+ category_description=CATEGORY_DESCRIPTION,
2423
+ required_scopes=["tool:artifact:load", "tool:artifact:create"],
2424
+ parameters=adk_types.Schema(
2425
+ type=adk_types.Type.OBJECT,
2426
+ properties={
2427
+ "filename": adk_types.Schema(
2428
+ type=adk_types.Type.STRING,
2429
+ description="The name of the artifact to search/replace in.",
2430
+ ),
2431
+ "search_expression": adk_types.Schema(
2432
+ type=adk_types.Type.STRING,
2433
+ description="The pattern to search for (single replacement mode). If is_regexp is true, this is treated as a regular expression. Otherwise, it's a literal string. Do not use if 'replacements' is provided.",
2434
+ nullable=True,
2435
+ ),
2436
+ "replace_expression": adk_types.Schema(
2437
+ type=adk_types.Type.STRING,
2438
+ description="The replacement text (single replacement mode). For regex mode, supports capture group references using $1, $2, etc. Use $$ to insert a literal dollar sign. Do not use if 'replacements' is provided.",
2439
+ nullable=True,
2440
+ ),
2441
+ "is_regexp": adk_types.Schema(
2442
+ type=adk_types.Type.BOOLEAN,
2443
+ description="If true, treat search_expression as a regular expression. If false, treat as literal string. Only used in single replacement mode.",
2444
+ nullable=True,
2445
+ ),
2446
+ "version": adk_types.Schema(
2447
+ type=adk_types.Type.STRING,
2448
+ description="The version of the artifact to operate on. Can be an integer version number or 'latest'. Defaults to 'latest'.",
2449
+ nullable=True,
2450
+ ),
2451
+ "regexp_flags": adk_types.Schema(
2452
+ type=adk_types.Type.STRING,
2453
+ description="Flags for regex behavior (only used when is_regexp=true in single mode). String of letters: 'g' (global/replace all), 'i' (case-insensitive), 'm' (multiline), 's' (dotall). Example: 'gim'. Defaults to empty string.",
2454
+ nullable=True,
2455
+ ),
2456
+ "new_filename": adk_types.Schema(
2457
+ type=adk_types.Type.STRING,
2458
+ description="Optional. If provided, saves the result as a new artifact with this name instead of creating a new version of the original.",
2459
+ nullable=True,
2460
+ ),
2461
+ "new_description": adk_types.Schema(
2462
+ type=adk_types.Type.STRING,
2463
+ description="Optional. Description for the new/updated artifact.",
2464
+ nullable=True,
2465
+ ),
2466
+ "replacements": adk_types.Schema(
2467
+ type=adk_types.Type.ARRAY,
2468
+ items=adk_types.Schema(
2469
+ type=adk_types.Type.OBJECT,
2470
+ properties={
2471
+ "search": adk_types.Schema(
2472
+ type=adk_types.Type.STRING,
2473
+ description="The search pattern (literal string or regex).",
2474
+ ),
2475
+ "replace": adk_types.Schema(
2476
+ type=adk_types.Type.STRING,
2477
+ description="The replacement text. For regex mode, supports $1, $2, etc. Use $$ for literal $.",
2478
+ ),
2479
+ "is_regexp": adk_types.Schema(
2480
+ type=adk_types.Type.BOOLEAN,
2481
+ description="If true, 'search' is a regex pattern. If false, literal string.",
2482
+ ),
2483
+ "regexp_flags": adk_types.Schema(
2484
+ type=adk_types.Type.STRING,
2485
+ description="Flags for regex: 'g' (global), 'i' (case-insensitive), 'm' (multiline), 's' (dotall). Default: ''.",
2486
+ nullable=True,
2487
+ ),
2488
+ },
2489
+ required=["search", "replace", "is_regexp"],
2490
+ ),
2491
+ description="Optional. Array of replacement operations to perform atomically. Each operation is processed sequentially on the cumulative result. If any operation fails, all changes are rolled back. Do not use with 'search_expression' or 'replace_expression'.",
2492
+ nullable=True,
2493
+ ),
2494
+ },
2495
+ required=["filename"],
2496
+ ),
2497
+ examples=[],
2498
+ )
2499
+
2500
+ tool_registry.register(artifact_search_and_replace_regex_tool_def)