solace-agent-mesh 1.11.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. solace_agent_mesh/__init__.py +0 -0
  2. solace_agent_mesh/agent/__init__.py +0 -0
  3. solace_agent_mesh/agent/adk/__init__.py +0 -0
  4. solace_agent_mesh/agent/adk/adk_llm.txt +226 -0
  5. solace_agent_mesh/agent/adk/adk_llm_detail.txt +566 -0
  6. solace_agent_mesh/agent/adk/alembic/README +74 -0
  7. solace_agent_mesh/agent/adk/alembic/env.py +77 -0
  8. solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
  9. solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
  10. solace_agent_mesh/agent/adk/alembic.ini +112 -0
  11. solace_agent_mesh/agent/adk/app_llm_agent.py +52 -0
  12. solace_agent_mesh/agent/adk/artifacts/__init__.py +1 -0
  13. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
  14. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +545 -0
  15. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +609 -0
  16. solace_agent_mesh/agent/adk/callbacks.py +2318 -0
  17. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +406 -0
  18. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +415 -0
  19. solace_agent_mesh/agent/adk/mcp_content_processor.py +666 -0
  20. solace_agent_mesh/agent/adk/models/lite_llm.py +1026 -0
  21. solace_agent_mesh/agent/adk/models/models_llm.txt +189 -0
  22. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +132 -0
  23. solace_agent_mesh/agent/adk/runner.py +390 -0
  24. solace_agent_mesh/agent/adk/schema_migration.py +88 -0
  25. solace_agent_mesh/agent/adk/services.py +468 -0
  26. solace_agent_mesh/agent/adk/setup.py +1325 -0
  27. solace_agent_mesh/agent/adk/stream_parser.py +415 -0
  28. solace_agent_mesh/agent/adk/tool_wrapper.py +165 -0
  29. solace_agent_mesh/agent/agent_llm.txt +369 -0
  30. solace_agent_mesh/agent/agent_llm_detail.txt +1702 -0
  31. solace_agent_mesh/agent/protocol/__init__.py +0 -0
  32. solace_agent_mesh/agent/protocol/event_handlers.py +2041 -0
  33. solace_agent_mesh/agent/protocol/protocol_llm.txt +81 -0
  34. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +92 -0
  35. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  36. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  37. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
  38. solace_agent_mesh/agent/proxies/a2a/app.py +56 -0
  39. solace_agent_mesh/agent/proxies/a2a/component.py +1585 -0
  40. solace_agent_mesh/agent/proxies/a2a/config.py +216 -0
  41. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  42. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  43. solace_agent_mesh/agent/proxies/base/app.py +100 -0
  44. solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
  45. solace_agent_mesh/agent/proxies/base/component.py +816 -0
  46. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  47. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +19 -0
  48. solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
  49. solace_agent_mesh/agent/sac/__init__.py +0 -0
  50. solace_agent_mesh/agent/sac/app.py +595 -0
  51. solace_agent_mesh/agent/sac/component.py +3668 -0
  52. solace_agent_mesh/agent/sac/patch_adk.py +103 -0
  53. solace_agent_mesh/agent/sac/sac_llm.txt +189 -0
  54. solace_agent_mesh/agent/sac/sac_llm_detail.txt +200 -0
  55. solace_agent_mesh/agent/sac/task_execution_context.py +415 -0
  56. solace_agent_mesh/agent/testing/__init__.py +3 -0
  57. solace_agent_mesh/agent/testing/debug_utils.py +135 -0
  58. solace_agent_mesh/agent/testing/testing_llm.txt +58 -0
  59. solace_agent_mesh/agent/testing/testing_llm_detail.txt +68 -0
  60. solace_agent_mesh/agent/tools/__init__.py +16 -0
  61. solace_agent_mesh/agent/tools/audio_tools.py +1740 -0
  62. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +2500 -0
  63. solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +244 -0
  64. solace_agent_mesh/agent/tools/dynamic_tool.py +396 -0
  65. solace_agent_mesh/agent/tools/general_agent_tools.py +572 -0
  66. solace_agent_mesh/agent/tools/image_tools.py +1185 -0
  67. solace_agent_mesh/agent/tools/peer_agent_tool.py +363 -0
  68. solace_agent_mesh/agent/tools/registry.py +38 -0
  69. solace_agent_mesh/agent/tools/test_tools.py +136 -0
  70. solace_agent_mesh/agent/tools/time_tools.py +126 -0
  71. solace_agent_mesh/agent/tools/tool_config_types.py +93 -0
  72. solace_agent_mesh/agent/tools/tool_definition.py +53 -0
  73. solace_agent_mesh/agent/tools/tools_llm.txt +276 -0
  74. solace_agent_mesh/agent/tools/tools_llm_detail.txt +275 -0
  75. solace_agent_mesh/agent/tools/web_tools.py +392 -0
  76. solace_agent_mesh/agent/utils/__init__.py +0 -0
  77. solace_agent_mesh/agent/utils/artifact_helpers.py +1353 -0
  78. solace_agent_mesh/agent/utils/config_parser.py +49 -0
  79. solace_agent_mesh/agent/utils/context_helpers.py +77 -0
  80. solace_agent_mesh/agent/utils/utils_llm.txt +152 -0
  81. solace_agent_mesh/agent/utils/utils_llm_detail.txt +149 -0
  82. solace_agent_mesh/assets/docs/404.html +16 -0
  83. solace_agent_mesh/assets/docs/assets/css/styles.8162edfb.css +1 -0
  84. solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
  85. solace_agent_mesh/assets/docs/assets/images/sam-enterprise-credentials-b269f095349473118b2b33bdfcc40122.png +0 -0
  86. solace_agent_mesh/assets/docs/assets/js/032c2d61.f3d37824.js +1 -0
  87. solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
  88. solace_agent_mesh/assets/docs/assets/js/0bcf40b7.c019ad46.js +1 -0
  89. solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
  90. solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
  91. solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
  92. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
  93. solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
  94. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
  95. solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
  96. solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
  97. solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
  98. solace_agent_mesh/assets/docs/assets/js/2131ec11.5c7a1f6e.js +1 -0
  99. solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
  100. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
  101. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js.LICENSE.txt +13 -0
  102. solace_agent_mesh/assets/docs/assets/js/2334.1cf50a20.js +1 -0
  103. solace_agent_mesh/assets/docs/assets/js/240a0364.9ad94d1b.js +1 -0
  104. solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
  105. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +1 -0
  106. solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
  107. solace_agent_mesh/assets/docs/assets/js/341393d4.0fac2613.js +1 -0
  108. solace_agent_mesh/assets/docs/assets/js/3624.0eaa1fd0.js +1 -0
  109. solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
  110. solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
  111. solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +1 -0
  112. solace_agent_mesh/assets/docs/assets/js/3ac1795d.28b7c67b.js +1 -0
  113. solace_agent_mesh/assets/docs/assets/js/3ff0015d.2ddc75c0.js +1 -0
  114. solace_agent_mesh/assets/docs/assets/js/41adc471.48b12a4e.js +1 -0
  115. solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
  116. solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
  117. solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
  118. solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
  119. solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
  120. solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
  121. solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
  122. solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
  123. solace_agent_mesh/assets/docs/assets/js/509e993c.a1fbf45a.js +1 -0
  124. solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
  125. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
  126. solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
  127. solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
  128. solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
  129. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.90a87880.js +1 -0
  130. solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
  131. solace_agent_mesh/assets/docs/assets/js/6063ff4c.ef84f702.js +1 -0
  132. solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
  133. solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
  134. solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
  135. solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
  136. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
  137. solace_agent_mesh/assets/docs/assets/js/66d4869e.b77431fc.js +1 -0
  138. solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
  139. solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
  140. solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
  141. solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
  142. solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
  143. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
  144. solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
  145. solace_agent_mesh/assets/docs/assets/js/6fdfefc7.99de744e.js +1 -0
  146. solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
  147. solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
  148. solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
  149. solace_agent_mesh/assets/docs/assets/js/722f809d.965da774.js +1 -0
  150. solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
  151. solace_agent_mesh/assets/docs/assets/js/742f027b.46c07808.js +1 -0
  152. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  153. solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
  154. solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
  155. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
  156. solace_agent_mesh/assets/docs/assets/js/81a99df0.2484b8d9.js +1 -0
  157. solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
  158. solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
  159. solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
  160. solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
  161. solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
  162. solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js +2 -0
  163. solace_agent_mesh/assets/docs/assets/js/8591.5d015485.js.LICENSE.txt +61 -0
  164. solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
  165. solace_agent_mesh/assets/docs/assets/js/8731.6c1dbf0c.js +1 -0
  166. solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
  167. solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
  168. solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
  169. solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
  170. solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
  171. solace_agent_mesh/assets/docs/assets/js/945fb41e.6f4cdffd.js +1 -0
  172. solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
  173. solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
  174. solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
  175. solace_agent_mesh/assets/docs/assets/js/9bb13469.b2333011.js +1 -0
  176. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  177. solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
  178. solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
  179. solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +1 -0
  180. solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
  181. solace_agent_mesh/assets/docs/assets/js/ad71b5ed.af3ecfd1.js +1 -0
  182. solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
  183. solace_agent_mesh/assets/docs/assets/js/c198a0dc.8f31f867.js +1 -0
  184. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
  185. solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
  186. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
  187. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  188. solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
  189. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
  190. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  191. solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
  192. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
  193. solace_agent_mesh/assets/docs/assets/js/de5f4c65.e8241890.js +1 -0
  194. solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
  195. solace_agent_mesh/assets/docs/assets/js/e04b235d.52cb25ed.js +1 -0
  196. solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.b1068f9b.js +1 -0
  197. solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
  198. solace_agent_mesh/assets/docs/assets/js/e6f9706b.4488e34c.js +1 -0
  199. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
  200. solace_agent_mesh/assets/docs/assets/js/f284c35a.250993bf.js +1 -0
  201. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
  202. solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js +2 -0
  203. solace_agent_mesh/assets/docs/assets/js/main.7acf7ace.js.LICENSE.txt +81 -0
  204. solace_agent_mesh/assets/docs/assets/js/runtime~main.9e0813a2.js +1 -0
  205. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +154 -0
  206. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +99 -0
  207. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +90 -0
  208. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +107 -0
  209. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +166 -0
  210. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +101 -0
  211. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +219 -0
  212. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +92 -0
  213. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +29 -0
  214. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +55 -0
  215. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +110 -0
  216. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
  217. solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
  218. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +345 -0
  219. solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
  220. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +83 -0
  221. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +84 -0
  222. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +25 -0
  223. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
  224. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +85 -0
  225. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +60 -0
  226. solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
  227. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +144 -0
  228. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +191 -0
  229. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +128 -0
  230. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +54 -0
  231. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  232. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +34 -0
  233. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +55 -0
  234. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +267 -0
  235. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +142 -0
  236. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +116 -0
  237. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +86 -0
  238. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +164 -0
  239. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +140 -0
  240. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +57 -0
  241. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +72 -0
  242. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +102 -0
  243. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
  244. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
  245. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
  246. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +37 -0
  247. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +86 -0
  248. solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
  249. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +247 -0
  250. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
  251. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +184 -0
  252. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
  253. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +75 -0
  254. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +54 -0
  255. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +85 -0
  256. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +41 -0
  257. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
  258. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +78 -0
  259. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +25 -0
  260. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +78 -0
  261. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +160 -0
  262. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +142 -0
  263. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
  264. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
  265. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +100 -0
  266. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +52 -0
  267. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
  268. solace_agent_mesh/assets/docs/img/logo.png +0 -0
  269. solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
  270. solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
  271. solace_agent_mesh/assets/docs/img/sam-enterprise-credentials.png +0 -0
  272. solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
  273. solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
  274. solace_agent_mesh/assets/docs/lunr-index-1765810064709.json +1 -0
  275. solace_agent_mesh/assets/docs/lunr-index.json +1 -0
  276. solace_agent_mesh/assets/docs/search-doc-1765810064709.json +1 -0
  277. solace_agent_mesh/assets/docs/search-doc.json +1 -0
  278. solace_agent_mesh/assets/docs/sitemap.xml +1 -0
  279. solace_agent_mesh/cli/__init__.py +1 -0
  280. solace_agent_mesh/cli/commands/__init__.py +0 -0
  281. solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
  282. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
  283. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +729 -0
  284. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
  285. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +102 -0
  286. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +114 -0
  287. solace_agent_mesh/cli/commands/docs_cmd.py +60 -0
  288. solace_agent_mesh/cli/commands/eval_cmd.py +46 -0
  289. solace_agent_mesh/cli/commands/init_cmd/__init__.py +439 -0
  290. solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
  291. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  292. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
  293. solace_agent_mesh/cli/commands/init_cmd/env_step.py +238 -0
  294. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
  295. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +464 -0
  296. solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
  297. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +119 -0
  298. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +215 -0
  299. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +20 -0
  300. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +137 -0
  301. solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
  302. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +144 -0
  303. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +306 -0
  304. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +283 -0
  305. solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
  306. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
  307. solace_agent_mesh/cli/commands/run_cmd.py +215 -0
  308. solace_agent_mesh/cli/main.py +52 -0
  309. solace_agent_mesh/cli/utils.py +262 -0
  310. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-Dj3JtK42.js +1 -0
  311. solace_agent_mesh/client/webui/frontend/static/assets/client-ZKk9kEJ5.js +25 -0
  312. solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
  313. solace_agent_mesh/client/webui/frontend/static/assets/main-BcUaNZ-Q.css +1 -0
  314. solace_agent_mesh/client/webui/frontend/static/assets/main-vjch4RYc.js +435 -0
  315. solace_agent_mesh/client/webui/frontend/static/assets/vendor-BNV4kZN0.js +535 -0
  316. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +15 -0
  317. solace_agent_mesh/client/webui/frontend/static/index.html +16 -0
  318. solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
  319. solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
  320. solace_agent_mesh/common/__init__.py +1 -0
  321. solace_agent_mesh/common/a2a/__init__.py +241 -0
  322. solace_agent_mesh/common/a2a/a2a_llm.txt +175 -0
  323. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +193 -0
  324. solace_agent_mesh/common/a2a/artifact.py +368 -0
  325. solace_agent_mesh/common/a2a/events.py +213 -0
  326. solace_agent_mesh/common/a2a/message.py +375 -0
  327. solace_agent_mesh/common/a2a/protocol.py +689 -0
  328. solace_agent_mesh/common/a2a/task.py +127 -0
  329. solace_agent_mesh/common/a2a/translation.py +655 -0
  330. solace_agent_mesh/common/a2a/types.py +55 -0
  331. solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
  332. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +445 -0
  333. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +736 -0
  334. solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
  335. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +48 -0
  336. solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
  337. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +41 -0
  338. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +330 -0
  339. solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
  340. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +48 -0
  341. solace_agent_mesh/common/agent_registry.py +122 -0
  342. solace_agent_mesh/common/common_llm.txt +230 -0
  343. solace_agent_mesh/common/common_llm_detail.txt +2562 -0
  344. solace_agent_mesh/common/constants.py +6 -0
  345. solace_agent_mesh/common/data_parts.py +150 -0
  346. solace_agent_mesh/common/exceptions.py +49 -0
  347. solace_agent_mesh/common/middleware/__init__.py +12 -0
  348. solace_agent_mesh/common/middleware/config_resolver.py +132 -0
  349. solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
  350. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +185 -0
  351. solace_agent_mesh/common/middleware/registry.py +127 -0
  352. solace_agent_mesh/common/oauth/__init__.py +17 -0
  353. solace_agent_mesh/common/oauth/oauth_client.py +408 -0
  354. solace_agent_mesh/common/oauth/utils.py +50 -0
  355. solace_agent_mesh/common/sac/__init__.py +0 -0
  356. solace_agent_mesh/common/sac/sac_llm.txt +71 -0
  357. solace_agent_mesh/common/sac/sac_llm_detail.txt +82 -0
  358. solace_agent_mesh/common/sac/sam_component_base.py +730 -0
  359. solace_agent_mesh/common/sam_events/__init__.py +9 -0
  360. solace_agent_mesh/common/sam_events/event_service.py +208 -0
  361. solace_agent_mesh/common/sam_events/sam_events_llm.txt +104 -0
  362. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +115 -0
  363. solace_agent_mesh/common/services/__init__.py +4 -0
  364. solace_agent_mesh/common/services/employee_service.py +164 -0
  365. solace_agent_mesh/common/services/identity_service.py +134 -0
  366. solace_agent_mesh/common/services/providers/__init__.py +4 -0
  367. solace_agent_mesh/common/services/providers/local_file_identity_service.py +151 -0
  368. solace_agent_mesh/common/services/providers/providers_llm.txt +81 -0
  369. solace_agent_mesh/common/services/services_llm.txt +368 -0
  370. solace_agent_mesh/common/services/services_llm_detail.txt +459 -0
  371. solace_agent_mesh/common/utils/__init__.py +7 -0
  372. solace_agent_mesh/common/utils/artifact_utils.py +31 -0
  373. solace_agent_mesh/common/utils/asyncio_macos_fix.py +88 -0
  374. solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
  375. solace_agent_mesh/common/utils/embeds/constants.py +56 -0
  376. solace_agent_mesh/common/utils/embeds/converter.py +447 -0
  377. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +220 -0
  378. solace_agent_mesh/common/utils/embeds/evaluators.py +395 -0
  379. solace_agent_mesh/common/utils/embeds/modifiers.py +793 -0
  380. solace_agent_mesh/common/utils/embeds/resolver.py +967 -0
  381. solace_agent_mesh/common/utils/embeds/types.py +23 -0
  382. solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
  383. solace_agent_mesh/common/utils/initializer.py +52 -0
  384. solace_agent_mesh/common/utils/log_formatters.py +64 -0
  385. solace_agent_mesh/common/utils/message_utils.py +80 -0
  386. solace_agent_mesh/common/utils/mime_helpers.py +172 -0
  387. solace_agent_mesh/common/utils/push_notification_auth.py +135 -0
  388. solace_agent_mesh/common/utils/pydantic_utils.py +159 -0
  389. solace_agent_mesh/common/utils/rbac_utils.py +69 -0
  390. solace_agent_mesh/common/utils/templates/__init__.py +8 -0
  391. solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
  392. solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
  393. solace_agent_mesh/common/utils/type_utils.py +28 -0
  394. solace_agent_mesh/common/utils/utils_llm.txt +335 -0
  395. solace_agent_mesh/common/utils/utils_llm_detail.txt +572 -0
  396. solace_agent_mesh/config_portal/__init__.py +0 -0
  397. solace_agent_mesh/config_portal/backend/__init__.py +0 -0
  398. solace_agent_mesh/config_portal/backend/common.py +77 -0
  399. solace_agent_mesh/config_portal/backend/plugin_catalog/__init__.py +0 -0
  400. solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
  401. solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
  402. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +166 -0
  403. solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
  404. solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
  405. solace_agent_mesh/config_portal/backend/server.py +644 -0
  406. solace_agent_mesh/config_portal/frontend/static/client/Solace_community_logo.png +0 -0
  407. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DiOiAjzL.js +103 -0
  408. solace_agent_mesh/config_portal/frontend/static/client/assets/components-Rk0n-9cK.js +140 -0
  409. solace_agent_mesh/config_portal/frontend/static/client/assets/entry.client-mvZjNKiz.js +19 -0
  410. solace_agent_mesh/config_portal/frontend/static/client/assets/index-DzNKzXrc.js +68 -0
  411. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-ba77705e.js +1 -0
  412. solace_agent_mesh/config_portal/frontend/static/client/assets/root-B17tZKK7.css +1 -0
  413. solace_agent_mesh/config_portal/frontend/static/client/assets/root-V2BeTIUc.js +10 -0
  414. solace_agent_mesh/config_portal/frontend/static/client/favicon.ico +0 -0
  415. solace_agent_mesh/config_portal/frontend/static/client/index.html +7 -0
  416. solace_agent_mesh/core_a2a/__init__.py +1 -0
  417. solace_agent_mesh/core_a2a/core_a2a_llm.txt +90 -0
  418. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +101 -0
  419. solace_agent_mesh/core_a2a/service.py +307 -0
  420. solace_agent_mesh/evaluation/__init__.py +0 -0
  421. solace_agent_mesh/evaluation/evaluator.py +691 -0
  422. solace_agent_mesh/evaluation/message_organizer.py +553 -0
  423. solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
  424. solace_agent_mesh/evaluation/report/chart_section.html +141 -0
  425. solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
  426. solace_agent_mesh/evaluation/report/modal.html +59 -0
  427. solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
  428. solace_agent_mesh/evaluation/report/modal_script.js +296 -0
  429. solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
  430. solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
  431. solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
  432. solace_agent_mesh/evaluation/report/templates/header.html +340 -0
  433. solace_agent_mesh/evaluation/report_data_processor.py +970 -0
  434. solace_agent_mesh/evaluation/report_generator.py +607 -0
  435. solace_agent_mesh/evaluation/run.py +954 -0
  436. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  437. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  438. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  439. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  440. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  441. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  442. solace_agent_mesh/evaluation/subscriber.py +776 -0
  443. solace_agent_mesh/evaluation/summary_builder.py +880 -0
  444. solace_agent_mesh/gateway/__init__.py +0 -0
  445. solace_agent_mesh/gateway/adapter/__init__.py +1 -0
  446. solace_agent_mesh/gateway/adapter/base.py +143 -0
  447. solace_agent_mesh/gateway/adapter/types.py +221 -0
  448. solace_agent_mesh/gateway/base/__init__.py +1 -0
  449. solace_agent_mesh/gateway/base/app.py +345 -0
  450. solace_agent_mesh/gateway/base/base_llm.txt +226 -0
  451. solace_agent_mesh/gateway/base/base_llm_detail.txt +235 -0
  452. solace_agent_mesh/gateway/base/component.py +2030 -0
  453. solace_agent_mesh/gateway/base/task_context.py +75 -0
  454. solace_agent_mesh/gateway/gateway_llm.txt +369 -0
  455. solace_agent_mesh/gateway/gateway_llm_detail.txt +3885 -0
  456. solace_agent_mesh/gateway/generic/__init__.py +1 -0
  457. solace_agent_mesh/gateway/generic/app.py +50 -0
  458. solace_agent_mesh/gateway/generic/component.py +727 -0
  459. solace_agent_mesh/gateway/http_sse/__init__.py +0 -0
  460. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +345 -0
  461. solace_agent_mesh/gateway/http_sse/alembic/env.py +87 -0
  462. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  463. solace_agent_mesh/gateway/http_sse/alembic/versions/20250910_d5b3f8f2e9a0_create_initial_database.py +58 -0
  464. solace_agent_mesh/gateway/http_sse/alembic/versions/20250911_b1c2d3e4f5g6_add_database_indexes.py +83 -0
  465. solace_agent_mesh/gateway/http_sse/alembic/versions/20250916_f6e7d8c9b0a1_convert_timestamps_to_epoch_and_align_columns.py +412 -0
  466. solace_agent_mesh/gateway/http_sse/alembic/versions/20251006_98882922fa59_add_tasks_events_feedback_chat_tasks.py +190 -0
  467. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  468. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
  469. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
  470. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
  471. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
  472. solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
  473. solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
  474. solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
  475. solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
  476. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +161 -0
  477. solace_agent_mesh/gateway/http_sse/alembic.ini +109 -0
  478. solace_agent_mesh/gateway/http_sse/app.py +351 -0
  479. solace_agent_mesh/gateway/http_sse/component.py +2360 -0
  480. solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
  481. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +105 -0
  482. solace_agent_mesh/gateway/http_sse/components/task_logger_forwarder.py +109 -0
  483. solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +110 -0
  484. solace_agent_mesh/gateway/http_sse/dependencies.py +653 -0
  485. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +299 -0
  486. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +3278 -0
  487. solace_agent_mesh/gateway/http_sse/main.py +789 -0
  488. solace_agent_mesh/gateway/http_sse/repository/__init__.py +46 -0
  489. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +102 -0
  490. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +11 -0
  491. solace_agent_mesh/gateway/http_sse/repository/entities/chat_task.py +75 -0
  492. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +221 -0
  493. solace_agent_mesh/gateway/http_sse/repository/entities/feedback.py +20 -0
  494. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
  495. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
  496. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +66 -0
  497. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +0 -0
  498. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +32 -0
  499. solace_agent_mesh/gateway/http_sse/repository/entities/task_event.py +21 -0
  500. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +125 -0
  501. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +239 -0
  502. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +34 -0
  503. solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
  504. solace_agent_mesh/gateway/http_sse/repository/models/chat_task_model.py +31 -0
  505. solace_agent_mesh/gateway/http_sse/repository/models/feedback_model.py +21 -0
  506. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +257 -0
  507. solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
  508. solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
  509. solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
  510. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +53 -0
  511. solace_agent_mesh/gateway/http_sse/repository/models/task_event_model.py +25 -0
  512. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +39 -0
  513. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
  514. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
  515. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +308 -0
  516. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +268 -0
  517. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +248 -0
  518. solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
  519. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +74 -0
  520. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +1137 -0
  521. solace_agent_mesh/gateway/http_sse/routers/auth.py +311 -0
  522. solace_agent_mesh/gateway/http_sse/routers/config.py +371 -0
  523. solace_agent_mesh/gateway/http_sse/routers/dto/__init__.py +10 -0
  524. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +450 -0
  525. solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
  526. solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
  527. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +15 -0
  528. solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
  529. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +133 -0
  530. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +33 -0
  531. solace_agent_mesh/gateway/http_sse/routers/dto/requests/task_requests.py +58 -0
  532. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +18 -0
  533. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +42 -0
  534. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
  535. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +123 -0
  536. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +33 -0
  537. solace_agent_mesh/gateway/http_sse/routers/dto/responses/task_responses.py +30 -0
  538. solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
  539. solace_agent_mesh/gateway/http_sse/routers/feedback.py +168 -0
  540. solace_agent_mesh/gateway/http_sse/routers/people.py +38 -0
  541. solace_agent_mesh/gateway/http_sse/routers/projects.py +767 -0
  542. solace_agent_mesh/gateway/http_sse/routers/prompts.py +1415 -0
  543. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +312 -0
  544. solace_agent_mesh/gateway/http_sse/routers/sessions.py +634 -0
  545. solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
  546. solace_agent_mesh/gateway/http_sse/routers/sse.py +230 -0
  547. solace_agent_mesh/gateway/http_sse/routers/tasks.py +1089 -0
  548. solace_agent_mesh/gateway/http_sse/routers/users.py +83 -0
  549. solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
  550. solace_agent_mesh/gateway/http_sse/routers/visualization.py +1220 -0
  551. solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
  552. solace_agent_mesh/gateway/http_sse/services/agent_card_service.py +71 -0
  553. solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
  554. solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
  555. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +273 -0
  556. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +250 -0
  557. solace_agent_mesh/gateway/http_sse/services/people_service.py +78 -0
  558. solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
  559. solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
  560. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +303 -0
  561. solace_agent_mesh/gateway/http_sse/services/session_service.py +702 -0
  562. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +593 -0
  563. solace_agent_mesh/gateway/http_sse/services/task_service.py +119 -0
  564. solace_agent_mesh/gateway/http_sse/session_manager.py +219 -0
  565. solace_agent_mesh/gateway/http_sse/shared/__init__.py +146 -0
  566. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  567. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +252 -0
  568. solace_agent_mesh/gateway/http_sse/shared/database_exceptions.py +274 -0
  569. solace_agent_mesh/gateway/http_sse/shared/database_helpers.py +43 -0
  570. solace_agent_mesh/gateway/http_sse/shared/enums.py +40 -0
  571. solace_agent_mesh/gateway/http_sse/shared/error_dto.py +107 -0
  572. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +217 -0
  573. solace_agent_mesh/gateway/http_sse/shared/exceptions.py +192 -0
  574. solace_agent_mesh/gateway/http_sse/shared/pagination.py +138 -0
  575. solace_agent_mesh/gateway/http_sse/shared/response_utils.py +134 -0
  576. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +319 -0
  577. solace_agent_mesh/gateway/http_sse/shared/timestamp_utils.py +97 -0
  578. solace_agent_mesh/gateway/http_sse/shared/types.py +50 -0
  579. solace_agent_mesh/gateway/http_sse/shared/utils.py +22 -0
  580. solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +88 -0
  581. solace_agent_mesh/gateway/http_sse/sse_manager.py +491 -0
  582. solace_agent_mesh/gateway/http_sse/utils/__init__.py +1 -0
  583. solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
  584. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +72 -0
  585. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +47 -0
  586. solace_agent_mesh/llm.txt +228 -0
  587. solace_agent_mesh/llm_detail.txt +2835 -0
  588. solace_agent_mesh/services/__init__.py +0 -0
  589. solace_agent_mesh/services/platform/__init__.py +18 -0
  590. solace_agent_mesh/services/platform/alembic/env.py +85 -0
  591. solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
  592. solace_agent_mesh/services/platform/alembic.ini +109 -0
  593. solace_agent_mesh/services/platform/api/__init__.py +3 -0
  594. solace_agent_mesh/services/platform/api/dependencies.py +147 -0
  595. solace_agent_mesh/services/platform/api/main.py +280 -0
  596. solace_agent_mesh/services/platform/api/middleware.py +51 -0
  597. solace_agent_mesh/services/platform/api/routers/__init__.py +24 -0
  598. solace_agent_mesh/services/platform/app.py +114 -0
  599. solace_agent_mesh/services/platform/component.py +235 -0
  600. solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
  601. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +8599 -0
  602. solace_agent_mesh/templates/agent_template.yaml +53 -0
  603. solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
  604. solace_agent_mesh/templates/gateway_app_template.py +75 -0
  605. solace_agent_mesh/templates/gateway_component_template.py +484 -0
  606. solace_agent_mesh/templates/gateway_config_template.yaml +38 -0
  607. solace_agent_mesh/templates/logging_config_template.yaml +48 -0
  608. solace_agent_mesh/templates/main_orchestrator.yaml +66 -0
  609. solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
  610. solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
  611. solace_agent_mesh/templates/plugin_custom_template.py +10 -0
  612. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +60 -0
  613. solace_agent_mesh/templates/plugin_pyproject_template.toml +32 -0
  614. solace_agent_mesh/templates/plugin_readme_template.md +12 -0
  615. solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
  616. solace_agent_mesh/templates/plugin_tools_template.py +224 -0
  617. solace_agent_mesh/templates/shared_config.yaml +112 -0
  618. solace_agent_mesh/templates/templates_llm.txt +147 -0
  619. solace_agent_mesh/templates/webui.yaml +177 -0
  620. solace_agent_mesh-1.11.2.dist-info/METADATA +504 -0
  621. solace_agent_mesh-1.11.2.dist-info/RECORD +624 -0
  622. solace_agent_mesh-1.11.2.dist-info/WHEEL +4 -0
  623. solace_agent_mesh-1.11.2.dist-info/entry_points.txt +3 -0
  624. solace_agent_mesh-1.11.2.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,1740 @@
