solace-agent-mesh 0.2.4__py3-none-any.whl → 1.0.1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (518) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
  2. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  3. solace_agent_mesh/agent/adk/callbacks.py +1694 -0
  4. solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
  5. solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
  6. solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
  7. solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
  8. solace_agent_mesh/agent/adk/runner.py +353 -0
  9. solace_agent_mesh/agent/adk/services.py +240 -0
  10. solace_agent_mesh/agent/adk/setup.py +751 -0
  11. solace_agent_mesh/agent/adk/stream_parser.py +214 -0
  12. solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
  13. solace_agent_mesh/agent/agent_llm.txt +41 -0
  14. solace_agent_mesh/agent/protocol/event_handlers.py +1469 -0
  15. solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
  16. solace_agent_mesh/agent/sac/app.py +640 -0
  17. solace_agent_mesh/agent/sac/component.py +3388 -0
  18. solace_agent_mesh/agent/sac/patch_adk.py +111 -0
  19. solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
  20. solace_agent_mesh/agent/sac/task_execution_context.py +176 -0
  21. solace_agent_mesh/agent/testing/__init__.py +3 -0
  22. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  23. solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
  24. solace_agent_mesh/agent/tools/__init__.py +14 -0
  25. solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
  26. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
  27. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
  28. solace_agent_mesh/agent/tools/general_agent_tools.py +569 -0
  29. solace_agent_mesh/agent/tools/image_tools.py +1184 -0
  30. solace_agent_mesh/agent/tools/peer_agent_tool.py +289 -0
  31. solace_agent_mesh/agent/tools/registry.py +36 -0
  32. solace_agent_mesh/agent/tools/test_tools.py +135 -0
  33. solace_agent_mesh/agent/tools/tool_definition.py +45 -0
  34. solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
  35. solace_agent_mesh/agent/tools/web_tools.py +381 -0
  36. solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
  37. solace_agent_mesh/agent/utils/config_parser.py +47 -0
  38. solace_agent_mesh/agent/utils/context_helpers.py +60 -0
  39. solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
  40. solace_agent_mesh/assets/docs/404.html +16 -0
  41. solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
  42. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  43. solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
  44. solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
  45. solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
  46. solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  53. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  54. solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
  55. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
  56. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
  57. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.23bccffb.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
  95. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  96. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  101. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  102. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
  103. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
  104. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  105. solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/f897a61a.2c2e152c.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js +2 -0
  131. solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js.LICENSE.txt +81 -0
  132. solace_agent_mesh/assets/docs/assets/js/runtime~main.d9520ae2.js +1 -0
  133. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
  134. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
  135. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
  137. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
  138. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
  139. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +60 -0
  140. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
  141. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
  142. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
  143. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
  146. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
  147. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
  148. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
  149. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
  150. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
  151. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
  152. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
  153. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
  154. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
  155. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
  156. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
  157. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
  158. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
  159. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
  160. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
  161. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
  162. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
  163. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
  164. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
  165. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
  166. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  167. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  168. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  169. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  170. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  171. solace_agent_mesh/assets/docs/lunr-index-1753813536522.json +1 -0
  172. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  173. solace_agent_mesh/assets/docs/search-doc-1753813536522.json +1 -0
  174. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  175. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  176. solace_agent_mesh/cli/__init__.py +1 -1
  177. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  178. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  179. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
  180. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  181. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
  182. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
  183. solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
  184. solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
  185. solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
  186. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  187. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  188. solace_agent_mesh/cli/commands/init_cmd/env_step.py +197 -0
  189. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  190. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +387 -0
  191. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  192. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
  193. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
  194. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
  195. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
  196. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  197. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +138 -0
  198. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
  199. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +174 -0
  200. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  201. solace_agent_mesh/cli/commands/run_cmd.py +158 -0
  202. solace_agent_mesh/cli/main.py +17 -294
  203. solace_agent_mesh/cli/utils.py +135 -204
  204. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
  205. solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
  206. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  207. solace_agent_mesh/client/webui/frontend/static/assets/main-An0a5j5k.js +663 -0
  208. solace_agent_mesh/client/webui/frontend/static/assets/main-Bu5-4Bac.css +1 -0
  209. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
  210. solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
  211. solace_agent_mesh/common/__init__.py +1 -0
  212. solace_agent_mesh/common/a2a_protocol.py +564 -0
  213. solace_agent_mesh/common/agent_registry.py +42 -0
  214. solace_agent_mesh/common/client/__init__.py +4 -0
  215. solace_agent_mesh/common/client/card_resolver.py +21 -0
  216. solace_agent_mesh/common/client/client.py +85 -0
  217. solace_agent_mesh/common/client/client_llm.txt +133 -0
  218. solace_agent_mesh/common/common_llm.txt +144 -0
  219. solace_agent_mesh/common/constants.py +1 -14
  220. solace_agent_mesh/common/middleware/__init__.py +12 -0
  221. solace_agent_mesh/common/middleware/config_resolver.py +130 -0
  222. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  223. solace_agent_mesh/common/middleware/registry.py +125 -0
  224. solace_agent_mesh/common/server/__init__.py +4 -0
  225. solace_agent_mesh/common/server/server.py +122 -0
  226. solace_agent_mesh/common/server/server_llm.txt +169 -0
  227. solace_agent_mesh/common/server/task_manager.py +291 -0
  228. solace_agent_mesh/common/server/utils.py +28 -0
  229. solace_agent_mesh/common/services/__init__.py +4 -0
  230. solace_agent_mesh/common/services/employee_service.py +162 -0
  231. solace_agent_mesh/common/services/identity_service.py +129 -0
  232. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  233. solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
  234. solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
  235. solace_agent_mesh/common/services/services_llm.txt +132 -0
  236. solace_agent_mesh/common/types.py +411 -0
  237. solace_agent_mesh/common/utils/__init__.py +7 -0
  238. solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
  239. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  240. solace_agent_mesh/common/utils/embeds/constants.py +55 -0
  241. solace_agent_mesh/common/utils/embeds/converter.py +452 -0
  242. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
  243. solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
  244. solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
  245. solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
  246. solace_agent_mesh/common/utils/embeds/types.py +14 -0
  247. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  248. solace_agent_mesh/common/utils/log_formatters.py +44 -0
  249. solace_agent_mesh/common/utils/mime_helpers.py +106 -0
  250. solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
  251. solace_agent_mesh/common/utils/utils_llm.txt +67 -0
  252. solace_agent_mesh/config_portal/backend/common.py +66 -24
  253. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +23 -0
  254. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  255. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +160 -0
  256. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +525 -0
  257. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +216 -0
  258. solace_agent_mesh/config_portal/backend/server.py +550 -181
  259. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DNxCwAGB.js +48 -0
  260. solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
  261. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
  262. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
  263. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d2b54a97.js +1 -0
  264. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
  265. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
  266. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  267. solace_agent_mesh/core_a2a/__init__.py +1 -0
  268. solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
  269. solace_agent_mesh/core_a2a/service.py +331 -0
  270. solace_agent_mesh/evaluation/config_loader.py +657 -0
  271. solace_agent_mesh/evaluation/evaluator.py +667 -0
  272. solace_agent_mesh/evaluation/message_organizer.py +568 -0
  273. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  274. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  275. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  276. solace_agent_mesh/evaluation/report/modal.html +59 -0
  277. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  278. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  279. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  280. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  281. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  282. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  283. solace_agent_mesh/evaluation/report_data_processor.py +972 -0
  284. solace_agent_mesh/evaluation/report_generator.py +613 -0
  285. solace_agent_mesh/evaluation/run.py +613 -0
  286. solace_agent_mesh/evaluation/subscriber.py +872 -0
  287. solace_agent_mesh/evaluation/summary_builder.py +775 -0
  288. solace_agent_mesh/evaluation/test_case_loader.py +714 -0
  289. solace_agent_mesh/gateway/base/__init__.py +1 -0
  290. solace_agent_mesh/gateway/base/app.py +266 -0
  291. solace_agent_mesh/gateway/base/base_llm.txt +119 -0
  292. solace_agent_mesh/gateway/base/component.py +1542 -0
  293. solace_agent_mesh/gateway/base/task_context.py +74 -0
  294. solace_agent_mesh/gateway/gateway_llm.txt +125 -0
  295. solace_agent_mesh/gateway/http_sse/app.py +190 -0
  296. solace_agent_mesh/gateway/http_sse/component.py +1602 -0
  297. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  298. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
  299. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
  300. solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
  301. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
  302. solace_agent_mesh/gateway/http_sse/main.py +442 -0
  303. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  304. solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
  305. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +821 -0
  306. solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
  307. solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
  308. solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
  309. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
  310. solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
  311. solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
  312. solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
  313. solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
  314. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
  315. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  316. solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
  317. solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
  318. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
  319. solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
  320. solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
  321. solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
  322. solace_agent_mesh/llm.txt +228 -0
  323. solace_agent_mesh/llm_detail.txt +2835 -0
  324. solace_agent_mesh/templates/agent_template.yaml +53 -0
  325. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  326. solace_agent_mesh/templates/gateway_app_template.py +73 -0
  327. solace_agent_mesh/templates/gateway_component_template.py +400 -0
  328. solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
  329. solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
  330. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  331. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  332. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  333. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
  334. solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
  335. solace_agent_mesh/templates/plugin_readme_template.md +34 -0
  336. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  337. solace_agent_mesh/templates/shared_config.yaml +66 -0
  338. solace_agent_mesh/templates/templates_llm.txt +147 -0
  339. solace_agent_mesh/templates/webui.yaml +53 -0
  340. solace_agent_mesh-1.0.1.dist-info/METADATA +432 -0
  341. solace_agent_mesh-1.0.1.dist-info/RECORD +359 -0
  342. solace_agent_mesh-1.0.1.dist-info/entry_points.txt +3 -0
  343. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.1.dist-info}/licenses/LICENSE +1 -1
  344. solace_agent_mesh/agents/base_agent_component.py +0 -256
  345. solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
  346. solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
  347. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
  348. solace_agent_mesh/agents/global/actions/create_file.py +0 -70
  349. solace_agent_mesh/agents/global/actions/error_action.py +0 -45
  350. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
  351. solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
  352. solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
  353. solace_agent_mesh/agents/global/global_agent_component.py +0 -38
  354. solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
  355. solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
  356. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
  357. solace_agent_mesh/agents/slack/__init__.py +0 -1
  358. solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
  359. solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
  360. solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
  361. solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
  362. solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
  363. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
  364. solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
  365. solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
  366. solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
  367. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
  368. solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
  369. solace_agent_mesh/assets/web-visualizer/index.html +0 -14
  370. solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
  371. solace_agent_mesh/cli/commands/add/__init__.py +0 -3
  372. solace_agent_mesh/cli/commands/add/add.py +0 -88
  373. solace_agent_mesh/cli/commands/add/agent.py +0 -110
  374. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
  375. solace_agent_mesh/cli/commands/add/gateway.py +0 -374
  376. solace_agent_mesh/cli/commands/build.py +0 -670
  377. solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
  378. solace_agent_mesh/cli/commands/chat/chat.py +0 -361
  379. solace_agent_mesh/cli/commands/config.py +0 -29
  380. solace_agent_mesh/cli/commands/init/__init__.py +0 -3
  381. solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
  382. solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
  383. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
  384. solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
  385. solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
  386. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
  387. solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
  388. solace_agent_mesh/cli/commands/init/init.py +0 -92
  389. solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
  390. solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
  391. solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
  392. solace_agent_mesh/cli/commands/plugin/add.py +0 -100
  393. solace_agent_mesh/cli/commands/plugin/build.py +0 -268
  394. solace_agent_mesh/cli/commands/plugin/create.py +0 -117
  395. solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
  396. solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
  397. solace_agent_mesh/cli/commands/run.py +0 -68
  398. solace_agent_mesh/cli/commands/visualizer.py +0 -138
  399. solace_agent_mesh/cli/config.py +0 -85
  400. solace_agent_mesh/common/action.py +0 -91
  401. solace_agent_mesh/common/action_list.py +0 -37
  402. solace_agent_mesh/common/action_response.py +0 -340
  403. solace_agent_mesh/common/mysql_database.py +0 -40
  404. solace_agent_mesh/common/postgres_database.py +0 -85
  405. solace_agent_mesh/common/prompt_templates.py +0 -28
  406. solace_agent_mesh/common/stimulus_utils.py +0 -152
  407. solace_agent_mesh/common/time.py +0 -24
  408. solace_agent_mesh/common/utils.py +0 -712
  409. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-a-zJ6rLx.js +0 -46
  410. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
  411. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-44c41103.js +0 -1
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
  414. solace_agent_mesh/configs/agent_global.yaml +0 -74
  415. solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
  416. solace_agent_mesh/configs/agent_slack.yaml +0 -64
  417. solace_agent_mesh/configs/agent_web_request.yaml +0 -75
  418. solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
  419. solace_agent_mesh/configs/error_catcher.yaml +0 -56
  420. solace_agent_mesh/configs/monitor.yaml +0 -0
  421. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
  422. solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
  423. solace_agent_mesh/configs/orchestrator.yaml +0 -241
  424. solace_agent_mesh/configs/service_embedding.yaml +0 -81
  425. solace_agent_mesh/configs/service_llm.yaml +0 -265
  426. solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
  427. solace_agent_mesh/gateway/components/gateway_base.py +0 -47
  428. solace_agent_mesh/gateway/components/gateway_input.py +0 -278
  429. solace_agent_mesh/gateway/components/gateway_output.py +0 -298
  430. solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
  431. solace_agent_mesh/gateway/identity/identity_base.py +0 -10
  432. solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
  433. solace_agent_mesh/gateway/identity/no_identity.py +0 -9
  434. solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
  435. solace_agent_mesh/monitors/base_monitor_component.py +0 -26
  436. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
  437. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
  438. solace_agent_mesh/orchestrator/__init__.py +0 -0
  439. solace_agent_mesh/orchestrator/action_manager.py +0 -237
  440. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  441. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
  442. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
  443. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
  444. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
  445. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
  446. solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
  447. solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
  448. solace_agent_mesh/services/__init__.py +0 -0
  449. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
  450. solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
  451. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
  452. solace_agent_mesh/services/common/__init__.py +0 -4
  453. solace_agent_mesh/services/common/auto_expiry.py +0 -45
  454. solace_agent_mesh/services/common/singleton.py +0 -18
  455. solace_agent_mesh/services/file_service/__init__.py +0 -14
  456. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  457. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
  458. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
  459. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
  460. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
  461. solace_agent_mesh/services/file_service/file_service.py +0 -437
  462. solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
  463. solace_agent_mesh/services/file_service/file_transformations.py +0 -141
  464. solace_agent_mesh/services/file_service/file_utils.py +0 -324
  465. solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
  466. solace_agent_mesh/services/history_service/__init__.py +0 -3
  467. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  468. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
  469. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
  470. solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
  471. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
  472. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
  473. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
  474. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
  475. solace_agent_mesh/services/history_service/history_service.py +0 -413
  476. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  477. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
  478. solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
  479. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
  480. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  481. solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
  482. solace_agent_mesh/templates/action.py +0 -38
  483. solace_agent_mesh/templates/agent.py +0 -29
  484. solace_agent_mesh/templates/agent.yaml +0 -70
  485. solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
  486. solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
  487. solace_agent_mesh/templates/gateway-flows.yaml +0 -78
  488. solace_agent_mesh/templates/gateway-header.yaml +0 -16
  489. solace_agent_mesh/templates/gateway_base.py +0 -15
  490. solace_agent_mesh/templates/gateway_input.py +0 -98
  491. solace_agent_mesh/templates/gateway_output.py +0 -71
  492. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
  493. solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
  494. solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
  495. solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
  496. solace_agent_mesh/templates/slack-default-config.yaml +0 -16
  497. solace_agent_mesh/templates/slack-flows.yaml +0 -81
  498. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
  499. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
  500. solace_agent_mesh/templates/web-default-config.yaml +0 -10
  501. solace_agent_mesh/templates/web-flows.yaml +0 -76
  502. solace_agent_mesh/tools/__init__.py +0 -0
  503. solace_agent_mesh/tools/components/__init__.py +0 -0
  504. solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
  505. solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
  506. solace_agent_mesh/tools/config/runtime_config.py +0 -26
  507. solace_agent_mesh-0.2.4.dist-info/METADATA +0 -176
  508. solace_agent_mesh-0.2.4.dist-info/RECORD +0 -193
  509. solace_agent_mesh-0.2.4.dist-info/entry_points.txt +0 -3
  510. /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
  511. /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
  512. /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
  513. /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
  514. /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
  515. /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
  516. /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
  517. /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
  518. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1954 @@
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 uuid
9
+ import json
10
+ import re
11
+ import fnmatch
12
+ from typing import Any, Dict, Optional, Union, TYPE_CHECKING
13
+ from datetime import datetime, timezone
14
+ from google.adk.tools import ToolContext
15
+
16
+ if TYPE_CHECKING:
17
+ from google.adk.agents.invocation_context import InvocationContext
18
+ from google.genai import types as adk_types
19
+ from solace_ai_connector.common.log import log
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 ...agent.utils.context_helpers import get_original_session_id
36
+ from ...agent.adk.models.lite_llm import LiteLlm
37
+ from google.adk.models import LlmRequest
38
+ from google.adk.models.registry import LLMRegistry
39
+ from ...common.utils.mime_helpers import is_text_based_file
40
+
41
+ async def _internal_create_artifact(
42
+ filename: str,
43
+ content: str,
44
+ mime_type: str,
45
+ tool_context: ToolContext = None,
46
+ description: Optional[str] = None,
47
+ metadata_json: Optional[str] = None,
48
+ schema_max_keys: Optional[int] = None,
49
+ ) -> Dict[str, Any]:
50
+ """
51
+ Internal helper to create an artifact with its first chunk of content and metadata.
52
+ This function is not intended to be called directly by the LLM.
53
+ It is used by callbacks that process fenced artifact blocks.
54
+
55
+ Args:
56
+ filename: The desired name for the artifact.
57
+ content: The first chunk of the artifact content, as a string.
58
+ If the mime_type suggests binary data, this string is expected
59
+ to be base64 encoded.
60
+ mime_type: The MIME type of the content.
61
+ tool_context: The ADK ToolContext, required for accessing services.
62
+ description (str, optional): A description for the artifact.
63
+ metadata_json (str, optional): A JSON string of additional metadata.
64
+ schema_max_keys (int, optional): Max keys for schema inference.
65
+
66
+
67
+ Returns:
68
+ A dictionary indicating the result, returned by save_artifact_with_metadata.
69
+ """
70
+ if not tool_context:
71
+ return {
72
+ "status": "error",
73
+ "filename": filename,
74
+ "message": "ToolContext is missing, cannot save artifact.",
75
+ }
76
+
77
+ if not is_filename_safe(filename):
78
+ return {
79
+ "status": "error",
80
+ "filename": filename,
81
+ "message": "Filename is invalid or contains disallowed characters (e.g., '/', '..').",
82
+ }
83
+
84
+ log_identifier = f"[BuiltinArtifactTool:_internal_create_artifact:{filename}]"
85
+
86
+ final_metadata = {}
87
+ if description:
88
+ final_metadata["description"] = description
89
+ if metadata_json:
90
+ try:
91
+ final_metadata.update(json.loads(metadata_json))
92
+ except (json.JSONDecodeError, TypeError):
93
+ log.warning(
94
+ "%s Invalid JSON in metadata_json attribute: %s",
95
+ log_identifier,
96
+ metadata_json,
97
+ )
98
+
99
+ final_metadata["metadata_parsing_error"] = (
100
+ f"Invalid JSON provided: {metadata_json}"
101
+ )
102
+
103
+ log.debug("%s Processing request with metadata: %s", log_identifier, final_metadata)
104
+
105
+ try:
106
+ inv_context = tool_context._invocation_context
107
+ artifact_bytes, final_mime_type = decode_and_get_bytes(
108
+ content, mime_type, log_identifier
109
+ )
110
+ max_keys_to_use = (
111
+ schema_max_keys if schema_max_keys is not None else DEFAULT_SCHEMA_MAX_KEYS
112
+ )
113
+ if schema_max_keys is not None:
114
+ log.debug(
115
+ "%s Using schema_max_keys provided by LLM: %d",
116
+ log_identifier,
117
+ schema_max_keys,
118
+ )
119
+ else:
120
+ log.debug(
121
+ "%s Using default schema_max_keys: %d",
122
+ log_identifier,
123
+ DEFAULT_SCHEMA_MAX_KEYS,
124
+ )
125
+
126
+ artifact_service = inv_context.artifact_service
127
+ if not artifact_service:
128
+ raise ValueError("ArtifactService is not available in the context.")
129
+ session_last_update_time = inv_context.session.last_update_time
130
+ timestamp_for_artifact: datetime
131
+ if isinstance(session_last_update_time, datetime):
132
+ timestamp_for_artifact = session_last_update_time
133
+ elif isinstance(session_last_update_time, (int, float)):
134
+ log.debug(
135
+ "%s Converting numeric session.last_update_time (%s) to datetime.",
136
+ log_identifier,
137
+ session_last_update_time,
138
+ )
139
+ try:
140
+ timestamp_for_artifact = datetime.fromtimestamp(
141
+ session_last_update_time, timezone.utc
142
+ )
143
+ except Exception as e:
144
+ log.warning(
145
+ "%s Failed to convert numeric timestamp %s to datetime: %s. Using current time.",
146
+ log_identifier,
147
+ session_last_update_time,
148
+ e,
149
+ )
150
+ timestamp_for_artifact = datetime.now(timezone.utc)
151
+ else:
152
+ if session_last_update_time is not None:
153
+ log.warning(
154
+ "%s Unexpected type for session.last_update_time: %s. Using current time.",
155
+ log_identifier,
156
+ type(session_last_update_time),
157
+ )
158
+ timestamp_for_artifact = datetime.now(timezone.utc)
159
+ result = await save_artifact_with_metadata(
160
+ artifact_service=artifact_service,
161
+ app_name=inv_context.app_name,
162
+ user_id=inv_context.user_id,
163
+ session_id=get_original_session_id(inv_context),
164
+ filename=filename,
165
+ content_bytes=artifact_bytes,
166
+ mime_type=final_mime_type,
167
+ metadata_dict=final_metadata,
168
+ timestamp=timestamp_for_artifact,
169
+ schema_max_keys=max_keys_to_use,
170
+ tool_context=tool_context,
171
+ )
172
+ log.info(
173
+ "%s Result from save_artifact_with_metadata: %s", log_identifier, result
174
+ )
175
+ return result
176
+ except Exception as e:
177
+ log.exception(
178
+ "%s Error creating artifact '%s': %s", log_identifier, filename, e
179
+ )
180
+ return {
181
+ "status": "error",
182
+ "filename": filename,
183
+ "message": f"Failed to create artifact: {e}",
184
+ }
185
+
186
+
187
+ async def list_artifacts(tool_context: ToolContext = None) -> Dict[str, Any]:
188
+ """
189
+ Lists all available data artifact filenames and their versions for the current session.
190
+ Includes a summary of the latest version's metadata for each artifact.
191
+
192
+ Args:
193
+ tool_context: The context provided by the ADK framework.
194
+
195
+ Returns:
196
+ A dictionary containing the list of artifacts with metadata summaries or an error.
197
+ """
198
+ if not tool_context:
199
+ return {"status": "error", "message": "ToolContext is missing."}
200
+ log_identifier = "[BuiltinArtifactTool:list_artifacts]"
201
+ log.debug("%s Processing request.", log_identifier)
202
+ try:
203
+ artifact_service = tool_context._invocation_context.artifact_service
204
+ if not artifact_service:
205
+ raise ValueError("ArtifactService is not available in the context.")
206
+ app_name = tool_context._invocation_context.app_name
207
+ user_id = tool_context._invocation_context.user_id
208
+ session_id = get_original_session_id(tool_context._invocation_context)
209
+ list_keys_method = getattr(artifact_service, "list_artifact_keys")
210
+ all_keys = await list_keys_method(
211
+ app_name=app_name, user_id=user_id, session_id=session_id
212
+ )
213
+ response_files = []
214
+ processed_data_files = set()
215
+ for key in all_keys:
216
+ if key.endswith(METADATA_SUFFIX):
217
+ continue # Skip metadata files initially
218
+
219
+ if key in processed_data_files:
220
+ continue # Already processed this data file
221
+
222
+ filename = key
223
+ metadata_summary = None
224
+ versions = []
225
+ try:
226
+ versions = await artifact_service.list_versions(
227
+ app_name=app_name,
228
+ user_id=user_id,
229
+ session_id=session_id,
230
+ filename=filename,
231
+ )
232
+ if not versions:
233
+ log.warning(
234
+ "%s Found artifact key '%s' but no versions listed. Skipping.",
235
+ log_identifier,
236
+ filename,
237
+ )
238
+ continue
239
+ latest_version = max(versions)
240
+ metadata_filename = f"{filename}{METADATA_SUFFIX}"
241
+ if metadata_filename in all_keys:
242
+ try:
243
+ metadata_part = await artifact_service.load_artifact(
244
+ app_name=app_name,
245
+ user_id=user_id,
246
+ session_id=session_id,
247
+ filename=metadata_filename,
248
+ version=latest_version,
249
+ )
250
+ if metadata_part and metadata_part.inline_data:
251
+ try:
252
+ metadata_dict = json.loads(
253
+ metadata_part.inline_data.data.decode("utf-8")
254
+ )
255
+ schema = metadata_dict.get("schema", {})
256
+ metadata_summary = {
257
+ "description": metadata_dict.get("description"),
258
+ "source": metadata_dict.get("source"),
259
+ "type": metadata_dict.get("mime_type"),
260
+ "size": metadata_dict.get("size_bytes"),
261
+ "schema_type": schema.get(
262
+ "type", metadata_dict.get("mime_type")
263
+ ),
264
+ "schema_inferred": schema.get("inferred"),
265
+ }
266
+ metadata_summary = {
267
+ k: v
268
+ for k, v in metadata_summary.items()
269
+ if v is not None
270
+ }
271
+ log.debug(
272
+ "%s Loaded metadata summary for '%s' v%d.",
273
+ log_identifier,
274
+ filename,
275
+ latest_version,
276
+ )
277
+ except json.JSONDecodeError as json_err:
278
+ log.warning(
279
+ "%s Failed to parse metadata JSON for '%s' v%d: %s",
280
+ log_identifier,
281
+ metadata_filename,
282
+ latest_version,
283
+ json_err,
284
+ )
285
+ metadata_summary = {"error": "Failed to parse metadata"}
286
+ except Exception as fmt_err:
287
+ log.warning(
288
+ "%s Failed to format metadata summary for '%s' v%d: %s",
289
+ log_identifier,
290
+ metadata_filename,
291
+ latest_version,
292
+ fmt_err,
293
+ )
294
+ metadata_summary = {
295
+ "error": "Failed to format metadata"
296
+ }
297
+ else:
298
+ log.warning(
299
+ "%s Metadata file '%s' v%d found but empty or unreadable.",
300
+ log_identifier,
301
+ metadata_filename,
302
+ latest_version,
303
+ )
304
+ metadata_summary = {
305
+ "error": "Metadata file empty or unreadable"
306
+ }
307
+ except Exception as load_err:
308
+ log.warning(
309
+ "%s Failed to load metadata file '%s' v%d: %s",
310
+ log_identifier,
311
+ metadata_filename,
312
+ latest_version,
313
+ load_err,
314
+ )
315
+ metadata_summary = {
316
+ "error": f"Failed to load metadata: {load_err}"
317
+ }
318
+ else:
319
+ log.debug(
320
+ "%s No companion metadata file found for '%s'.",
321
+ log_identifier,
322
+ filename,
323
+ )
324
+ metadata_summary = {"info": "No metadata file found"}
325
+ except Exception as version_err:
326
+ log.warning(
327
+ "%s Failed to list versions or process metadata for file '%s': %s. Skipping file.",
328
+ log_identifier,
329
+ filename,
330
+ version_err,
331
+ )
332
+ continue
333
+ response_files.append(
334
+ {
335
+ "filename": filename,
336
+ "versions": versions,
337
+ "metadata_summary": metadata_summary,
338
+ }
339
+ )
340
+ processed_data_files.add(filename)
341
+ log.info(
342
+ "%s Found %d data artifacts for session %s.",
343
+ log_identifier,
344
+ len(response_files),
345
+ session_id,
346
+ )
347
+ return {"status": "success", "artifacts": response_files}
348
+ except Exception as e:
349
+ log.exception("%s Error listing artifacts: %s", log_identifier, e)
350
+ return {"status": "error", "message": f"Failed to list artifacts: {e}"}
351
+
352
+
353
+ async def load_artifact(
354
+ filename: str,
355
+ version: int,
356
+ load_metadata_only: bool = False,
357
+ max_content_length: Optional[int] = None,
358
+ tool_context: ToolContext = None,
359
+ ) -> Dict[str, Any]:
360
+ """
361
+ Loads the content or metadata of a specific artifact version.
362
+ Early-stage embeds in the filename argument are resolved.
363
+
364
+ If load_metadata_only is True, loads the full metadata dictionary.
365
+ Otherwise, loads text content (potentially truncated) or binary metadata summary.
366
+
367
+ Args:
368
+ filename: The name of the artifact to load. May contain embeds.
369
+ version: The specific version number to load. Must be explicitly provided.
370
+ load_metadata_only (bool): If True, load only the metadata JSON. Default False.
371
+ max_content_length (Optional[int]): Maximum character length for text content.
372
+ If None, uses app configuration. Range: 100-100,000.
373
+ tool_context: The context provided by the ADK framework.
374
+
375
+ Returns:
376
+ A dictionary containing the artifact details and content/metadata or an error.
377
+ """
378
+ if not tool_context:
379
+ return {
380
+ "status": "error",
381
+ "filename": filename,
382
+ "version": version,
383
+ "message": "ToolContext is missing.",
384
+ }
385
+ log_identifier = f"[BuiltinArtifactTool:load_artifact:{filename}:{version}]"
386
+ log.debug(
387
+ "%s Processing request (load_metadata_only=%s).",
388
+ log_identifier,
389
+ load_metadata_only,
390
+ )
391
+ if version is None:
392
+ version = "latest"
393
+ try:
394
+ artifact_service = tool_context._invocation_context.artifact_service
395
+ if not artifact_service:
396
+ raise ValueError("ArtifactService is not available in the context.")
397
+ app_name = tool_context._invocation_context.app_name
398
+ user_id = tool_context._invocation_context.user_id
399
+ session_id = get_original_session_id(tool_context._invocation_context)
400
+ agent = getattr(tool_context._invocation_context, "agent", None)
401
+ host_component = getattr(agent, "host_component", None) if agent else None
402
+ result = await load_artifact_content_or_metadata(
403
+ artifact_service=artifact_service,
404
+ app_name=app_name,
405
+ user_id=user_id,
406
+ session_id=session_id,
407
+ filename=filename,
408
+ version=version,
409
+ load_metadata_only=load_metadata_only,
410
+ max_content_length=max_content_length,
411
+ component=host_component,
412
+ log_identifier_prefix="[BuiltinArtifactTool:load_artifact]",
413
+ )
414
+ return result
415
+ except FileNotFoundError as fnf_err:
416
+ log.warning(
417
+ "%s Artifact not found (reported by helper): %s", log_identifier, fnf_err
418
+ )
419
+ return {
420
+ "status": "error",
421
+ "filename": filename,
422
+ "version": version,
423
+ "message": str(fnf_err),
424
+ }
425
+ except ValueError as val_err:
426
+ log.warning(
427
+ "%s Value error during load (reported by helper): %s",
428
+ log_identifier,
429
+ val_err,
430
+ )
431
+ return {
432
+ "status": "error",
433
+ "filename": filename,
434
+ "version": version,
435
+ "message": str(val_err),
436
+ }
437
+ except Exception as e:
438
+ log.exception(
439
+ "%s Unexpected error in load_artifact tool: %s", log_identifier, e
440
+ )
441
+ return {
442
+ "status": "error",
443
+ "filename": filename,
444
+ "version": version,
445
+ "message": f"Unexpected error processing load request: {e}",
446
+ }
447
+
448
+
449
+ async def signal_artifact_for_return(
450
+ filename: str,
451
+ version: int,
452
+ tool_context: ToolContext = None,
453
+ ) -> Dict[str, Any]:
454
+ """
455
+ Signals that a specific version of an artifact should be returned to the
456
+ original caller as part of the final task result.
457
+
458
+ Args:
459
+ filename: The name of the artifact to return. May contain embeds.
460
+ version: The specific version number to return. Must be explicitly provided.
461
+ """
462
+ if not tool_context:
463
+ return {
464
+ "status": "error",
465
+ "filename": filename,
466
+ "version": version,
467
+ "message": "ToolContext is missing.",
468
+ }
469
+
470
+ log_identifier = (
471
+ f"[BuiltinArtifactTool:signal_artifact_for_return:{filename}:{version}]"
472
+ )
473
+ log.debug("%s Processing request after potential embed resolution.", log_identifier)
474
+
475
+ if version is None:
476
+ return {
477
+ "status": "error",
478
+ "filename": filename,
479
+ "version": None,
480
+ "message": "Version parameter is required. Use list_artifacts() to find available versions.",
481
+ }
482
+
483
+ try:
484
+ inv_context = tool_context._invocation_context
485
+ artifact_service = inv_context.artifact_service
486
+ host_component = getattr(inv_context.agent, "host_component", None)
487
+
488
+ if not artifact_service:
489
+ raise ValueError("ArtifactService is not available in the context.")
490
+ if not host_component:
491
+ raise ValueError("Host component is not available.")
492
+
493
+ app_name = inv_context.app_name
494
+ user_id = inv_context.user_id
495
+ session_id = get_original_session_id(inv_context)
496
+
497
+ versions = await artifact_service.list_versions(
498
+ app_name=app_name,
499
+ user_id=user_id,
500
+ session_id=session_id,
501
+ filename=filename,
502
+ )
503
+ if version not in versions:
504
+ raise FileNotFoundError(
505
+ f"Artifact '{filename}' version {version} not found."
506
+ )
507
+
508
+ a2a_context = tool_context.state.get("a2a_context", {})
509
+ logical_task_id = a2a_context.get("logical_task_id")
510
+ if not logical_task_id:
511
+ raise ValueError("Could not determine logical_task_id for signaling.")
512
+
513
+ with host_component.active_tasks_lock:
514
+ task_execution_context = host_component.active_tasks.get(logical_task_id)
515
+
516
+ if not task_execution_context:
517
+ raise ValueError(
518
+ f"TaskExecutionContext not found for task {logical_task_id}."
519
+ )
520
+
521
+ signal_data = {"filename": filename, "version": version}
522
+ task_execution_context.add_artifact_signal(signal_data)
523
+
524
+ log.info(
525
+ "%s Added artifact signal to TaskExecutionContext for task %s.",
526
+ log_identifier,
527
+ logical_task_id,
528
+ )
529
+
530
+ # Also add a placeholder to state_delta. This acts as a trigger
531
+ # for the host component to check the cache at the end of the turn.
532
+ # The key is unique to avoid collisions, but the content is just a placeholder.
533
+ trigger_key = f"temp:a2a_return_artifact:{uuid.uuid4().hex}"
534
+ tool_context.actions.state_delta[trigger_key] = {"triggered": True}
535
+ log.debug(
536
+ "%s Set state_delta trigger key '%s' to ensure signal processing.",
537
+ log_identifier,
538
+ trigger_key,
539
+ )
540
+
541
+ return {
542
+ "status": "success",
543
+ "message": f"Artifact '{filename}' (version {version}) has been signaled for return.",
544
+ }
545
+
546
+ except FileNotFoundError as fnf_err:
547
+ log.warning("%s Artifact not found: %s", log_identifier, fnf_err)
548
+ return {
549
+ "status": "error",
550
+ "filename": filename,
551
+ "version": version,
552
+ "message": str(fnf_err),
553
+ }
554
+ except Exception as e:
555
+ log.exception("%s Error signaling artifact for return: %s", log_identifier, e)
556
+ return {
557
+ "status": "error",
558
+ "filename": filename,
559
+ "version": version,
560
+ "message": f"Failed to signal artifact for return: {e}",
561
+ }
562
+
563
+
564
+ async def apply_embed_and_create_artifact(
565
+ output_filename: str,
566
+ embed_directive: str,
567
+ output_metadata: Optional[Dict[str, Any]] = None,
568
+ tool_context: ToolContext = None,
569
+ ) -> Dict[str, Any]:
570
+ """
571
+ Resolves an 'artifact_content' embed directive (including modifiers and formatting)
572
+ and saves the resulting content as a new artifact. The entire embed directive
573
+ must be provided as a string as the embed_directive argument.
574
+
575
+ Args:
576
+ output_filename: The desired name for the new artifact.
577
+ embed_directive: The full '«artifact_content:...>>>...>>>format:...»' string.
578
+ output_metadata (dict, optional): Metadata for the new artifact.
579
+ tool_context: The context provided by the ADK framework.
580
+
581
+ Returns:
582
+ A dictionary indicating the result, including the new filename and version.
583
+ """
584
+ if not tool_context:
585
+ return {"status": "error", "message": "ToolContext is missing."}
586
+
587
+ log_identifier = f"[BuiltinArtifactTool:apply_embed:{output_filename}]"
588
+ log.info(
589
+ "%s Processing request with directive: %s", log_identifier, embed_directive
590
+ )
591
+
592
+ match = EMBED_REGEX.fullmatch(embed_directive)
593
+ if not match:
594
+ return {
595
+ "status": "error",
596
+ "message": f"Invalid embed directive format: {embed_directive}",
597
+ }
598
+
599
+ embed_type = match.group(1)
600
+ expression = match.group(2)
601
+ format_spec = match.group(3)
602
+
603
+ if embed_type != "artifact_content":
604
+ return {
605
+ "status": "error",
606
+ "message": f"This tool only supports 'artifact_content' embeds, got '{embed_type}'.",
607
+ }
608
+
609
+ try:
610
+ inv_context = tool_context._invocation_context
611
+ artifact_service = inv_context.artifact_service
612
+ if not artifact_service:
613
+ raise ValueError("ArtifactService not available.")
614
+
615
+ host_component = getattr(inv_context.agent, "host_component", None)
616
+ if not host_component:
617
+ log.warning(
618
+ "%s Could not access host component config for limits. Proceeding without them.",
619
+ log_identifier,
620
+ )
621
+ embed_config = {}
622
+ else:
623
+ embed_config = {
624
+ "gateway_artifact_content_limit_bytes": host_component.get_config(
625
+ "gateway_artifact_content_limit_bytes", -1
626
+ ),
627
+ "gateway_recursive_embed_depth": host_component.get_config(
628
+ "gateway_recursive_embed_depth", 3
629
+ ),
630
+ }
631
+
632
+ gateway_context = {
633
+ "artifact_service": artifact_service,
634
+ "session_context": {
635
+ "app_name": inv_context.app_name,
636
+ "user_id": inv_context.user_id,
637
+ "session_id": get_original_session_id(inv_context),
638
+ },
639
+ }
640
+ except Exception as ctx_err:
641
+ log.error(
642
+ "%s Failed to prepare context/config for embed evaluation: %s",
643
+ log_identifier,
644
+ ctx_err,
645
+ )
646
+ return {
647
+ "status": "error",
648
+ "message": f"Internal error preparing context: {ctx_err}",
649
+ }
650
+
651
+ resolved_content_str, error_msg_from_eval, _ = await evaluate_embed(
652
+ embed_type=embed_type,
653
+ expression=expression,
654
+ format_spec=format_spec,
655
+ context=gateway_context,
656
+ log_identifier=log_identifier,
657
+ config=embed_config,
658
+ )
659
+
660
+ if error_msg_from_eval or (
661
+ resolved_content_str and resolved_content_str.startswith("[Error:")
662
+ ):
663
+ error_to_report = error_msg_from_eval or resolved_content_str
664
+ log.error("%s Embed resolution failed: %s", log_identifier, error_to_report)
665
+ return {
666
+ "status": "error",
667
+ "message": f"Embed resolution failed: {error_to_report}",
668
+ }
669
+
670
+ output_mime_type = "text/plain"
671
+ final_format = None
672
+ chain_parts = expression.split(EMBED_CHAIN_DELIMITER)
673
+ if len(chain_parts) > 1:
674
+ last_part = chain_parts[-1].strip()
675
+ format_match = re.match(r"format:(.*)", last_part, re.DOTALL)
676
+ if format_match:
677
+ final_format = format_match.group(1).strip().lower()
678
+ elif format_spec:
679
+ final_format = format_spec.strip().lower()
680
+
681
+ if final_format:
682
+ if final_format == "html":
683
+ output_mime_type = "text/html"
684
+ elif final_format == "json" or final_format == "json_pretty":
685
+ output_mime_type = "application/json"
686
+ elif final_format == "csv":
687
+ output_mime_type = "text/csv"
688
+ elif final_format == "datauri":
689
+ output_mime_type = "text/plain"
690
+ log.warning(
691
+ "%s Embed resolved to data URI; saving new artifact as text/plain.",
692
+ log_identifier,
693
+ )
694
+
695
+ log.debug("%s Determined output MIME type as: %s", log_identifier, output_mime_type)
696
+
697
+ try:
698
+ resolved_bytes = resolved_content_str.encode("utf-8")
699
+ inv_context = tool_context._invocation_context
700
+ artifact_service = inv_context.artifact_service
701
+ if not artifact_service:
702
+ raise ValueError("ArtifactService is not available in the context.")
703
+
704
+ save_result = await save_artifact_with_metadata(
705
+ artifact_service=artifact_service,
706
+ app_name=inv_context.app_name,
707
+ user_id=inv_context.user_id,
708
+ session_id=get_original_session_id(inv_context),
709
+ filename=output_filename,
710
+ content_bytes=resolved_bytes,
711
+ mime_type=output_mime_type,
712
+ metadata_dict=(
713
+ lambda base_meta, user_meta: (
714
+ base_meta.update(user_meta or {}),
715
+ base_meta,
716
+ )[1]
717
+ )({"source_directive": embed_directive}, output_metadata),
718
+ timestamp=inv_context.session.last_update_time
719
+ or datetime.now(timezone.utc),
720
+ schema_max_keys=(
721
+ host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
722
+ if host_component
723
+ else DEFAULT_SCHEMA_MAX_KEYS
724
+ ),
725
+ tool_context=tool_context,
726
+ )
727
+
728
+ log.info(
729
+ "%s Successfully applied embed and saved new artifact '%s' (v%s).",
730
+ log_identifier,
731
+ output_filename,
732
+ save_result.get("data_version"),
733
+ )
734
+ return {
735
+ "status": "success",
736
+ "output_filename": output_filename,
737
+ "output_version": save_result.get("data_version"),
738
+ "output_mime_type": output_mime_type,
739
+ "message": f"Successfully created artifact '{output_filename}' v{save_result.get('data_version')} from embed directive.",
740
+ }
741
+
742
+ except Exception as save_err:
743
+ log.exception(
744
+ "%s Failed to save resolved content as artifact '%s': %s",
745
+ log_identifier,
746
+ output_filename,
747
+ save_err,
748
+ )
749
+ return {
750
+ "status": "error",
751
+ "message": f"Failed to save new artifact: {save_err}",
752
+ }
753
+
754
+
755
+ async def extract_content_from_artifact(
756
+ filename: str,
757
+ extraction_goal: str,
758
+ version: Optional[str] = "latest",
759
+ output_filename_base: Optional[str] = None,
760
+ tool_context: ToolContext = None,
761
+ ) -> Dict[str, Any]:
762
+ """
763
+ Loads an existing artifact, uses an internal LLM to process its content
764
+ based on an "extraction_goal," and manages the output by returning it
765
+ or saving it as a new artifact.
766
+
767
+ The tool's description for the LLM might dynamically update based on
768
+ the 'supported_binary_mime_types' configuration of the agent, indicating
769
+ which binary types it can attempt to process.
770
+
771
+ Args:
772
+ filename (str): Name of the source artifact. May contain embeds.
773
+ extraction_goal (str): Natural language instruction for the LLM on what
774
+ to extract or how to transform the content.
775
+ May contain embeds.
776
+ version (Optional[Union[int, str]]): Version of the source artifact.
777
+ Can be an integer or "latest".
778
+ Defaults to "latest". May contain embeds.
779
+ output_filename_base (Optional[str]): Optional base name for the new
780
+ artifact if the extracted content
781
+ is saved. May contain embeds.
782
+ tool_context (ToolContext): Provided by the ADK framework.
783
+
784
+ Returns:
785
+ Dict[str, Any]: A dictionary containing the status of the operation,
786
+ a message for the LLM, and potentially the extracted
787
+ data or details of a newly saved artifact.
788
+ Refer to the design document for specific response structures.
789
+ """
790
+ log_identifier = f"[BuiltinArtifactTool:extract_content:{filename}:{version}]"
791
+ log.debug(
792
+ "%s Processing request. Goal: '%s', Output base: '%s'",
793
+ log_identifier,
794
+ extraction_goal,
795
+ output_filename_base,
796
+ )
797
+
798
+ if not tool_context:
799
+ return {
800
+ "status": "error_tool_context_missing",
801
+ "message_to_llm": "Tool execution failed: ToolContext is missing.",
802
+ "filename": filename,
803
+ "version_requested": str(version),
804
+ }
805
+ if not filename:
806
+ return {
807
+ "status": "error_missing_filename",
808
+ "message_to_llm": "Tool execution failed: 'filename' parameter is required.",
809
+ "version_requested": str(version),
810
+ }
811
+ if not extraction_goal:
812
+ return {
813
+ "status": "error_missing_extraction_goal",
814
+ "message_to_llm": "Tool execution failed: 'extraction_goal' parameter is required.",
815
+ "filename": filename,
816
+ "version_requested": str(version),
817
+ }
818
+
819
+ inv_context = tool_context._invocation_context
820
+ host_component = getattr(inv_context.agent, "host_component", None)
821
+ if not host_component:
822
+ log.error(
823
+ "%s Host component not found on agent. Cannot retrieve config.",
824
+ log_identifier,
825
+ )
826
+ return {
827
+ "status": "error_internal_configuration",
828
+ "message_to_llm": "Tool configuration error: Host component not accessible.",
829
+ "filename": filename,
830
+ "version_requested": str(version),
831
+ }
832
+
833
+ try:
834
+ save_threshold = host_component.get_config(
835
+ "tool_output_save_threshold_bytes", 2048
836
+ )
837
+ llm_max_bytes = host_component.get_config(
838
+ "tool_output_llm_return_max_bytes", 4096
839
+ )
840
+ extraction_config = host_component.get_config(
841
+ "extract_content_from_artifact_config", {}
842
+ )
843
+ supported_binary_mime_types = extraction_config.get(
844
+ "supported_binary_mime_types", []
845
+ )
846
+ model_config_for_extraction = extraction_config.get("model")
847
+ except Exception as e:
848
+ log.exception("%s Error retrieving tool configuration: %s", log_identifier, e)
849
+ return {
850
+ "status": "error_internal_configuration",
851
+ "message_to_llm": f"Tool configuration error: {e}",
852
+ "filename": filename,
853
+ "version_requested": str(version),
854
+ }
855
+
856
+ source_artifact_data = None
857
+ processed_version: Union[int, str]
858
+
859
+ if version is None or (
860
+ isinstance(version, str) and version.strip().lower() == "latest"
861
+ ):
862
+ processed_version = "latest"
863
+ else:
864
+ try:
865
+ processed_version = int(version)
866
+ except ValueError:
867
+ log.warning(
868
+ "%s Invalid version string: '%s'. Must be an integer or 'latest'.",
869
+ log_identifier,
870
+ version,
871
+ )
872
+ return {
873
+ "status": "error_invalid_version_format",
874
+ "message_to_llm": f"Invalid version format '{version}'. Version must be an integer or 'latest'.",
875
+ "filename": filename,
876
+ "version_requested": str(version),
877
+ }
878
+ try:
879
+ log.debug(
880
+ "%s Loading source artifact '%s' version '%s' (processed as: %s)",
881
+ log_identifier,
882
+ filename,
883
+ version,
884
+ processed_version,
885
+ )
886
+ source_artifact_data = await load_artifact_content_or_metadata(
887
+ artifact_service=inv_context.artifact_service,
888
+ app_name=inv_context.app_name,
889
+ user_id=inv_context.user_id,
890
+ session_id=get_original_session_id(inv_context),
891
+ filename=filename,
892
+ version=processed_version,
893
+ return_raw_bytes=True,
894
+ log_identifier_prefix=log_identifier,
895
+ )
896
+ if source_artifact_data.get("status") != "success":
897
+ raise FileNotFoundError(
898
+ source_artifact_data.get("message", "Failed to load artifact")
899
+ )
900
+ log.info(
901
+ "%s Successfully loaded source artifact '%s' version %s (actual: v%s)",
902
+ log_identifier,
903
+ filename,
904
+ version,
905
+ source_artifact_data.get("version"),
906
+ )
907
+ except FileNotFoundError as e:
908
+ log.warning("%s Source artifact not found: %s", log_identifier, e)
909
+ return {
910
+ "status": "error_artifact_not_found",
911
+ "message_to_llm": f"Could not extract content. Source artifact '{filename}' (version {version}) was not found: {e}",
912
+ "filename": filename,
913
+ "version_requested": str(version),
914
+ }
915
+ except Exception as e:
916
+ log.exception("%s Error loading source artifact: %s", log_identifier, e)
917
+ return {
918
+ "status": "error_loading_artifact",
919
+ "message_to_llm": f"Error loading source artifact '{filename}': {e}",
920
+ "filename": filename,
921
+ "version_requested": str(version),
922
+ }
923
+
924
+ source_artifact_content_bytes = source_artifact_data.get("raw_bytes")
925
+ source_mime_type = source_artifact_data.get("mime_type", "application/octet-stream")
926
+ actual_source_version = source_artifact_data.get("version", "unknown")
927
+
928
+ chosen_llm = None
929
+ try:
930
+ if model_config_for_extraction:
931
+ if isinstance(model_config_for_extraction, str):
932
+ chosen_llm = LLMRegistry.new_llm(model_config_for_extraction)
933
+ log.info(
934
+ "%s Using tool-specific LLM (string): %s",
935
+ log_identifier,
936
+ model_config_for_extraction,
937
+ )
938
+ elif isinstance(model_config_for_extraction, dict):
939
+ chosen_llm = LiteLlm(**model_config_for_extraction)
940
+ log.info(
941
+ "%s Using tool-specific LLM (dict): %s",
942
+ log_identifier,
943
+ model_config_for_extraction.get("model"),
944
+ )
945
+ else:
946
+ log.warning(
947
+ "%s Invalid 'model' config for extraction tool. Falling back to agent default.",
948
+ log_identifier,
949
+ )
950
+ chosen_llm = inv_context.agent.canonical_model
951
+ else:
952
+ chosen_llm = inv_context.agent.canonical_model
953
+ log.info(
954
+ "%s Using agent's default LLM: %s", log_identifier, chosen_llm.model
955
+ )
956
+ except Exception as e:
957
+ log.exception("%s Error initializing LLM for extraction: %s", log_identifier, e)
958
+ return {
959
+ "status": "error_internal_llm_setup",
960
+ "message_to_llm": f"Failed to set up LLM for extraction: {e}",
961
+ "filename": filename,
962
+ "version_requested": str(version),
963
+ }
964
+
965
+ llm_parts = []
966
+ is_binary_supported = False
967
+
968
+ normalized_source_mime_type = source_mime_type.lower() if source_mime_type else ""
969
+
970
+ is_text_based = is_text_based_file(
971
+ mime_type=normalized_source_mime_type,
972
+ content_bytes=source_artifact_content_bytes,
973
+ )
974
+
975
+ if is_text_based:
976
+ try:
977
+ artifact_text_content = source_artifact_content_bytes.decode("utf-8")
978
+ llm_parts.append(
979
+ adk_types.Part(
980
+ text=f"Artifact Content (MIME type: {source_mime_type}):\n```\n{artifact_text_content}\n```"
981
+ )
982
+ )
983
+ log.debug("%s Prepared text content for LLM.", log_identifier)
984
+ except UnicodeDecodeError as e:
985
+ log.warning(
986
+ "%s Failed to decode text artifact as UTF-8: %s. Treating as opaque binary.",
987
+ log_identifier,
988
+ e,
989
+ )
990
+ llm_parts.append(
991
+ adk_types.Part(
992
+ text=f"The artifact '{filename}' is a binary file of type '{source_mime_type}' and could not be decoded as text."
993
+ )
994
+ )
995
+ else: # Binary
996
+ for supported_pattern in supported_binary_mime_types:
997
+ if fnmatch.fnmatch(source_mime_type, supported_pattern):
998
+ is_binary_supported = True
999
+ break
1000
+ if is_binary_supported:
1001
+ llm_parts.append(
1002
+ adk_types.Part(
1003
+ inline_data=adk_types.Blob(
1004
+ mime_type=source_mime_type, data=source_artifact_content_bytes
1005
+ )
1006
+ )
1007
+ )
1008
+ llm_parts.append(
1009
+ adk_types.Part(
1010
+ text=f"The above is the content of artifact '{filename}' (MIME type: {source_mime_type})."
1011
+ )
1012
+ )
1013
+ log.debug(
1014
+ "%s Prepared supported binary content (MIME: %s) for LLM.",
1015
+ log_identifier,
1016
+ source_mime_type,
1017
+ )
1018
+ else:
1019
+ llm_parts.append(
1020
+ adk_types.Part(
1021
+ 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."
1022
+ )
1023
+ )
1024
+ log.debug(
1025
+ "%s Prepared message for unsupported binary content (MIME: %s) for LLM.",
1026
+ log_identifier,
1027
+ source_mime_type,
1028
+ )
1029
+
1030
+ internal_llm_contents = [
1031
+ adk_types.Content(
1032
+ role="user", parts=[adk_types.Part(text=extraction_goal)] + llm_parts
1033
+ )
1034
+ ]
1035
+ internal_llm_request = LlmRequest(
1036
+ model=chosen_llm.model,
1037
+ contents=internal_llm_contents,
1038
+ config=adk_types.GenerateContentConfig(
1039
+ temperature=0.1,
1040
+ ),
1041
+ )
1042
+
1043
+ extracted_content_str = ""
1044
+ try:
1045
+ log.info(
1046
+ "%s Executing internal LLM call for extraction. Goal: %s",
1047
+ log_identifier,
1048
+ extraction_goal,
1049
+ )
1050
+ if hasattr(chosen_llm, "generate_content") and not hasattr(
1051
+ chosen_llm, "generate_content_async"
1052
+ ):
1053
+ llm_response = chosen_llm.generate_content(request=internal_llm_request)
1054
+ if llm_response.parts:
1055
+ extracted_content_str = llm_response.parts[0].text or ""
1056
+ else:
1057
+ extracted_content_str = ""
1058
+ elif hasattr(chosen_llm, "generate_content_async"):
1059
+ log.debug(
1060
+ "%s Calling LLM's generate_content_async (non-streaming) for extraction.",
1061
+ log_identifier,
1062
+ )
1063
+ try:
1064
+ llm_response_obj = None
1065
+ async for response_event in chosen_llm.generate_content_async(
1066
+ internal_llm_request
1067
+ ):
1068
+ llm_response_obj = response_event
1069
+ break
1070
+ if (
1071
+ llm_response_obj
1072
+ and hasattr(llm_response_obj, "text")
1073
+ and llm_response_obj.text
1074
+ ):
1075
+ extracted_content_str = llm_response_obj.text
1076
+ elif (
1077
+ llm_response_obj
1078
+ and hasattr(llm_response_obj, "parts")
1079
+ and llm_response_obj.parts
1080
+ ):
1081
+ extracted_content_str = "".join(
1082
+ [
1083
+ part.text
1084
+ for part in llm_response_obj.parts
1085
+ if hasattr(part, "text") and part.text
1086
+ ]
1087
+ )
1088
+ elif (
1089
+ llm_response_obj
1090
+ and hasattr(llm_response_obj, "content")
1091
+ and hasattr(llm_response_obj.content, "parts")
1092
+ and llm_response_obj.content.parts
1093
+ ):
1094
+ extracted_content_str = "".join(
1095
+ [
1096
+ part.text
1097
+ for part in llm_response_obj.content.parts
1098
+ if hasattr(part, "text") and part.text
1099
+ ]
1100
+ )
1101
+ else:
1102
+ extracted_content_str = ""
1103
+ log.warning(
1104
+ "%s LLM response object or its text/parts were not found or empty after non-streaming call.",
1105
+ log_identifier,
1106
+ )
1107
+
1108
+ except Exception as llm_async_err:
1109
+ log.exception(
1110
+ "%s Asynchronous LLM call for extraction failed: %s",
1111
+ log_identifier,
1112
+ llm_async_err,
1113
+ )
1114
+ extracted_content_str = (
1115
+ f"[ERROR: Asynchronous LLM call failed: {llm_async_err}]"
1116
+ )
1117
+ else:
1118
+ log.error(
1119
+ "%s LLM does not have a known generate_content or generate_content_async method. Extraction will be empty.",
1120
+ log_identifier,
1121
+ )
1122
+ extracted_content_str = "[ERROR: LLM method not found]"
1123
+
1124
+ log.info(
1125
+ "%s Internal LLM call completed. Extracted content length: %d chars",
1126
+ log_identifier,
1127
+ len(extracted_content_str),
1128
+ )
1129
+ if not extracted_content_str.strip():
1130
+ log.warning(
1131
+ "%s Internal LLM produced empty or whitespace-only content for extraction goal.",
1132
+ log_identifier,
1133
+ )
1134
+
1135
+ except Exception as e:
1136
+ log.exception(
1137
+ "%s Internal LLM call for extraction failed: %s", log_identifier, e
1138
+ )
1139
+ return {
1140
+ "status": "error_extraction_failed",
1141
+ "message_to_llm": f"The LLM failed to process the artifact content for your goal '{extraction_goal}'. Error: {e}",
1142
+ "filename": filename,
1143
+ "version_requested": str(version),
1144
+ }
1145
+
1146
+ extracted_content_bytes = extracted_content_str.encode("utf-8")
1147
+ extracted_content_size_bytes = len(extracted_content_bytes)
1148
+ output_mime_type = "text/plain"
1149
+ try:
1150
+ json.loads(extracted_content_str)
1151
+ output_mime_type = "application/json"
1152
+ log.debug(
1153
+ "%s Extracted content appears to be valid JSON. Setting output MIME to application/json.",
1154
+ log_identifier,
1155
+ )
1156
+ except json.JSONDecodeError:
1157
+ log.debug(
1158
+ "%s Extracted content is not JSON. Using output MIME text/plain.",
1159
+ log_identifier,
1160
+ )
1161
+
1162
+ response_for_llm_str = extracted_content_str
1163
+ saved_extracted_artifact_details = None
1164
+ final_status = "success"
1165
+ message_to_llm_parts = [
1166
+ f"Successfully extracted content from '{filename}' (v{actual_source_version}) based on your goal: '{extraction_goal}'."
1167
+ ]
1168
+ was_saved = False
1169
+ was_truncated = False
1170
+
1171
+ if extracted_content_size_bytes > save_threshold:
1172
+ log.info(
1173
+ "%s Extracted content size (%d bytes) exceeds save threshold (%d bytes). Saving as new artifact.",
1174
+ log_identifier,
1175
+ extracted_content_size_bytes,
1176
+ save_threshold,
1177
+ )
1178
+ saved_extracted_artifact_details = await _save_extracted_artifact(
1179
+ tool_context,
1180
+ host_component,
1181
+ extracted_content_bytes,
1182
+ filename,
1183
+ actual_source_version,
1184
+ extraction_goal,
1185
+ output_filename_base,
1186
+ output_mime_type,
1187
+ )
1188
+ if saved_extracted_artifact_details.get("status") == "success":
1189
+ was_saved = True
1190
+ message_to_llm_parts.append(
1191
+ f"The full extracted content was saved as artifact '{saved_extracted_artifact_details.get('data_filename')}' "
1192
+ f"(version {saved_extracted_artifact_details.get('data_version')}). "
1193
+ f"You can retrieve it using 'load_artifact' or perform further extractions on it using 'extract_content_from_artifact' "
1194
+ f"with this new filename and version."
1195
+ )
1196
+ else:
1197
+ message_to_llm_parts.append(
1198
+ f"Attempted to save the large extracted content, but failed: {saved_extracted_artifact_details.get('message')}"
1199
+ )
1200
+
1201
+ if len(extracted_content_str.encode("utf-8")) > llm_max_bytes:
1202
+ was_truncated = True
1203
+ log.info(
1204
+ "%s Original extracted content (%d bytes) exceeds LLM return max bytes (%d bytes). Truncating for LLM response.",
1205
+ log_identifier,
1206
+ len(extracted_content_str.encode("utf-8")),
1207
+ llm_max_bytes,
1208
+ )
1209
+
1210
+ if not was_saved:
1211
+ log.info(
1212
+ "%s Saving extracted content now because it needs truncation for LLM response and wasn't saved previously.",
1213
+ log_identifier,
1214
+ )
1215
+ saved_extracted_artifact_details = await _save_extracted_artifact(
1216
+ tool_context,
1217
+ host_component,
1218
+ extracted_content_bytes,
1219
+ filename,
1220
+ actual_source_version,
1221
+ extraction_goal,
1222
+ output_filename_base,
1223
+ output_mime_type,
1224
+ )
1225
+ if saved_extracted_artifact_details.get("status") == "success":
1226
+ was_saved = True
1227
+ message_to_llm_parts.append(
1228
+ f"The full extracted content (which is being truncated for this response) was saved as artifact "
1229
+ f"'{saved_extracted_artifact_details.get('data_filename')}' (version {saved_extracted_artifact_details.get('data_version')}). "
1230
+ f"You can retrieve the full content using 'load_artifact' or perform further extractions on it."
1231
+ )
1232
+ else:
1233
+ message_to_llm_parts.append(
1234
+ f"Attempted to save the extracted content before truncation, but failed: {saved_extracted_artifact_details.get('message')}"
1235
+ )
1236
+
1237
+ truncation_suffix = "... [Content truncated]"
1238
+ adjusted_max_bytes = llm_max_bytes - len(truncation_suffix.encode("utf-8"))
1239
+ if adjusted_max_bytes < 0:
1240
+ adjusted_max_bytes = 0
1241
+
1242
+ temp_response_bytes = extracted_content_str.encode("utf-8")
1243
+ truncated_bytes = temp_response_bytes[:adjusted_max_bytes]
1244
+ response_for_llm_str = (
1245
+ truncated_bytes.decode("utf-8", "ignore") + truncation_suffix
1246
+ )
1247
+
1248
+ message_to_llm_parts.append(
1249
+ "The extracted content provided in 'extracted_data_preview' has been truncated due to size limits. "
1250
+ "If saved, the full version is available in the specified artifact."
1251
+ )
1252
+
1253
+ if was_saved and was_truncated:
1254
+ final_status = "success_full_content_saved_preview_returned"
1255
+ elif was_saved:
1256
+ final_status = "success_full_content_saved_and_returned"
1257
+ elif was_truncated:
1258
+ final_status = "success_content_returned_truncated_and_saved"
1259
+ else:
1260
+ final_status = "success_content_returned"
1261
+
1262
+ final_response_dict = {
1263
+ "status": final_status,
1264
+ "message_to_llm": " ".join(list(dict.fromkeys(message_to_llm_parts))),
1265
+ "source_filename": filename,
1266
+ "source_version_processed": actual_source_version,
1267
+ "extraction_goal_used": extraction_goal,
1268
+ }
1269
+
1270
+ if was_truncated:
1271
+ final_response_dict["extracted_data_preview"] = response_for_llm_str
1272
+ else:
1273
+ final_response_dict["extracted_data"] = response_for_llm_str
1274
+
1275
+ if (
1276
+ saved_extracted_artifact_details
1277
+ and saved_extracted_artifact_details.get("status") == "success"
1278
+ ):
1279
+ final_response_dict["saved_extracted_artifact_details"] = (
1280
+ saved_extracted_artifact_details
1281
+ )
1282
+ elif saved_extracted_artifact_details:
1283
+ final_response_dict["saved_extracted_artifact_attempt_details"] = (
1284
+ saved_extracted_artifact_details
1285
+ )
1286
+
1287
+ log.info(
1288
+ "%s Tool execution finished. Final status: %s. Response preview: %s",
1289
+ log_identifier,
1290
+ final_status,
1291
+ final_response_dict,
1292
+ )
1293
+ return final_response_dict
1294
+
1295
+
1296
+ async def append_to_artifact(
1297
+ filename: str,
1298
+ content_chunk: str,
1299
+ mime_type: str,
1300
+ tool_context: ToolContext = None,
1301
+ ) -> Dict[str, Any]:
1302
+ """
1303
+ Appends a chunk of content to an existing artifact. This operation will
1304
+ create a new version of the artifact. The content_chunk should be a string,
1305
+ potentially base64 encoded if it represents binary data (indicated by mime_type).
1306
+ The chunk size should be limited (e.g., max 3KB) by the LLM.
1307
+
1308
+ Args:
1309
+ filename: The name of the artifact to append to. May contain embeds.
1310
+ content_chunk: The chunk of content to append (max approx. 3KB).
1311
+ If mime_type suggests binary, this should be base64 encoded.
1312
+ May contain embeds.
1313
+ mime_type: The MIME type of the content_chunk. This helps determine if
1314
+ base64 decoding is needed for the chunk. The overall artifact's
1315
+ MIME type will be preserved from its latest version.
1316
+ May contain embeds.
1317
+ tool_context: The context provided by the ADK framework.
1318
+
1319
+ Returns:
1320
+ A dictionary indicating the result, including the new version of the artifact.
1321
+ """
1322
+ if not tool_context:
1323
+ return {
1324
+ "status": "error",
1325
+ "filename": filename,
1326
+ "message": "ToolContext is missing, cannot append to artifact.",
1327
+ }
1328
+
1329
+ log_identifier = f"[BuiltinArtifactTool:append_to_artifact:{filename}]"
1330
+ log.debug("%s Processing request to append chunk.", log_identifier)
1331
+
1332
+ try:
1333
+ inv_context = tool_context._invocation_context
1334
+ artifact_service = inv_context.artifact_service
1335
+ if not artifact_service:
1336
+ raise ValueError("ArtifactService is not available in the context.")
1337
+
1338
+ app_name = inv_context.app_name
1339
+ user_id = inv_context.user_id
1340
+ session_id = get_original_session_id(inv_context)
1341
+ host_component = getattr(inv_context.agent, "host_component", None)
1342
+
1343
+ log.debug(
1344
+ "%s Loading latest version of artifact '%s' content to append to.",
1345
+ log_identifier,
1346
+ filename,
1347
+ )
1348
+ content_load_result = await load_artifact_content_or_metadata(
1349
+ artifact_service=artifact_service,
1350
+ app_name=app_name,
1351
+ user_id=user_id,
1352
+ session_id=session_id,
1353
+ filename=filename,
1354
+ version="latest",
1355
+ load_metadata_only=False,
1356
+ return_raw_bytes=True,
1357
+ component=host_component,
1358
+ log_identifier_prefix=f"{log_identifier}[LoadOriginalContent]",
1359
+ )
1360
+
1361
+ if content_load_result.get("status") != "success":
1362
+ log.error(
1363
+ "%s Failed to load original artifact content '%s': %s",
1364
+ log_identifier,
1365
+ filename,
1366
+ content_load_result.get("message"),
1367
+ )
1368
+ return {
1369
+ "status": "error",
1370
+ "filename": filename,
1371
+ "message": f"Failed to load original artifact content to append to: {content_load_result.get('message')}",
1372
+ }
1373
+
1374
+ original_artifact_bytes = content_load_result.get("raw_bytes", b"")
1375
+ original_mime_type = content_load_result.get(
1376
+ "mime_type", "application/octet-stream"
1377
+ )
1378
+ original_version_loaded = content_load_result.get("version", "unknown")
1379
+ log.info(
1380
+ "%s Loaded original artifact content '%s' v%s, type: %s, size: %d bytes.",
1381
+ log_identifier,
1382
+ filename,
1383
+ original_version_loaded,
1384
+ original_mime_type,
1385
+ len(original_artifact_bytes),
1386
+ )
1387
+
1388
+ log.debug(
1389
+ "%s Loading latest version of artifact '%s' metadata.",
1390
+ log_identifier,
1391
+ filename,
1392
+ )
1393
+ metadata_load_result = await load_artifact_content_or_metadata(
1394
+ artifact_service=artifact_service,
1395
+ app_name=app_name,
1396
+ user_id=user_id,
1397
+ session_id=session_id,
1398
+ filename=filename,
1399
+ version="latest",
1400
+ load_metadata_only=True,
1401
+ component=host_component,
1402
+ log_identifier_prefix=f"{log_identifier}[LoadOriginalMetadata]",
1403
+ )
1404
+ original_metadata_dict = {}
1405
+ if metadata_load_result.get("status") == "success":
1406
+ original_metadata_dict = metadata_load_result.get("metadata", {})
1407
+ log.info(
1408
+ "%s Loaded original artifact metadata for '%s' v%s.",
1409
+ log_identifier,
1410
+ filename,
1411
+ metadata_load_result.get("version", "unknown"),
1412
+ )
1413
+ else:
1414
+ log.warning(
1415
+ "%s Failed to load original artifact metadata for '%s': %s. Proceeding with minimal metadata.",
1416
+ log_identifier,
1417
+ filename,
1418
+ metadata_load_result.get("message"),
1419
+ )
1420
+
1421
+ chunk_bytes, _ = decode_and_get_bytes(
1422
+ content_chunk, mime_type, f"{log_identifier}[DecodeChunk]"
1423
+ )
1424
+ log.debug(
1425
+ "%s Decoded content_chunk (declared type: %s) to %d bytes.",
1426
+ log_identifier,
1427
+ mime_type,
1428
+ len(chunk_bytes),
1429
+ )
1430
+
1431
+ combined_bytes = original_artifact_bytes + chunk_bytes
1432
+ log.debug(
1433
+ "%s Appended chunk. New total size: %d bytes.",
1434
+ log_identifier,
1435
+ len(combined_bytes),
1436
+ )
1437
+
1438
+ new_metadata_for_save = {
1439
+ key: value
1440
+ for key, value in original_metadata_dict.items()
1441
+ if key
1442
+ not in [
1443
+ "filename",
1444
+ "mime_type",
1445
+ "size_bytes",
1446
+ "timestamp_utc",
1447
+ "schema",
1448
+ "version",
1449
+ ]
1450
+ }
1451
+ new_metadata_for_save["appended_from_version"] = original_version_loaded
1452
+ new_metadata_for_save["appended_chunk_declared_mime_type"] = mime_type
1453
+
1454
+ schema_max_keys = (
1455
+ host_component.get_config("schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS)
1456
+ if host_component
1457
+ else DEFAULT_SCHEMA_MAX_KEYS
1458
+ )
1459
+
1460
+ save_result = await save_artifact_with_metadata(
1461
+ artifact_service=artifact_service,
1462
+ app_name=app_name,
1463
+ user_id=user_id,
1464
+ session_id=session_id,
1465
+ filename=filename,
1466
+ content_bytes=combined_bytes,
1467
+ mime_type=original_mime_type,
1468
+ metadata_dict=new_metadata_for_save,
1469
+ timestamp=datetime.now(timezone.utc),
1470
+ schema_max_keys=schema_max_keys,
1471
+ tool_context=tool_context,
1472
+ )
1473
+
1474
+ log.info(
1475
+ "%s Result from save_artifact_with_metadata after append: %s",
1476
+ log_identifier,
1477
+ save_result,
1478
+ )
1479
+
1480
+ if save_result.get("status") == "error":
1481
+ raise IOError(
1482
+ f"Failed to save appended artifact: {save_result.get('message', 'Unknown error')}"
1483
+ )
1484
+
1485
+ return {
1486
+ "status": "success",
1487
+ "filename": filename,
1488
+ "new_version": save_result.get("data_version"),
1489
+ "total_size_bytes": len(combined_bytes),
1490
+ "message": f"Chunk appended to '{filename}'. New version is {save_result.get('data_version')} with total size {len(combined_bytes)} bytes.",
1491
+ }
1492
+
1493
+ except FileNotFoundError as e:
1494
+ log.warning("%s Original artifact not found for append: %s", log_identifier, e)
1495
+ return {
1496
+ "status": "error",
1497
+ "filename": filename,
1498
+ "message": f"Original artifact '{filename}' not found: {e}",
1499
+ }
1500
+ except ValueError as e:
1501
+ log.warning("%s Value error during append: %s", log_identifier, e)
1502
+ return {"status": "error", "filename": filename, "message": str(e)}
1503
+ except IOError as e:
1504
+ log.warning("%s IO error during append: %s", log_identifier, e)
1505
+ return {"status": "error", "filename": filename, "message": str(e)}
1506
+ except Exception as e:
1507
+ log.exception(
1508
+ "%s Unexpected error appending to artifact '%s': %s",
1509
+ log_identifier,
1510
+ filename,
1511
+ e,
1512
+ )
1513
+ return {
1514
+ "status": "error",
1515
+ "filename": filename,
1516
+ "message": f"Failed to append to artifact: {e}",
1517
+ }
1518
+
1519
+
1520
+ async def _save_extracted_artifact(
1521
+ tool_context: ToolContext,
1522
+ host_component: Any,
1523
+ extracted_content_bytes: bytes,
1524
+ source_artifact_filename: str,
1525
+ source_artifact_version: Union[int, str],
1526
+ extraction_goal: str,
1527
+ output_filename_base: Optional[str],
1528
+ output_mime_type: str,
1529
+ ) -> Dict[str, Any]:
1530
+ """
1531
+ Saves the extracted content as a new artifact with comprehensive metadata.
1532
+
1533
+ Args:
1534
+ tool_context: The ADK ToolContext.
1535
+ host_component: The A2A_ADK_HostComponent instance for accessing config and services.
1536
+ extracted_content_bytes: The raw byte content of the extracted data.
1537
+ source_artifact_filename: The filename of the original artifact.
1538
+ source_artifact_version: The version of the original artifact.
1539
+ extraction_goal: The natural language goal used for extraction.
1540
+ output_filename_base: Optional base for the new artifact's filename.
1541
+ output_mime_type: The MIME type of the extracted content.
1542
+
1543
+ Returns:
1544
+ A dictionary containing details of the saved artifact, as returned by
1545
+ `save_artifact_with_metadata`.
1546
+ """
1547
+ log_identifier = f"[BuiltinArtifactTool:_save_extracted_artifact]"
1548
+ log.debug("%s Saving extracted content...", log_identifier)
1549
+
1550
+ try:
1551
+ base_name = output_filename_base or f"{source_artifact_filename}_extracted"
1552
+ base_name_sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", base_name)
1553
+ base_name_sanitized = base_name_sanitized.strip("_")
1554
+
1555
+ suffix = uuid.uuid4().hex[:8]
1556
+ extension_map = {
1557
+ "text/plain": ".txt",
1558
+ "application/json": ".json",
1559
+ "text/csv": ".csv",
1560
+ "text/html": ".html",
1561
+ "image/png": ".png",
1562
+ "image/jpeg": ".jpg",
1563
+ "application/pdf": ".pdf",
1564
+ }
1565
+ ext = extension_map.get(output_mime_type.lower(), ".dat")
1566
+ filename = f"{base_name_sanitized}_{suffix}{ext}"
1567
+ log.debug("%s Generated output filename: %s", log_identifier, filename)
1568
+
1569
+ timestamp = datetime.now(timezone.utc)
1570
+ metadata_for_saving = {
1571
+ "description": f"Content extracted/transformed from artifact '{source_artifact_filename}' (version {source_artifact_version}) using goal: '{extraction_goal}'.",
1572
+ "source_artifact_filename": source_artifact_filename,
1573
+ "source_artifact_version": source_artifact_version,
1574
+ "extraction_goal_used": extraction_goal,
1575
+ }
1576
+ log.debug(
1577
+ "%s Prepared metadata for saving: %s", log_identifier, metadata_for_saving
1578
+ )
1579
+
1580
+ inv_context = tool_context._invocation_context
1581
+ artifact_service = inv_context.artifact_service
1582
+ if not artifact_service:
1583
+ raise ValueError("ArtifactService is not available in the context.")
1584
+
1585
+ app_name = inv_context.app_name
1586
+ user_id = inv_context.user_id
1587
+ session_id = get_original_session_id(inv_context)
1588
+ schema_max_keys = host_component.get_config(
1589
+ "schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS
1590
+ )
1591
+
1592
+ log.debug(
1593
+ "%s Calling save_artifact_with_metadata for '%s' (app: %s, user: %s, session: %s, schema_keys: %d)",
1594
+ log_identifier,
1595
+ filename,
1596
+ app_name,
1597
+ user_id,
1598
+ session_id,
1599
+ schema_max_keys,
1600
+ )
1601
+
1602
+ save_result = await save_artifact_with_metadata(
1603
+ artifact_service=artifact_service,
1604
+ app_name=app_name,
1605
+ user_id=user_id,
1606
+ session_id=session_id,
1607
+ filename=filename,
1608
+ content_bytes=extracted_content_bytes,
1609
+ mime_type=output_mime_type,
1610
+ metadata_dict=metadata_for_saving,
1611
+ timestamp=timestamp,
1612
+ schema_max_keys=schema_max_keys,
1613
+ tool_context=tool_context,
1614
+ )
1615
+
1616
+ log.info(
1617
+ "%s Extracted content saved as artifact '%s' (version %s). Result: %s",
1618
+ log_identifier,
1619
+ save_result.get("data_filename", filename),
1620
+ save_result.get("data_version", "N/A"),
1621
+ save_result.get("status"),
1622
+ )
1623
+ return save_result
1624
+
1625
+ except Exception as e:
1626
+ log.exception(
1627
+ "%s Error in _save_extracted_artifact for source '%s': %s",
1628
+ log_identifier,
1629
+ source_artifact_filename,
1630
+ e,
1631
+ )
1632
+ return {
1633
+ "status": "error",
1634
+ "data_filename": filename if "filename" in locals() else "unknown_filename",
1635
+ "message": f"Failed to save extracted content as artifact: {e}",
1636
+ }
1637
+
1638
+
1639
+ async def _notify_artifact_save(
1640
+ filename: str,
1641
+ version: int,
1642
+ status: str,
1643
+ tool_context: ToolContext = None, # Keep tool_context for signature consistency
1644
+ ) -> Dict[str, Any]:
1645
+ """
1646
+ An internal tool used by the system to confirm that a fenced artifact block
1647
+ has been successfully saved. It performs no actions and simply returns its
1648
+ arguments to get the result into the ADK history.
1649
+ """
1650
+ return {"filename": filename, "version": version, "status": status}
1651
+
1652
+
1653
+ _notify_artifact_save_tool_def = BuiltinTool(
1654
+ name="_notify_artifact_save",
1655
+ implementation=_notify_artifact_save,
1656
+ description="INTERNAL TOOL. This tool is used by the system to confirm that a fenced artifact block has been saved. You MUST NOT call this tool directly.",
1657
+ category="internal",
1658
+ required_scopes=[], # No scopes needed for an internal notification tool
1659
+ parameters=adk_types.Schema(
1660
+ type=adk_types.Type.OBJECT,
1661
+ properties={
1662
+ "filename": adk_types.Schema(
1663
+ type=adk_types.Type.STRING,
1664
+ description="The name of the artifact that was saved.",
1665
+ ),
1666
+ "version": adk_types.Schema(
1667
+ type=adk_types.Type.INTEGER,
1668
+ description="The version number of the saved artifact.",
1669
+ ),
1670
+ "status": adk_types.Schema(
1671
+ type=adk_types.Type.STRING,
1672
+ description="The status of the save operation.",
1673
+ ),
1674
+ },
1675
+ required=["filename", "version", "status"],
1676
+ ),
1677
+ examples=[],
1678
+ )
1679
+
1680
+ append_to_artifact_tool_def = BuiltinTool(
1681
+ name="append_to_artifact",
1682
+ implementation=append_to_artifact,
1683
+ 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.",
1684
+ category="artifact_management",
1685
+ required_scopes=["tool:artifact:append"],
1686
+ parameters=adk_types.Schema(
1687
+ type=adk_types.Type.OBJECT,
1688
+ properties={
1689
+ "filename": adk_types.Schema(
1690
+ type=adk_types.Type.STRING,
1691
+ description="The name of the artifact to append to. May contain embeds.",
1692
+ ),
1693
+ "content_chunk": adk_types.Schema(
1694
+ type=adk_types.Type.STRING,
1695
+ description="The chunk of content to append (max approx. 3KB). If mime_type suggests binary, this should be base64 encoded. May contain embeds.",
1696
+ ),
1697
+ "mime_type": adk_types.Schema(
1698
+ type=adk_types.Type.STRING,
1699
+ 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.",
1700
+ ),
1701
+ },
1702
+ required=["filename", "content_chunk", "mime_type"],
1703
+ ),
1704
+ examples=[],
1705
+ )
1706
+
1707
+ list_artifacts_tool_def = BuiltinTool(
1708
+ name="list_artifacts",
1709
+ implementation=list_artifacts,
1710
+ 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.",
1711
+ category="artifact_management",
1712
+ required_scopes=["tool:artifact:list"],
1713
+ parameters=adk_types.Schema(
1714
+ type=adk_types.Type.OBJECT,
1715
+ properties={},
1716
+ required=[],
1717
+ ),
1718
+ examples=[],
1719
+ )
1720
+
1721
+ load_artifact_tool_def = BuiltinTool(
1722
+ name="load_artifact",
1723
+ implementation=load_artifact,
1724
+ 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.",
1725
+ category="artifact_management",
1726
+ required_scopes=["tool:artifact:load"],
1727
+ parameters=adk_types.Schema(
1728
+ type=adk_types.Type.OBJECT,
1729
+ properties={
1730
+ "filename": adk_types.Schema(
1731
+ type=adk_types.Type.STRING,
1732
+ description="The name of the artifact to load. May contain embeds.",
1733
+ ),
1734
+ "version": adk_types.Schema(
1735
+ type=adk_types.Type.INTEGER,
1736
+ description="The specific version number to load. Must be explicitly provided.",
1737
+ ),
1738
+ "load_metadata_only": adk_types.Schema(
1739
+ type=adk_types.Type.BOOLEAN,
1740
+ description="If True, load only the metadata JSON. Default False.",
1741
+ nullable=True,
1742
+ ),
1743
+ "max_content_length": adk_types.Schema(
1744
+ type=adk_types.Type.INTEGER,
1745
+ description="Optional. Maximum character length for text content. If None, uses app configuration. Range: 100-100,000.",
1746
+ nullable=True,
1747
+ ),
1748
+ },
1749
+ required=["filename", "version"],
1750
+ ),
1751
+ examples=[],
1752
+ )
1753
+
1754
+ signal_artifact_for_return_tool_def = BuiltinTool(
1755
+ name="signal_artifact_for_return",
1756
+ implementation=signal_artifact_for_return,
1757
+ description="Signals the host component to return a specific artifact version to the original caller of the task. This tool does not load the artifact content itself; it just flags it for return.",
1758
+ category="artifact_management",
1759
+ required_scopes=["tool:artifact:signal_return"],
1760
+ parameters=adk_types.Schema(
1761
+ type=adk_types.Type.OBJECT,
1762
+ properties={
1763
+ "filename": adk_types.Schema(
1764
+ type=adk_types.Type.STRING,
1765
+ description="The name of the artifact to return. May contain embeds.",
1766
+ ),
1767
+ "version": adk_types.Schema(
1768
+ type=adk_types.Type.INTEGER,
1769
+ description="The specific version number to return. Use list_artifacts() first to find available versions.",
1770
+ ),
1771
+ },
1772
+ required=["filename", "version"],
1773
+ ),
1774
+ examples=[],
1775
+ )
1776
+
1777
+ apply_embed_and_create_artifact_tool_def = BuiltinTool(
1778
+ name="apply_embed_and_create_artifact",
1779
+ implementation=apply_embed_and_create_artifact,
1780
+ 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.",
1781
+ category="artifact_management",
1782
+ required_scopes=["tool:artifact:create", "tool:artifact:load"],
1783
+ parameters=adk_types.Schema(
1784
+ type=adk_types.Type.OBJECT,
1785
+ properties={
1786
+ "output_filename": adk_types.Schema(
1787
+ type=adk_types.Type.STRING,
1788
+ description="The desired name for the new artifact.",
1789
+ ),
1790
+ "embed_directive": adk_types.Schema(
1791
+ type=adk_types.Type.STRING,
1792
+ description="The full '«artifact_content:...>>>...>>>format:...»' string.",
1793
+ ),
1794
+ "output_metadata": adk_types.Schema(
1795
+ type=adk_types.Type.OBJECT,
1796
+ description="Optional metadata for the new artifact.",
1797
+ nullable=True,
1798
+ ),
1799
+ },
1800
+ required=["output_filename", "embed_directive"],
1801
+ ),
1802
+ raw_string_args=["embed_directive"],
1803
+ examples=[],
1804
+ )
1805
+
1806
+ extract_content_from_artifact_tool_def = BuiltinTool(
1807
+ name="extract_content_from_artifact",
1808
+ implementation=extract_content_from_artifact,
1809
+ 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.",
1810
+ category="artifact_management",
1811
+ required_scopes=["tool:artifact:load", "tool:artifact:create"],
1812
+ parameters=adk_types.Schema(
1813
+ type=adk_types.Type.OBJECT,
1814
+ properties={
1815
+ "filename": adk_types.Schema(
1816
+ type=adk_types.Type.STRING,
1817
+ description="Name of the source artifact. May contain embeds.",
1818
+ ),
1819
+ "extraction_goal": adk_types.Schema(
1820
+ type=adk_types.Type.STRING,
1821
+ description="Natural language instruction for the LLM on what to extract or how to transform the content. May contain embeds.",
1822
+ ),
1823
+ "version": adk_types.Schema(
1824
+ type=adk_types.Type.STRING,
1825
+ description="Version of the source artifact. Can be an integer or 'latest'. Defaults to 'latest'. May contain embeds.",
1826
+ nullable=True,
1827
+ ),
1828
+ "output_filename_base": adk_types.Schema(
1829
+ type=adk_types.Type.STRING,
1830
+ description="Optional base name for the new artifact if the extracted content is saved. May contain embeds.",
1831
+ nullable=True,
1832
+ ),
1833
+ },
1834
+ required=["filename", "extraction_goal"],
1835
+ ),
1836
+ examples=[],
1837
+ )
1838
+
1839
+ tool_registry.register(_notify_artifact_save_tool_def)
1840
+ tool_registry.register(append_to_artifact_tool_def)
1841
+ tool_registry.register(list_artifacts_tool_def)
1842
+ tool_registry.register(load_artifact_tool_def)
1843
+ tool_registry.register(signal_artifact_for_return_tool_def)
1844
+ tool_registry.register(apply_embed_and_create_artifact_tool_def)
1845
+ tool_registry.register(extract_content_from_artifact_tool_def)
1846
+
1847
+
1848
+ async def delete_artifact(
1849
+ filename: str,
1850
+ version: Optional[int] = None,
1851
+ tool_context: ToolContext = None,
1852
+ ) -> Dict[str, Any]:
1853
+ """
1854
+ Deletes a specific version of an artifact, or all versions if no version is specified.
1855
+
1856
+ Args:
1857
+ filename: The name of the artifact to delete.
1858
+ version: The specific version number to delete. If not provided, all versions will be deleted.
1859
+ tool_context: The context provided by the ADK framework.
1860
+
1861
+ Returns:
1862
+ A dictionary indicating the result of the deletion.
1863
+ """
1864
+ if not tool_context:
1865
+ return {
1866
+ "status": "error",
1867
+ "filename": filename,
1868
+ "message": "ToolContext is missing, cannot delete artifact.",
1869
+ }
1870
+
1871
+ log_identifier = (
1872
+ f"[BuiltinArtifactTool:delete_artifact:{filename}:{version or 'all'}]"
1873
+ )
1874
+ log.debug("%s Processing request.", log_identifier)
1875
+
1876
+ try:
1877
+ inv_context = tool_context._invocation_context
1878
+ artifact_service = inv_context.artifact_service
1879
+ if not artifact_service:
1880
+ raise ValueError("ArtifactService is not available in the context.")
1881
+
1882
+ app_name = inv_context.app_name
1883
+ user_id = inv_context.user_id
1884
+ session_id = get_original_session_id(inv_context)
1885
+
1886
+ if not hasattr(artifact_service, "delete_artifact"):
1887
+ raise NotImplementedError(
1888
+ "ArtifactService does not support deleting artifacts."
1889
+ )
1890
+
1891
+ await artifact_service.delete_artifact(
1892
+ app_name=app_name,
1893
+ user_id=user_id,
1894
+ session_id=session_id,
1895
+ filename=filename,
1896
+ version=version,
1897
+ )
1898
+
1899
+ log.info(
1900
+ "%s Successfully deleted artifact '%s' version '%s'.",
1901
+ log_identifier,
1902
+ filename,
1903
+ version or "all",
1904
+ )
1905
+ return {
1906
+ "status": "success",
1907
+ "filename": filename,
1908
+ "version": version or "all",
1909
+ "message": f"Artifact '{filename}' version '{version or 'all'}' deleted successfully.",
1910
+ }
1911
+
1912
+ except FileNotFoundError as e:
1913
+ log.warning("%s Artifact not found for deletion: %s", log_identifier, e)
1914
+ return {
1915
+ "status": "error",
1916
+ "filename": filename,
1917
+ "message": f"Artifact '{filename}' not found.",
1918
+ }
1919
+ except Exception as e:
1920
+ log.exception(
1921
+ "%s Error deleting artifact '%s': %s", log_identifier, filename, e
1922
+ )
1923
+ return {
1924
+ "status": "error",
1925
+ "filename": filename,
1926
+ "message": f"Failed to delete artifact: {e}",
1927
+ }
1928
+
1929
+
1930
+ delete_artifact_tool_def = BuiltinTool(
1931
+ name="delete_artifact",
1932
+ implementation=delete_artifact,
1933
+ description="Deletes a specific version of an artifact, or all versions if no version is specified.",
1934
+ category="artifact_management",
1935
+ required_scopes=["tool:artifact:delete"],
1936
+ parameters=adk_types.Schema(
1937
+ type=adk_types.Type.OBJECT,
1938
+ properties={
1939
+ "filename": adk_types.Schema(
1940
+ type=adk_types.Type.STRING,
1941
+ description="The name of the artifact to delete.",
1942
+ ),
1943
+ "version": adk_types.Schema(
1944
+ type=adk_types.Type.INTEGER,
1945
+ description="The specific version number to delete. If not provided, all versions will be deleted.",
1946
+ nullable=True,
1947
+ ),
1948
+ },
1949
+ required=["filename"],
1950
+ ),
1951
+ examples=[],
1952
+ )
1953
+
1954
+ tool_registry.register(delete_artifact_tool_def)