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

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

Potentially problematic release.


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

Files changed (521) hide show
  1. solace_agent_mesh/__init__.py +5 -0
  2. solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
  3. solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
  4. solace_agent_mesh/agent/adk/callbacks.py +1716 -0
  5. solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
  6. solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
  7. solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
  8. solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
  9. solace_agent_mesh/agent/adk/runner.py +357 -0
  10. solace_agent_mesh/agent/adk/services.py +240 -0
  11. solace_agent_mesh/agent/adk/setup.py +751 -0
  12. solace_agent_mesh/agent/adk/stream_parser.py +214 -0
  13. solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
  14. solace_agent_mesh/agent/agent_llm.txt +41 -0
  15. solace_agent_mesh/agent/protocol/event_handlers.py +1444 -0
  16. solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
  17. solace_agent_mesh/agent/sac/app.py +640 -0
  18. solace_agent_mesh/agent/sac/component.py +3496 -0
  19. solace_agent_mesh/agent/sac/patch_adk.py +111 -0
  20. solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
  21. solace_agent_mesh/agent/sac/task_execution_context.py +185 -0
  22. solace_agent_mesh/agent/testing/__init__.py +3 -0
  23. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  24. solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
  25. solace_agent_mesh/agent/tools/__init__.py +14 -0
  26. solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
  27. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
  28. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
  29. solace_agent_mesh/agent/tools/general_agent_tools.py +571 -0
  30. solace_agent_mesh/agent/tools/image_tools.py +1184 -0
  31. solace_agent_mesh/agent/tools/peer_agent_tool.py +290 -0
  32. solace_agent_mesh/agent/tools/registry.py +36 -0
  33. solace_agent_mesh/agent/tools/test_tools.py +135 -0
  34. solace_agent_mesh/agent/tools/tool_definition.py +45 -0
  35. solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
  36. solace_agent_mesh/agent/tools/web_tools.py +381 -0
  37. solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
  38. solace_agent_mesh/agent/utils/config_parser.py +47 -0
  39. solace_agent_mesh/agent/utils/context_helpers.py +60 -0
  40. solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
  41. solace_agent_mesh/assets/docs/404.html +16 -0
  42. solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
  43. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  44. solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
  45. solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
  46. solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  54. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  55. solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
  56. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
  57. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
  58. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.a8c5ce5a.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  95. solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
  96. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  101. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  102. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
  104. solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
  105. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/f897a61a.f8c53b0f.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
  131. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +2 -0
  132. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js.LICENSE.txt +81 -0
  133. solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +1 -0
  134. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
  135. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
  137. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
  138. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
  139. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
  140. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +77 -0
  141. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
  142. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
  143. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
  146. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
  147. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
  148. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
  149. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
  150. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
  151. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
  152. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
  153. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
  154. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
  155. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
  156. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
  157. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
  158. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
  159. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
  160. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
  161. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
  162. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
  163. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
  164. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
  165. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
  166. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
  167. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  168. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  169. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  170. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  171. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  172. solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +1 -0
  173. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  174. solace_agent_mesh/assets/docs/search-doc-1754075282978.json +1 -0
  175. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  176. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  177. solace_agent_mesh/cli/__init__.py +1 -1
  178. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  179. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  180. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
  181. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  182. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
  183. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
  184. solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
  185. solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
  186. solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
  187. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  188. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  189. solace_agent_mesh/cli/commands/init_cmd/env_step.py +205 -0
  190. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  191. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +407 -0
  192. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  193. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
  194. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
  195. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
  196. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
  197. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  198. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +139 -0
  199. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
  200. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
  201. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  202. solace_agent_mesh/cli/commands/run_cmd.py +158 -0
  203. solace_agent_mesh/cli/main.py +17 -294
  204. solace_agent_mesh/cli/utils.py +135 -204
  205. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
  206. solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
  207. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  208. solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +1 -0
  209. solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +663 -0
  210. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
  211. solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
  212. solace_agent_mesh/common/__init__.py +1 -0
  213. solace_agent_mesh/common/a2a_protocol.py +564 -0
  214. solace_agent_mesh/common/agent_registry.py +42 -0
  215. solace_agent_mesh/common/client/__init__.py +4 -0
  216. solace_agent_mesh/common/client/card_resolver.py +21 -0
  217. solace_agent_mesh/common/client/client.py +85 -0
  218. solace_agent_mesh/common/client/client_llm.txt +133 -0
  219. solace_agent_mesh/common/common_llm.txt +144 -0
  220. solace_agent_mesh/common/constants.py +1 -14
  221. solace_agent_mesh/common/middleware/__init__.py +12 -0
  222. solace_agent_mesh/common/middleware/config_resolver.py +130 -0
  223. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  224. solace_agent_mesh/common/middleware/registry.py +125 -0
  225. solace_agent_mesh/common/server/__init__.py +4 -0
  226. solace_agent_mesh/common/server/server.py +122 -0
  227. solace_agent_mesh/common/server/server_llm.txt +169 -0
  228. solace_agent_mesh/common/server/task_manager.py +291 -0
  229. solace_agent_mesh/common/server/utils.py +28 -0
  230. solace_agent_mesh/common/services/__init__.py +4 -0
  231. solace_agent_mesh/common/services/employee_service.py +162 -0
  232. solace_agent_mesh/common/services/identity_service.py +129 -0
  233. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  234. solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
  235. solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
  236. solace_agent_mesh/common/services/services_llm.txt +132 -0
  237. solace_agent_mesh/common/types.py +411 -0
  238. solace_agent_mesh/common/utils/__init__.py +7 -0
  239. solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
  240. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  241. solace_agent_mesh/common/utils/embeds/constants.py +55 -0
  242. solace_agent_mesh/common/utils/embeds/converter.py +452 -0
  243. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
  244. solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
  245. solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
  246. solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
  247. solace_agent_mesh/common/utils/embeds/types.py +14 -0
  248. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  249. solace_agent_mesh/common/utils/initializer.py +51 -0
  250. solace_agent_mesh/common/utils/log_formatters.py +44 -0
  251. solace_agent_mesh/common/utils/mime_helpers.py +106 -0
  252. solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
  253. solace_agent_mesh/common/utils/utils_llm.txt +67 -0
  254. solace_agent_mesh/config_portal/backend/common.py +66 -24
  255. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
  256. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  257. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +164 -0
  258. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
  259. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
  260. solace_agent_mesh/config_portal/backend/server.py +551 -181
  261. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-_7yox_eh.js +48 -0
  262. solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
  263. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
  264. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
  265. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-e5c3acfe.js +1 -0
  266. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
  267. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
  268. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  269. solace_agent_mesh/core_a2a/__init__.py +1 -0
  270. solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
  271. solace_agent_mesh/core_a2a/service.py +331 -0
  272. solace_agent_mesh/evaluation/config_loader.py +657 -0
  273. solace_agent_mesh/evaluation/evaluator.py +667 -0
  274. solace_agent_mesh/evaluation/message_organizer.py +568 -0
  275. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  276. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  277. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  278. solace_agent_mesh/evaluation/report/modal.html +59 -0
  279. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  280. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  281. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  282. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  283. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  284. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  285. solace_agent_mesh/evaluation/report_data_processor.py +972 -0
  286. solace_agent_mesh/evaluation/report_generator.py +613 -0
  287. solace_agent_mesh/evaluation/run.py +613 -0
  288. solace_agent_mesh/evaluation/subscriber.py +872 -0
  289. solace_agent_mesh/evaluation/summary_builder.py +775 -0
  290. solace_agent_mesh/evaluation/test_case_loader.py +714 -0
  291. solace_agent_mesh/gateway/base/__init__.py +1 -0
  292. solace_agent_mesh/gateway/base/app.py +266 -0
  293. solace_agent_mesh/gateway/base/base_llm.txt +119 -0
  294. solace_agent_mesh/gateway/base/component.py +1542 -0
  295. solace_agent_mesh/gateway/base/task_context.py +74 -0
  296. solace_agent_mesh/gateway/gateway_llm.txt +125 -0
  297. solace_agent_mesh/gateway/http_sse/app.py +190 -0
  298. solace_agent_mesh/gateway/http_sse/component.py +1602 -0
  299. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  300. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
  301. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
  302. solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
  303. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
  304. solace_agent_mesh/gateway/http_sse/main.py +442 -0
  305. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  306. solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
  307. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +827 -0
  308. solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
  309. solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
  310. solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
  311. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
  312. solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
  313. solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
  314. solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
  315. solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
  316. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
  317. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  318. solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
  319. solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
  320. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
  321. solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
  322. solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
  323. solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
  324. solace_agent_mesh/llm.txt +228 -0
  325. solace_agent_mesh/llm_detail.txt +2835 -0
  326. solace_agent_mesh/templates/agent_template.yaml +53 -0
  327. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  328. solace_agent_mesh/templates/gateway_app_template.py +73 -0
  329. solace_agent_mesh/templates/gateway_component_template.py +431 -0
  330. solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
  331. solace_agent_mesh/templates/logging_config_template.ini +64 -0
  332. solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
  333. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  334. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  335. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  336. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
  337. solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
  338. solace_agent_mesh/templates/plugin_readme_template.md +34 -0
  339. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  340. solace_agent_mesh/templates/shared_config.yaml +66 -0
  341. solace_agent_mesh/templates/templates_llm.txt +147 -0
  342. solace_agent_mesh/templates/webui.yaml +53 -0
  343. solace_agent_mesh-1.0.2.dist-info/METADATA +432 -0
  344. solace_agent_mesh-1.0.2.dist-info/RECORD +361 -0
  345. solace_agent_mesh-1.0.2.dist-info/entry_points.txt +3 -0
  346. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/licenses/LICENSE +1 -1
  347. solace_agent_mesh/agents/base_agent_component.py +0 -256
  348. solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
  349. solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
  350. solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
  351. solace_agent_mesh/agents/global/actions/create_file.py +0 -70
  352. solace_agent_mesh/agents/global/actions/error_action.py +0 -45
  353. solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
  354. solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
  355. solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
  356. solace_agent_mesh/agents/global/global_agent_component.py +0 -38
  357. solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
  358. solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
  359. solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
  360. solace_agent_mesh/agents/slack/__init__.py +0 -1
  361. solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
  362. solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
  363. solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
  364. solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
  365. solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
  366. solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
  367. solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
  368. solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
  369. solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
  370. solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
  371. solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
  372. solace_agent_mesh/assets/web-visualizer/index.html +0 -14
  373. solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
  374. solace_agent_mesh/cli/commands/add/__init__.py +0 -3
  375. solace_agent_mesh/cli/commands/add/add.py +0 -88
  376. solace_agent_mesh/cli/commands/add/agent.py +0 -110
  377. solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
  378. solace_agent_mesh/cli/commands/add/gateway.py +0 -374
  379. solace_agent_mesh/cli/commands/build.py +0 -670
  380. solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
  381. solace_agent_mesh/cli/commands/chat/chat.py +0 -361
  382. solace_agent_mesh/cli/commands/config.py +0 -29
  383. solace_agent_mesh/cli/commands/init/__init__.py +0 -3
  384. solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
  385. solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
  386. solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
  387. solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
  388. solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
  389. solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
  390. solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
  391. solace_agent_mesh/cli/commands/init/init.py +0 -92
  392. solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
  393. solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
  394. solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
  395. solace_agent_mesh/cli/commands/plugin/add.py +0 -100
  396. solace_agent_mesh/cli/commands/plugin/build.py +0 -268
  397. solace_agent_mesh/cli/commands/plugin/create.py +0 -117
  398. solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
  399. solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
  400. solace_agent_mesh/cli/commands/run.py +0 -68
  401. solace_agent_mesh/cli/commands/visualizer.py +0 -138
  402. solace_agent_mesh/cli/config.py +0 -85
  403. solace_agent_mesh/common/action.py +0 -91
  404. solace_agent_mesh/common/action_list.py +0 -37
  405. solace_agent_mesh/common/action_response.py +0 -340
  406. solace_agent_mesh/common/mysql_database.py +0 -40
  407. solace_agent_mesh/common/postgres_database.py +0 -85
  408. solace_agent_mesh/common/prompt_templates.py +0 -28
  409. solace_agent_mesh/common/stimulus_utils.py +0 -152
  410. solace_agent_mesh/common/time.py +0 -24
  411. solace_agent_mesh/common/utils.py +0 -712
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-a-zJ6rLx.js +0 -46
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
  414. solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
  415. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-44c41103.js +0 -1
  416. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
  417. solace_agent_mesh/configs/agent_global.yaml +0 -74
  418. solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
  419. solace_agent_mesh/configs/agent_slack.yaml +0 -64
  420. solace_agent_mesh/configs/agent_web_request.yaml +0 -75
  421. solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
  422. solace_agent_mesh/configs/error_catcher.yaml +0 -56
  423. solace_agent_mesh/configs/monitor.yaml +0 -0
  424. solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
  425. solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
  426. solace_agent_mesh/configs/orchestrator.yaml +0 -241
  427. solace_agent_mesh/configs/service_embedding.yaml +0 -81
  428. solace_agent_mesh/configs/service_llm.yaml +0 -265
  429. solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
  430. solace_agent_mesh/gateway/components/gateway_base.py +0 -47
  431. solace_agent_mesh/gateway/components/gateway_input.py +0 -278
  432. solace_agent_mesh/gateway/components/gateway_output.py +0 -298
  433. solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
  434. solace_agent_mesh/gateway/identity/identity_base.py +0 -10
  435. solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
  436. solace_agent_mesh/gateway/identity/no_identity.py +0 -9
  437. solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
  438. solace_agent_mesh/monitors/base_monitor_component.py +0 -26
  439. solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
  440. solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
  441. solace_agent_mesh/orchestrator/__init__.py +0 -0
  442. solace_agent_mesh/orchestrator/action_manager.py +0 -237
  443. solace_agent_mesh/orchestrator/components/__init__.py +0 -0
  444. solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
  445. solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
  446. solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
  447. solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
  448. solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
  449. solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
  450. solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
  451. solace_agent_mesh/services/__init__.py +0 -0
  452. solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
  453. solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
  454. solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
  455. solace_agent_mesh/services/common/__init__.py +0 -4
  456. solace_agent_mesh/services/common/auto_expiry.py +0 -45
  457. solace_agent_mesh/services/common/singleton.py +0 -18
  458. solace_agent_mesh/services/file_service/__init__.py +0 -14
  459. solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
  460. solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
  461. solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
  462. solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
  463. solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
  464. solace_agent_mesh/services/file_service/file_service.py +0 -437
  465. solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
  466. solace_agent_mesh/services/file_service/file_transformations.py +0 -141
  467. solace_agent_mesh/services/file_service/file_utils.py +0 -324
  468. solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
  469. solace_agent_mesh/services/history_service/__init__.py +0 -3
  470. solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
  471. solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
  472. solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
  473. solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
  474. solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
  475. solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
  476. solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
  477. solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
  478. solace_agent_mesh/services/history_service/history_service.py +0 -413
  479. solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
  480. solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
  481. solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
  482. solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
  483. solace_agent_mesh/services/middleware_service/__init__.py +0 -0
  484. solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
  485. solace_agent_mesh/templates/action.py +0 -38
  486. solace_agent_mesh/templates/agent.py +0 -29
  487. solace_agent_mesh/templates/agent.yaml +0 -70
  488. solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
  489. solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
  490. solace_agent_mesh/templates/gateway-flows.yaml +0 -78
  491. solace_agent_mesh/templates/gateway-header.yaml +0 -16
  492. solace_agent_mesh/templates/gateway_base.py +0 -15
  493. solace_agent_mesh/templates/gateway_input.py +0 -98
  494. solace_agent_mesh/templates/gateway_output.py +0 -71
  495. solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
  496. solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
  497. solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
  498. solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
  499. solace_agent_mesh/templates/slack-default-config.yaml +0 -16
  500. solace_agent_mesh/templates/slack-flows.yaml +0 -81
  501. solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
  502. solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
  503. solace_agent_mesh/templates/web-default-config.yaml +0 -10
  504. solace_agent_mesh/templates/web-flows.yaml +0 -76
  505. solace_agent_mesh/tools/__init__.py +0 -0
  506. solace_agent_mesh/tools/components/__init__.py +0 -0
  507. solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
  508. solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
  509. solace_agent_mesh/tools/config/runtime_config.py +0 -26
  510. solace_agent_mesh-0.2.4.dist-info/METADATA +0 -176
  511. solace_agent_mesh-0.2.4.dist-info/RECORD +0 -193
  512. solace_agent_mesh-0.2.4.dist-info/entry_points.txt +0 -3
  513. /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
  514. /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
  515. /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
  516. /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
  517. /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
  518. /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
  519. /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
  520. /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
  521. {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1184 @@
1
+ """
2
+ Collection of Python tools for image generation, manipulation, and multimodal content analysis.
3
+ Includes tools for image description and audio description using vision and audio APIs.
4
+ """
5
+
6
+ import asyncio
7
+ import base64
8
+ import inspect
9
+ import json
10
+ import os
11
+ import uuid
12
+ from datetime import datetime, timezone
13
+ from typing import Any, Dict, Optional
14
+
15
+ import httpx
16
+ from google.adk.tools import ToolContext
17
+ from solace_ai_connector.common.log import log
18
+
19
+ from ..utils.artifact_helpers import (
20
+ save_artifact_with_metadata,
21
+ DEFAULT_SCHEMA_MAX_KEYS,
22
+ )
23
+ from ..utils.context_helpers import get_original_session_id
24
+
25
+ from google.genai import types as adk_types
26
+ from .tool_definition import BuiltinTool
27
+ from .registry import tool_registry
28
+
29
+
30
+ async def create_image_from_description(
31
+ image_description: str,
32
+ output_filename: Optional[str] = None,
33
+ tool_context: ToolContext = None,
34
+ tool_config: Optional[Dict[str, Any]] = None,
35
+ ) -> Dict[str, Any]:
36
+ """
37
+ Generates an image based on a textual description using LiteLLM and saves it as a PNG artifact.
38
+ Configuration for LiteLLM (model, api_key, etc.) is expected in `tool_config`.
39
+
40
+ Args:
41
+ image_description: The textual prompt to use for image generation.
42
+ output_filename: Optional. The desired filename for the output PNG image.
43
+ If not provided, a unique name like 'generated_image_<uuid>.png' will be used.
44
+ tool_context: The context provided by the ADK framework.
45
+ tool_config: Optional dictionary containing specific configuration for this tool.
46
+
47
+ Returns:
48
+ A dictionary containing:
49
+ - "status": "success" or "error".
50
+ - "message": A descriptive message about the outcome.
51
+ - "output_filename": The name of the saved image artifact (if successful).
52
+ - "output_version": The version of the saved image artifact (if successful).
53
+ - "result_preview": A brief preview message (if successful).
54
+ """
55
+ log_identifier = f"[ImageTools:create_image_from_description]"
56
+ if not tool_context:
57
+ log.error(f"{log_identifier} ToolContext is missing.")
58
+ return {"status": "error", "message": "ToolContext is missing."}
59
+
60
+ try:
61
+ inv_context = tool_context._invocation_context
62
+ if not inv_context:
63
+ raise ValueError("InvocationContext is not available.")
64
+
65
+ app_name = getattr(inv_context, "app_name", None)
66
+ user_id = getattr(inv_context, "user_id", None)
67
+ session_id = get_original_session_id(inv_context)
68
+ artifact_service = getattr(inv_context, "artifact_service", None)
69
+
70
+ if not all([app_name, user_id, session_id, artifact_service]):
71
+ missing_parts = [
72
+ part
73
+ for part, val in [
74
+ ("app_name", app_name),
75
+ ("user_id", user_id),
76
+ ("session_id", session_id),
77
+ ("artifact_service", artifact_service),
78
+ ]
79
+ if not val
80
+ ]
81
+ raise ValueError(
82
+ f"Missing required context parts: {', '.join(missing_parts)}"
83
+ )
84
+
85
+ log.info(f"{log_identifier} Processing request for session {session_id}.")
86
+
87
+ current_tool_config = tool_config if tool_config is not None else {}
88
+
89
+ if not current_tool_config:
90
+ log.warning(
91
+ f"{log_identifier} Tool-specific configuration (tool_config) is empty."
92
+ )
93
+
94
+ model_name = current_tool_config.get("model")
95
+ api_key = current_tool_config.get("api_key")
96
+ api_base = current_tool_config.get("api_base")
97
+ extra_params = current_tool_config.get("extra_params", {})
98
+
99
+ if not model_name:
100
+ raise ValueError("'model' configuration is missing in tool_config.")
101
+ if not api_key:
102
+ raise ValueError("'api_key' configuration is missing in tool_config.")
103
+ if not api_base:
104
+ raise ValueError("'api_base' configuration is missing in tool_config.")
105
+
106
+ if "/" in model_name:
107
+ original_model_name = model_name
108
+ model_name = model_name.split("/", 1)[-1]
109
+ log.debug(
110
+ f"{log_identifier} Original model name '{original_model_name}' processed to '{model_name}' for API call."
111
+ )
112
+
113
+ log.debug(
114
+ f"{log_identifier} Using image generation model: {model_name} via direct API call to: {api_base}"
115
+ )
116
+
117
+ api_url = f"{api_base.rstrip('/')}/v1/images/generations"
118
+ headers = {
119
+ "Content-Type": "application/json",
120
+ "Authorization": f"Bearer {api_key}",
121
+ }
122
+ payload = {"model": model_name, "prompt": image_description, **extra_params}
123
+
124
+ log.debug(
125
+ f"{log_identifier} Calling image generation API with prompt: '{image_description[:100]}...' and payload: {json.dumps(payload)}"
126
+ )
127
+
128
+ try:
129
+ async with httpx.AsyncClient(timeout=60.0) as client:
130
+ http_response = await client.post(
131
+ api_url, headers=headers, json=payload
132
+ )
133
+ http_response.raise_for_status()
134
+ response_data = http_response.json()
135
+ except httpx.HTTPStatusError as hse:
136
+ log.error(
137
+ f"{log_identifier} HTTP error calling image generation API {hse.request.url}: {hse.response.status_code} - {hse.response.text}"
138
+ )
139
+ return {
140
+ "status": "error",
141
+ "message": f"API error generating image: {hse.response.status_code} - {hse.response.text}",
142
+ }
143
+ except httpx.RequestError as re:
144
+ log.error(
145
+ f"{log_identifier} Request error calling image generation API {re.request.url}: {re}"
146
+ )
147
+ return {
148
+ "status": "error",
149
+ "message": f"Request error generating image: {re}",
150
+ }
151
+ except Exception as e:
152
+ log.error(f"{log_identifier} Error calling image generation API: {e}")
153
+ return {"status": "error", "message": f"Error generating image: {e}"}
154
+
155
+ log.debug(f"{log_identifier} Image generation API response received.")
156
+
157
+ if (
158
+ not response_data
159
+ or not response_data.get("data")
160
+ or not response_data["data"][0]
161
+ ):
162
+ log.error(
163
+ f"{log_identifier} API did not return valid image data. Response: {json.dumps(response_data)}"
164
+ )
165
+ raise ValueError("Image generation API did not return valid image data.")
166
+
167
+ image_data_item = response_data["data"][0]
168
+ image_bytes = None
169
+
170
+ if image_data_item.get("url"):
171
+ image_url = image_data_item["url"]
172
+ log.info(f"{log_identifier} Fetching image from URL: {image_url}")
173
+ async with httpx.AsyncClient() as client:
174
+ http_response = await client.get(image_url, timeout=30.0)
175
+ http_response.raise_for_status()
176
+ image_bytes = http_response.content
177
+ log.info(f"{log_identifier} Image fetched successfully from URL.")
178
+ elif image_data_item.get("b64_json"):
179
+ log.info(f"{log_identifier} Decoding image from b64_json.")
180
+ image_bytes = base64.b64decode(image_data_item["b64_json"])
181
+ log.info(f"{log_identifier} Image decoded successfully from b64_json.")
182
+ else:
183
+ raise ValueError(
184
+ "No valid image data (URL or b64_json) found in LiteLLM response."
185
+ )
186
+
187
+ if not image_bytes:
188
+ raise ValueError("Failed to retrieve image bytes.")
189
+
190
+ final_output_filename = ""
191
+ if output_filename:
192
+ if not output_filename.lower().endswith(".png"):
193
+ final_output_filename = f"{output_filename}.png"
194
+ else:
195
+ final_output_filename = output_filename
196
+ else:
197
+ final_output_filename = f"generated_image_{uuid.uuid4()}.png"
198
+ log.debug(
199
+ f"{log_identifier} Determined output filename: {final_output_filename}"
200
+ )
201
+
202
+ output_mime_type = "image/png"
203
+ current_timestamp_iso = datetime.now(timezone.utc).isoformat()
204
+
205
+ metadata_dict = {
206
+ "description": f"Image generated from prompt: {image_description}",
207
+ "source_prompt": image_description,
208
+ "generation_tool": "direct_api",
209
+ "generation_model": model_name,
210
+ "request_timestamp": current_timestamp_iso,
211
+ "original_requested_filename": (
212
+ output_filename if output_filename else "N/A"
213
+ ),
214
+ }
215
+ if extra_params:
216
+ metadata_dict["api_request_params"] = json.dumps(extra_params)
217
+
218
+ log.info(
219
+ f"{log_identifier} Saving artifact '{final_output_filename}' with mime_type '{output_mime_type}'."
220
+ )
221
+ save_result = await save_artifact_with_metadata(
222
+ artifact_service=artifact_service,
223
+ app_name=app_name,
224
+ user_id=user_id,
225
+ session_id=session_id,
226
+ filename=final_output_filename,
227
+ content_bytes=image_bytes,
228
+ mime_type=output_mime_type,
229
+ metadata_dict=metadata_dict,
230
+ timestamp=datetime.now(timezone.utc),
231
+ schema_max_keys=DEFAULT_SCHEMA_MAX_KEYS,
232
+ tool_context=tool_context,
233
+ )
234
+
235
+ if save_result.get("status") == "error":
236
+ raise IOError(
237
+ f"Failed to save image artifact: {save_result.get('message', 'Unknown error')}"
238
+ )
239
+
240
+ log.info(
241
+ f"{log_identifier} Artifact '{final_output_filename}' v{save_result['data_version']} saved successfully."
242
+ )
243
+
244
+ return {
245
+ "status": "success",
246
+ "message": "Image generated and saved successfully.",
247
+ "output_filename": final_output_filename,
248
+ "output_version": save_result["data_version"],
249
+ "result_preview": f"Image '{final_output_filename}' (v{save_result['data_version']}) created from prompt: \"{image_description[:50]}...\"",
250
+ }
251
+
252
+ except ValueError as ve:
253
+ log.error(f"{log_identifier} Value error: {ve}")
254
+ return {"status": "error", "message": str(ve)}
255
+ except httpx.HTTPStatusError as hse:
256
+ log.error(
257
+ f"{log_identifier} HTTP error fetching image from URL {hse.request.url}: {hse.response.status_code} - {hse.response.text}"
258
+ )
259
+ return {
260
+ "status": "error",
261
+ "message": f"HTTP error fetching image: {hse.response.status_code}",
262
+ }
263
+ except httpx.RequestError as re:
264
+ log.error(
265
+ f"{log_identifier} Request error fetching image from URL {re.request.url}: {re}"
266
+ )
267
+ return {"status": "error", "message": f"Request error fetching image: {re}"}
268
+ except IOError as ioe:
269
+ log.error(f"{log_identifier} IO error: {ioe}")
270
+ return {"status": "error", "message": str(ioe)}
271
+ except Exception as e:
272
+ log.exception(
273
+ f"{log_identifier} Unexpected error in create_image_from_description: {e}"
274
+ )
275
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
276
+
277
+
278
+ def _get_image_mime_type(filename: str) -> str:
279
+ """Get MIME type from file extension."""
280
+ ext = os.path.splitext(filename)[1].lower()
281
+ mime_mapping = {
282
+ ".jpg": "image/jpeg",
283
+ ".jpeg": "image/jpeg",
284
+ ".png": "image/png",
285
+ ".webp": "image/webp",
286
+ ".gif": "image/gif",
287
+ }
288
+ return mime_mapping.get(ext, "application/octet-stream")
289
+
290
+
291
+ def _is_supported_image_format(filename: str) -> bool:
292
+ """Check if the image format is supported."""
293
+ ext = os.path.splitext(filename)[1].lower()
294
+ supported_formats = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
295
+ return ext in supported_formats
296
+
297
+
298
+ def _create_data_url(image_bytes: bytes, mime_type: str) -> str:
299
+ """Create base64 data URL from image bytes."""
300
+ base64_data = base64.b64encode(image_bytes).decode("utf-8")
301
+ return f"data:{mime_type};base64,{base64_data}"
302
+
303
+
304
+ async def describe_image(
305
+ image_filename: str,
306
+ prompt: str = "What is in this image?",
307
+ tool_context: ToolContext = None,
308
+ tool_config: Optional[Dict[str, Any]] = None,
309
+ ) -> Dict[str, Any]:
310
+ """
311
+ Describes an image using an OpenAI-compatible vision API.
312
+
313
+ Args:
314
+ image_filename: The filename (and optional :version) of the input image artifact.
315
+ prompt: Custom prompt for image analysis (default: "What is in this image?").
316
+ tool_context: The context provided by the ADK framework.
317
+ tool_config: Configuration dictionary containing model, api_base, api_key.
318
+
319
+ Returns:
320
+ A dictionary containing:
321
+ - "status": "success" or "error".
322
+ - "message": A descriptive message about the outcome.
323
+ - "description": The image description from the API (if successful).
324
+ - "image_filename": The name of the input image artifact (if successful).
325
+ - "image_version": The version of the input image artifact (if successful).
326
+ - "tokens_used": Token usage information from the API (if available).
327
+ """
328
+ log_identifier = f"[ImageTools:describe_image:{image_filename}]"
329
+ if not tool_context:
330
+ log.error(f"{log_identifier} ToolContext is missing.")
331
+ return {"status": "error", "message": "ToolContext is missing."}
332
+
333
+ try:
334
+ inv_context = tool_context._invocation_context
335
+ if not inv_context:
336
+ raise ValueError("InvocationContext is not available.")
337
+
338
+ app_name = getattr(inv_context, "app_name", None)
339
+ user_id = getattr(inv_context, "user_id", None)
340
+ session_id = get_original_session_id(inv_context)
341
+ artifact_service = getattr(inv_context, "artifact_service", None)
342
+
343
+ if not all([app_name, user_id, session_id, artifact_service]):
344
+ missing_parts = [
345
+ part
346
+ for part, val in [
347
+ ("app_name", app_name),
348
+ ("user_id", user_id),
349
+ ("session_id", session_id),
350
+ ("artifact_service", artifact_service),
351
+ ]
352
+ if not val
353
+ ]
354
+ raise ValueError(
355
+ f"Missing required context parts: {', '.join(missing_parts)}"
356
+ )
357
+
358
+ log.info(f"{log_identifier} Processing request for session {session_id}.")
359
+
360
+ current_tool_config = tool_config if tool_config is not None else {}
361
+
362
+ if not current_tool_config:
363
+ log.warning(
364
+ f"{log_identifier} Tool-specific configuration (tool_config) is empty."
365
+ )
366
+
367
+ model_name = current_tool_config.get("model")
368
+ api_key = current_tool_config.get("api_key")
369
+ api_base = current_tool_config.get("api_base")
370
+
371
+ if not model_name:
372
+ raise ValueError("'model' configuration is missing in tool_config.")
373
+ if not api_key:
374
+ raise ValueError("'api_key' configuration is missing in tool_config.")
375
+ if not api_base:
376
+ raise ValueError("'api_base' configuration is missing in tool_config.")
377
+
378
+ log.debug(f"{log_identifier} Using model: {model_name}, API base: {api_base}")
379
+
380
+ parts = image_filename.rsplit(":", 1)
381
+ filename_base_for_load = parts[0]
382
+ version_str = parts[1] if len(parts) > 1 else None
383
+ version_to_load = int(version_str) if version_str else None
384
+
385
+ if not _is_supported_image_format(filename_base_for_load):
386
+ raise ValueError(
387
+ f"Unsupported image format. Supported formats: .png, .jpg, .jpeg, .webp, .gif"
388
+ )
389
+
390
+ if version_to_load is None:
391
+ list_versions_method = getattr(artifact_service, "list_versions")
392
+ if inspect.iscoroutinefunction(list_versions_method):
393
+ versions = await list_versions_method(
394
+ app_name=app_name,
395
+ user_id=user_id,
396
+ session_id=session_id,
397
+ filename=filename_base_for_load,
398
+ )
399
+ else:
400
+ versions = await asyncio.to_thread(
401
+ list_versions_method,
402
+ app_name=app_name,
403
+ user_id=user_id,
404
+ session_id=session_id,
405
+ filename=filename_base_for_load,
406
+ )
407
+ if not versions:
408
+ raise FileNotFoundError(
409
+ f"Image artifact '{filename_base_for_load}' not found."
410
+ )
411
+ version_to_load = max(versions)
412
+ log.debug(
413
+ f"{log_identifier} Using latest version for input: {version_to_load}"
414
+ )
415
+
416
+ load_artifact_method = getattr(artifact_service, "load_artifact")
417
+ if inspect.iscoroutinefunction(load_artifact_method):
418
+ image_artifact_part = await load_artifact_method(
419
+ app_name=app_name,
420
+ user_id=user_id,
421
+ session_id=session_id,
422
+ filename=filename_base_for_load,
423
+ version=version_to_load,
424
+ )
425
+ else:
426
+ image_artifact_part = await asyncio.to_thread(
427
+ load_artifact_method,
428
+ app_name=app_name,
429
+ user_id=user_id,
430
+ session_id=session_id,
431
+ filename=filename_base_for_load,
432
+ version=version_to_load,
433
+ )
434
+
435
+ if not image_artifact_part or not image_artifact_part.inline_data:
436
+ raise FileNotFoundError(
437
+ f"Content for image artifact '{filename_base_for_load}' v{version_to_load} not found."
438
+ )
439
+
440
+ image_bytes = image_artifact_part.inline_data.data
441
+ log.debug(f"{log_identifier} Loaded image artifact: {len(image_bytes)} bytes")
442
+
443
+ mime_type = _get_image_mime_type(filename_base_for_load)
444
+ data_url = _create_data_url(image_bytes, mime_type)
445
+ log.debug(f"{log_identifier} Created data URL with MIME type: {mime_type}")
446
+
447
+ api_url = f"{api_base.rstrip('/')}/v1/chat/completions"
448
+ headers = {
449
+ "Content-Type": "application/json",
450
+ "Authorization": f"Bearer {api_key}",
451
+ }
452
+
453
+ request_data = {
454
+ "model": model_name,
455
+ "messages": [
456
+ {
457
+ "role": "user",
458
+ "content": [
459
+ {"type": "text", "text": prompt},
460
+ {"type": "image_url", "image_url": {"url": data_url}},
461
+ ],
462
+ }
463
+ ],
464
+ }
465
+
466
+ log.debug(
467
+ f"{log_identifier} Calling vision API with prompt: '{prompt[:100]}...'"
468
+ )
469
+
470
+ async with httpx.AsyncClient(timeout=60.0) as client:
471
+ response = await client.post(api_url, headers=headers, json=request_data)
472
+ response.raise_for_status()
473
+ response_data = response.json()
474
+ log.debug(f"{log_identifier} Vision API response received.")
475
+
476
+ if not response_data.get("choices") or not response_data["choices"]:
477
+ raise ValueError("API response does not contain valid choices.")
478
+
479
+ choice = response_data["choices"][0]
480
+ if not choice.get("message") or not choice["message"].get("content"):
481
+ raise ValueError("API response does not contain valid message content.")
482
+
483
+ description = choice["message"]["content"]
484
+
485
+ tokens_used = response_data.get("usage", {})
486
+
487
+ log.info(
488
+ f"{log_identifier} Image described successfully. Description length: {len(description)} characters"
489
+ )
490
+
491
+ return {
492
+ "status": "success",
493
+ "message": "Image described successfully",
494
+ "description": description,
495
+ "image_filename": filename_base_for_load,
496
+ "image_version": version_to_load,
497
+ "tokens_used": tokens_used,
498
+ }
499
+
500
+ except FileNotFoundError as e:
501
+ log.warning(f"{log_identifier} File not found error: {e}")
502
+ return {"status": "error", "message": str(e)}
503
+ except ValueError as ve:
504
+ log.error(f"{log_identifier} Value error: {ve}")
505
+ return {"status": "error", "message": str(ve)}
506
+ except httpx.HTTPStatusError as hse:
507
+ log.error(
508
+ f"{log_identifier} HTTP error calling vision API: {hse.response.status_code} - {hse.response.text}"
509
+ )
510
+ return {"status": "error", "message": f"API error: {hse.response.status_code}"}
511
+ except httpx.RequestError as re:
512
+ log.error(f"{log_identifier} Request error calling vision API: {re}")
513
+ return {"status": "error", "message": f"Request error: {re}"}
514
+ except json.JSONDecodeError as jde:
515
+ log.error(f"{log_identifier} JSON decode error: {jde}")
516
+ return {"status": "error", "message": "Invalid JSON response from API"}
517
+ except Exception as e:
518
+ log.exception(f"{log_identifier} Unexpected error in describe_image: {e}")
519
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
520
+
521
+
522
+ def _get_audio_format(filename: str) -> str:
523
+ """Get audio format from file extension."""
524
+ ext = os.path.splitext(filename)[1].lower()
525
+ format_mapping = {".wav": "wav", ".mp3": "mp3"}
526
+ return format_mapping.get(ext, "wav")
527
+
528
+
529
+ def _is_supported_audio_format(filename: str) -> bool:
530
+ """Check if the audio format is supported."""
531
+ ext = os.path.splitext(filename)[1].lower()
532
+ supported_formats = {".wav", ".mp3"}
533
+ return ext in supported_formats
534
+
535
+
536
+ def _encode_audio_to_base64(audio_bytes: bytes) -> str:
537
+ """Encode audio bytes to base64 string."""
538
+ return base64.b64encode(audio_bytes).decode("utf-8")
539
+
540
+
541
+ async def describe_audio(
542
+ audio_filename: str,
543
+ prompt: str = "What is in this recording?",
544
+ tool_context: ToolContext = None,
545
+ tool_config: Optional[Dict[str, Any]] = None,
546
+ ) -> Dict[str, Any]:
547
+ """
548
+ Describes an audio recording using an OpenAI-compatible audio API.
549
+
550
+ Args:
551
+ audio_filename: The filename (and optional :version) of the input audio artifact.
552
+ prompt: Custom prompt for audio analysis (default: "What is in this recording?").
553
+ tool_context: The context provided by the ADK framework.
554
+ tool_config: Configuration dictionary containing model, api_base, api_key.
555
+
556
+ Returns:
557
+ A dictionary containing:
558
+ - "status": "success" or "error".
559
+ - "message": A descriptive message about the outcome.
560
+ - "description": The audio description from the API (if successful).
561
+ - "audio_filename": The name of the input audio artifact (if successful).
562
+ - "audio_version": The version of the input audio artifact (if successful).
563
+ - "tokens_used": Token usage information from the API (if available).
564
+ """
565
+ log_identifier = f"[ImageTools:describe_audio:{audio_filename}]"
566
+ if not tool_context:
567
+ log.error(f"{log_identifier} ToolContext is missing.")
568
+ return {"status": "error", "message": "ToolContext is missing."}
569
+
570
+ try:
571
+ inv_context = tool_context._invocation_context
572
+ if not inv_context:
573
+ raise ValueError("InvocationContext is not available.")
574
+
575
+ app_name = getattr(inv_context, "app_name", None)
576
+ user_id = getattr(inv_context, "user_id", None)
577
+ session_id = get_original_session_id(inv_context)
578
+ artifact_service = getattr(inv_context, "artifact_service", None)
579
+
580
+ if not all([app_name, user_id, session_id, artifact_service]):
581
+ missing_parts = [
582
+ part
583
+ for part, val in [
584
+ ("app_name", app_name),
585
+ ("user_id", user_id),
586
+ ("session_id", session_id),
587
+ ("artifact_service", artifact_service),
588
+ ]
589
+ if not val
590
+ ]
591
+ raise ValueError(
592
+ f"Missing required context parts: {', '.join(missing_parts)}"
593
+ )
594
+
595
+ log.info(f"{log_identifier} Processing request for session {session_id}.")
596
+
597
+ current_tool_config = tool_config if tool_config is not None else {}
598
+
599
+ if not current_tool_config:
600
+ log.warning(
601
+ f"{log_identifier} Tool-specific configuration (tool_config) is empty."
602
+ )
603
+
604
+ model_name = current_tool_config.get("model")
605
+ api_key = current_tool_config.get("api_key")
606
+ api_base = current_tool_config.get("api_base")
607
+
608
+ if not model_name:
609
+ raise ValueError("'model' configuration is missing in tool_config.")
610
+ if not api_key:
611
+ raise ValueError("'api_key' configuration is missing in tool_config.")
612
+ if not api_base:
613
+ raise ValueError("'api_base' configuration is missing in tool_config.")
614
+
615
+ log.debug(f"{log_identifier} Using model: {model_name}, API base: {api_base}")
616
+
617
+ parts = audio_filename.rsplit(":", 1)
618
+ filename_base_for_load = parts[0]
619
+ version_str = parts[1] if len(parts) > 1 else None
620
+ version_to_load = int(version_str) if version_str else None
621
+
622
+ if not _is_supported_audio_format(filename_base_for_load):
623
+ raise ValueError(f"Unsupported audio format. Supported formats: .wav, .mp3")
624
+
625
+ if version_to_load is None:
626
+ list_versions_method = getattr(artifact_service, "list_versions")
627
+ if inspect.iscoroutinefunction(list_versions_method):
628
+ versions = await list_versions_method(
629
+ app_name=app_name,
630
+ user_id=user_id,
631
+ session_id=session_id,
632
+ filename=filename_base_for_load,
633
+ )
634
+ else:
635
+ versions = await asyncio.to_thread(
636
+ list_versions_method,
637
+ app_name=app_name,
638
+ user_id=user_id,
639
+ session_id=session_id,
640
+ filename=filename_base_for_load,
641
+ )
642
+ if not versions:
643
+ raise FileNotFoundError(
644
+ f"Audio artifact '{filename_base_for_load}' not found."
645
+ )
646
+ version_to_load = max(versions)
647
+ log.debug(
648
+ f"{log_identifier} Using latest version for input: {version_to_load}"
649
+ )
650
+
651
+ load_artifact_method = getattr(artifact_service, "load_artifact")
652
+ if inspect.iscoroutinefunction(load_artifact_method):
653
+ audio_artifact_part = await load_artifact_method(
654
+ app_name=app_name,
655
+ user_id=user_id,
656
+ session_id=session_id,
657
+ filename=filename_base_for_load,
658
+ version=version_to_load,
659
+ )
660
+ else:
661
+ audio_artifact_part = await asyncio.to_thread(
662
+ load_artifact_method,
663
+ app_name=app_name,
664
+ user_id=user_id,
665
+ session_id=session_id,
666
+ filename=filename_base_for_load,
667
+ version=version_to_load,
668
+ )
669
+
670
+ if not audio_artifact_part or not audio_artifact_part.inline_data:
671
+ raise FileNotFoundError(
672
+ f"Content for audio artifact '{filename_base_for_load}' v{version_to_load} not found."
673
+ )
674
+
675
+ audio_bytes = audio_artifact_part.inline_data.data
676
+ log.debug(f"{log_identifier} Loaded audio artifact: {len(audio_bytes)} bytes")
677
+
678
+ audio_format = _get_audio_format(filename_base_for_load)
679
+ base64_audio = _encode_audio_to_base64(audio_bytes)
680
+ log.debug(
681
+ f"{log_identifier} Encoded audio to base64 with format: {audio_format}"
682
+ )
683
+
684
+ api_url = f"{api_base.rstrip('/')}/v1/chat/completions"
685
+ headers = {
686
+ "Content-Type": "application/json",
687
+ "Authorization": f"Bearer {api_key}",
688
+ }
689
+
690
+ request_data = {
691
+ "model": model_name,
692
+ "modalities": ["audio", "text"],
693
+ "audio": {"voice": "alloy", "format": audio_format},
694
+ "messages": [
695
+ {
696
+ "role": "user",
697
+ "content": [
698
+ {"type": "text", "text": prompt},
699
+ {
700
+ "type": "input_audio",
701
+ "input_audio": {
702
+ "data": base64_audio,
703
+ "format": audio_format,
704
+ },
705
+ },
706
+ ],
707
+ }
708
+ ],
709
+ }
710
+
711
+ log.debug(
712
+ f"{log_identifier} Calling audio API with prompt: '{prompt[:100]}...'"
713
+ )
714
+
715
+ async with httpx.AsyncClient(timeout=60.0) as client:
716
+ response = await client.post(api_url, headers=headers, json=request_data)
717
+ response.raise_for_status()
718
+ response_data = response.json()
719
+
720
+ log.debug(f"{log_identifier} Audio API response received.")
721
+
722
+ if not response_data.get("choices") or not response_data["choices"]:
723
+ raise ValueError("API response does not contain valid choices.")
724
+
725
+ choice = response_data["choices"][0]
726
+ if not choice.get("message") or not choice["message"].get("content"):
727
+ raise ValueError("API response does not contain valid message content.")
728
+
729
+ description = choice["message"]["content"]
730
+
731
+ tokens_used = response_data.get("usage", {})
732
+
733
+ log.info(
734
+ f"{log_identifier} Audio described successfully. Description length: {len(description)} characters"
735
+ )
736
+
737
+ return {
738
+ "status": "success",
739
+ "message": "Audio described successfully",
740
+ "description": description,
741
+ "audio_filename": filename_base_for_load,
742
+ "audio_version": version_to_load,
743
+ "tokens_used": tokens_used,
744
+ }
745
+
746
+ except FileNotFoundError as e:
747
+ log.warning(f"{log_identifier} File not found error: {e}")
748
+ return {"status": "error", "message": str(e)}
749
+ except ValueError as ve:
750
+ log.error(f"{log_identifier} Value error: {ve}")
751
+ return {"status": "error", "message": str(ve)}
752
+ except httpx.HTTPStatusError as hse:
753
+ log.error(
754
+ f"{log_identifier} HTTP error calling audio API: {hse.response.status_code} - {hse.response.text}"
755
+ )
756
+ return {"status": "error", "message": f"API error: {hse.response.status_code}"}
757
+ except httpx.RequestError as re:
758
+ log.error(f"{log_identifier} Request error calling audio API: {re}")
759
+ return {"status": "error", "message": f"Request error: {re}"}
760
+ except json.JSONDecodeError as jde:
761
+ log.error(f"{log_identifier} JSON decode error: {jde}")
762
+ return {"status": "error", "message": "Invalid JSON response from API"}
763
+ except Exception as e:
764
+ log.exception(f"{log_identifier} Unexpected error in describe_audio: {e}")
765
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
766
+
767
+
768
+ async def edit_image_with_gemini(
769
+ image_filename: str,
770
+ edit_prompt: str,
771
+ output_filename: Optional[str] = None,
772
+ tool_context: ToolContext = None,
773
+ tool_config: Optional[Dict[str, Any]] = None,
774
+ ) -> Dict[str, Any]:
775
+ """
776
+ Edits an existing image based on a text prompt using Google's Gemini 2.0 Flash Preview Image Generation model.
777
+
778
+ Args:
779
+ image_filename: The filename (and optional :version) of the input image artifact.
780
+ edit_prompt: Text description of the desired edits to apply to the image.
781
+ output_filename: Optional. The desired filename for the output edited image.
782
+ If not provided, a unique name like 'edited_image_<uuid>.jpg' will be used.
783
+ tool_context: The context provided by the ADK framework.
784
+ tool_config: Configuration dictionary containing gemini_api_key and model.
785
+
786
+ Returns:
787
+ A dictionary containing:
788
+ - "status": "success" or "error".
789
+ - "message": A descriptive message about the outcome.
790
+ - "output_filename": The name of the saved edited image artifact (if successful).
791
+ - "output_version": The version of the saved edited image artifact (if successful).
792
+ - "result_preview": A brief preview message (if successful).
793
+ - "original_filename": The name of the input image artifact (if successful).
794
+ - "original_version": The version of the input image artifact (if successful).
795
+ """
796
+ log_identifier = f"[ImageTools:edit_image_with_gemini:{image_filename}]"
797
+ if not tool_context:
798
+ log.error(f"{log_identifier} ToolContext is missing.")
799
+ return {"status": "error", "message": "ToolContext is missing."}
800
+
801
+ try:
802
+ try:
803
+ from google import genai
804
+ from google.genai import types
805
+ from PIL import Image as PILImage
806
+ from io import BytesIO
807
+ except ImportError as ie:
808
+ log.error(f"{log_identifier} Required dependencies not available: {ie}")
809
+ return {
810
+ "status": "error",
811
+ "message": f"Required dependencies not available: {ie}",
812
+ }
813
+
814
+ inv_context = tool_context._invocation_context
815
+ if not inv_context:
816
+ raise ValueError("InvocationContext is not available.")
817
+
818
+ app_name = getattr(inv_context, "app_name", None)
819
+ user_id = getattr(inv_context, "user_id", None)
820
+ session_id = get_original_session_id(inv_context)
821
+ artifact_service = getattr(inv_context, "artifact_service", None)
822
+
823
+ if not all([app_name, user_id, session_id, artifact_service]):
824
+ missing_parts = [
825
+ part
826
+ for part, val in [
827
+ ("app_name", app_name),
828
+ ("user_id", user_id),
829
+ ("session_id", session_id),
830
+ ("artifact_service", artifact_service),
831
+ ]
832
+ if not val
833
+ ]
834
+ raise ValueError(
835
+ f"Missing required context parts: {', '.join(missing_parts)}"
836
+ )
837
+
838
+ log.info(
839
+ f"{log_identifier} Processing image edit request for session {session_id}."
840
+ )
841
+
842
+ current_tool_config = tool_config if tool_config is not None else {}
843
+
844
+ if not current_tool_config:
845
+ log.warning(
846
+ f"{log_identifier} Tool-specific configuration (tool_config) is empty."
847
+ )
848
+
849
+ gemini_api_key = current_tool_config.get("gemini_api_key")
850
+ model_name = current_tool_config.get(
851
+ "model", "gemini-2.0-flash-preview-image-generation"
852
+ )
853
+
854
+ if not gemini_api_key:
855
+ raise ValueError(
856
+ "'gemini_api_key' configuration is missing in tool_config."
857
+ )
858
+
859
+ log.debug(f"{log_identifier} Using Gemini model: {model_name}")
860
+
861
+ parts = image_filename.rsplit(":", 1)
862
+ filename_base_for_load = parts[0]
863
+ version_str = parts[1] if len(parts) > 1 else None
864
+ version_to_load = int(version_str) if version_str else None
865
+
866
+ if not _is_supported_image_format(filename_base_for_load):
867
+ raise ValueError(
868
+ f"Unsupported image format. Supported formats: .png, .jpg, .jpeg, .webp, .gif"
869
+ )
870
+
871
+ if version_to_load is None:
872
+ list_versions_method = getattr(artifact_service, "list_versions")
873
+ if inspect.iscoroutinefunction(list_versions_method):
874
+ versions = await list_versions_method(
875
+ app_name=app_name,
876
+ user_id=user_id,
877
+ session_id=session_id,
878
+ filename=filename_base_for_load,
879
+ )
880
+ else:
881
+ versions = await asyncio.to_thread(
882
+ list_versions_method,
883
+ app_name=app_name,
884
+ user_id=user_id,
885
+ session_id=session_id,
886
+ filename=filename_base_for_load,
887
+ )
888
+ if not versions:
889
+ raise FileNotFoundError(
890
+ f"Image artifact '{filename_base_for_load}' not found."
891
+ )
892
+ version_to_load = max(versions)
893
+ log.debug(
894
+ f"{log_identifier} Using latest version for input: {version_to_load}"
895
+ )
896
+
897
+ load_artifact_method = getattr(artifact_service, "load_artifact")
898
+ if inspect.iscoroutinefunction(load_artifact_method):
899
+ image_artifact_part = await load_artifact_method(
900
+ app_name=app_name,
901
+ user_id=user_id,
902
+ session_id=session_id,
903
+ filename=filename_base_for_load,
904
+ version=version_to_load,
905
+ )
906
+ else:
907
+ image_artifact_part = await asyncio.to_thread(
908
+ load_artifact_method,
909
+ app_name=app_name,
910
+ user_id=user_id,
911
+ session_id=session_id,
912
+ filename=filename_base_for_load,
913
+ version=version_to_load,
914
+ )
915
+
916
+ if not image_artifact_part or not image_artifact_part.inline_data:
917
+ raise FileNotFoundError(
918
+ f"Content for image artifact '{filename_base_for_load}' v{version_to_load} not found."
919
+ )
920
+
921
+ image_bytes = image_artifact_part.inline_data.data
922
+ log.debug(f"{log_identifier} Loaded image artifact: {len(image_bytes)} bytes")
923
+
924
+ try:
925
+ from PIL import UnidentifiedImageError
926
+
927
+ pil_image = PILImage.open(BytesIO(image_bytes))
928
+ log.debug(
929
+ f"{log_identifier} Converted to PIL Image: {pil_image.size}, mode: {pil_image.mode}"
930
+ )
931
+ except UnidentifiedImageError as e:
932
+ log.error(f"{log_identifier} Unidentified image error: {e}")
933
+ raise ValueError(f"Cannot identify image file: {e}")
934
+ except IOError as e:
935
+ log.error(f"{log_identifier} IO error: {e}")
936
+ raise ValueError(f"Cannot identify image file: {e}")
937
+ except Exception as e:
938
+ raise ValueError(f"Failed to process image data: {e}")
939
+
940
+ try:
941
+ client = genai.Client(api_key=gemini_api_key)
942
+ log.debug(f"{log_identifier} Initialized Gemini client")
943
+ except Exception as e:
944
+ raise ValueError(f"Failed to initialize Gemini client: {e}")
945
+
946
+ text_input = (edit_prompt,)
947
+
948
+ log.debug(
949
+ f"{log_identifier} Calling Gemini API with edit prompt: '{edit_prompt[:100]}...'"
950
+ )
951
+
952
+ try:
953
+ response = await asyncio.to_thread(
954
+ client.models.generate_content,
955
+ model=model_name,
956
+ contents=[text_input, pil_image],
957
+ config=types.GenerateContentConfig(
958
+ response_modalities=["TEXT", "IMAGE"]
959
+ ),
960
+ )
961
+ log.debug(f"{log_identifier} Gemini API response received.")
962
+ except Exception as e:
963
+ raise ValueError(f"Gemini API call failed: {e}")
964
+
965
+ edited_image_bytes = None
966
+ response_text = None
967
+
968
+ if not response.candidates or not response.candidates[0].content.parts:
969
+ raise ValueError("Gemini API did not return valid content.")
970
+
971
+ for part in response.candidates[0].content.parts:
972
+ if part.text is not None:
973
+ response_text = part.text
974
+ log.debug(
975
+ f"{log_identifier} Received text response: {response_text[:100]}..."
976
+ )
977
+ elif part.inline_data is not None:
978
+ edited_pil_image = PILImage.open(BytesIO(part.inline_data.data))
979
+ output_buffer = BytesIO()
980
+ if edited_pil_image.mode == "RGBA":
981
+ rgb_image = PILImage.new(
982
+ "RGB", edited_pil_image.size, (255, 255, 255)
983
+ )
984
+ rgb_image.paste(edited_pil_image, mask=edited_pil_image.split()[-1])
985
+ rgb_image.save(output_buffer, format="JPEG", quality=95)
986
+ else:
987
+ edited_pil_image.save(output_buffer, format="JPEG", quality=95)
988
+ edited_image_bytes = output_buffer.getvalue()
989
+ log.debug(
990
+ f"{log_identifier} Processed edited image: {len(edited_image_bytes)} bytes"
991
+ )
992
+
993
+ if not edited_image_bytes:
994
+ raise ValueError("No edited image data received from Gemini API.")
995
+
996
+ final_output_filename = ""
997
+ if output_filename:
998
+ sane_filename = os.path.basename(output_filename)
999
+ if not sane_filename.lower().endswith((".jpg", ".jpeg")):
1000
+ final_output_filename = f"{sane_filename}.jpg"
1001
+ else:
1002
+ final_output_filename = sane_filename
1003
+ else:
1004
+ base_name = os.path.splitext(filename_base_for_load)[0]
1005
+ final_output_filename = f"edited_{base_name}_{uuid.uuid4().hex[:8]}.jpg"
1006
+
1007
+ log.debug(
1008
+ f"{log_identifier} Determined output filename: {final_output_filename}"
1009
+ )
1010
+
1011
+ output_mime_type = "image/jpeg"
1012
+ current_timestamp_iso = datetime.now(timezone.utc).isoformat()
1013
+
1014
+ metadata_dict = {
1015
+ "description": f"Image edited with prompt: {edit_prompt}",
1016
+ "original_image": filename_base_for_load,
1017
+ "original_version": version_to_load,
1018
+ "edit_prompt": edit_prompt,
1019
+ "editing_tool": "gemini",
1020
+ "editing_model": model_name,
1021
+ "request_timestamp": current_timestamp_iso,
1022
+ "original_requested_filename": (
1023
+ output_filename if output_filename else "N/A"
1024
+ ),
1025
+ }
1026
+ if response_text:
1027
+ metadata_dict["gemini_response_text"] = response_text
1028
+
1029
+ log.info(
1030
+ f"{log_identifier} Saving edited image artifact '{final_output_filename}' with mime_type '{output_mime_type}'."
1031
+ )
1032
+ save_result = await save_artifact_with_metadata(
1033
+ artifact_service=artifact_service,
1034
+ app_name=app_name,
1035
+ user_id=user_id,
1036
+ session_id=session_id,
1037
+ filename=final_output_filename,
1038
+ content_bytes=edited_image_bytes,
1039
+ mime_type=output_mime_type,
1040
+ metadata_dict=metadata_dict,
1041
+ timestamp=datetime.now(timezone.utc),
1042
+ schema_max_keys=DEFAULT_SCHEMA_MAX_KEYS,
1043
+ tool_context=tool_context,
1044
+ )
1045
+
1046
+ if save_result.get("status") == "error":
1047
+ raise IOError(
1048
+ f"Failed to save edited image artifact: {save_result.get('message', 'Unknown error')}"
1049
+ )
1050
+
1051
+ log.info(
1052
+ f"{log_identifier} Edited image artifact '{final_output_filename}' v{save_result['data_version']} saved successfully."
1053
+ )
1054
+
1055
+ return {
1056
+ "status": "success",
1057
+ "message": "Image edited and saved successfully.",
1058
+ "output_filename": final_output_filename,
1059
+ "output_version": save_result["data_version"],
1060
+ "result_preview": f"Edited image '{final_output_filename}' (v{save_result['data_version']}) created from '{filename_base_for_load}' with prompt: \"{edit_prompt[:50]}...\"",
1061
+ "original_filename": filename_base_for_load,
1062
+ "original_version": version_to_load,
1063
+ }
1064
+
1065
+ except FileNotFoundError as e:
1066
+ log.warning(f"{log_identifier} File not found error: {e}")
1067
+ return {"status": "error", "message": str(e)}
1068
+ except ValueError as ve:
1069
+ log.error(f"{log_identifier} Value error: {ve}")
1070
+ return {"status": "error", "message": str(ve)}
1071
+ except IOError as ioe:
1072
+ log.error(f"{log_identifier} IO error: {ioe}")
1073
+ return {"status": "error", "message": str(ioe)}
1074
+ except Exception as e:
1075
+ log.exception(
1076
+ f"{log_identifier} Unexpected error in edit_image_with_gemini: {e}"
1077
+ )
1078
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
1079
+
1080
+
1081
+ create_image_from_description_tool_def = BuiltinTool(
1082
+ name="create_image_from_description",
1083
+ implementation=create_image_from_description,
1084
+ description="Generates an image based on a textual description using a configured image generation model (e.g., via LiteLLM) and saves it as a PNG artifact.",
1085
+ category="image",
1086
+ required_scopes=["tool:image:create"],
1087
+ parameters=adk_types.Schema(
1088
+ type=adk_types.Type.OBJECT,
1089
+ properties={
1090
+ "image_description": adk_types.Schema(
1091
+ type=adk_types.Type.STRING,
1092
+ description="The textual prompt to use for image generation.",
1093
+ ),
1094
+ "output_filename": adk_types.Schema(
1095
+ type=adk_types.Type.STRING,
1096
+ description="Optional. The desired filename for the output PNG image.",
1097
+ nullable=True,
1098
+ ),
1099
+ },
1100
+ required=["image_description"],
1101
+ ),
1102
+ examples=[],
1103
+ )
1104
+
1105
+ describe_image_tool_def = BuiltinTool(
1106
+ name="describe_image",
1107
+ implementation=describe_image,
1108
+ description="Describes an image using an OpenAI-compatible vision API.",
1109
+ category="image",
1110
+ required_scopes=["tool:image:describe"],
1111
+ parameters=adk_types.Schema(
1112
+ type=adk_types.Type.OBJECT,
1113
+ properties={
1114
+ "image_filename": adk_types.Schema(
1115
+ type=adk_types.Type.STRING,
1116
+ description="The filename (and optional :version) of the input image artifact.",
1117
+ ),
1118
+ "prompt": adk_types.Schema(
1119
+ type=adk_types.Type.STRING,
1120
+ description="Custom prompt for image analysis.",
1121
+ nullable=True,
1122
+ ),
1123
+ },
1124
+ required=["image_filename"],
1125
+ ),
1126
+ examples=[],
1127
+ )
1128
+
1129
+ describe_audio_tool_def = BuiltinTool(
1130
+ name="describe_audio",
1131
+ implementation=describe_audio,
1132
+ description="Describes an audio recording using a multimodal API.",
1133
+ category="image",
1134
+ required_scopes=["tool:audio:describe"],
1135
+ parameters=adk_types.Schema(
1136
+ type=adk_types.Type.OBJECT,
1137
+ properties={
1138
+ "audio_filename": adk_types.Schema(
1139
+ type=adk_types.Type.STRING,
1140
+ description="The filename (and optional :version) of the input audio artifact.",
1141
+ ),
1142
+ "prompt": adk_types.Schema(
1143
+ type=adk_types.Type.STRING,
1144
+ description="Custom prompt for audio analysis.",
1145
+ nullable=True,
1146
+ ),
1147
+ },
1148
+ required=["audio_filename"],
1149
+ ),
1150
+ examples=[],
1151
+ )
1152
+
1153
+ edit_image_with_gemini_tool_def = BuiltinTool(
1154
+ name="edit_image_with_gemini",
1155
+ implementation=edit_image_with_gemini,
1156
+ description="Edits an existing image based on a text prompt using Google's Gemini 2.0 Flash Preview Image Generation model.",
1157
+ category="image",
1158
+ required_scopes=["tool:image:edit"],
1159
+ parameters=adk_types.Schema(
1160
+ type=adk_types.Type.OBJECT,
1161
+ properties={
1162
+ "image_filename": adk_types.Schema(
1163
+ type=adk_types.Type.STRING,
1164
+ description="The filename (and optional :version) of the input image artifact.",
1165
+ ),
1166
+ "edit_prompt": adk_types.Schema(
1167
+ type=adk_types.Type.STRING,
1168
+ description="Text description of the desired edits to apply to the image.",
1169
+ ),
1170
+ "output_filename": adk_types.Schema(
1171
+ type=adk_types.Type.STRING,
1172
+ description="Optional. The desired filename for the output edited image.",
1173
+ nullable=True,
1174
+ ),
1175
+ },
1176
+ required=["image_filename", "edit_prompt"],
1177
+ ),
1178
+ examples=[],
1179
+ )
1180
+
1181
+ tool_registry.register(create_image_from_description_tool_def)
1182
+ tool_registry.register(describe_image_tool_def)
1183
+ tool_registry.register(describe_audio_tool_def)
1184
+ tool_registry.register(edit_image_with_gemini_tool_def)