1
+ """
2
+ Collection of Python tools for audio processing and text-to-speech generation.
3
+ """
4
+
5
+ import logging
6
+ import asyncio
7
+ import inspect
8
+ import io
9
+ import json
10
+ import os
11
+ import random
12
+ import tempfile
13
+ import uuid
14
+ import wave
15
+ from datetime import datetime, timezone
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ import httpx
19
+
20
+ from google import genai
21
+ from google.genai import types as adk_types
22
+ from google.adk.tools import ToolContext
23
+ from pydub import AudioSegment
24
+
25
+ from ...agent.utils.artifact_helpers import (
26
+ load_artifact_content_or_metadata,
27
+ save_artifact_with_metadata,
28
+ ensure_correct_extension,
29
+ DEFAULT_SCHEMA_MAX_KEYS,
30
+ )
31
+ from ...agent.utils.context_helpers import get_original_session_id
32
+
33
+ from .tool_definition import BuiltinTool
34
+ from .registry import tool_registry
35
+
36
+ log = logging.getLogger(__name__)
37
+
38
+ VOICE_TONE_MAPPING = {
39
+ "bright": ["Zephyr", "Autonoe"],
40
+ "upbeat": ["Puck", "Laomedeia"],
41
+ "informative": ["Charon", "Rasalgethi"],
42
+ "firm": ["Kore", "Orus", "Alnilam"],
43
+ "excitable": ["Fenrir"],
44
+ "youthful": ["Leda"],
45
+ "breezy": ["Aoede"],
46
+ "easy-going": ["Callirrhoe", "Umbriel"],
47
+ "breathy": ["Enceladus"],
48
+ "clear": ["Iapetus", "Erinome"],
49
+ "smooth": ["Algieba", "Despina"],
50
+ "gravelly": ["Algenib"],
51
+ "soft": ["Achernar"],
52
+ "even": ["Schedar"],
53
+ "mature": ["Gacrux"],
54
+ "forward": ["Pulcherrima"],
55
+ "friendly": ["Achird"],
56
+ "casual": ["Zubenelgenubi"],
57
+ "gentle": ["Vindemiatrix"],
58
+ "lively": ["Sadachbia"],
59
+ "knowledgeable": ["Sadaltager"],
60
+ "warm": ["Sulafat"],
61
+ }
62
+
63
+ GENDER_TO_VOICE_MAPPING = {
64
+ "male": [
65
+ "Puck",
66
+ "Charon",
67
+ "Orus",
68
+ "Enceladus",
69
+ "Iapetus",
70
+ "Algieba",
71
+ "Algenib",
72
+ "Alnilam",
73
+ "Schedar",
74
+ "Achird",
75
+ "Zubenelgenubi",
76
+ "Sadachbia",
77
+ "Sadaltager",
78
+ ],
79
+ "female": [
80
+ "Zephyr",
81
+ "Kore",
82
+ "Leda",
83
+ "Aoede",
84
+ "Callirrhoe",
85
+ "Autonoe",
86
+ "Despina",
87
+ "Erinome",
88
+ "Laomedeia",
89
+ "Achernar",
90
+ "Gacrux",
91
+ "Pulcherrima",
92
+ "Vindemiatrix",
93
+ "Sulafat",
94
+ ],
95
+ "neutral": [
96
+ "Fenrir",
97
+ "Umbriel",
98
+ "Rasalgethi",
99
+ ],
100
+ }
101
+
102
+ _all_voices_set = set(
103
+ voice for voices_in_tone in VOICE_TONE_MAPPING.values() for voice in voices_in_tone
104
+ )
105
+ _all_voices_set.update(
106
+ voice
107
+ for voices_in_gender in GENDER_TO_VOICE_MAPPING.values()
108
+ for voice in voices_in_gender
109
+ )
110
+ ALL_AVAILABLE_VOICES = list(_all_voices_set)
111
+
112
+ if not ALL_AVAILABLE_VOICES:
113
+ ALL_AVAILABLE_VOICES = [
114
+ "Kore",
115
+ "Puck",
116
+ "Zephyr",
117
+ "Charon",
118
+ "Rasalgethi",
119
+ "Alnilam",
120
+ "Fenrir",
121
+ "Leda",
122
+ "Aoede",
123
+ "Callirrhoe",
124
+ "Umbriel",
125
+ "Enceladus",
126
+ "Iapetus",
127
+ "Erinome",
128
+ "Algieba",
129
+ "Despina",
130
+ "Algenib",
131
+ "Achernar",
132
+ "Schedar",
133
+ "Gacrux",
134
+ "Pulcherrima",
135
+ "Achird",
136
+ "Zubenelgenubi",
137
+ "Vindemiatrix",
138
+ "Sadachbia",
139
+ "Sadaltager",
140
+ "Sulafat",
141
+ "Autonoe",
142
+ "Laomedeia",
143
+ "Orus",
144
+ ]
145
+
146
+
147
+ SUPPORTED_LANGUAGES = {
148
+ "arabic": "ar-EG",
149
+ "arabic_egyptian": "ar-EG",
150
+ "german": "de-DE",
151
+ "english": "en-US",
152
+ "english_us": "en-US",
153
+ "english_india": "en-IN",
154
+ "spanish": "es-US",
155
+ "spanish_us": "es-US",
156
+ "french": "fr-FR",
157
+ "hindi": "hi-IN",
158
+ "indonesian": "id-ID",
159
+ "italian": "it-IT",
160
+ "japanese": "ja-JP",
161
+ "korean": "ko-KR",
162
+ "portuguese": "pt-BR",
163
+ "portuguese_brazil": "pt-BR",
164
+ "russian": "ru-RU",
165
+ "dutch": "nl-NL",
166
+ "polish": "pl-PL",
167
+ "thai": "th-TH",
168
+ "turkish": "tr-TR",
169
+ "vietnamese": "vi-VN",
170
+ "romanian": "ro-RO",
171
+ "ukrainian": "uk-UA",
172
+ "bengali": "bn-BD",
173
+ "marathi": "mr-IN",
174
+ "tamil": "ta-IN",
175
+ "telugu": "te-IN",
176
+ }
177
+
178
+ from typing import Set
179
+
180
+ DEFAULT_VOICE = "Kore"
181
+
182
+
183
+ def _get_effective_tone_voices(
184
+ tone: Optional[str], voice_tone_mapping: Optional[Dict[str, List[str]]] = None
185
+ ) -> Optional[List[str]]:
186
+ """Helper to get voices for a tone, considering aliases."""
187
+ if not tone:
188
+ return None
189
+ mapping = voice_tone_mapping or VOICE_TONE_MAPPING
190
+ tone_lower = tone.lower().strip()
191
+ tone_aliases = {
192
+ "professional": "firm",
193
+ "business": "firm",
194
+ "corporate": "firm",
195
+ "cheerful": "upbeat",
196
+ "happy": "upbeat",
197
+ "energetic": "excitable",
198
+ "calm": "soft",
199
+ "relaxed": "easy-going",
200
+ "serious": "informative",
201
+ "educational": "knowledgeable",
202
+ "teaching": "knowledgeable",
203
+ "conversational": "casual",
204
+ "natural": "friendly",
205
+ "welcoming": "warm",
206
+ }
207
+ effective_tone = tone_aliases.get(tone_lower, tone_lower)
208
+ return mapping.get(effective_tone)
209
+
210
+
211
+ def _get_gender_voices(
212
+ gender: Optional[str], gender_voice_mapping: Optional[Dict[str, List[str]]] = None
213
+ ) -> Optional[List[str]]:
214
+ """Helper to get voices for a gender."""
215
+ if not gender:
216
+ return None
217
+ mapping = gender_voice_mapping or GENDER_TO_VOICE_MAPPING
218
+ return mapping.get(gender.lower().strip())
219
+
220
+
221
+ def _get_voice_for_speaker(
222
+ gender: Optional[str],
223
+ tone: Optional[str],
224
+ used_voices_in_current_call: Set[str],
225
+ voice_tone_mapping: Optional[Dict[str, List[str]]] = None,
226
+ gender_voice_mapping: Optional[Dict[str, List[str]]] = None,
227
+ ) -> str:
228
+ """
229
+ Selects a voice based on desired gender and/or tone, prioritizing uniqueness.
230
+
231
+ Selection Priority:
232
+ 1. Unique voice matching both specified gender and tone.
233
+ 2. Unique voice matching specified gender (if tone match failed or tone not specified).
234
+ 3. Unique voice matching specified tone (if gender match failed or gender not specified).
235
+ 4. Any unique voice from the global pool.
236
+ 5. If all unique options exhausted (reuse necessary):
237
+ a. Voice matching both specified gender and tone.
238
+ b. Voice matching specified gender.
239
+ c. Voice matching specified tone.
240
+ d. Any voice from the global pool.
241
+ 6. Fallback to DEFAULT_VOICE ("Kore").
242
+
243
+ Args:
244
+ gender: The desired gender ("male", "female", "neutral").
245
+ tone: The desired tone (e.g., "friendly", "professional").
246
+ used_voices_in_current_call: A set of voice names already used.
247
+ voice_tone_mapping: Optional custom tone-to-voice mapping.
248
+ gender_voice_mapping: Optional custom gender-to-voice mapping.
249
+
250
+ Returns:
251
+ A voice name string.
252
+ """
253
+ log_identifier_select = "[AudioTools:_get_voice_for_speaker]"
254
+ log.debug(
255
+ "%s Selecting voice for gender='%s', tone='%s', used_voices=%s",
256
+ log_identifier_select,
257
+ gender,
258
+ tone,
259
+ used_voices_in_current_call,
260
+ )
261
+
262
+ candidate_pool = list(ALL_AVAILABLE_VOICES)
263
+
264
+ gender_specific_voices = None
265
+ if gender:
266
+ gender_specific_voices = _get_gender_voices(gender, gender_voice_mapping)
267
+ if gender_specific_voices:
268
+ candidate_pool = [v for v in candidate_pool if v in gender_specific_voices]
269
+ log.debug(
270
+ "%s Filtered by gender '%s'. Candidates: %s",
271
+ log_identifier_select,
272
+ gender,
273
+ candidate_pool,
274
+ )
275
+ else:
276
+ log.warning(
277
+ "%s Gender '%s' not found in mapping or has no voices. Gender filter ignored for now.",
278
+ log_identifier_select,
279
+ gender,
280
+ )
281
+ gender_specific_voices = None
282
+
283
+ tone_specific_voices = None
284
+ if tone:
285
+ voices_for_tone = _get_effective_tone_voices(tone, voice_tone_mapping)
286
+ if voices_for_tone:
287
+ tone_specific_voices = voices_for_tone
288
+ candidate_pool = [v for v in candidate_pool if v in voices_for_tone]
289
+ log.debug(
290
+ "%s Filtered by tone '%s'. Candidates: %s",
291
+ log_identifier_select,
292
+ tone,
293
+ candidate_pool,
294
+ )
295
+ else:
296
+ log.warning(
297
+ "%s Tone '%s' not found in mapping or has no voices. Tone filter ignored.",
298
+ log_identifier_select,
299
+ tone,
300
+ )
301
+ tone_specific_voices = None
302
+
303
+ available_unique_voices = [
304
+ v for v in candidate_pool if v not in used_voices_in_current_call
305
+ ]
306
+ if available_unique_voices:
307
+ selected = random.choice(available_unique_voices)
308
+ log.info(
309
+ "%s Selected unique voice '%s' from gender/tone filtered pool.",
310
+ log_identifier_select,
311
+ selected,
312
+ )
313
+ return selected
314
+
315
+ log.debug(
316
+ "%s No unique voice in primary filtered pool. Trying fallbacks for uniqueness.",
317
+ log_identifier_select,
318
+ )
319
+ if gender_specific_voices:
320
+ available_gender_unique = [
321
+ v for v in gender_specific_voices if v not in used_voices_in_current_call
322
+ ]
323
+ if available_gender_unique:
324
+ selected = random.choice(available_gender_unique)
325
+ log.info(
326
+ "%s Selected unique voice '%s' from gender-only pool (tone constraint relaxed).",
327
+ log_identifier_select,
328
+ selected,
329
+ )
330
+ return selected
331
+ if tone_specific_voices:
332
+ available_tone_unique = [
333
+ v for v in tone_specific_voices if v not in used_voices_in_current_call
334
+ ]
335
+ if available_tone_unique:
336
+ selected = random.choice(available_tone_unique)
337
+ log.info(
338
+ "%s Selected unique voice '%s' from tone-only pool (gender constraint relaxed or not specified).",
339
+ log_identifier_select,
340
+ selected,
341
+ )
342
+ return selected
343
+ globally_available_unique = [
344
+ v for v in ALL_AVAILABLE_VOICES if v not in used_voices_in_current_call
345
+ ]
346
+ if globally_available_unique:
347
+ selected = random.choice(globally_available_unique)
348
+ log.info(
349
+ "%s Selected unique voice '%s' from global pool (all constraints relaxed).",
350
+ log_identifier_select,
351
+ selected,
352
+ )
353
+ return selected
354
+
355
+ log.warning("%s All voices are used. Reusing a voice.", log_identifier_select)
356
+ if candidate_pool:
357
+ selected = random.choice(candidate_pool)
358
+ log.info(
359
+ "%s Reusing voice '%s' from gender/tone filtered pool.",
360
+ log_identifier_select,
361
+ selected,
362
+ )
363
+ return selected
364
+ if gender_specific_voices:
365
+ selected = random.choice(gender_specific_voices)
366
+ log.info(
367
+ "%s Reusing voice '%s' from gender-only pool.",
368
+ log_identifier_select,
369
+ selected,
370
+ )
371
+ return selected
372
+ if tone_specific_voices:
373
+ selected = random.choice(tone_specific_voices)
374
+ log.info(
375
+ "%s Reusing voice '%s' from tone-only pool.",
376
+ log_identifier_select,
377
+ selected,
378
+ )
379
+ return selected
380
+ if ALL_AVAILABLE_VOICES:
381
+ selected = random.choice(ALL_AVAILABLE_VOICES)
382
+ log.info(
383
+ "%s Reusing voice '%s' from global pool.", log_identifier_select, selected
384
+ )
385
+ return selected
386
+
387
+ log.error(
388
+ "%s No voices available in any mapping or pool. Using default '%s'.",
389
+ log_identifier_select,
390
+ DEFAULT_VOICE,
391
+ )
392
+ return DEFAULT_VOICE
393
+
394
+
395
+ def _get_language_code(language: str) -> str:
396
+ """
397
+ Get BCP-47 language code from language name or code.
398
+
399
+ Args:
400
+ language: Language name or BCP-47 code
401
+
402
+ Returns:
403
+ BCP-47 language code, defaults to "en-US"
404
+ """
405
+ if not language:
406
+ return "en-US"
407
+
408
+ language_lower = language.lower().strip()
409
+
410
+ if "-" in language_lower and len(language_lower) >= 5:
411
+ return language
412
+
413
+ if language_lower in SUPPORTED_LANGUAGES:
414
+ return SUPPORTED_LANGUAGES[language_lower]
415
+
416
+ log.warning(f"[AudioTools] Unknown language '{language}', using default 'en-US'")
417
+ return "en-US"
418
+
419
+
420
+ def _create_voice_config(voice_name: str) -> adk_types.VoiceConfig:
421
+ """Create a VoiceConfig for single-voice TTS."""
422
+ return adk_types.VoiceConfig(
423
+ prebuilt_voice_config=adk_types.PrebuiltVoiceConfig(voice_name=voice_name)
424
+ )
425
+
426
+
427
+ def _create_multi_speaker_config(
428
+ speaker_configs: List[Dict[str, str]],
429
+ ) -> adk_types.MultiSpeakerVoiceConfig:
430
+ """Create a MultiSpeakerVoiceConfig for multi-speaker TTS."""
431
+ speaker_voice_configs = []
432
+
433
+ for config in speaker_configs:
434
+ speaker_name = config.get("name", "Speaker")
435
+ voice_name = config.get("voice", "Kore")
436
+
437
+ speaker_voice_config = adk_types.SpeakerVoiceConfig(
438
+ speaker=speaker_name, voice_config=_create_voice_config(voice_name)
439
+ )
440
+ speaker_voice_configs.append(speaker_voice_config)
441
+
442
+ return adk_types.MultiSpeakerVoiceConfig(
443
+ speaker_voice_configs=speaker_voice_configs
444
+ )
445
+
446
+
447
+ async def _generate_audio_with_gemini(
448
+ client: genai.Client,
449
+ prompt: str,
450
+ speech_config: adk_types.SpeechConfig,
451
+ model: str = "gemini-2.5-flash-preview-tts",
452
+ language: str = "en-US",
453
+ ) -> bytes:
454
+ """
455
+ Shared function for generating audio using Gemini API.
456
+
457
+ Args:
458
+ client: Gemini client instance
459
+ prompt: Text prompt for TTS
460
+ speech_config: Speech configuration (single or multi-speaker)
461
+ model: Gemini model to use
462
+ language: BCP-47 language code
463
+
464
+ Returns:
465
+ Raw audio data as bytes
466
+ """
467
+ config = adk_types.GenerateContentConfig(
468
+ response_modalities=["AUDIO"],
469
+ speech_config=speech_config,
470
+ )
471
+
472
+ if hasattr(config, "language"):
473
+ config.language = language
474
+
475
+ response = await asyncio.to_thread(
476
+ client.models.generate_content, model=model, contents=prompt, config=config
477
+ )
478
+
479
+ if (
480
+ not response
481
+ or not response.candidates
482
+ or not response.candidates[0].content.parts
483
+ ):
484
+ raise ValueError("Gemini API did not return valid audio data")
485
+
486
+ audio_data = response.candidates[0].content.parts[0].inline_data.data
487
+ if not audio_data:
488
+ raise ValueError("No audio data received from Gemini API")
489
+
490
+ return audio_data
491
+
492
+
493
+ def _create_wav_file(
494
+ filename: str,
495
+ pcm_data: bytes,
496
+ channels: int = 1,
497
+ rate: int = 24000,
498
+ sample_width: int = 2,
499
+ ):
500
+ """Create a proper WAV file from PCM data (based on working example)."""
501
+ with wave.open(filename, "wb") as wf:
502
+ wf.setnchannels(channels)
503
+ wf.setsampwidth(sample_width)
504
+ wf.setframerate(rate)
505
+ wf.writeframes(pcm_data)
506
+
507
+
508
+ async def _convert_pcm_to_mp3(pcm_data: bytes) -> bytes:
509
+ """
510
+ Shared function for converting raw PCM data to MP3 format.
511
+
512
+ Args:
513
+ pcm_data: Raw PCM audio data from Gemini API
514
+
515
+ Returns:
516
+ MP3 audio data as bytes
517
+ """
518
+ wav_temp_path = None
519
+ mp3_temp_path = None
520
+
521
+ try:
522
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as wav_temp:
523
+ wav_temp_path = wav_temp.name
524
+
525
+ await asyncio.to_thread(_create_wav_file, wav_temp_path, pcm_data)
526
+
527
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as mp3_temp:
528
+ mp3_temp_path = mp3_temp.name
529
+
530
+ audio = await asyncio.to_thread(AudioSegment.from_wav, wav_temp_path)
531
+ await asyncio.to_thread(audio.export, mp3_temp_path, format="mp3")
532
+
533
+ with open(mp3_temp_path, "rb") as mp3_file:
534
+ mp3_data = mp3_file.read()
535
+
536
+ return mp3_data
537
+
538
+ finally:
539
+ if wav_temp_path:
540
+ try:
541
+ os.remove(wav_temp_path)
542
+ except OSError:
543
+ pass
544
+ if mp3_temp_path:
545
+ try:
546
+ os.remove(mp3_temp_path)
547
+ except OSError:
548
+ pass
549
+
550
+
551
+ async def _save_audio_artifact(
552
+ audio_data: bytes,
553
+ filename: str,
554
+ metadata: Dict[str, Any],
555
+ tool_context: ToolContext,
556
+ ) -> Dict[str, Any]:
557
+ """
558
+ Shared function for saving audio artifacts with metadata.
559
+
560
+ Args:
561
+ audio_data: Audio data to save
562
+ filename: Filename for the artifact
563
+ metadata: Metadata dictionary
564
+ tool_context: ADK tool context
565
+
566
+ Returns:
567
+ Save result dictionary
568
+ """
569
+ inv_context = tool_context._invocation_context
570
+ app_name = inv_context.app_name
571
+ user_id = inv_context.user_id
572
+ session_id = get_original_session_id(inv_context)
573
+ artifact_service = inv_context.artifact_service
574
+
575
+ if not artifact_service:
576
+ raise ValueError("ArtifactService is not available in the context")
577
+
578
+ save_result = await save_artifact_with_metadata(
579
+ artifact_service=artifact_service,
580
+ app_name=app_name,
581
+ user_id=user_id,
582
+ session_id=session_id,
583
+ filename=filename,
584
+ content_bytes=audio_data,
585
+ mime_type="audio/mpeg",
586
+ metadata_dict=metadata,
587
+ timestamp=datetime.now(timezone.utc),
588
+ schema_max_keys=DEFAULT_SCHEMA_MAX_KEYS,
589
+ tool_context=tool_context,
590
+ )
591
+
592
+ if save_result.get("status") == "error":
593
+ raise IOError(
594
+ f"Failed to save audio artifact: {save_result.get('message', 'Unknown error')}"
595
+ )
596
+
597
+ return save_result
598
+
599
+
600
+ async def select_voice(
601
+ gender: Optional[str] = None,
602
+ tone: Optional[str] = None,
603
+ exclude_voices: Optional[List[str]] = None,
604
+ tool_context: ToolContext = None,
605
+ tool_config: Optional[Dict[str, Any]] = None,
606
+ ) -> Dict[str, Any]:
607
+ """
608
+ Selects a suitable voice name based on criteria like gender and tone.
609
+ Use this to get a consistent voice name that can be passed to the `text_to_speech` tool for multiple calls.
610
+
611
+ Args:
612
+ gender: Optional desired gender for the voice ("male", "female", "neutral").
613
+ tone: Optional tone preference (e.g., "friendly", "professional", "warm").
614
+ exclude_voices: Optional list of voice names to exclude from the selection.
615
+ tool_context: ADK tool context.
616
+ tool_config: Configuration including voice mappings.
617
+
618
+ Returns:
619
+ Dictionary with status and the selected voice name.
620
+ """
621
+ log_identifier = "[AudioTools:select_voice]"
622
+ log.info(
623
+ f"{log_identifier} Selecting voice for gender='{gender}', tone='{tone}', excluding='{exclude_voices}'"
624
+ )
625
+
626
+ try:
627
+ config = tool_config or {}
628
+ voice_tone_mapping = config.get("voice_tone_mapping", VOICE_TONE_MAPPING)
629
+ gender_voice_mapping = config.get(
630
+ "gender_voice_mapping", GENDER_TO_VOICE_MAPPING
631
+ )
632
+
633
+ used_voices = set(exclude_voices) if exclude_voices else set()
634
+
635
+ selected_voice = _get_voice_for_speaker(
636
+ gender=gender,
637
+ tone=tone,
638
+ used_voices_in_current_call=used_voices,
639
+ voice_tone_mapping=voice_tone_mapping,
640
+ gender_voice_mapping=gender_voice_mapping,
641
+ )
642
+
643
+ log.info(f"{log_identifier} Selected voice: {selected_voice}")
644
+
645
+ return {
646
+ "status": "success",
647
+ "message": f"Successfully selected voice '{selected_voice}'.",
648
+ "voice_name": selected_voice,
649
+ }
650
+
651
+ except Exception as e:
652
+ log.exception(f"{log_identifier} Unexpected error in select_voice: {e}")
653
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
654
+
655
+
656
+ async def text_to_speech(
657
+ text: str,
658
+ output_filename: Optional[str] = None,
659
+ voice_name: Optional[str] = None,
660
+ gender: Optional[str] = None,
661
+ tone: Optional[str] = None,
662
+ language: Optional[str] = None,
663
+ tool_context: ToolContext = None,
664
+ tool_config: Optional[Dict[str, Any]] = None,
665
+ ) -> Dict[str, Any]:
666
+ """
667
+ Converts text to speech using Gemini TTS API and saves as MP3 artifact.
668
+
669
+ Args:
670
+ text: The text to convert to speech.
671
+ output_filename: Optional filename for the output MP3.
672
+ voice_name: Optional specific voice name (e.g., "Kore", "Puck"). Overrides gender and tone.
673
+ gender: Optional desired gender for the voice ("male", "female", "neutral").
674
+ Used if 'voice_name' is not provided.
675
+ tone: Optional tone preference (e.g., "friendly", "professional", "warm").
676
+ Used if 'voice_name' is not provided, considered after 'gender'.
677
+ language: Optional language code (e.g., "en-US", "fr-FR", "ja-JP").
678
+ tool_context: ADK tool context.
679
+ tool_config: Configuration including API key, model settings, and voice mappings.
680
+
681
+ Returns:
682
+ Dictionary with status, output filename, version, and preview.
683
+ """
684
+ log_identifier = "[AudioTools:text_to_speech]"
685
+
686
+ if not tool_context:
687
+ return {"status": "error", "message": "ToolContext is missing"}
688
+
689
+ if not text or not text.strip():
690
+ return {"status": "error", "message": "Text input is required"}
691
+
692
+ try:
693
+ log.info(f"{log_identifier} Processing TTS request for text: '{text[:50]}...'")
694
+
695
+ config = tool_config or {}
696
+ api_key = config.get("gemini_api_key")
697
+ model = config.get("model", "gemini-2.5-flash-preview-tts")
698
+ default_voice = config.get("voice_name", DEFAULT_VOICE)
699
+ default_language = config.get("language", "en-US")
700
+ voice_tone_mapping = config.get("voice_tone_mapping", VOICE_TONE_MAPPING)
701
+ gender_voice_mapping = config.get(
702
+ "gender_voice_mapping", GENDER_TO_VOICE_MAPPING
703
+ )
704
+
705
+ if not api_key:
706
+ return {
707
+ "status": "error",
708
+ "message": "GEMINI_API_KEY is required in tool configuration",
709
+ }
710
+
711
+ final_voice = voice_name
712
+ if not final_voice:
713
+ final_voice = _get_voice_for_speaker(
714
+ gender=gender,
715
+ tone=tone,
716
+ used_voices_in_current_call=set(),
717
+ voice_tone_mapping=voice_tone_mapping,
718
+ gender_voice_mapping=gender_voice_mapping,
719
+ )
720
+ log.info(
721
+ f"{log_identifier} Selected voice '{final_voice}' for gender='{gender}', tone='{tone}'"
722
+ )
723
+ else:
724
+ log.info(
725
+ f"{log_identifier} Using specified voice_name '{final_voice}'. Gender/tone selection skipped."
726
+ )
727
+
728
+ if not final_voice:
729
+ final_voice = default_voice
730
+ log.warning(
731
+ f"{log_identifier} Voice selection resulted in None, using default '{default_voice}'."
732
+ )
733
+
734
+ final_language = _get_language_code(language or default_language)
735
+
736
+ client = genai.Client(api_key=api_key)
737
+
738
+ voice_config = _create_voice_config(final_voice)
739
+ speech_config = adk_types.SpeechConfig(voice_config=voice_config)
740
+
741
+ log.info(
742
+ f"{log_identifier} Generating audio with voice '{final_voice}' and language '{final_language}'"
743
+ )
744
+ wav_data = await _generate_audio_with_gemini(
745
+ client=client,
746
+ prompt=f"Say in a clear voice: {text}",
747
+ speech_config=speech_config,
748
+ model=model,
749
+ language=final_language,
750
+ )
751
+
752
+ log.info(f"{log_identifier} Converting audio to MP3 format")
753
+ mp3_data = await _convert_pcm_to_mp3(wav_data)
754
+
755
+ final_filename = output_filename
756
+ if not final_filename:
757
+ final_filename = f"tts_audio_{uuid.uuid4()}.mp3"
758
+ elif not final_filename.lower().endswith(".mp3"):
759
+ final_filename = f"{final_filename}.mp3"
760
+
761
+ metadata = {
762
+ "description": f"Text-to-speech audio generated from text input. Used voice: {final_voice}\nSource text: {text[:1500]}",
763
+ "source_text": text[:500],
764
+ "voice_name": final_voice,
765
+ "language": final_language,
766
+ "model": model,
767
+ "generation_tool": "text_to_speech",
768
+ "generation_timestamp": datetime.now(timezone.utc).isoformat(),
769
+ }
770
+
771
+ if voice_name:
772
+ metadata["requested_voice_name"] = voice_name
773
+ if gender:
774
+ metadata["requested_gender"] = gender
775
+ if tone:
776
+ metadata["requested_tone"] = tone
777
+ if language:
778
+ metadata["requested_language"] = language
779
+
780
+ log.info(f"{log_identifier} Saving audio artifact '{final_filename}'")
781
+ save_result = await _save_audio_artifact(
782
+ audio_data=mp3_data,
783
+ filename=final_filename,
784
+ metadata=metadata,
785
+ tool_context=tool_context,
786
+ )
787
+
788
+ log.info(
789
+ f"{log_identifier} Audio artifact saved successfully: {final_filename} v{save_result['data_version']}"
790
+ )
791
+
792
+ return {
793
+ "status": "success",
794
+ "message": f"Text-to-speech audio generated and saved successfully",
795
+ "output_filename": final_filename,
796
+ "output_version": save_result["data_version"],
797
+ "voice_used": final_voice,
798
+ "language_used": final_language,
799
+ "result_preview": f"Audio '{final_filename}' (v{save_result['data_version']}) created from text: \"{text[:100]}...\"",
800
+ }
801
+
802
+ except ValueError as ve:
803
+ log.error(f"{log_identifier} Value error: {ve}")
804
+ return {"status": "error", "message": str(ve)}
805
+ except IOError as ioe:
806
+ log.error(f"{log_identifier} IO error: {ioe}")
807
+ return {"status": "error", "message": str(ioe)}
808
+ except Exception as e:
809
+ log.exception(f"{log_identifier} Unexpected error in text_to_speech: {e}")
810
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
811
+
812
+
813
+ async def multi_speaker_text_to_speech(
814
+ conversation_text: str,
815
+ output_filename: Optional[str] = None,
816
+ speaker_configs: Optional[List[Dict[str, str]]] = None,
817
+ language: Optional[str] = None,
818
+ tool_context: ToolContext = None,
819
+ tool_config: Optional[Dict[str, Any]] = None,
820
+ ) -> Dict[str, Any]:
821
+ """
822
+ Converts conversation text with speaker labels to speech using multiple voices.
823
+
824
+ Args:
825
+ conversation_text: Text with speaker labels (e.g., "Speaker1: Hello\\nSpeaker2: Hi there").
826
+ output_filename: Optional filename for the output MP3.
827
+ speaker_configs: Optional list of speaker configurations. Each item is a dictionary:
828
+ `{"name": "Speaker1", "voice": "Kore", "gender": "female", "tone": "firm"}`.
829
+ - `name` (str): The speaker's name as it appears in `conversation_text`.
830
+ - `voice` (Optional[str]): Specific voice name to use. Overrides gender/tone.
831
+ - `gender` (Optional[str]): Desired gender ("male", "female", "neutral").
832
+ - `tone` (Optional[str]): Desired tone (e.g., "friendly").
833
+ If only `gender` and/or `tone` are provided, a voice is selected.
834
+ If no config for a speaker in text, or if speaker_configs is empty,
835
+ default speakers from tool_config are used, or a default voice is assigned.
836
+ language: Optional language code (e.g., "en-US", "fr-FR", "ja-JP").
837
+ tool_context: ADK tool context.
838
+ tool_config: Configuration including API key, model, default speakers, and voice mappings.
839
+
840
+ Returns:
841
+ Dictionary with status, output filename, version, and preview.
842
+ """
843
+ log_identifier = "[AudioTools:multi_speaker_text_to_speech]"
844
+
845
+ if not tool_context:
846
+ return {"status": "error", "message": "ToolContext is missing"}
847
+
848
+ if not conversation_text or not conversation_text.strip():
849
+ return {"status": "error", "message": "Conversation text input is required"}
850
+
851
+ try:
852
+ log.info(
853
+ f"{log_identifier} Processing multi-speaker TTS request for text: '{conversation_text[:50]}...'"
854
+ )
855
+
856
+ config = tool_config or {}
857
+ api_key = config.get("gemini_api_key")
858
+ model = config.get("model", "gemini-2.5-flash-preview-tts")
859
+ default_language = config.get("language", "en-US")
860
+ default_speakers = config.get(
861
+ "default_speakers",
862
+ [
863
+ {"name": "Speaker1", "voice": "Kore"},
864
+ {
865
+ "name": "Speaker2",
866
+ "voice": "Puck",
867
+ "gender": "male",
868
+ "tone": "upbeat",
869
+ },
870
+ {
871
+ "name": "Speaker3",
872
+ "voice": "Zephyr",
873
+ "gender": "female",
874
+ "tone": "bright",
875
+ },
876
+ ],
877
+ )
878
+ voice_tone_mapping = config.get("voice_tone_mapping", VOICE_TONE_MAPPING)
879
+ gender_voice_mapping = config.get(
880
+ "gender_voice_mapping", GENDER_TO_VOICE_MAPPING
881
+ )
882
+
883
+ if not api_key:
884
+ return {
885
+ "status": "error",
886
+ "message": "GEMINI_API_KEY is required in tool configuration",
887
+ }
888
+
889
+ final_speaker_configs = []
890
+ used_voices_in_current_call: Set[str] = set()
891
+
892
+ configs_to_process = speaker_configs if speaker_configs else default_speakers
893
+ if not configs_to_process and conversation_text:
894
+ log.warning(
895
+ "%s No speaker_configs and no default_speakers. Creating a default speaker.",
896
+ log_identifier,
897
+ )
898
+ configs_to_process = [
899
+ {"name": "Speaker1", "gender": "neutral", "tone": "neutral"}
900
+ ]
901
+
902
+ for i, config_item in enumerate(configs_to_process):
903
+ speaker_name = config_item.get("name", f"Speaker{i+1}")
904
+ voice_name_from_config = config_item.get("voice")
905
+ gender_from_config = config_item.get("gender")
906
+ tone_from_config = config_item.get("tone")
907
+ final_voice_for_speaker = None
908
+
909
+ if voice_name_from_config:
910
+ final_voice_for_speaker = voice_name_from_config
911
+ log.info(
912
+ f"{log_identifier} Using specified voice '{final_voice_for_speaker}' for speaker '{speaker_name}'."
913
+ )
914
+ else:
915
+ final_voice_for_speaker = _get_voice_for_speaker(
916
+ gender=gender_from_config,
917
+ tone=tone_from_config,
918
+ used_voices_in_current_call=used_voices_in_current_call,
919
+ voice_tone_mapping=voice_tone_mapping,
920
+ gender_voice_mapping=gender_voice_mapping,
921
+ )
922
+ log.info(
923
+ f"{log_identifier} Selected voice '{final_voice_for_speaker}' for speaker '{speaker_name}' (gender='{gender_from_config}', tone='{tone_from_config}')."
924
+ )
925
+
926
+ if not final_voice_for_speaker:
927
+ final_voice_for_speaker = DEFAULT_VOICE
928
+ log.warning(
929
+ f"{log_identifier} Voice selection for speaker '{speaker_name}' resulted in None, using default '{DEFAULT_VOICE}'."
930
+ )
931
+
932
+ final_speaker_configs.append(
933
+ {"name": speaker_name, "voice": final_voice_for_speaker}
934
+ )
935
+ if final_voice_for_speaker:
936
+ used_voices_in_current_call.add(final_voice_for_speaker)
937
+
938
+ final_language = _get_language_code(language or default_language)
939
+
940
+ client = genai.Client(api_key=api_key)
941
+
942
+ multi_speaker_config = _create_multi_speaker_config(final_speaker_configs)
943
+ speech_config = adk_types.SpeechConfig(
944
+ multi_speaker_voice_config=multi_speaker_config
945
+ )
946
+
947
+ log.info(
948
+ f"{log_identifier} Generating multi-speaker audio with {len(final_speaker_configs)} speakers and language '{final_language}'"
949
+ )
950
+ wav_data = await _generate_audio_with_gemini(
951
+ client=client,
952
+ prompt=f"TTS the following conversation: {conversation_text}",
953
+ speech_config=speech_config,
954
+ model=model,
955
+ language=final_language,
956
+ )
957
+
958
+ log.info(f"{log_identifier} Converting audio to MP3 format")
959
+ mp3_data = await _convert_pcm_to_mp3(wav_data)
960
+
961
+ final_filename = output_filename
962
+ if not final_filename:
963
+ final_filename = f"multi_speaker_tts_{uuid.uuid4()}.mp3"
964
+ elif not final_filename.lower().endswith(".mp3"):
965
+ final_filename = f"{final_filename}.mp3"
966
+
967
+ voice_info = ", ".join(
968
+ [f"{s['name']} ({s['voice']})" for s in final_speaker_configs]
969
+ )
970
+
971
+ metadata = {
972
+ "description": f"Multi-speaker text-to-speech audio generated from conversation. Voices used: {voice_info}\nSource text: {conversation_text[:1500]}...",
973
+ "source_text": conversation_text[:500],
974
+ "language": final_language,
975
+ "model": model,
976
+ "generation_tool": "multi_speaker_text_to_speech",
977
+ "generation_timestamp": datetime.now(timezone.utc).isoformat(),
978
+ "speaker_count": len(final_speaker_configs),
979
+ "speakers_used_details": json.dumps(final_speaker_configs),
980
+ }
981
+
982
+ if language:
983
+ metadata["requested_language"] = language
984
+ if speaker_configs:
985
+ metadata["requested_speaker_configs"] = json.dumps(speaker_configs)
986
+
987
+ log.info(f"{log_identifier} Saving audio artifact '{final_filename}'")
988
+ save_result = await _save_audio_artifact(
989
+ audio_data=mp3_data,
990
+ filename=final_filename,
991
+ metadata=metadata,
992
+ tool_context=tool_context,
993
+ )
994
+
995
+ log.info(
996
+ f"{log_identifier} Multi-speaker audio artifact saved successfully: {final_filename} v{save_result['data_version']}"
997
+ )
998
+
999
+ speaker_summary = ", ".join(
1000
+ [f"{s['name']} ({s['voice']})" for s in final_speaker_configs]
1001
+ )
1002
+
1003
+ return {
1004
+ "status": "success",
1005
+ "message": f"Multi-speaker text-to-speech audio generated and saved successfully: Speakers: {speaker_summary}",
1006
+ "output_filename": final_filename,
1007
+ "output_version": save_result["data_version"],
1008
+ "speakers_used": speaker_summary,
1009
+ "language_used": final_language,
1010
+ "result_preview": f"Multi-speaker audio '{final_filename}' (v{save_result['data_version']}) created with {len(final_speaker_configs)} speakers from conversation: \"{conversation_text[:100]}...\"",
1011
+ }
1012
+
1013
+ except ValueError as ve:
1014
+ log.error(f"{log_identifier} Value error: {ve}")
1015
+ return {"status": "error", "message": str(ve)}
1016
+ except IOError as ioe:
1017
+ log.error(f"{log_identifier} IO error: {ioe}")
1018
+ return {"status": "error", "message": str(ioe)}
1019
+ except Exception as e:
1020
+ log.exception(
1021
+ f"{log_identifier} Unexpected error in multi_speaker_text_to_speech: {e}"
1022
+ )
1023
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
1024
+
1025
+
1026
+ def _is_supported_audio_format_for_transcription(filename: str) -> bool:
1027
+ """Check if the audio format is supported for transcription."""
1028
+ ext = os.path.splitext(filename)[1].lower()
1029
+ supported_formats = {".wav", ".mp3"}
1030
+ return ext in supported_formats
1031
+
1032
+
1033
+ def _get_audio_mime_type(filename: str) -> str:
1034
+ """Get MIME type from audio file extension."""
1035
+ ext = os.path.splitext(filename)[1].lower()
1036
+ mime_mapping = {".wav": "audio/wav", ".mp3": "audio/mpeg"}
1037
+ return mime_mapping.get(ext, "audio/wav")
1038
+
1039
+
1040
+ async def concatenate_audio(
1041
+ clips_to_join: List[Dict[str, Any]],
1042
+ output_filename: Optional[str] = None,
1043
+ tool_context: ToolContext = None,
1044
+ tool_config: Optional[Dict[str, Any]] = None,
1045
+ ) -> Dict[str, Any]:
1046
+ """
1047
+ Combines multiple audio artifacts in a specified order into a single audio file.
1048
+ Allows for custom pause durations between each clip.
1049
+
1050
+ Args:
1051
+ clips_to_join: An ordered list of clip objects to be joined. Each object should contain:
1052
+ - `filename` (str): The artifact filename of the audio clip (with optional :version).
1053
+ - `pause_after_ms` (Optional[int]): The duration of silence, in milliseconds,
1054
+ to insert *after* this clip. If omitted, a default pause will be used. The gap between
1055
+ two people speaking in a conversation is typically around 250ms.
1056
+ output_filename: Optional. The desired filename for the final combined audio artifact.
1057
+ tool_context: The context provided by the ADK framework.
1058
+ tool_config: Configuration dictionary.
1059
+
1060
+ Returns:
1061
+ A dictionary with status and output artifact details.
1062
+ """
1063
+ log_identifier = "[AudioTools:concatenate_audio]"
1064
+ if not tool_context:
1065
+ return {"status": "error", "message": "ToolContext is missing"}
1066
+ if not clips_to_join:
1067
+ return {
1068
+ "status": "error",
1069
+ "message": "The 'clips_to_join' list cannot be empty.",
1070
+ }
1071
+
1072
+ try:
1073
+ inv_context = tool_context._invocation_context
1074
+ app_name = inv_context.app_name
1075
+ user_id = inv_context.user_id
1076
+ session_id = get_original_session_id(inv_context)
1077
+ artifact_service = inv_context.artifact_service
1078
+ host_component = getattr(inv_context.agent, "host_component", None)
1079
+
1080
+ if not artifact_service:
1081
+ raise ValueError("ArtifactService is not available in the context.")
1082
+
1083
+ config = tool_config or {}
1084
+ default_pause_ms = config.get("default_pause_ms", 250)
1085
+
1086
+ combined_audio = None
1087
+ source_filenames = []
1088
+
1089
+ for i, clip_info in enumerate(clips_to_join):
1090
+ clip_filename_with_version = clip_info.get("filename")
1091
+ if not clip_filename_with_version:
1092
+ raise ValueError(
1093
+ f"Clip at index {i} is missing the required 'filename' key."
1094
+ )
1095
+
1096
+ source_filenames.append(clip_filename_with_version)
1097
+
1098
+ parts = clip_filename_with_version.rsplit(":", 1)
1099
+ filename_base = parts[0]
1100
+ version_str = None
1101
+ if len(parts) > 1 and parts[1].isdigit():
1102
+ version_str = parts[1]
1103
+ version_to_load = int(version_str) if version_str else "latest"
1104
+
1105
+ load_result = await load_artifact_content_or_metadata(
1106
+ artifact_service=artifact_service,
1107
+ app_name=app_name,
1108
+ user_id=user_id,
1109
+ session_id=session_id,
1110
+ filename=filename_base,
1111
+ version=version_to_load,
1112
+ return_raw_bytes=True,
1113
+ component=host_component,
1114
+ log_identifier_prefix=f"{log_identifier}[LoadClip:{clip_filename_with_version}]",
1115
+ )
1116
+
1117
+ if load_result.get("status") != "success":
1118
+ raise FileNotFoundError(
1119
+ f"Failed to load audio clip '{clip_filename_with_version}': {load_result.get('message')}"
1120
+ )
1121
+
1122
+ audio_bytes = load_result.get("raw_bytes")
1123
+ mime_type = load_result.get("mime_type", "audio/mpeg")
1124
+
1125
+ audio_format = "mp3"
1126
+ if "wav" in mime_type:
1127
+ audio_format = "wav"
1128
+ elif "mpeg" in mime_type:
1129
+ audio_format = "mp3"
1130
+
1131
+ log.debug(
1132
+ f"{log_identifier} Loading clip '{clip_filename_with_version}' with format '{audio_format}'"
1133
+ )
1134
+
1135
+ clip_segment = await asyncio.to_thread(
1136
+ AudioSegment.from_file, io.BytesIO(audio_bytes), format=audio_format
1137
+ )
1138
+
1139
+ if combined_audio is None:
1140
+ combined_audio = clip_segment
1141
+ else:
1142
+ combined_audio += clip_segment
1143
+
1144
+ if i < len(clips_to_join) - 1:
1145
+ pause_ms = clip_info.get("pause_after_ms", default_pause_ms)
1146
+ if pause_ms > 0:
1147
+ pause_segment = AudioSegment.silent(duration=pause_ms)
1148
+ combined_audio += pause_segment
1149
+ log.debug(
1150
+ f"{log_identifier} Added {pause_ms}ms pause after '{clip_filename_with_version}'."
1151
+ )
1152
+
1153
+ if combined_audio is None:
1154
+ return {
1155
+ "status": "error",
1156
+ "message": "No audio clips were successfully processed.",
1157
+ }
1158
+
1159
+ output_buffer = io.BytesIO()
1160
+ await asyncio.to_thread(combined_audio.export, output_buffer, format="mp3")
1161
+ mp3_data = output_buffer.getvalue()
1162
+
1163
+ final_filename = output_filename
1164
+ if not final_filename:
1165
+ final_filename = f"concatenated_audio_{uuid.uuid4()}.mp3"
1166
+ elif not final_filename.lower().endswith(".mp3"):
1167
+ final_filename = f"{final_filename}.mp3"
1168
+
1169
+ metadata = {
1170
+ "description": f"Concatenated audio created from {len(clips_to_join)} clips.",
1171
+ "source_clips": source_filenames,
1172
+ "generation_tool": "concatenate_audio",
1173
+ "generation_timestamp": datetime.now(timezone.utc).isoformat(),
1174
+ }
1175
+
1176
+ log.info(
1177
+ f"{log_identifier} Saving concatenated audio artifact '{final_filename}'"
1178
+ )
1179
+ save_result = await _save_audio_artifact(
1180
+ audio_data=mp3_data,
1181
+ filename=final_filename,
1182
+ metadata=metadata,
1183
+ tool_context=tool_context,
1184
+ )
1185
+
1186
+ log.info(
1187
+ f"{log_identifier} Concatenated audio artifact saved successfully: {final_filename} v{save_result['data_version']}"
1188
+ )
1189
+
1190
+ return {
1191
+ "status": "success",
1192
+ "message": f"Audio clips concatenated and saved successfully.",
1193
+ "output_filename": final_filename,
1194
+ "output_version": save_result["data_version"],
1195
+ "result_preview": f"Concatenated audio '{final_filename}' (v{save_result['data_version']}) created from {len(clips_to_join)} clips.",
1196
+ }
1197
+
1198
+ except FileNotFoundError as e:
1199
+ log.warning(f"{log_identifier} File not found error: {e}")
1200
+ return {"status": "error", "message": str(e)}
1201
+ except ValueError as ve:
1202
+ log.error(f"{log_identifier} Value error: {ve}")
1203
+ return {"status": "error", "message": str(ve)}
1204
+ except IOError as ioe:
1205
+ log.error(f"{log_identifier} IO error: {ioe}")
1206
+ return {"status": "error", "message": str(ioe)}
1207
+ except Exception as e:
1208
+ log.exception(f"{log_identifier} Unexpected error in concatenate_audio: {e}")
1209
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
1210
+
1211
+
1212
+ async def transcribe_audio(
1213
+ audio_filename: str,
1214
+ output_filename: Optional[str] = None,
1215
+ description: Optional[str] = None,
1216
+ tool_context: ToolContext = None,
1217
+ tool_config: Optional[Dict[str, Any]] = None,
1218
+ ) -> Dict[str, Any]:
1219
+ """
1220
+ Transcribes an audio recording and saves the transcription as a text artifact.
1221
+
1222
+ Args:
1223
+ audio_filename: The filename (and optional :version) of the input audio artifact.
1224
+ output_filename: Optional filename for the transcription text file (without extension).
1225
+ description: Optional description of the transcription for metadata.
1226
+ tool_context: The context provided by the ADK framework.
1227
+ tool_config: Configuration dictionary containing model, api_base, api_key.
1228
+
1229
+ Returns:
1230
+ A dictionary containing:
1231
+ - "status": "success" or "error".
1232
+ - "message": A descriptive message about the outcome.
1233
+ - "output_filename": The name of the saved transcription artifact.
1234
+ - "output_version": The version of the saved transcription artifact.
1235
+ - "audio_filename": The name of the input audio artifact.
1236
+ - "audio_version": The version of the input audio artifact.
1237
+ """
1238
+ log_identifier = f"[AudioTools:transcribe_audio:{audio_filename}]"
1239
+ if not tool_context:
1240
+ log.error(f"{log_identifier} ToolContext is missing.")
1241
+ return {"status": "error", "message": "ToolContext is missing."}
1242
+
1243
+ try:
1244
+ inv_context = tool_context._invocation_context
1245
+ if not inv_context:
1246
+ raise ValueError("InvocationContext is not available.")
1247
+
1248
+ app_name = getattr(inv_context, "app_name", None)
1249
+ user_id = getattr(inv_context, "user_id", None)
1250
+ session_id = get_original_session_id(inv_context)
1251
+ artifact_service = getattr(inv_context, "artifact_service", None)
1252
+
1253
+ if not all([app_name, user_id, session_id, artifact_service]):
1254
+ missing_parts = [
1255
+ part
1256
+ for part, val in [
1257
+ ("app_name", app_name),
1258
+ ("user_id", user_id),
1259
+ ("session_id", session_id),
1260
+ ("artifact_service", artifact_service),
1261
+ ]
1262
+ if not val
1263
+ ]
1264
+ raise ValueError(
1265
+ f"Missing required context parts: {', '.join(missing_parts)}"
1266
+ )
1267
+
1268
+ log.info(f"{log_identifier} Processing request for session {session_id}.")
1269
+
1270
+ current_tool_config = tool_config if tool_config is not None else {}
1271
+
1272
+ if not current_tool_config:
1273
+ log.warning(
1274
+ f"{log_identifier} Tool-specific configuration (tool_config) is empty."
1275
+ )
1276
+
1277
+ model_name = current_tool_config.get("model")
1278
+ api_key = current_tool_config.get("api_key")
1279
+ api_base = current_tool_config.get("api_base")
1280
+
1281
+ if not model_name:
1282
+ raise ValueError("'model' configuration is missing in tool_config.")
1283
+ if not api_key:
1284
+ raise ValueError("'api_key' configuration is missing in tool_config.")
1285
+ if not api_base:
1286
+ raise ValueError("'api_base' configuration is missing in tool_config.")
1287
+
1288
+ log.debug(f"{log_identifier} Using model: {model_name}, API base: {api_base}")
1289
+
1290
+ parts = audio_filename.split(":", 1)
1291
+ filename_base_for_load = parts[0]
1292
+ version_str = parts[1] if len(parts) > 1 else None
1293
+ version_to_load = int(version_str) if version_str else None
1294
+
1295
+ if not _is_supported_audio_format_for_transcription(filename_base_for_load):
1296
+ raise ValueError(f"Unsupported audio format. Supported formats: .wav, .mp3")
1297
+
1298
+ if version_to_load is None:
1299
+ list_versions_method = getattr(artifact_service, "list_versions")
1300
+ if inspect.iscoroutinefunction(list_versions_method):
1301
+ versions = await list_versions_method(
1302
+ app_name=app_name,
1303
+ user_id=user_id,
1304
+ session_id=session_id,
1305
+ filename=filename_base_for_load,
1306
+ )
1307
+ else:
1308
+ versions = await asyncio.to_thread(
1309
+ list_versions_method,
1310
+ app_name=app_name,
1311
+ user_id=user_id,
1312
+ session_id=session_id,
1313
+ filename=filename_base_for_load,
1314
+ )
1315
+ if not versions:
1316
+ raise FileNotFoundError(
1317
+ f"Audio artifact '{filename_base_for_load}' not found."
1318
+ )
1319
+ version_to_load = max(versions)
1320
+ log.debug(
1321
+ f"{log_identifier} Using latest version for input: {version_to_load}"
1322
+ )
1323
+
1324
+ load_artifact_method = getattr(artifact_service, "load_artifact")
1325
+ if inspect.iscoroutinefunction(load_artifact_method):
1326
+ audio_artifact_part = await load_artifact_method(
1327
+ app_name=app_name,
1328
+ user_id=user_id,
1329
+ session_id=session_id,
1330
+ filename=filename_base_for_load,
1331
+ version=version_to_load,
1332
+ )
1333
+ else:
1334
+ audio_artifact_part = await asyncio.to_thread(
1335
+ load_artifact_method,
1336
+ app_name=app_name,
1337
+ user_id=user_id,
1338
+ session_id=session_id,
1339
+ filename=filename_base_for_load,
1340
+ version=version_to_load,
1341
+ )
1342
+
1343
+ if not audio_artifact_part or not audio_artifact_part.inline_data:
1344
+ raise FileNotFoundError(
1345
+ f"Content for audio artifact '{filename_base_for_load}' v{version_to_load} not found."
1346
+ )
1347
+
1348
+ audio_bytes = audio_artifact_part.inline_data.data
1349
+ audio_mime_type = audio_artifact_part.inline_data.mime_type or "application/octet-stream"
1350
+ log.debug(f"{log_identifier} Loaded audio artifact: {len(audio_bytes)} bytes")
1351
+
1352
+ # Load source audio metadata to copy description
1353
+ source_audio_metadata = {}
1354
+ try:
1355
+ metadata_result = await load_artifact_content_or_metadata(
1356
+ artifact_service=artifact_service,
1357
+ app_name=app_name,
1358
+ user_id=user_id,
1359
+ session_id=session_id,
1360
+ filename=filename_base_for_load,
1361
+ version=version_to_load,
1362
+ load_metadata_only=True,
1363
+ log_identifier_prefix=f"{log_identifier}[source_metadata]",
1364
+ )
1365
+ if metadata_result.get("status") == "success":
1366
+ source_audio_metadata = metadata_result.get("metadata", {})
1367
+ log.debug(f"{log_identifier} Loaded source audio metadata")
1368
+ except Exception as meta_err:
1369
+ log.warning(f"{log_identifier} Could not load source audio metadata: {meta_err}")
1370
+
1371
+ temp_file_path = None
1372
+ try:
1373
+ file_ext = os.path.splitext(filename_base_for_load)[1]
1374
+
1375
+ with tempfile.NamedTemporaryFile(
1376
+ suffix=file_ext, delete=False
1377
+ ) as temp_file:
1378
+ temp_file_path = temp_file.name
1379
+ temp_file.write(audio_bytes)
1380
+
1381
+ log.debug(f"{log_identifier} Created temporary file: {temp_file_path}")
1382
+
1383
+ api_url = f"{api_base.rstrip('/')}/v1/audio/transcriptions"
1384
+ headers = {"Authorization": f"Bearer {api_key}"}
1385
+
1386
+ mime_type = _get_audio_mime_type(filename_base_for_load)
1387
+
1388
+ with open(temp_file_path, "rb") as audio_file:
1389
+ files = {
1390
+ "file": (filename_base_for_load, audio_file, mime_type),
1391
+ "model": (None, model_name),
1392
+ }
1393
+
1394
+ log.debug(f"{log_identifier} Calling transcription API...")
1395
+
1396
+ async with httpx.AsyncClient(timeout=60.0) as client:
1397
+ response = await client.post(api_url, headers=headers, files=files)
1398
+ response.raise_for_status()
1399
+ response_data = response.json()
1400
+
1401
+ log.debug(f"{log_identifier} Transcription API response received.")
1402
+
1403
+ if not response_data.get("text"):
1404
+ raise ValueError("API response does not contain transcription text.")
1405
+
1406
+ transcription = response_data["text"]
1407
+
1408
+ log.info(
1409
+ f"{log_identifier} Audio transcribed successfully. Transcription length: {len(transcription)} characters"
1410
+ )
1411
+
1412
+ # Determine output filename
1413
+ if output_filename:
1414
+ final_filename = ensure_correct_extension(output_filename, "txt")
1415
+ else:
1416
+ # Auto-generate from source audio filename
1417
+ base_name = os.path.splitext(filename_base_for_load)[0]
1418
+ final_filename = f"{base_name}_transcription.txt"
1419
+
1420
+ # Build comprehensive metadata
1421
+ transcription_word_count = len(transcription.split())
1422
+ transcription_char_count = len(transcription)
1423
+
1424
+ # Build description from multiple sources
1425
+ description_parts = []
1426
+
1427
+ # Add user-provided description
1428
+ if description:
1429
+ description_parts.append(description)
1430
+
1431
+ # Add source audio description if available
1432
+ source_description = source_audio_metadata.get("description")
1433
+ if source_description:
1434
+ description_parts.append(f"Source: {source_description}")
1435
+
1436
+ # Add source audio info
1437
+ description_parts.append(f"Transcribed from audio file '{filename_base_for_load}' (version {version_to_load}, {audio_mime_type})")
1438
+
1439
+ # Combine all description parts
1440
+ final_description = ". ".join(description_parts)
1441
+
1442
+ metadata = {
1443
+ "description": final_description,
1444
+ "source_audio_filename": filename_base_for_load,
1445
+ "source_audio_version": version_to_load,
1446
+ "source_audio_mime_type": audio_mime_type,
1447
+ "transcription_model": model_name,
1448
+ "transcription_timestamp": datetime.now(timezone.utc).isoformat(),
1449
+ "transcription_word_count": transcription_word_count,
1450
+ "transcription_char_count": transcription_char_count,
1451
+ "generation_tool": "transcribe_audio",
1452
+ }
1453
+
1454
+ # Copy source audio description separately for reference
1455
+ if source_description:
1456
+ metadata["source_audio_description"] = source_description
1457
+
1458
+ # Add user-provided description separately if provided
1459
+ if description:
1460
+ metadata["user_provided_description"] = description
1461
+
1462
+ # Save transcription as text artifact
1463
+ transcription_bytes = transcription.encode("utf-8")
1464
+
1465
+ save_result = await save_artifact_with_metadata(
1466
+ artifact_service=artifact_service,
1467
+ app_name=app_name,
1468
+ user_id=user_id,
1469
+ session_id=session_id,
1470
+ filename=final_filename,
1471
+ content_bytes=transcription_bytes,
1472
+ mime_type="text/plain",
1473
+ metadata_dict=metadata,
1474
+ timestamp=datetime.now(timezone.utc),
1475
+ schema_max_keys=DEFAULT_SCHEMA_MAX_KEYS,
1476
+ tool_context=tool_context,
1477
+ )
1478
+
1479
+ if save_result.get("status") != "success":
1480
+ error_msg = save_result.get("message", "Failed to save transcription artifact")
1481
+ log.error(f"{log_identifier} {error_msg}")
1482
+ return {
1483
+ "status": "error",
1484
+ "message": f"Transcription succeeded but failed to save as artifact: {error_msg}",
1485
+ }
1486
+
1487
+ log.info(
1488
+ f"{log_identifier} Transcription saved to '{final_filename}' v{save_result['data_version']}"
1489
+ )
1490
+
1491
+ return {
1492
+ "status": "success",
1493
+ "message": "Audio transcribed and saved successfully",
1494
+ "output_filename": final_filename,
1495
+ "output_version": save_result["data_version"],
1496
+ "audio_filename": filename_base_for_load,
1497
+ "audio_version": version_to_load,
1498
+ "result_preview": f"Transcription saved to '{final_filename}' (v{save_result['data_version']}). Length: {transcription_char_count} characters, {transcription_word_count} words."
1499
+ }
1500
+
1501
+ finally:
1502
+ if temp_file_path and os.path.exists(temp_file_path):
1503
+ try:
1504
+ os.remove(temp_file_path)
1505
+ log.debug(
1506
+ f"{log_identifier} Cleaned up temporary file: {temp_file_path}"
1507
+ )
1508
+ except OSError as e:
1509
+ log.warning(
1510
+ f"{log_identifier} Failed to clean up temporary file {temp_file_path}: {e}"
1511
+ )
1512
+
1513
+ except FileNotFoundError as e:
1514
+ log.warning(f"{log_identifier} File not found error: {e}")
1515
+ return {"status": "error", "message": str(e)}
1516
+ except ValueError as ve:
1517
+ log.error(f"{log_identifier} Value error: {ve}")
1518
+ return {"status": "error", "message": str(ve)}
1519
+ except httpx.HTTPStatusError as hse:
1520
+ log.error(
1521
+ f"{log_identifier} HTTP error calling transcription API: {hse.response.status_code} - {hse.response.text}"
1522
+ )
1523
+ return {"status": "error", "message": f"API error: {hse.response.status_code}"}
1524
+ except httpx.RequestError as re:
1525
+ log.error(f"{log_identifier} Request error calling transcription API: {re}")
1526
+ return {"status": "error", "message": f"Request error: {re}"}
1527
+ except json.JSONDecodeError as jde:
1528
+ log.error(f"{log_identifier} JSON decode error: {jde}")
1529
+ return {"status": "error", "message": "Invalid JSON response from API"}
1530
+ except Exception as e:
1531
+ log.exception(f"{log_identifier} Unexpected error in transcribe_audio: {e}")
1532
+ return {"status": "error", "message": f"An unexpected error occurred: {e}"}
1533
+
1534
+
1535
+ select_voice_tool_def = BuiltinTool(
1536
+ name="select_voice",
1537
+ implementation=select_voice,
1538
+ description="Selects a suitable voice name based on criteria like gender and tone. Use this to get a consistent voice name that can be passed to the `text_to_speech` tool for multiple calls.",
1539
+ category="audio",
1540
+ required_scopes=["tool:audio:tts"],
1541
+ parameters=adk_types.Schema(
1542
+ type=adk_types.Type.OBJECT,
1543
+ properties={
1544
+ "gender": adk_types.Schema(
1545
+ type=adk_types.Type.STRING,
1546
+ description="Optional desired gender for the voice ('male', 'female', 'neutral').",
1547
+ nullable=True,
1548
+ ),
1549
+ "tone": adk_types.Schema(
1550
+ type=adk_types.Type.STRING,
1551
+ description="Optional tone preference (e.g., 'friendly', 'professional').",
1552
+ nullable=True,
1553
+ ),
1554
+ "exclude_voices": adk_types.Schema(
1555
+ type=adk_types.Type.ARRAY,
1556
+ items=adk_types.Schema(type=adk_types.Type.STRING),
1557
+ description="Optional list of voice names to exclude from the selection.",
1558
+ nullable=True,
1559
+ ),
1560
+ },
1561
+ required=[],
1562
+ ),
1563
+ examples=[],
1564
+ )
1565
+
1566
+ text_to_speech_tool_def = BuiltinTool(
1567
+ name="text_to_speech",
1568
+ implementation=text_to_speech,
1569
+ description="Converts text to speech using Gemini TTS API and saves as MP3 artifact.",
1570
+ category="audio",
1571
+ required_scopes=["tool:audio:tts"],
1572
+ parameters=adk_types.Schema(
1573
+ type=adk_types.Type.OBJECT,
1574
+ properties={
1575
+ "text": adk_types.Schema(
1576
+ type=adk_types.Type.STRING, description="The text to convert to speech."
1577
+ ),
1578
+ "output_filename": adk_types.Schema(
1579
+ type=adk_types.Type.STRING,
1580
+ description="Optional filename for the output MP3.",
1581
+ nullable=True,
1582
+ ),
1583
+ "voice_name": adk_types.Schema(
1584
+ type=adk_types.Type.STRING,
1585
+ description="Optional specific voice name (e.g., 'Kore', 'Puck'). Overrides gender and tone.",
1586
+ nullable=True,
1587
+ ),
1588
+ "gender": adk_types.Schema(
1589
+ type=adk_types.Type.STRING,
1590
+ description="Optional desired gender for the voice ('male', 'female', 'neutral'). Used if 'voice_name' is not provided.",
1591
+ nullable=True,
1592
+ ),
1593
+ "tone": adk_types.Schema(
1594
+ type=adk_types.Type.STRING,
1595
+ description="Optional tone preference (e.g., 'friendly', 'professional'). Used if 'voice_name' is not provided.",
1596
+ nullable=True,
1597
+ ),
1598
+ "language": adk_types.Schema(
1599
+ type=adk_types.Type.STRING,
1600
+ description="Optional language code (e.g., 'en-US', 'fr-FR').",
1601
+ nullable=True,
1602
+ ),
1603
+ },
1604
+ required=["text"],
1605
+ ),
1606
+ examples=[],
1607
+ )
1608
+
1609
+ multi_speaker_text_to_speech_tool_def = BuiltinTool(
1610
+ name="multi_speaker_text_to_speech",
1611
+ implementation=multi_speaker_text_to_speech,
1612
+ description="Converts conversation text with speaker labels to speech using multiple voices.",
1613
+ category="audio",
1614
+ required_scopes=["tool:audio:tts"],
1615
+ parameters=adk_types.Schema(
1616
+ type=adk_types.Type.OBJECT,
1617
+ properties={
1618
+ "conversation_text": adk_types.Schema(
1619
+ type=adk_types.Type.STRING,
1620
+ description="Text with speaker labels (e.g., 'Speaker1: Hello\\nSpeaker2: Hi there').",
1621
+ ),
1622
+ "output_filename": adk_types.Schema(
1623
+ type=adk_types.Type.STRING,
1624
+ description="Optional filename for the output MP3.",
1625
+ nullable=True,
1626
+ ),
1627
+ "speaker_configs": adk_types.Schema(
1628
+ type=adk_types.Type.ARRAY,
1629
+ items=adk_types.Schema(
1630
+ type=adk_types.Type.OBJECT,
1631
+ description="Configuration for a single speaker.",
1632
+ properties={
1633
+ "name": adk_types.Schema(
1634
+ type=adk_types.Type.STRING,
1635
+ description="The speaker's name as it appears in conversation_text.",
1636
+ ),
1637
+ "voice": adk_types.Schema(
1638
+ type=adk_types.Type.STRING,
1639
+ description="Specific voice name to use. Overrides gender/tone.",
1640
+ nullable=True,
1641
+ ),
1642
+ "gender": adk_types.Schema(
1643
+ type=adk_types.Type.STRING,
1644
+ description="Desired gender ('male', 'female', 'neutral').",
1645
+ nullable=True,
1646
+ ),
1647
+ "tone": adk_types.Schema(
1648
+ type=adk_types.Type.STRING,
1649
+ description="Desired tone (e.g., 'friendly').",
1650
+ nullable=True,
1651
+ ),
1652
+ },
1653
+ ),
1654
+ description="Optional list of speaker configurations.",
1655
+ nullable=True,
1656
+ ),
1657
+ "language": adk_types.Schema(
1658
+ type=adk_types.Type.STRING,
1659
+ description="Optional language code (e.g., 'en-US', 'fr-FR').",
1660
+ nullable=True,
1661
+ ),
1662
+ },
1663
+ required=["conversation_text"],
1664
+ ),
1665
+ examples=[],
1666
+ )
1667
+
1668
+ concatenate_audio_tool_def = BuiltinTool(
1669
+ name="concatenate_audio",
1670
+ implementation=concatenate_audio,
1671
+ description="Combines multiple audio artifacts in a specified order into a single audio file. Allows for custom pause durations between each clip.",
1672
+ category="audio",
1673
+ required_scopes=["tool:audio:edit", "tool:artifact:create", "tool:artifact:load"],
1674
+ parameters=adk_types.Schema(
1675
+ type=adk_types.Type.OBJECT,
1676
+ properties={
1677
+ "clips_to_join": adk_types.Schema(
1678
+ type=adk_types.Type.ARRAY,
1679
+ description="An ordered list of clip objects to be joined.",
1680
+ items=adk_types.Schema(
1681
+ type=adk_types.Type.OBJECT,
1682
+ properties={
1683
+ "filename": adk_types.Schema(
1684
+ type=adk_types.Type.STRING,
1685
+ description="The artifact filename of the audio clip (with optional :version).",
1686
+ ),
1687
+ "pause_after_ms": adk_types.Schema(
1688
+ type=adk_types.Type.INTEGER,
1689
+ description="Optional duration of silence in milliseconds to insert *after* this clip. Defaults to 500ms.",
1690
+ nullable=True,
1691
+ ),
1692
+ },
1693
+ required=["filename"],
1694
+ ),
1695
+ ),
1696
+ "output_filename": adk_types.Schema(
1697
+ type=adk_types.Type.STRING,
1698
+ description="Optional. The desired filename for the final combined audio artifact.",
1699
+ nullable=True,
1700
+ ),
1701
+ },
1702
+ required=["clips_to_join"],
1703
+ ),
1704
+ examples=[],
1705
+ )
1706
+
1707
+ transcribe_audio_tool_def = BuiltinTool(
1708
+ name="transcribe_audio",
1709
+ implementation=transcribe_audio,
1710
+ description="Transcribes an audio recording and saves the transcription as a text artifact.",
1711
+ category="audio",
1712
+ required_scopes=["tool:audio:transcribe", "tool:artifact:create"],
1713
+ parameters=adk_types.Schema(
1714
+ type=adk_types.Type.OBJECT,
1715
+ properties={
1716
+ "audio_filename": adk_types.Schema(
1717
+ type=adk_types.Type.STRING,
1718
+ description="The filename (and optional :version) of the input audio artifact.",
1719
+ ),
1720
+ "output_filename": adk_types.Schema(
1721
+ type=adk_types.Type.STRING,
1722
+ description="Optional filename for the transcription text file (without .txt extension). If not provided, will auto-generate from source audio filename.",
1723
+ nullable=True,
1724
+ ),
1725
+ "description": adk_types.Schema(
1726
+ type=adk_types.Type.STRING,
1727
+ description="Optional description of the transcription for metadata (e.g., 'Transcription of customer support call about billing inquiry'). Will be combined with source audio description if available.",
1728
+ nullable=True,
1729
+ ),
1730
+ },
1731
+ required=["audio_filename"],
1732
+ ),
1733
+ examples=[],
1734
+ )
1735
+
1736
+ tool_registry.register(select_voice_tool_def)
1737
+ tool_registry.register(text_to_speech_tool_def)
1738
+ tool_registry.register(multi_speaker_text_to_speech_tool_def)
1739
+ tool_registry.register(concatenate_audio_tool_def)
1740
+ tool_registry.register(transcribe_audio_tool_def)