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,1585 @@
1
+ """
2
+ Concrete implementation of a proxy for standard A2A-over-HTTPS agents.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import uuid
9
+ from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
10
+ from urllib.parse import urlparse
11
+
12
+ import httpx
13
+
14
+ from a2a.client import (
15
+ A2ACardResolver,
16
+ Client,
17
+ ClientConfig,
18
+ ClientFactory,
19
+ A2AClientHTTPError,
20
+ AuthInterceptor,
21
+ InMemoryContextCredentialStore,
22
+ )
23
+ from a2a.client.errors import A2AClientJSONRPCError
24
+ from .oauth_token_cache import OAuth2TokenCache
25
+ from a2a.types import (
26
+ A2ARequest,
27
+ AgentCard,
28
+ Artifact,
29
+ CancelTaskRequest,
30
+ DataPart,
31
+ Message,
32
+ SendMessageRequest,
33
+ SendStreamingMessageRequest,
34
+ Task,
35
+ TaskArtifactUpdateEvent,
36
+ TaskState,
37
+ TaskStatus,
38
+ TaskStatusUpdateEvent,
39
+ TextPart,
40
+ TransportProtocol,
41
+ )
42
+
43
+ from solace_ai_connector.common.log import log
44
+
45
+ from datetime import datetime, timezone
46
+
47
+ from ....common import a2a
48
+ from ....common.oauth import OAuth2Client, validate_https_url
49
+ from ....common.data_parts import AgentProgressUpdateData
50
+ from ....agent.utils.artifact_helpers import format_artifact_uri
51
+ from ..base.component import BaseProxyComponent
52
+
53
+ if TYPE_CHECKING:
54
+ from ..base.proxy_task_context import ProxyTaskContext
55
+
56
+ info = {
57
+ "class_name": "A2AProxyComponent",
58
+ "description": "A proxy for standard A2A-over-HTTPS agents.",
59
+ "config_parameters": [],
60
+ "input_schema": {},
61
+ "output_schema": {},
62
+ }
63
+
64
+
65
+ class A2AProxyComponent(BaseProxyComponent):
66
+ """
67
+ Concrete proxy component for standard A2A-over-HTTPS agents.
68
+ """
69
+
70
+ def __init__(self, **kwargs: Any):
71
+ super().__init__(**kwargs)
72
+ # Cache Client instances per (agent_name, session_id, is_streaming) to ensure
73
+ # each session gets its own client with session-specific credentials and streaming mode
74
+ self._a2a_clients: Dict[Tuple[str, str, bool], Client] = {}
75
+ self._credential_store: InMemoryContextCredentialStore = (
76
+ InMemoryContextCredentialStore()
77
+ )
78
+ self._auth_interceptor: AuthInterceptor = AuthInterceptor(
79
+ self._credential_store
80
+ )
81
+ # OAuth 2.0 token cache for client credentials flow
82
+ # Why use asyncio.Lock: Ensures thread-safe access to the token cache
83
+ # when multiple concurrent requests target the same agent
84
+ self._oauth_token_cache: OAuth2TokenCache = OAuth2TokenCache()
85
+
86
+ # OAuth 2.0 client for protocol operations (no retry for A2A)
87
+ self._oauth_client = OAuth2Client()
88
+
89
+ # Index agent configs by name for O(1) lookup (performance optimization)
90
+ self._agent_config_by_name: Dict[str, Dict[str, Any]] = {
91
+ agent["name"]: agent for agent in self.proxied_agents_config
92
+ }
93
+
94
+ # NEW: OAuth 2.0 authorization code support (enterprise feature)
95
+ # Stores paused tasks waiting for user authorization
96
+ self._paused_a2a_oauth2_tasks: Dict[str, Dict[str, Any]] = {}
97
+ # Caches CredentialManagerWithDiscovery instances per agent
98
+ self._a2a_oauth2_credential_managers: Dict[str, Any] = {}
99
+
100
+ # NEW: Initialize enterprise features for OAuth2 support
101
+ try:
102
+ from solace_agent_mesh_enterprise.init_enterprise_component import (
103
+ init_enterprise_proxy_features
104
+ )
105
+ init_enterprise_proxy_features(self)
106
+ except ImportError:
107
+ pass # Enterprise not installed
108
+
109
+ # OAuth 2.0 configuration is now validated by Pydantic models at app initialization
110
+ # No need for separate _validate_oauth_config() method
111
+
112
+ def _get_agent_config(self, agent_name: str) -> Optional[Dict[str, Any]]:
113
+ """
114
+ O(1) lookup of agent configuration by name.
115
+
116
+ Args:
117
+ agent_name: The name of the agent to look up.
118
+
119
+ Returns:
120
+ The agent configuration dictionary, or None if not found.
121
+ """
122
+ return self._agent_config_by_name.get(agent_name)
123
+
124
+ async def _build_headers(
125
+ self,
126
+ agent_name: str,
127
+ agent_config: Dict[str, Any],
128
+ custom_headers_key: str,
129
+ use_auth: bool = True,
130
+ ) -> Dict[str, str]:
131
+ """
132
+ Builds HTTP headers for requests, applying authentication and custom headers.
133
+
134
+ Args:
135
+ agent_name: The name of the agent.
136
+ agent_config: The agent configuration dictionary.
137
+ custom_headers_key: Key to look up custom headers in config ('agent_card_headers' or 'task_headers').
138
+ use_auth: Whether to apply authentication headers.
139
+
140
+ Returns:
141
+ Dictionary of HTTP headers. Custom headers are applied after auth headers.
142
+ Note: For task invocations, the A2A SDK's AuthInterceptor may further
143
+ modify authentication headers after these are set.
144
+ """
145
+ headers: Dict[str, str] = {}
146
+
147
+ # Step 1: Add authentication headers if requested
148
+ if use_auth:
149
+ auth_config = agent_config.get("authentication")
150
+ if auth_config:
151
+ auth_type = auth_config.get("type")
152
+
153
+ # Determine auth type (with backward compatibility)
154
+ if not auth_type:
155
+ scheme = auth_config.get("scheme", "bearer")
156
+ auth_type = "static_bearer" if scheme == "bearer" else "static_apikey"
157
+
158
+ # Apply authentication based on type
159
+ if auth_type == "static_bearer":
160
+ token = auth_config.get("token")
161
+ if token:
162
+ headers["Authorization"] = f"Bearer {token}"
163
+ elif auth_type == "static_apikey":
164
+ token = auth_config.get("token")
165
+ if token:
166
+ headers["X-API-Key"] = token
167
+ elif auth_type == "oauth2_client_credentials":
168
+ # Fetch OAuth token
169
+ try:
170
+ access_token = await self._fetch_oauth2_token(agent_name, auth_config)
171
+ headers["Authorization"] = f"Bearer {access_token}"
172
+ except Exception as e:
173
+ log.error(
174
+ "%s Failed to obtain OAuth 2.0 token for headers: %s",
175
+ self.log_identifier,
176
+ e,
177
+ )
178
+ # Continue without auth header - let the request fail downstream
179
+
180
+ # Step 2: Add custom headers (these override auth headers)
181
+ custom_headers_list = agent_config.get(custom_headers_key)
182
+ if custom_headers_list:
183
+ for header_config in custom_headers_list:
184
+ header_name = header_config.get("name")
185
+ header_value = header_config.get("value")
186
+ if header_name and header_value:
187
+ headers[header_name] = header_value
188
+
189
+ return headers
190
+
191
+ async def _fetch_agent_card(
192
+ self, agent_config: Dict[str, Any]
193
+ ) -> Optional[AgentCard]:
194
+ """
195
+ Fetches the AgentCard from a downstream A2A agent via HTTPS.
196
+
197
+ Applies authentication and custom headers based on configuration:
198
+ - If use_auth_for_agent_card=true, applies the configured authentication
199
+ - Custom agent_card_headers override authentication headers
200
+ """
201
+ agent_name = agent_config.get("name")
202
+ agent_url = agent_config.get("url")
203
+ agent_card_path = agent_config.get("agent_card_path", "/agent/card.json")
204
+ log_identifier = f"{self.log_identifier}[FetchCard:{agent_name}]"
205
+
206
+ if not agent_url:
207
+ log.error("%s No URL configured for agent.", log_identifier)
208
+ return None
209
+
210
+ try:
211
+ # Build headers based on configuration
212
+ use_auth = agent_config.get("use_auth_for_agent_card", False)
213
+ headers = await self._build_headers(
214
+ agent_name=agent_name,
215
+ agent_config=agent_config,
216
+ custom_headers_key="agent_card_headers",
217
+ use_auth=use_auth,
218
+ )
219
+
220
+ if headers:
221
+ log.debug(
222
+ "%s Fetching agent card with %d custom header(s) (auth=%s)",
223
+ log_identifier,
224
+ len(headers),
225
+ use_auth,
226
+ )
227
+ else:
228
+ log.debug("%s Fetching agent card without authentication", log_identifier)
229
+
230
+ log.info("%s Fetching agent card from %s", log_identifier, agent_url)
231
+ async with httpx.AsyncClient(headers=headers) as client:
232
+ resolver = A2ACardResolver(httpx_client=client, base_url=agent_url)
233
+ agent_card = await resolver.get_agent_card()
234
+ return agent_card
235
+ except A2AClientHTTPError as e:
236
+ log.error(
237
+ "%s HTTP error fetching agent card from %s: %s",
238
+ log_identifier,
239
+ agent_url,
240
+ e,
241
+ )
242
+ except Exception as e:
243
+ log.exception(
244
+ "%s Unexpected error fetching agent card from %s: %s",
245
+ log_identifier,
246
+ agent_url,
247
+ e,
248
+ )
249
+ return None
250
+
251
+ async def _forward_request(
252
+ self, task_context: ProxyTaskContext, request: A2ARequest, agent_name: str
253
+ ) -> None:
254
+ """
255
+ Forwards an A2A request to a downstream A2A-over-HTTPS agent.
256
+
257
+ Implements automatic retry logic for OAuth 2.0 authentication failures.
258
+ If a 401 Unauthorized response is received and the agent uses OAuth 2.0,
259
+ the cached token is invalidated and the request is retried once with a
260
+ fresh token.
261
+ """
262
+ log_identifier = (
263
+ f"{self.log_identifier}[ForwardRequest:{task_context.task_id}:{agent_name}]"
264
+ )
265
+
266
+ # Store original request for potential resumption (OAuth2 authorization code flow)
267
+ task_context.original_request = request
268
+
269
+ # Step 1: Initialize retry counter
270
+ # Why only retry once: Prevents infinite loops on persistent auth failures.
271
+ # First 401 may be due to token expiration between cache check and request;
272
+ # second 401 indicates a configuration or authorization issue (not transient).
273
+ max_auth_retries: int = 1
274
+ auth_retry_count: int = 0
275
+
276
+ # Step 2: Check for OAuth2 authorization code flow
277
+ # This auth type requires user interaction and can pause the task,
278
+ # so we check it before attempting normal request flow
279
+ agent_config = self._get_agent_config(agent_name)
280
+ auth_config = agent_config.get("authentication") if agent_config else None
281
+ auth_type = auth_config.get("type") if auth_config else None
282
+
283
+ if auth_type == "oauth2_authorization_code":
284
+ try:
285
+ from solace_agent_mesh_enterprise.auth.a2a import (
286
+ check_authorization_required,
287
+ request_authorization,
288
+ )
289
+
290
+ # Check if user authorization is needed
291
+ needs_auth = await check_authorization_required(
292
+ component=self,
293
+ agent_name=agent_name,
294
+ task_context=task_context,
295
+ )
296
+
297
+ if needs_auth:
298
+ # Pause task and request authorization
299
+ log.info(
300
+ "%s User authorization required for agent '%s'. Pausing task.",
301
+ log_identifier,
302
+ agent_name,
303
+ )
304
+ await request_authorization(
305
+ component=self,
306
+ agent_name=agent_name,
307
+ task_context=task_context,
308
+ )
309
+ return # Exit - task paused, will resume after OAuth callback
310
+
311
+ except ImportError:
312
+ log.error(
313
+ "%s Agent '%s' requires OAuth2 authorization code flow, "
314
+ "but solace-agent-mesh-enterprise is not installed.",
315
+ log_identifier,
316
+ agent_name,
317
+ )
318
+ raise ValueError(
319
+ f"Agent '{agent_name}' requires OAuth2 authorization code flow, "
320
+ "but solace-agent-mesh-enterprise is not installed."
321
+ )
322
+
323
+ # Step 3: Normal request flow for all other auth types
324
+ # (static_bearer, static_apikey, oauth2_client_credentials, or authorized oauth2_authorization_code)
325
+ while auth_retry_count <= max_auth_retries:
326
+ try:
327
+ # Get or create A2AClient
328
+ client = await self._get_or_create_a2a_client(agent_name, task_context)
329
+ if not client:
330
+ raise ValueError(
331
+ f"Could not create A2A client for agent '{agent_name}'"
332
+ )
333
+
334
+ # Create context with sessionId (camelCase!) so AuthInterceptor can look up credentials
335
+ from a2a.client.middleware import ClientCallContext
336
+
337
+ session_id = task_context.a2a_context.get(
338
+ "session_id", "default_session"
339
+ )
340
+ call_context = ClientCallContext(state={"sessionId": session_id})
341
+
342
+ # Forward the request with context
343
+ if isinstance(
344
+ request, (SendStreamingMessageRequest, SendMessageRequest)
345
+ ):
346
+ # Extract the Message from the request params
347
+ message_to_send = request.params.message
348
+
349
+ # Check if this is a RUN_BASED request by inspecting message metadata
350
+ # For RUN_BASED requests, omit context_id to indicate independent tasks
351
+ if message_to_send.metadata:
352
+ session_behavior = message_to_send.metadata.get("sessionBehavior")
353
+ if session_behavior:
354
+ session_behavior = str(session_behavior).upper()
355
+ if session_behavior == "RUN_BASED" and message_to_send.context_id:
356
+ # For RUN_BASED requests, omit context_id entirely
357
+ # Each request is independent with no logical grouping
358
+ log.debug(
359
+ "%s RUN_BASED request detected. Omitting context_id "
360
+ "(independent task)",
361
+ log_identifier,
362
+ )
363
+ message_to_send = message_to_send.model_copy(
364
+ update={"context_id": None}
365
+ )
366
+
367
+ # WORKAROUND: The A2A SDK has a bug in ClientTaskManager that breaks streaming.
368
+ # For streaming requests, we bypass the Client.send_message() method and call
369
+ # the transport directly to avoid the buggy ClientTaskManager.
370
+ # Non-streaming requests work fine with the normal client method.
371
+ # TODO: Remove this workaround once SDK bug is fixed upstream.
372
+ if task_context.a2a_context.get("is_streaming", True):
373
+ # Access transport directly (private API) to bypass ClientTaskManager
374
+ log.debug(
375
+ "%s Using transport directly for streaming request (SDK bug workaround)",
376
+ log_identifier,
377
+ )
378
+ async for raw_event in client._transport.send_message_streaming(
379
+ request.params, context=call_context
380
+ ):
381
+ # Process raw events directly without ClientTaskManager
382
+ await self._process_downstream_response(
383
+ raw_event, task_context, client, agent_name
384
+ )
385
+ else:
386
+ # Non-streaming: use normal client method (works fine)
387
+ log.debug(
388
+ "%s Using normal client method for non-streaming request",
389
+ log_identifier,
390
+ )
391
+ async for event in client.send_message(
392
+ message_to_send, context=call_context
393
+ ):
394
+ await self._process_downstream_response(
395
+ event, task_context, client, agent_name
396
+ )
397
+ elif isinstance(request, CancelTaskRequest):
398
+ # Forward cancel request to downstream agent using the downstream task ID
399
+ # The request.params.id contains SAM's task ID, but we need to send
400
+ # the downstream agent's task ID for the cancel to work
401
+
402
+ if not task_context.downstream_task_id:
403
+ log.error(
404
+ "%s Cannot forward cancel request: downstream task ID not yet captured for SAM task %s",
405
+ log_identifier,
406
+ task_context.task_id,
407
+ )
408
+ # Create an error response
409
+ from a2a.types import InvalidRequestError
410
+ error = InvalidRequestError(
411
+ message=f"Cannot cancel task {task_context.task_id}: downstream task ID not available",
412
+ data={"taskId": task_context.task_id}
413
+ )
414
+ # Publish error response
415
+ await self._publish_error_response(error, task_context.a2a_context)
416
+ break
417
+
418
+ log.info(
419
+ "%s Forwarding cancel request for task %s (SAM ID: %s, downstream ID: %s) to downstream agent.",
420
+ log_identifier,
421
+ task_context.downstream_task_id,
422
+ task_context.task_id,
423
+ task_context.downstream_task_id,
424
+ )
425
+
426
+ # Create new params with the downstream task ID
427
+ from a2a.types import TaskIdParams
428
+ downstream_params = TaskIdParams(id=task_context.downstream_task_id)
429
+
430
+ # Use the modern client's cancel_task method with the downstream task ID
431
+ result = await client.cancel_task(
432
+ downstream_params, context=call_context
433
+ )
434
+ # Publish the canceled task response
435
+ await self._publish_task_response(result, task_context.a2a_context)
436
+ else:
437
+ log.warning(
438
+ "%s Unhandled request type for forwarding: %s",
439
+ log_identifier,
440
+ type(request),
441
+ )
442
+
443
+ # Step 5: Success - break out of retry loop
444
+ break
445
+
446
+ except RuntimeError as e:
447
+ # WORKAROUND: The A2A SDK raises StopAsyncIteration for connection failures,
448
+ # which Python 3.7+ automatically converts to RuntimeError (PEP 479).
449
+ # We catch this here to provide a more meaningful error message.
450
+ # This should be fixed upstream in the A2A SDK to raise proper connection exceptions.
451
+ if "StopAsyncIteration" in str(e):
452
+ error_msg = (
453
+ f"Failed to connect to agent '{agent_name}': "
454
+ "Connection refused or agent unreachable"
455
+ )
456
+ log.error(
457
+ "%s Connection error (SDK raised StopAsyncIteration): %s",
458
+ log_identifier,
459
+ error_msg,
460
+ )
461
+ # Raise a more descriptive error that will be caught by the outer handler
462
+ raise ConnectionError(error_msg) from e
463
+ else:
464
+ # Some other RuntimeError - re-raise it
465
+ raise
466
+
467
+ except A2AClientJSONRPCError as e:
468
+ # Handle JSON-RPC protocol errors
469
+
470
+ # Special case: Task already in terminal state (canceled/completed/failed)
471
+ # This is not a fatal error - the cancellation is effectively a no-op
472
+ if (e.error.code == -32002 and
473
+ "cannot be canceled" in e.error.message.lower() and
474
+ isinstance(request, CancelTaskRequest)):
475
+ log.warning(
476
+ "%s Task %s is already in terminal state: %s. Treating as successful cancellation.",
477
+ log_identifier,
478
+ task_context.downstream_task_id,
479
+ e.error.message,
480
+ )
481
+ # Task is already done - return success (cancellation is effectively complete)
482
+ # We don't need to publish a response because the task already sent its final response
483
+ break
484
+
485
+ log.error(
486
+ "%s JSON-RPC error from agent '%s': %s",
487
+ log_identifier,
488
+ agent_name,
489
+ e.error,
490
+ )
491
+ # TODO: Publish error response to Solace
492
+ # Do not retry - this is a protocol-level error
493
+ raise
494
+
495
+ except ConnectionError as e:
496
+ # Connection errors (including those converted from RuntimeError above)
497
+ log.error(
498
+ "%s Connection error forwarding request to agent '%s': %s",
499
+ log_identifier,
500
+ agent_name,
501
+ e,
502
+ )
503
+ raise
504
+
505
+ except A2AClientHTTPError as e:
506
+ # Step 4: Add specific handling for 401 Unauthorized errors
507
+ # The error might be wrapped in an SSE parsing error, so we need to check
508
+ # if the underlying cause is a 401
509
+ is_401_error = False
510
+
511
+ # Check if this is directly a 401
512
+ if hasattr(e, "status_code") and e.status_code == 401:
513
+ is_401_error = True
514
+ # Check if this is an SSE parsing error caused by a 401 response
515
+ elif "401" in str(e) or "Unauthorized" in str(e):
516
+ is_401_error = True
517
+ # Check if the error message mentions application/json content type
518
+ # (which is what 401 responses typically return)
519
+ elif "application/json" in str(e) and "text/event-stream" in str(e):
520
+ # This is likely an SSE parsing error caused by a 401 JSON response
521
+ is_401_error = True
522
+
523
+ if is_401_error and auth_retry_count < max_auth_retries:
524
+ log.warning(
525
+ "%s Received 401 Unauthorized from agent '%s' (detected from error: %s). Attempting token refresh (retry %d/%d).",
526
+ log_identifier,
527
+ agent_name,
528
+ str(e)[:100],
529
+ auth_retry_count + 1,
530
+ max_auth_retries,
531
+ )
532
+
533
+ should_retry = await self._handle_auth_error(
534
+ agent_name, task_context
535
+ )
536
+ if should_retry:
537
+ auth_retry_count += 1
538
+ continue # Retry with fresh token
539
+
540
+ # Not a retryable auth error, or max retries exceeded
541
+ log.exception(
542
+ "%s HTTP error forwarding request: %s",
543
+ log_identifier,
544
+ e,
545
+ )
546
+ raise
547
+
548
+ except Exception as e:
549
+ log.exception(
550
+ "%s Unexpected error forwarding request: %s",
551
+ log_identifier,
552
+ e,
553
+ )
554
+ # Let base class exception handler in _handle_a2a_request catch this
555
+ # and publish an error response.
556
+ raise
557
+
558
+ async def _handle_auth_error(
559
+ self, agent_name: str, task_context: ProxyTaskContext
560
+ ) -> bool:
561
+ """
562
+ Handles authentication errors by invalidating cached tokens and clients.
563
+
564
+ This method is called when a 401 Unauthorized response is received from
565
+ a downstream agent. It checks if the agent uses OAuth 2.0 authentication,
566
+ and if so, invalidates the cached token and removes ALL cached clients
567
+ for this agent/session combination (both streaming and non-streaming).
568
+
569
+ Args:
570
+ agent_name: The name of the agent that returned 401.
571
+ task_context: The current task context.
572
+
573
+ Returns:
574
+ True if token was invalidated and retry should be attempted.
575
+ False if no retry should be attempted (e.g., static token).
576
+ """
577
+ log_identifier = f"{self.log_identifier}[AuthError:{agent_name}]"
578
+
579
+ # Step 1: Retrieve agent configuration using O(1) lookup
580
+ agent_config = self._get_agent_config(agent_name)
581
+
582
+ if not agent_config:
583
+ log.warning(
584
+ "%s Agent configuration not found. Cannot handle auth error.",
585
+ log_identifier,
586
+ )
587
+ return False
588
+
589
+ # Step 2: Check authentication type
590
+ auth_config = agent_config.get("authentication")
591
+ if not auth_config:
592
+ log.debug(
593
+ "%s No authentication configured for agent. No retry needed.",
594
+ log_identifier,
595
+ )
596
+ return False
597
+
598
+ auth_type = auth_config.get("type")
599
+ if not auth_type:
600
+ # Legacy config - infer from scheme
601
+ scheme = auth_config.get("scheme", "bearer")
602
+ auth_type = "static_bearer" if scheme == "bearer" else "static_apikey"
603
+
604
+ if auth_type != "oauth2_client_credentials":
605
+ log.debug(
606
+ "%s Agent uses '%s' authentication (not OAuth 2.0). No retry for static tokens.",
607
+ log_identifier,
608
+ auth_type,
609
+ )
610
+ return False
611
+
612
+ # Step 3: Invalidate cached OAuth token
613
+ log.info(
614
+ "%s Invalidating cached OAuth 2.0 token for agent '%s'.",
615
+ log_identifier,
616
+ agent_name,
617
+ )
618
+ await self._oauth_token_cache.invalidate(agent_name)
619
+
620
+ # Step 4: Remove ALL cached Clients for this agent/session combination
621
+ # We clear both streaming and non-streaming clients because:
622
+ # 1. Both share the same session_id in the credential store
623
+ # 2. Both would have been created with the same expired token
624
+ # 3. We want fresh tokens for any subsequent requests
625
+ # The cache key is a 3-tuple: (agent_name, session_id, is_streaming)
626
+ session_id = task_context.a2a_context.get("session_id", "default_session")
627
+
628
+ clients_removed = 0
629
+ for is_streaming in [True, False]:
630
+ cache_key = (agent_name, session_id, is_streaming)
631
+ if cache_key in self._a2a_clients:
632
+ self._a2a_clients.pop(cache_key)
633
+ clients_removed += 1
634
+ log.info(
635
+ "%s Removed cached Client for agent '%s' session '%s' streaming=%s.",
636
+ log_identifier,
637
+ agent_name,
638
+ session_id,
639
+ is_streaming,
640
+ )
641
+
642
+ if clients_removed == 0:
643
+ log.warning(
644
+ "%s No cached Clients found for agent '%s' session '%s'. This is unexpected.",
645
+ log_identifier,
646
+ agent_name,
647
+ session_id,
648
+ )
649
+ else:
650
+ log.info(
651
+ "%s Removed %d cached Client(s). Will create fresh client(s) with new token on retry.",
652
+ log_identifier,
653
+ clients_removed,
654
+ )
655
+
656
+ # Step 5: Return True to signal retry should be attempted
657
+ log.info(
658
+ "%s Auth error handling complete. Retry will be attempted with fresh token.",
659
+ log_identifier,
660
+ )
661
+ return True
662
+
663
+ async def _fetch_oauth2_token(
664
+ self, agent_name: str, auth_config: Dict[str, Any]
665
+ ) -> str:
666
+ """
667
+ Fetches an OAuth 2.0 access token using the client credentials flow.
668
+
669
+ This method implements token caching to avoid unnecessary token requests.
670
+ Tokens are cached per agent and automatically expire based on the configured
671
+ cache duration (default: 55 minutes).
672
+
673
+ Args:
674
+ agent_name: The name of the agent (used as cache key).
675
+ auth_config: Authentication configuration dictionary containing:
676
+ - token_url: OAuth 2.0 token endpoint (required)
677
+ - client_id: OAuth 2.0 client identifier (required)
678
+ - client_secret: OAuth 2.0 client secret (required)
679
+ - scope: (optional) Space-separated scope string
680
+ - token_cache_duration_seconds: (optional) Cache duration in seconds
681
+
682
+ Returns:
683
+ A valid OAuth 2.0 access token (string).
684
+
685
+ Raises:
686
+ ValueError: If required OAuth parameters are missing or invalid.
687
+ httpx.HTTPStatusError: If token request returns non-2xx status.
688
+ httpx.RequestError: If network error occurs.
689
+ """
690
+ log_identifier = f"{self.log_identifier}[OAuth2:{agent_name}]"
691
+
692
+ # Step 1: Check cache first
693
+ cached_token = await self._oauth_token_cache.get(agent_name)
694
+ if cached_token:
695
+ log.debug("%s Using cached OAuth token.", log_identifier)
696
+ return cached_token
697
+
698
+ # Step 2: Validate required parameters
699
+ token_url = auth_config.get("token_url")
700
+ client_id = auth_config.get("client_id")
701
+ client_secret = auth_config.get("client_secret")
702
+
703
+ if not all([token_url, client_id, client_secret]):
704
+ raise ValueError(
705
+ f"{log_identifier} OAuth 2.0 client credentials flow requires "
706
+ "'token_url', 'client_id', and 'client_secret'."
707
+ )
708
+
709
+ # SECURITY: Enforce HTTPS for token URL using common utility
710
+ validate_https_url(token_url)
711
+
712
+ # Step 3: Extract optional parameters
713
+ scope = auth_config.get("scope", "")
714
+ # Why 3300 seconds (55 minutes): Provides a 5-minute safety margin before
715
+ # typical 60-minute token expiration, preventing token expiration mid-request
716
+ cache_duration = auth_config.get("token_cache_duration_seconds", 3300)
717
+
718
+ # Step 4: Log token acquisition attempt
719
+ # SECURITY: Never log client_secret or access_token to prevent credential leakage
720
+ log.info(
721
+ "%s Fetching new OAuth 2.0 token from %s (scope: %s)",
722
+ log_identifier,
723
+ token_url,
724
+ scope or "default",
725
+ )
726
+
727
+ try:
728
+ # Step 5: Fetch token using common OAuth client (no retry for A2A)
729
+ token_data = await self._oauth_client.fetch_client_credentials_token(
730
+ token_url=token_url,
731
+ client_id=client_id,
732
+ client_secret=client_secret,
733
+ scope=scope,
734
+ verify=True,
735
+ timeout=30.0,
736
+ )
737
+
738
+ access_token = token_data["access_token"]
739
+
740
+ # Step 6: Cache the token
741
+ await self._oauth_token_cache.set(
742
+ agent_name, access_token, cache_duration
743
+ )
744
+
745
+ # Step 7: Log success
746
+ log.info(
747
+ "%s Successfully obtained OAuth 2.0 token (cached for %ds)",
748
+ log_identifier,
749
+ cache_duration,
750
+ )
751
+
752
+ # Step 8: Return access token
753
+ return access_token
754
+
755
+ except httpx.HTTPStatusError as e:
756
+ log.error(
757
+ "%s OAuth 2.0 token request failed with status %d: %s",
758
+ log_identifier,
759
+ e.response.status_code,
760
+ e.response.text,
761
+ )
762
+ raise
763
+ except httpx.RequestError as e:
764
+ log.error(
765
+ "%s OAuth 2.0 token request failed: %s",
766
+ log_identifier,
767
+ e,
768
+ )
769
+ raise
770
+ except Exception as e:
771
+ log.exception(
772
+ "%s Unexpected error fetching OAuth 2.0 token: %s",
773
+ log_identifier,
774
+ e,
775
+ )
776
+ raise
777
+
778
+ async def _get_or_create_a2a_client(
779
+ self, agent_name: str, task_context: ProxyTaskContext
780
+ ) -> Optional[Client]:
781
+ """
782
+ Gets a cached Client or creates a new one for the given agent, session, and streaming mode.
783
+
784
+ Caches clients per (agent_name, session_id, is_streaming) to ensure each session gets its
785
+ own client with session-specific credentials and the correct streaming mode. This is necessary because:
786
+ 1. The A2A SDK's AuthInterceptor uses session-based credential lookup
787
+ 2. The Client's streaming mode is set at creation time and cannot be changed
788
+
789
+ Supports multiple authentication types:
790
+ - static_bearer: Static bearer token authentication
791
+ - static_apikey: Static API key authentication
792
+ - oauth2_client_credentials: OAuth 2.0 Client Credentials flow with automatic token refresh
793
+ - oauth2_authorization_code: OAuth 2.0 Authorization Code flow
794
+
795
+ For backward compatibility, legacy configurations without a 'type' field
796
+ will have their type inferred from the 'scheme' field.
797
+
798
+ The client's streaming mode is determined by the original request type from
799
+ the gateway (message/send vs message/stream).
800
+ """
801
+ session_id = task_context.a2a_context.get("session_id", "default_session")
802
+ is_streaming = task_context.a2a_context.get("is_streaming", True)
803
+ cache_key = (agent_name, session_id, is_streaming)
804
+
805
+ if cache_key in self._a2a_clients:
806
+ return self._a2a_clients[cache_key]
807
+
808
+ # Use O(1) lookup for agent configuration
809
+ agent_config = self._get_agent_config(agent_name)
810
+ if not agent_config:
811
+ log.error(f"No configuration found for proxied agent '{agent_name}'")
812
+ return None
813
+
814
+ agent_card = self.agent_registry.get_agent(agent_name)
815
+ if not agent_card:
816
+ log.error(f"Agent card not found for '{agent_name}' in registry.")
817
+ return None
818
+
819
+ # Check if we should use the configured URL or the agent card URL
820
+ use_agent_card_url = agent_config.get("use_agent_card_url", True)
821
+ if not use_agent_card_url:
822
+ # Override the agent card URL with the configured URL
823
+ configured_url = agent_config.get("url")
824
+ log.info(
825
+ "%s Overriding agent card URL with configured URL for agent '%s': %s",
826
+ self.log_identifier,
827
+ agent_name,
828
+ configured_url,
829
+ )
830
+ # Create a modified copy of the agent card with the configured URL
831
+ agent_card = agent_card.model_copy(update={"url": configured_url})
832
+
833
+ # Resolve timeout - ensure we always have a valid timeout value
834
+ default_timeout = self.get_config("default_request_timeout_seconds", 300)
835
+ agent_timeout = agent_config.get("request_timeout_seconds")
836
+ if agent_timeout is None:
837
+ agent_timeout = default_timeout
838
+ log.info("Using timeout of %ss for agent '%s'.", agent_timeout, agent_name)
839
+
840
+ # Build custom headers for task invocation
841
+ # Note: We build headers here but apply them via the httpx client
842
+ # The A2A SDK's AuthInterceptor will add auth headers via the credential store,
843
+ # but we want custom headers to override those, so we apply them at the client level
844
+ task_headers = await self._build_headers(
845
+ agent_name=agent_name,
846
+ agent_config=agent_config,
847
+ custom_headers_key="task_headers",
848
+ use_auth=False, # Auth will be handled by AuthInterceptor below
849
+ )
850
+
851
+ # Create a new httpx client with the specific timeout and custom headers for this agent
852
+ # httpx.Timeout requires explicit values for connect, read, write, and pool
853
+ httpx_client_for_agent = httpx.AsyncClient(
854
+ timeout=httpx.Timeout(
855
+ connect=agent_timeout,
856
+ read=agent_timeout,
857
+ write=agent_timeout,
858
+ pool=agent_timeout,
859
+ ),
860
+ headers=task_headers if task_headers else None,
861
+ )
862
+
863
+ if task_headers:
864
+ log.info(
865
+ "%s Applied %d custom task header(s) for agent '%s'",
866
+ self.log_identifier,
867
+ len(task_headers),
868
+ agent_name,
869
+ )
870
+
871
+ # Setup authentication if configured
872
+ auth_config = agent_config.get("authentication")
873
+ if auth_config:
874
+ auth_type = auth_config.get("type")
875
+
876
+ # Determine auth type (with backward compatibility)
877
+ if not auth_type:
878
+ # Legacy config: infer type from 'scheme' field
879
+ scheme = auth_config.get("scheme", "bearer")
880
+ if scheme == "bearer":
881
+ auth_type = "static_bearer"
882
+ elif scheme == "apikey":
883
+ auth_type = "static_apikey"
884
+ else:
885
+ raise ValueError(
886
+ f"Unknown legacy authentication scheme '{scheme}' for agent '{agent_name}'. "
887
+ f"Supported schemes: 'bearer', 'apikey'."
888
+ )
889
+
890
+ log.warning(
891
+ "%s Using legacy authentication config for agent '%s'. "
892
+ "Consider migrating to 'type' field.",
893
+ self.log_identifier,
894
+ agent_name,
895
+ )
896
+
897
+ log.info(
898
+ "%s Configuring authentication type '%s' for agent '%s'",
899
+ self.log_identifier,
900
+ auth_type,
901
+ agent_name,
902
+ )
903
+
904
+ # Route to appropriate handler
905
+ if auth_type == "static_bearer":
906
+ token = auth_config.get("token")
907
+ if not token:
908
+ raise ValueError(
909
+ f"Authentication type 'static_bearer' requires 'token' for agent '{agent_name}'"
910
+ )
911
+ await self._credential_store.set_credentials(
912
+ session_id, "bearer", token
913
+ )
914
+
915
+ elif auth_type == "static_apikey":
916
+ token = auth_config.get("token")
917
+ if not token:
918
+ raise ValueError(
919
+ f"Authentication type 'static_apikey' requires 'token' for agent '{agent_name}'"
920
+ )
921
+ await self._credential_store.set_credentials(
922
+ session_id, "apikey", token
923
+ )
924
+
925
+ elif auth_type == "oauth2_client_credentials":
926
+ # NEW: OAuth 2.0 Client Credentials Flow
927
+ try:
928
+ access_token = await self._fetch_oauth2_token(
929
+ agent_name, auth_config
930
+ )
931
+ await self._credential_store.set_credentials(
932
+ session_id, "bearer", access_token
933
+ )
934
+ except Exception as e:
935
+ log.error(
936
+ "%s Failed to obtain OAuth 2.0 token for agent '%s': %s",
937
+ self.log_identifier,
938
+ agent_name,
939
+ e,
940
+ )
941
+ raise
942
+
943
+ elif auth_type == "oauth2_authorization_code":
944
+ # NEW: OAuth 2.0 Authorization Code Flow (enterprise feature)
945
+ # At this point, user has already authorized (checked in _forward_request)
946
+ # We just need to get the access token from enterprise helpers
947
+ try:
948
+ from solace_agent_mesh_enterprise.auth.a2a import get_access_token
949
+
950
+ # Get access token (enterprise handles refresh if needed)
951
+ access_token = await get_access_token(
952
+ component=self,
953
+ agent_name=agent_name,
954
+ task_context=task_context,
955
+ )
956
+
957
+ if not access_token:
958
+ raise ValueError(
959
+ f"No OAuth2 credential found for agent '{agent_name}'. "
960
+ "User authorization should have completed in _forward_request()."
961
+ )
962
+
963
+ # Find the OAuth2 authorization code scheme name from agent card
964
+ oauth_scheme_name = None
965
+ if agent_card and agent_card.security_schemes:
966
+ for scheme_name, scheme_wrapper in agent_card.security_schemes.items():
967
+ scheme = scheme_wrapper.root
968
+ if (hasattr(scheme, 'type') and scheme.type.lower() == 'oauth2' and
969
+ hasattr(scheme, 'flows') and scheme.flows and
970
+ scheme.flows.authorization_code):
971
+ oauth_scheme_name = scheme_name
972
+ break
973
+
974
+ # Fallback if not found
975
+ if not oauth_scheme_name:
976
+ oauth_scheme_name = "oauth2_authorization_code"
977
+ log.warning(
978
+ "%s No OAuth2 authorization code scheme found in agent card, using default name",
979
+ self.log_identifier
980
+ )
981
+
982
+ # Store in credential store for AuthInterceptor
983
+ await self._credential_store.set_credentials(
984
+ session_id, oauth_scheme_name, access_token
985
+ )
986
+
987
+ except ImportError:
988
+ log.error(
989
+ "%s OAuth2 authorization code requires solace-agent-mesh-enterprise package",
990
+ self.log_identifier,
991
+ )
992
+ raise ValueError(
993
+ "OAuth2 authorization code requires solace-agent-mesh-enterprise package"
994
+ )
995
+
996
+ else:
997
+ raise ValueError(
998
+ f"Unsupported authentication type '{auth_type}' for agent '{agent_name}'. "
999
+ f"Supported types: static_bearer, static_apikey, oauth2_client_credentials, oauth2_authorization_code."
1000
+ )
1001
+
1002
+ # Create ClientConfig for the modern client
1003
+ # Use the streaming mode from the original request
1004
+ config = ClientConfig(
1005
+ streaming=is_streaming,
1006
+ polling=False,
1007
+ httpx_client=httpx_client_for_agent,
1008
+ supported_transports=[TransportProtocol.jsonrpc],
1009
+ accepted_output_modes=[],
1010
+ )
1011
+
1012
+ # Create client using ClientFactory
1013
+ factory = ClientFactory(config)
1014
+ client = factory.create(
1015
+ agent_card,
1016
+ consumers=None,
1017
+ interceptors=[self._auth_interceptor],
1018
+ )
1019
+
1020
+ self._a2a_clients[cache_key] = client
1021
+ return client
1022
+
1023
+ async def _handle_outbound_artifacts(
1024
+ self,
1025
+ response: Any,
1026
+ task_context: ProxyTaskContext,
1027
+ agent_name: str,
1028
+ ) -> List[Dict[str, Any]]:
1029
+ """
1030
+ Finds artifacts with byte content, saves them to the proxy's artifact store,
1031
+ and mutates the response object to replace bytes with a URI.
1032
+ It also uses TextParts within an artifact as a description for the saved file.
1033
+
1034
+ Returns:
1035
+ A list of dictionaries, each representing a saved artifact with its filename and version.
1036
+ """
1037
+ from ....agent.utils.artifact_helpers import save_artifact_with_metadata
1038
+
1039
+ log_identifier = (
1040
+ f"{self.log_identifier}[HandleOutboundArtifacts:{task_context.task_id}]"
1041
+ )
1042
+ saved_artifacts_manifest = []
1043
+
1044
+ artifacts_to_process: List[Artifact] = []
1045
+ if isinstance(response, Task) and response.artifacts:
1046
+ artifacts_to_process = response.artifacts
1047
+ elif isinstance(response, TaskArtifactUpdateEvent):
1048
+ artifacts_to_process = [response.artifact]
1049
+
1050
+ if not artifacts_to_process:
1051
+ return saved_artifacts_manifest
1052
+
1053
+ if not self.artifact_service:
1054
+ log.warning(
1055
+ "%s Artifact service not configured. Cannot save outbound artifacts.",
1056
+ log_identifier,
1057
+ )
1058
+ return saved_artifacts_manifest
1059
+
1060
+ for artifact in artifacts_to_process:
1061
+ contextual_description = "\n".join(
1062
+ [
1063
+ a2a.get_text_from_text_part(part.root)
1064
+ for part in artifact.parts
1065
+ if a2a.is_text_part(part)
1066
+ ]
1067
+ )
1068
+
1069
+ for i, part_container in enumerate(artifact.parts):
1070
+ part = part_container.root
1071
+ if (
1072
+ a2a.is_file_part(part_container)
1073
+ and a2a.is_file_part_bytes(part)
1074
+ and a2a.get_bytes_from_file_part(part)
1075
+ ):
1076
+ file_part = part
1077
+ file_content = file_part.file
1078
+ log.info(
1079
+ "%s Found outbound artifact '%s' with byte content. Saving...",
1080
+ log_identifier,
1081
+ file_content.name,
1082
+ )
1083
+
1084
+ metadata_to_save = artifact.metadata or {}
1085
+ if artifact.description:
1086
+ metadata_to_save["description"] = artifact.description
1087
+ elif contextual_description:
1088
+ metadata_to_save["description"] = contextual_description
1089
+ else:
1090
+ metadata_to_save["description"] = (
1091
+ f"Artifact created by {agent_name}"
1092
+ )
1093
+
1094
+ metadata_to_save["proxied_from_artifact_id"] = artifact.artifact_id
1095
+ user_id = task_context.a2a_context.get("user_id", "default_user")
1096
+ session_id = task_context.a2a_context.get("session_id")
1097
+
1098
+ # Get file content using facade helpers
1099
+ content_bytes = a2a.get_bytes_from_file_part(file_part)
1100
+ filename = a2a.get_filename_from_file_part(file_part)
1101
+ mime_type = a2a.get_mimetype_from_file_part(file_part)
1102
+
1103
+ save_result = await save_artifact_with_metadata(
1104
+ artifact_service=self.artifact_service,
1105
+ app_name=agent_name,
1106
+ user_id=user_id,
1107
+ session_id=session_id,
1108
+ filename=filename,
1109
+ content_bytes=content_bytes,
1110
+ mime_type=mime_type,
1111
+ metadata_dict=metadata_to_save,
1112
+ timestamp=datetime.now(timezone.utc),
1113
+ )
1114
+
1115
+ if save_result.get("status") in ["success", "partial_success"]:
1116
+ data_version = save_result.get("data_version")
1117
+ saved_uri = format_artifact_uri(
1118
+ app_name=agent_name,
1119
+ user_id=user_id,
1120
+ session_id=session_id,
1121
+ filename=filename,
1122
+ version=data_version,
1123
+ )
1124
+
1125
+ new_file_part = a2a.create_file_part_from_uri(
1126
+ uri=saved_uri,
1127
+ name=filename,
1128
+ mime_type=mime_type,
1129
+ metadata=a2a.get_metadata_from_part(file_part),
1130
+ )
1131
+ from a2a.types import Part
1132
+
1133
+ artifact.parts[i] = Part(root=new_file_part)
1134
+
1135
+ saved_artifacts_manifest.append(
1136
+ {"filename": filename, "version": data_version}
1137
+ )
1138
+ log.info(
1139
+ "%s Saved artifact '%s' as version %d. URI: %s",
1140
+ log_identifier,
1141
+ filename,
1142
+ data_version,
1143
+ saved_uri,
1144
+ )
1145
+ else:
1146
+ log.error(
1147
+ "%s Failed to save artifact '%s': %s",
1148
+ log_identifier,
1149
+ filename,
1150
+ save_result.get("message"),
1151
+ )
1152
+
1153
+ return saved_artifacts_manifest
1154
+
1155
+ async def _process_downstream_response(
1156
+ self,
1157
+ event: Union[
1158
+ tuple, Message, Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent
1159
+ ],
1160
+ task_context: ProxyTaskContext,
1161
+ client: Client,
1162
+ agent_name: str,
1163
+ ) -> None:
1164
+ """
1165
+ Processes a single event from the downstream agent.
1166
+
1167
+ When using the normal client (non-streaming), events are:
1168
+ - A ClientEvent tuple: (Task, Optional[UpdateEvent])
1169
+ - A Message object (for direct responses)
1170
+
1171
+ When using transport directly (streaming workaround), events are raw:
1172
+ - Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, or Message objects
1173
+ """
1174
+ log_identifier = (
1175
+ f"{self.log_identifier}[ProcessResponse:{task_context.task_id}]"
1176
+ )
1177
+
1178
+ # Use facade helpers to determine event type
1179
+ event_payload = None
1180
+
1181
+ # Handle raw transport events (from streaming workaround)
1182
+ if isinstance(event, (Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent)):
1183
+ event_payload = event
1184
+ log.debug(
1185
+ "%s Received raw transport event: %s",
1186
+ log_identifier,
1187
+ type(event).__name__,
1188
+ )
1189
+ elif a2a.is_client_event(event):
1190
+ # Unpack the ClientEvent tuple
1191
+ task, update_event = a2a.unpack_client_event(event)
1192
+ # If there's an update event, that's what we should process
1193
+ # The task is just context; the update is the actual event
1194
+ if update_event is not None:
1195
+ event_payload = update_event
1196
+ log.debug(
1197
+ "%s Received ClientEvent with update: %s (task state: %s)",
1198
+ log_identifier,
1199
+ type(update_event).__name__,
1200
+ task.status.state if task.status else "unknown",
1201
+ )
1202
+ else:
1203
+ # No update event means this is the final task state
1204
+ event_payload = task
1205
+ log.debug(
1206
+ "%s Received ClientEvent with final task state: %s",
1207
+ log_identifier,
1208
+ task.status.state if task.status else "unknown",
1209
+ )
1210
+ elif a2a.is_message_object(event):
1211
+ # Direct Message response
1212
+ event_payload = event
1213
+ log.debug(
1214
+ "%s Received direct Message response",
1215
+ log_identifier,
1216
+ )
1217
+ else:
1218
+ log.warning(
1219
+ "%s Received unexpected event type: %s",
1220
+ log_identifier,
1221
+ type(event).__name__,
1222
+ )
1223
+ return
1224
+
1225
+ if not event_payload:
1226
+ log.warning(
1227
+ "%s Received an event with no processable payload: %s",
1228
+ log_identifier,
1229
+ event,
1230
+ )
1231
+ return
1232
+
1233
+ produced_artifacts = await self._handle_outbound_artifacts(
1234
+ event_payload, task_context, agent_name
1235
+ )
1236
+
1237
+ # Add produced_artifacts to metadata if any artifacts were processed
1238
+ if produced_artifacts and isinstance(
1239
+ event_payload, (Task, TaskStatusUpdateEvent)
1240
+ ):
1241
+ if not event_payload.metadata:
1242
+ event_payload.metadata = {}
1243
+ event_payload.metadata["produced_artifacts"] = produced_artifacts
1244
+ log.info(
1245
+ "%s Added manifest of %d produced artifacts to %s metadata.",
1246
+ log_identifier,
1247
+ len(produced_artifacts),
1248
+ type(event_payload).__name__,
1249
+ )
1250
+
1251
+ # Add agent_name to metadata for all response types
1252
+ if isinstance(
1253
+ event_payload, (Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
1254
+ ):
1255
+ if not event_payload.metadata:
1256
+ event_payload.metadata = {}
1257
+ event_payload.metadata["agent_name"] = agent_name
1258
+ log.debug(
1259
+ "%s Added agent_name '%s' to %s metadata.",
1260
+ log_identifier,
1261
+ agent_name,
1262
+ type(event_payload).__name__,
1263
+ )
1264
+
1265
+ # Convert TextParts to AgentProgressUpdateData for intermediate status updates if configured
1266
+ # Only convert non-final status updates; final status updates are used to construct the final Task
1267
+ if isinstance(event_payload, TaskStatusUpdateEvent) and not event_payload.final:
1268
+ agent_config = self._get_agent_config(agent_name)
1269
+ convert_progress = agent_config.get("convert_progress_updates", True) if agent_config else True
1270
+
1271
+ # DEBUG: Log config lookup results
1272
+ log.info(
1273
+ "%s DEBUG convert_progress_updates: agent_name='%s', agent_config_name='%s', agent_config_keys=%s, convert_progress_value=%s, convert_progress=%s",
1274
+ log_identifier,
1275
+ agent_name,
1276
+ agent_config.get('name') if agent_config else None,
1277
+ list(agent_config.keys()) if agent_config else None,
1278
+ agent_config.get("convert_progress_updates") if agent_config else None,
1279
+ convert_progress,
1280
+ )
1281
+
1282
+ if convert_progress and event_payload.status and event_payload.status.message:
1283
+ message = event_payload.status.message
1284
+ original_parts = a2a.get_parts_from_message(message)
1285
+
1286
+ if original_parts:
1287
+ converted_parts = []
1288
+ text_parts_converted = 0
1289
+
1290
+ for part in original_parts:
1291
+ if isinstance(part, TextPart) and part.text:
1292
+ # Convert TextPart to DataPart with AgentProgressUpdateData
1293
+ progress_data = AgentProgressUpdateData(
1294
+ type="agent_progress_update",
1295
+ status_text=part.text
1296
+ )
1297
+ data_part = DataPart(
1298
+ kind="data",
1299
+ data=progress_data.model_dump(),
1300
+ metadata=part.metadata
1301
+ )
1302
+ converted_parts.append(data_part)
1303
+ text_parts_converted += 1
1304
+ else:
1305
+ # Keep non-text parts as-is
1306
+ converted_parts.append(part)
1307
+
1308
+ if text_parts_converted > 0:
1309
+ # Update the message with converted parts
1310
+ event_payload.status.message = a2a.update_message_parts(
1311
+ message, converted_parts
1312
+ )
1313
+ log.debug(
1314
+ "%s Converted %d TextPart(s) to AgentProgressUpdateData in status update",
1315
+ log_identifier,
1316
+ text_parts_converted,
1317
+ )
1318
+
1319
+ # Capture the downstream task ID before we replace it
1320
+ # This is needed for forwarding cancellation requests to the downstream agent
1321
+ downstream_id = None
1322
+ if hasattr(event_payload, "task_id") and event_payload.task_id:
1323
+ downstream_id = event_payload.task_id
1324
+ elif hasattr(event_payload, "id") and event_payload.id:
1325
+ downstream_id = event_payload.id
1326
+
1327
+ # Store the downstream task ID in the context if we haven't already
1328
+ if downstream_id and not task_context.downstream_task_id:
1329
+ task_context.downstream_task_id = downstream_id
1330
+ log.debug(
1331
+ "%s Captured downstream task ID: %s (SAM task ID: %s)",
1332
+ log_identifier,
1333
+ downstream_id,
1334
+ task_context.task_id,
1335
+ )
1336
+
1337
+ # Replace the downstream task ID with SAM's task ID for upstream responses
1338
+ original_task_id = task_context.task_id
1339
+ if hasattr(event_payload, "task_id") and event_payload.task_id:
1340
+ event_payload.task_id = original_task_id
1341
+ elif hasattr(event_payload, "id") and event_payload.id:
1342
+ event_payload.id = original_task_id
1343
+
1344
+ if isinstance(event_payload, Task) and event_payload.artifacts:
1345
+ text_only_artifacts_content = []
1346
+ remaining_artifacts = []
1347
+ for artifact in event_payload.artifacts:
1348
+ if a2a.is_text_only_artifact(artifact):
1349
+ text_only_artifacts_content.extend(
1350
+ a2a.get_text_content_from_artifact(artifact)
1351
+ )
1352
+ else:
1353
+ remaining_artifacts.append(artifact)
1354
+
1355
+ if text_only_artifacts_content:
1356
+ log.info(
1357
+ "%s Consolidating %d text-only artifacts into status message.",
1358
+ log_identifier,
1359
+ len(event_payload.artifacts) - len(remaining_artifacts),
1360
+ )
1361
+ event_payload.artifacts = (
1362
+ remaining_artifacts if remaining_artifacts else None
1363
+ )
1364
+
1365
+ consolidated_text = "\n".join(text_only_artifacts_content)
1366
+ summary_message_part = TextPart(
1367
+ text=(
1368
+ "The following text-only artifacts were returned and have been consolidated into this message:\n\n---\n\n"
1369
+ f"{consolidated_text}"
1370
+ )
1371
+ )
1372
+
1373
+ if not event_payload.status.message:
1374
+ from a2a.types import Part
1375
+
1376
+ event_payload.status.message = Message(
1377
+ message_id=str(uuid.uuid4()),
1378
+ role="agent",
1379
+ parts=[Part(root=summary_message_part)],
1380
+ )
1381
+ else:
1382
+ from a2a.types import Part
1383
+
1384
+ event_payload.status.message.parts.append(
1385
+ Part(root=summary_message_part)
1386
+ )
1387
+
1388
+ # Convert text-only TaskArtifactUpdateEvents to TaskStatusUpdateEvents
1389
+ # Some A2A agents send text content as artifacts, which SAM expects as status updates
1390
+ if isinstance(event_payload, TaskArtifactUpdateEvent):
1391
+ artifact = event_payload.artifact
1392
+ if a2a.is_text_only_artifact(artifact):
1393
+ log.info(
1394
+ "%s Converting text-only artifact to status update",
1395
+ log_identifier,
1396
+ )
1397
+ # Extract text from text-only artifact
1398
+ text_content = "\n".join(a2a.get_text_content_from_artifact(artifact))
1399
+
1400
+ # Convert to status update
1401
+ text_message = a2a.create_agent_text_message(
1402
+ text=text_content,
1403
+ task_id=event_payload.task_id,
1404
+ context_id=event_payload.context_id,
1405
+ )
1406
+
1407
+ status_event = TaskStatusUpdateEvent(
1408
+ task_id=event_payload.task_id,
1409
+ context_id=event_payload.context_id,
1410
+ kind="status-update",
1411
+ status=TaskStatus(state=TaskState.working, message=text_message),
1412
+ final=False,
1413
+ metadata=event_payload.metadata,
1414
+ )
1415
+
1416
+ # Replace event_payload with the converted status update
1417
+ event_payload = status_event
1418
+ log.info(
1419
+ "%s Converted text-only artifact (length: %d bytes) to status update",
1420
+ log_identifier,
1421
+ len(text_content.encode("utf-8")),
1422
+ )
1423
+
1424
+ # Determine if this is a terminal event requiring cleanup
1425
+ should_cleanup_task = False
1426
+
1427
+ # Route based on event type
1428
+ if isinstance(event_payload, Task):
1429
+ # Discard initial Task events (non-completed states)
1430
+ # The final Task will be constructed from the final status update
1431
+ if event_payload.status.state != TaskState.completed:
1432
+ log.debug(
1433
+ "%s Discarding Task event with state=%s (not completed). Final Task will be constructed from final status update.",
1434
+ log_identifier,
1435
+ event_payload.status.state,
1436
+ )
1437
+ # Don't publish, don't cleanup - wait for final status update
1438
+ return
1439
+
1440
+ # Forward completed Task to reply topic
1441
+ await self._publish_task_response(event_payload, task_context.a2a_context)
1442
+
1443
+ # Completed Task is terminal - cleanup
1444
+ should_cleanup_task = True
1445
+ log.debug(
1446
+ "%s Task in terminal state: %s",
1447
+ log_identifier,
1448
+ event_payload.status.state,
1449
+ )
1450
+
1451
+ elif isinstance(event_payload, TaskStatusUpdateEvent):
1452
+ # Forward status update to status topic
1453
+ await self._publish_status_update(event_payload, task_context.a2a_context)
1454
+
1455
+ # Check if final event - construct and send Task
1456
+ if event_payload.final:
1457
+ log.info(
1458
+ "%s Received final status update (final=true). Constructing completed Task.",
1459
+ log_identifier,
1460
+ )
1461
+
1462
+ # Construct Task from final status update
1463
+ # Copy the status but ensure state is "completed"
1464
+ final_task_status = TaskStatus(
1465
+ state=TaskState.completed,
1466
+ message=event_payload.status.message if event_payload.status else None,
1467
+ )
1468
+
1469
+ final_task = Task(
1470
+ id=event_payload.task_id,
1471
+ context_id=event_payload.context_id,
1472
+ status=final_task_status,
1473
+ artifacts=None, # Artifacts come via separate events
1474
+ metadata=event_payload.metadata,
1475
+ )
1476
+
1477
+ # Add produced_artifacts metadata if any artifacts were processed
1478
+ if produced_artifacts:
1479
+ if not final_task.metadata:
1480
+ final_task.metadata = {}
1481
+ final_task.metadata["produced_artifacts"] = produced_artifacts
1482
+ log.info(
1483
+ "%s Added manifest of %d produced artifacts to final Task metadata.",
1484
+ log_identifier,
1485
+ len(produced_artifacts),
1486
+ )
1487
+
1488
+ # Publish the constructed Task
1489
+ await self._publish_task_response(final_task, task_context.a2a_context)
1490
+
1491
+ should_cleanup_task = True
1492
+ log.debug(
1493
+ "%s Published final Task constructed from status update",
1494
+ log_identifier,
1495
+ )
1496
+
1497
+ elif isinstance(event_payload, TaskArtifactUpdateEvent):
1498
+ # Forward artifact update to status topic
1499
+ await self._publish_artifact_update(event_payload, task_context.a2a_context)
1500
+
1501
+ elif isinstance(event_payload, Message):
1502
+ # Wrap Message in Task for gateway compatibility
1503
+ log.info(
1504
+ "%s Received direct Message response. Wrapping in completed Task.",
1505
+ log_identifier,
1506
+ )
1507
+ final_task = Task(
1508
+ id=task_context.task_id,
1509
+ context_id=task_context.a2a_context.get("session_id"),
1510
+ status=TaskStatus(state=TaskState.completed, message=event_payload),
1511
+ )
1512
+
1513
+ # Add produced_artifacts metadata if any artifacts were processed
1514
+ if produced_artifacts:
1515
+ final_task.metadata = {"produced_artifacts": produced_artifacts}
1516
+ log.info(
1517
+ "%s Added manifest of %d produced artifacts to wrapped Task metadata.",
1518
+ log_identifier,
1519
+ len(produced_artifacts),
1520
+ )
1521
+
1522
+ await self._publish_task_response(final_task, task_context.a2a_context)
1523
+ should_cleanup_task = True
1524
+
1525
+ else:
1526
+ log.warning(
1527
+ "%s Received unhandled response payload type: %s",
1528
+ log_identifier,
1529
+ type(event_payload).__name__,
1530
+ )
1531
+
1532
+ # Cleanup task state if terminal event detected
1533
+ if should_cleanup_task:
1534
+ log.info(
1535
+ "%s Terminal event detected for task %s. Cleaning up state.",
1536
+ log_identifier,
1537
+ task_context.task_id,
1538
+ )
1539
+ self._cleanup_task_state(task_context.task_id)
1540
+
1541
+ def clear_client_cache(self):
1542
+ """
1543
+ Clears all cached A2A clients and OAuth tokens.
1544
+ This is useful for testing when authentication configuration changes.
1545
+ """
1546
+ num_clients = len(self._a2a_clients)
1547
+ self._a2a_clients.clear()
1548
+ log.info(
1549
+ "%s Cleared all cached A2A clients (%d clients removed).",
1550
+ self.log_identifier,
1551
+ num_clients,
1552
+ )
1553
+
1554
+ def cleanup(self):
1555
+ """Cleans up resources on component shutdown."""
1556
+ log.info("%s Cleaning up A2A proxy component resources...", self.log_identifier)
1557
+
1558
+ # Token cache cleanup:
1559
+ # - OAuth2TokenCache is automatically garbage collected
1560
+ # - No persistent state to clean up
1561
+ # - Tokens are lost on component restart (by design)
1562
+
1563
+ async def _async_cleanup():
1564
+ # Close all created clients using public API
1565
+ for cache_key, client in self._a2a_clients.items():
1566
+ agent_name, session_id = cache_key
1567
+ log.info(
1568
+ "%s Closing client for agent '%s' session '%s'",
1569
+ self.log_identifier,
1570
+ agent_name,
1571
+ session_id,
1572
+ )
1573
+ await client.close()
1574
+ self._a2a_clients.clear()
1575
+
1576
+ if self._async_loop and self._async_loop.is_running():
1577
+ future = asyncio.run_coroutine_threadsafe(
1578
+ _async_cleanup(), self._async_loop
1579
+ )
1580
+ try:
1581
+ future.result(timeout=5)
1582
+ except Exception as e:
1583
+ log.error("%s Error during async cleanup: %s", self.log_identifier, e)
1584
+
1585
+ super().cleanup()