solace-agent-mesh 1.7.1__py3-none-any.whl → 1.13.2__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (447) hide show
  1. solace_agent_mesh/agent/adk/alembic/README +74 -0
  2. solace_agent_mesh/agent/adk/alembic/env.py +77 -0
  3. solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
  4. solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
  5. solace_agent_mesh/agent/adk/alembic.ini +112 -0
  6. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +164 -0
  7. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +163 -0
  8. solace_agent_mesh/agent/adk/callbacks.py +752 -127
  9. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +99 -7
  10. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +52 -5
  11. solace_agent_mesh/agent/adk/mcp_content_processor.py +1 -1
  12. solace_agent_mesh/agent/adk/models/lite_llm.py +34 -16
  13. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +24 -137
  14. solace_agent_mesh/agent/adk/runner.py +66 -8
  15. solace_agent_mesh/agent/adk/schema_migration.py +88 -0
  16. solace_agent_mesh/agent/adk/services.py +41 -1
  17. solace_agent_mesh/agent/adk/setup.py +220 -32
  18. solace_agent_mesh/agent/adk/stream_parser.py +229 -40
  19. solace_agent_mesh/agent/protocol/event_handlers.py +219 -33
  20. solace_agent_mesh/agent/proxies/a2a/component.py +572 -75
  21. solace_agent_mesh/agent/proxies/a2a/config.py +80 -4
  22. solace_agent_mesh/agent/proxies/base/component.py +188 -22
  23. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +3 -1
  24. solace_agent_mesh/agent/sac/app.py +37 -12
  25. solace_agent_mesh/agent/sac/component.py +322 -52
  26. solace_agent_mesh/agent/sac/patch_adk.py +8 -16
  27. solace_agent_mesh/agent/sac/task_execution_context.py +90 -0
  28. solace_agent_mesh/agent/tools/__init__.py +3 -0
  29. solace_agent_mesh/agent/tools/audio_tools.py +3 -3
  30. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +698 -24
  31. solace_agent_mesh/agent/tools/deep_research_tools.py +2161 -0
  32. solace_agent_mesh/agent/tools/peer_agent_tool.py +82 -15
  33. solace_agent_mesh/agent/tools/time_tools.py +126 -0
  34. solace_agent_mesh/agent/tools/tool_config_types.py +54 -2
  35. solace_agent_mesh/agent/tools/web_search_tools.py +279 -0
  36. solace_agent_mesh/agent/tools/web_tools.py +125 -17
  37. solace_agent_mesh/agent/utils/artifact_helpers.py +243 -5
  38. solace_agent_mesh/agent/utils/context_helpers.py +17 -0
  39. solace_agent_mesh/assets/docs/404.html +6 -6
  40. solace_agent_mesh/assets/docs/assets/css/{styles.906a1503.css → styles.8162edfb.css} +1 -1
  41. solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
  42. solace_agent_mesh/assets/docs/assets/js/15ba94aa.e186750d.js +1 -0
  43. solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
  44. solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
  45. solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
  46. solace_agent_mesh/assets/docs/assets/js/{17896441.a5e82f9b.js.LICENSE.txt → 2279.550aa580.js.LICENSE.txt} +6 -0
  47. solace_agent_mesh/assets/docs/assets/js/240a0364.83e37aa8.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.2f0db237.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/3a6c6137.7e61915d.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/3ac1795d.7f7ab1c1.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/3ff0015d.e53c9b78.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/41adc471.0e95b87c.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/4667dc50.bf2ad456.js +1 -0
  54. solace_agent_mesh/assets/docs/assets/js/49eed117.493d6f99.js +1 -0
  55. solace_agent_mesh/assets/docs/assets/js/{509e993c.4c7a1a6d.js → 509e993c.a1fbf45a.js} +1 -1
  56. solace_agent_mesh/assets/docs/assets/js/547e15cc.8e6da617.js +1 -0
  57. solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/5b8d9c11.d4eb37b8.js +1 -0
  59. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.1ee87753.js +1 -0
  60. solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
  61. solace_agent_mesh/assets/docs/assets/js/64195356.09dbd087.js +1 -0
  62. solace_agent_mesh/assets/docs/assets/js/66d4869e.30340bd3.js +1 -0
  63. solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
  64. solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
  65. solace_agent_mesh/assets/docs/assets/js/729898df.7249e9fd.js +1 -0
  66. solace_agent_mesh/assets/docs/assets/js/7e294c01.7c5f6906.js +1 -0
  67. solace_agent_mesh/assets/docs/assets/js/8024126c.e3467286.js +1 -0
  68. solace_agent_mesh/assets/docs/assets/js/81a99df0.7ed65d45.js +1 -0
  69. solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
  70. solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
  71. solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
  72. solace_agent_mesh/assets/docs/assets/js/9bb13469.4523ae20.js +1 -0
  73. solace_agent_mesh/assets/docs/assets/js/a7d42657.a956689d.js +1 -0
  74. solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
  75. solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e563275.js +1 -0
  76. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
  77. solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
  78. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
  79. solace_agent_mesh/assets/docs/assets/js/e04b235d.06d23db6.js +1 -0
  80. solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.deb2b62e.js +1 -0
  81. solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
  82. solace_agent_mesh/assets/docs/assets/js/e6f9706b.acc800d3.js +1 -0
  83. solace_agent_mesh/assets/docs/assets/js/e92d0134.c147a429.js +1 -0
  84. solace_agent_mesh/assets/docs/assets/js/ee0c2fe7.94d0a351.js +1 -0
  85. solace_agent_mesh/assets/docs/assets/js/f284c35a.cc97854c.js +1 -0
  86. solace_agent_mesh/assets/docs/assets/js/main.d634009f.js +2 -0
  87. solace_agent_mesh/assets/docs/assets/js/runtime~main.27bb82a7.js +1 -0
  88. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +68 -68
  89. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +50 -50
  90. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +42 -42
  91. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +55 -55
  92. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +75 -75
  93. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/image-tools/index.html +81 -0
  94. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +67 -50
  95. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/research-tools/index.html +136 -0
  96. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +178 -144
  97. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +43 -42
  98. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +20 -18
  99. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +23 -23
  100. solace_agent_mesh/assets/docs/docs/documentation/components/platform-service/index.html +33 -0
  101. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +45 -45
  102. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +98 -112
  103. solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
  104. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +208 -125
  105. solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
  106. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +28 -28
  107. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +29 -29
  108. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +14 -14
  109. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/index.html +47 -0
  110. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/kubernetes-deployment-guide/index.html +197 -0
  111. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +67 -53
  112. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +17 -17
  113. solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
  114. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +38 -38
  115. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +87 -87
  116. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +67 -49
  117. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +17 -17
  118. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +51 -51
  119. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +22 -22
  120. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +27 -27
  121. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +135 -135
  122. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +66 -66
  123. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +51 -51
  124. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +50 -38
  125. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +86 -86
  126. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +51 -51
  127. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +24 -24
  128. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +30 -30
  129. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +44 -44
  130. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
  131. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +50 -23
  132. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +29 -24
  133. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +21 -21
  134. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +40 -37
  135. solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
  136. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +96 -66
  137. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +181 -181
  138. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +75 -75
  139. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +27 -27
  140. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +44 -44
  141. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +39 -38
  142. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +30 -30
  143. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +18 -18
  144. solace_agent_mesh/assets/docs/docs/documentation/getting-started/vibe_coding/index.html +62 -0
  145. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +135 -114
  146. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +37 -37
  147. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +14 -14
  148. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +27 -25
  149. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +69 -69
  150. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +72 -72
  151. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +112 -112
  152. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +28 -28
  153. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +42 -42
  154. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +20 -20
  155. solace_agent_mesh/assets/docs/docs/documentation/migrations/platform-service-split/index.html +85 -0
  156. solace_agent_mesh/assets/docs/lunr-index-1768329217460.json +1 -0
  157. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  158. solace_agent_mesh/assets/docs/search-doc-1768329217460.json +1 -0
  159. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  160. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  161. solace_agent_mesh/cli/__init__.py +1 -1
  162. solace_agent_mesh/cli/commands/add_cmd/__init__.py +3 -1
  163. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -1
  164. solace_agent_mesh/cli/commands/add_cmd/proxy_cmd.py +100 -0
  165. solace_agent_mesh/cli/commands/eval_cmd.py +1 -1
  166. solace_agent_mesh/cli/commands/init_cmd/__init__.py +15 -0
  167. solace_agent_mesh/cli/commands/init_cmd/directory_step.py +1 -1
  168. solace_agent_mesh/cli/commands/init_cmd/env_step.py +30 -3
  169. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +3 -4
  170. solace_agent_mesh/cli/commands/init_cmd/platform_service_step.py +85 -0
  171. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +16 -3
  172. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +2 -1
  173. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +1 -0
  174. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +3 -3
  175. solace_agent_mesh/cli/commands/run_cmd.py +64 -49
  176. solace_agent_mesh/cli/commands/tools_cmd.py +315 -0
  177. solace_agent_mesh/cli/main.py +15 -0
  178. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-tcIFZLis.js → authCallback-KnKMP_vb.js} +1 -1
  179. solace_agent_mesh/client/webui/frontend/static/assets/client-DpBL2stg.js +25 -0
  180. solace_agent_mesh/client/webui/frontend/static/assets/main-Cd498TV2.js +435 -0
  181. solace_agent_mesh/client/webui/frontend/static/assets/main-rSf8Vu29.css +1 -0
  182. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CINwxvwV.js → vendor-CGk8Suyh.js} +189 -94
  183. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  184. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  185. solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
  186. solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
  187. solace_agent_mesh/common/a2a/types.py +1 -1
  188. solace_agent_mesh/common/agent_registry.py +38 -11
  189. solace_agent_mesh/common/data_parts.py +124 -0
  190. solace_agent_mesh/common/error_handlers.py +83 -0
  191. solace_agent_mesh/common/exceptions.py +24 -0
  192. solace_agent_mesh/common/oauth/__init__.py +17 -0
  193. solace_agent_mesh/common/oauth/oauth_client.py +408 -0
  194. solace_agent_mesh/common/oauth/utils.py +50 -0
  195. solace_agent_mesh/common/rag_dto.py +156 -0
  196. solace_agent_mesh/common/sac/sam_component_base.py +73 -1
  197. solace_agent_mesh/common/sam_events/event_service.py +2 -2
  198. solace_agent_mesh/common/utils/embeds/converter.py +1 -8
  199. solace_agent_mesh/common/utils/embeds/modifiers.py +2 -27
  200. solace_agent_mesh/common/utils/embeds/resolver.py +94 -25
  201. solace_agent_mesh/common/utils/embeds/types.py +1 -0
  202. solace_agent_mesh/common/utils/log_formatters.py +20 -0
  203. solace_agent_mesh/common/utils/mime_helpers.py +12 -5
  204. solace_agent_mesh/common/utils/rbac_utils.py +69 -0
  205. solace_agent_mesh/common/utils/templates/__init__.py +8 -0
  206. solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
  207. solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
  208. solace_agent_mesh/config_portal/backend/common.py +12 -0
  209. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-CljP4_mv.js +103 -0
  210. solace_agent_mesh/config_portal/frontend/static/client/assets/{components-Rk0n-9cK.js → components-CaC6hG8d.js} +22 -22
  211. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-mvZjNKiz.js → entry.client-H_TM0YBt.js} +3 -3
  212. solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DzNKzXrc.js → index-CnFykb2v.js} +16 -16
  213. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-f8439d40.js +1 -0
  214. solace_agent_mesh/config_portal/frontend/static/client/assets/root-BIMqslJB.css +1 -0
  215. solace_agent_mesh/config_portal/frontend/static/client/assets/root-mJmTIdIk.js +10 -0
  216. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  217. solace_agent_mesh/core_a2a/service.py +3 -2
  218. solace_agent_mesh/gateway/adapter/base.py +28 -1
  219. solace_agent_mesh/gateway/adapter/types.py +9 -0
  220. solace_agent_mesh/gateway/base/app.py +10 -0
  221. solace_agent_mesh/gateway/base/auth_interface.py +103 -0
  222. solace_agent_mesh/gateway/base/component.py +451 -10
  223. solace_agent_mesh/gateway/generic/component.py +274 -30
  224. solace_agent_mesh/gateway/http_sse/alembic/env.py +0 -7
  225. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +2 -43
  226. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +2 -2
  227. solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
  228. solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
  229. solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
  230. solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
  231. solace_agent_mesh/gateway/http_sse/alembic.ini +0 -36
  232. solace_agent_mesh/gateway/http_sse/app.py +23 -6
  233. solace_agent_mesh/gateway/http_sse/component.py +158 -73
  234. solace_agent_mesh/gateway/http_sse/dependencies.py +50 -57
  235. solace_agent_mesh/gateway/http_sse/main.py +58 -482
  236. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +2 -2
  237. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +1 -1
  238. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +1 -1
  239. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -2
  240. solace_agent_mesh/gateway/http_sse/repository/entities/task.py +7 -0
  241. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +2 -2
  242. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +2 -2
  243. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +5 -0
  244. solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
  245. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +1 -1
  246. solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +8 -1
  247. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +1 -1
  248. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +1 -1
  249. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +12 -107
  250. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +86 -2
  251. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +38 -7
  252. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +113 -7
  253. solace_agent_mesh/gateway/http_sse/routers/auth.py +69 -132
  254. solace_agent_mesh/gateway/http_sse/routers/config.py +235 -10
  255. solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
  256. solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
  257. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +1 -1
  258. solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +1 -1
  259. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +1 -0
  260. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +3 -2
  261. solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
  262. solace_agent_mesh/gateway/http_sse/routers/feedback.py +2 -2
  263. solace_agent_mesh/gateway/http_sse/routers/people.py +2 -2
  264. solace_agent_mesh/gateway/http_sse/routers/projects.py +250 -24
  265. solace_agent_mesh/gateway/http_sse/routers/prompts.py +1416 -0
  266. solace_agent_mesh/gateway/http_sse/routers/sessions.py +14 -5
  267. solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
  268. solace_agent_mesh/gateway/http_sse/routers/sse.py +117 -4
  269. solace_agent_mesh/gateway/http_sse/routers/tasks.py +509 -149
  270. solace_agent_mesh/gateway/http_sse/routers/users.py +1 -1
  271. solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
  272. solace_agent_mesh/gateway/http_sse/routers/visualization.py +2 -1
  273. solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
  274. solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
  275. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +1 -1
  276. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +1 -1
  277. solace_agent_mesh/gateway/http_sse/services/project_service.py +539 -12
  278. solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
  279. solace_agent_mesh/gateway/http_sse/services/session_service.py +198 -21
  280. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +354 -4
  281. solace_agent_mesh/gateway/http_sse/sse_manager.py +280 -169
  282. solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
  283. solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +41 -1
  284. solace_agent_mesh/services/__init__.py +0 -0
  285. solace_agent_mesh/services/platform/__init__.py +29 -0
  286. solace_agent_mesh/services/platform/alembic/env.py +85 -0
  287. solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
  288. solace_agent_mesh/services/platform/alembic.ini +109 -0
  289. solace_agent_mesh/services/platform/api/__init__.py +3 -0
  290. solace_agent_mesh/services/platform/api/dependencies.py +154 -0
  291. solace_agent_mesh/services/platform/api/main.py +314 -0
  292. solace_agent_mesh/services/platform/api/middleware.py +51 -0
  293. solace_agent_mesh/services/platform/api/routers/__init__.py +33 -0
  294. solace_agent_mesh/services/platform/api/routers/health_router.py +31 -0
  295. solace_agent_mesh/services/platform/app.py +215 -0
  296. solace_agent_mesh/services/platform/component.py +777 -0
  297. solace_agent_mesh/shared/__init__.py +14 -0
  298. solace_agent_mesh/shared/api/__init__.py +42 -0
  299. solace_agent_mesh/shared/auth/__init__.py +26 -0
  300. solace_agent_mesh/shared/auth/dependencies.py +204 -0
  301. solace_agent_mesh/shared/auth/middleware.py +347 -0
  302. solace_agent_mesh/shared/database/__init__.py +20 -0
  303. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/base_repository.py +1 -1
  304. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_exceptions.py +1 -1
  305. solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_helpers.py +1 -1
  306. solace_agent_mesh/shared/exceptions/__init__.py +36 -0
  307. solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exception_handlers.py +1 -1
  308. solace_agent_mesh/shared/utils/__init__.py +21 -0
  309. solace_agent_mesh/templates/logging_config_template.yaml +48 -0
  310. solace_agent_mesh/templates/main_orchestrator.yaml +12 -1
  311. solace_agent_mesh/templates/platform.yaml +49 -0
  312. solace_agent_mesh/templates/plugin_readme_template.md +3 -25
  313. solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
  314. solace_agent_mesh/templates/proxy_template.yaml +62 -0
  315. solace_agent_mesh/templates/webui.yaml +148 -6
  316. solace_agent_mesh/tools/web_search/__init__.py +18 -0
  317. solace_agent_mesh/tools/web_search/base.py +84 -0
  318. solace_agent_mesh/tools/web_search/google_search.py +247 -0
  319. solace_agent_mesh/tools/web_search/models.py +99 -0
  320. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/METADATA +29 -8
  321. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/RECORD +334 -313
  322. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/WHEEL +1 -1
  323. solace_agent_mesh/agent/adk/adk_llm.txt +0 -226
  324. solace_agent_mesh/agent/adk/adk_llm_detail.txt +0 -566
  325. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +0 -171
  326. solace_agent_mesh/agent/adk/models/models_llm.txt +0 -189
  327. solace_agent_mesh/agent/agent_llm.txt +0 -369
  328. solace_agent_mesh/agent/agent_llm_detail.txt +0 -1702
  329. solace_agent_mesh/agent/protocol/protocol_llm.txt +0 -81
  330. solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +0 -92
  331. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +0 -190
  332. solace_agent_mesh/agent/proxies/base/base_llm.txt +0 -148
  333. solace_agent_mesh/agent/proxies/proxies_llm.txt +0 -283
  334. solace_agent_mesh/agent/sac/sac_llm.txt +0 -189
  335. solace_agent_mesh/agent/sac/sac_llm_detail.txt +0 -200
  336. solace_agent_mesh/agent/testing/testing_llm.txt +0 -58
  337. solace_agent_mesh/agent/testing/testing_llm_detail.txt +0 -68
  338. solace_agent_mesh/agent/tools/tools_llm.txt +0 -276
  339. solace_agent_mesh/agent/tools/tools_llm_detail.txt +0 -275
  340. solace_agent_mesh/agent/utils/utils_llm.txt +0 -152
  341. solace_agent_mesh/agent/utils/utils_llm_detail.txt +0 -149
  342. solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +0 -1
  343. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +0 -1
  344. solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +0 -1
  345. solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +0 -2
  346. solace_agent_mesh/assets/docs/assets/js/240a0364.c39f8388.js +0 -1
  347. solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +0 -1
  348. solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +0 -1
  349. solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +0 -1
  350. solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +0 -1
  351. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +0 -1
  352. solace_agent_mesh/assets/docs/assets/js/55b7b518.f2b1d1ba.js +0 -1
  353. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +0 -1
  354. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +0 -1
  355. solace_agent_mesh/assets/docs/assets/js/66d4869e.830d443f.js +0 -1
  356. solace_agent_mesh/assets/docs/assets/js/6d84eae0.4a5fbf39.js +0 -1
  357. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +0 -1
  358. solace_agent_mesh/assets/docs/assets/js/81a99df0.07034dd9.js +0 -1
  359. solace_agent_mesh/assets/docs/assets/js/82fbfb93.139a1a1f.js +0 -1
  360. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +0 -1
  361. solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +0 -1
  362. solace_agent_mesh/assets/docs/assets/js/9bb13469.dd1c9b54.js +0 -1
  363. solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +0 -1
  364. solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +0 -1
  365. solace_agent_mesh/assets/docs/assets/js/c93cbaa0.eaff365e.js +0 -1
  366. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +0 -1
  367. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +0 -1
  368. solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +0 -1
  369. solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +0 -1
  370. solace_agent_mesh/assets/docs/assets/js/e6f9706b.045d0fa1.js +0 -1
  371. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +0 -1
  372. solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +0 -1
  373. solace_agent_mesh/assets/docs/assets/js/main.f213fe0c.js +0 -2
  374. solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +0 -1
  375. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +0 -47
  376. solace_agent_mesh/assets/docs/lunr-index-1762283454666.json +0 -1
  377. solace_agent_mesh/assets/docs/search-doc-1762283454666.json +0 -1
  378. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +0 -250
  379. solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +0 -365
  380. solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +0 -305
  381. solace_agent_mesh/client/webui/frontend/static/assets/client-CRYdKo2Q.js +0 -25
  382. solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +0 -1
  383. solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +0 -353
  384. solace_agent_mesh/common/a2a/a2a_llm.txt +0 -175
  385. solace_agent_mesh/common/a2a/a2a_llm_detail.txt +0 -193
  386. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +0 -445
  387. solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +0 -736
  388. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +0 -330
  389. solace_agent_mesh/common/common_llm.txt +0 -230
  390. solace_agent_mesh/common/common_llm_detail.txt +0 -2562
  391. solace_agent_mesh/common/middleware/middleware_llm.txt +0 -174
  392. solace_agent_mesh/common/middleware/middleware_llm_detail.txt +0 -185
  393. solace_agent_mesh/common/sac/sac_llm.txt +0 -71
  394. solace_agent_mesh/common/sac/sac_llm_detail.txt +0 -82
  395. solace_agent_mesh/common/sam_events/sam_events_llm.txt +0 -104
  396. solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +0 -115
  397. solace_agent_mesh/common/services/providers/providers_llm.txt +0 -81
  398. solace_agent_mesh/common/services/services_llm.txt +0 -368
  399. solace_agent_mesh/common/services/services_llm_detail.txt +0 -459
  400. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +0 -220
  401. solace_agent_mesh/common/utils/utils_llm.txt +0 -335
  402. solace_agent_mesh/common/utils/utils_llm_detail.txt +0 -572
  403. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +0 -98
  404. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-61038fc6.js +0 -1
  405. solace_agent_mesh/config_portal/frontend/static/client/assets/root-BWvk5-gF.js +0 -10
  406. solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +0 -1
  407. solace_agent_mesh/core_a2a/core_a2a_llm.txt +0 -90
  408. solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +0 -101
  409. solace_agent_mesh/gateway/base/base_llm.txt +0 -226
  410. solace_agent_mesh/gateway/base/base_llm_detail.txt +0 -235
  411. solace_agent_mesh/gateway/gateway_llm.txt +0 -369
  412. solace_agent_mesh/gateway/gateway_llm_detail.txt +0 -3885
  413. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +0 -345
  414. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +0 -92
  415. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +0 -161
  416. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +0 -105
  417. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +0 -299
  418. solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +0 -3278
  419. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +0 -221
  420. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +0 -257
  421. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +0 -308
  422. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +0 -450
  423. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +0 -133
  424. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +0 -123
  425. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +0 -312
  426. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +0 -303
  427. solace_agent_mesh/gateway/http_sse/shared/__init__.py +0 -146
  428. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +0 -319
  429. solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +0 -47
  430. solace_agent_mesh/llm.txt +0 -228
  431. solace_agent_mesh/llm_detail.txt +0 -2835
  432. solace_agent_mesh/solace_agent_mesh_llm.txt +0 -362
  433. solace_agent_mesh/solace_agent_mesh_llm_detail.txt +0 -8599
  434. solace_agent_mesh/templates/logging_config_template.ini +0 -45
  435. solace_agent_mesh/templates/templates_llm.txt +0 -147
  436. /solace_agent_mesh/assets/docs/assets/js/{main.f213fe0c.js.LICENSE.txt → main.d634009f.js.LICENSE.txt} +0 -0
  437. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/auth_utils.py +0 -0
  438. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/pagination.py +0 -0
  439. /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/response_utils.py +0 -0
  440. /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/error_dto.py +0 -0
  441. /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exceptions.py +0 -0
  442. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/enums.py +0 -0
  443. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/timestamp_utils.py +0 -0
  444. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/types.py +0 -0
  445. /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/utils.py +0 -0
  446. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/entry_points.txt +0 -0
  447. {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ Manages Server-Sent Event (SSE) connections for streaming task updates.
5
5
  import logging
6
6
  import asyncio
7
7
  import threading
8
- from typing import Dict, List, Any
8
+ from typing import Dict, List, Any, Callable, Optional
9
9
  import json
10
10
  import datetime
11
11
  import math
@@ -20,38 +20,21 @@ class SSEManager:
20
20
  """
21
21
  Manages active SSE connections and distributes events based on task ID.
22
22
  Uses asyncio Queues for buffering events per connection.
23
+
24
+ Note: This manager uses a threading.Lock to ensure thread-safety across
25
+ different event loops (e.g., FastAPI event loop and SAC component event loop).
23
26
  """
24
27
 
25
- def __init__(self, max_queue_size: int, event_buffer: SSEEventBuffer):
28
+ def __init__(self, max_queue_size: int, event_buffer: SSEEventBuffer, session_factory: Optional[Callable] = None):
26
29
  self._connections: Dict[str, List[asyncio.Queue]] = {}
27
30
  self._event_buffer = event_buffer
28
- self._locks: Dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
29
- self._locks_lock = threading.Lock()
31
+ # Use a single threading lock for cross-event-loop synchronization
32
+ self._lock = threading.Lock()
30
33
  self.log_identifier = "[SSEManager]"
31
34
  self._max_queue_size = max_queue_size
32
-
33
- def _get_lock(self) -> asyncio.Lock:
34
- """Get or create a lock for the current event loop."""
35
- try:
36
- current_loop = asyncio.get_running_loop()
37
- except RuntimeError:
38
- log.error(
39
- "%s _get_lock must be called from within an async context.",
40
- self.log_identifier,
41
- )
42
- raise RuntimeError(
43
- "SSEManager methods must be called from within an async context"
44
- )
45
-
46
- with self._locks_lock:
47
- if current_loop not in self._locks:
48
- self._locks[current_loop] = asyncio.Lock()
49
- log.debug(
50
- "%s Created new lock for event loop %s",
51
- self.log_identifier,
52
- id(current_loop),
53
- )
54
- return self._locks[current_loop]
35
+ self._session_factory = session_factory
36
+ self._background_task_cache: Dict[str, bool] = {} # Cache to avoid repeated DB queries
37
+ self._tasks_with_prior_connection: set = set() # Track tasks that have had at least one SSE connection
55
38
 
56
39
  def _sanitize_json(self, obj):
57
40
  if isinstance(obj, dict):
@@ -79,27 +62,40 @@ class SSEManager:
79
62
  Returns:
80
63
  An asyncio.Queue that the SSE endpoint can consume from.
81
64
  """
82
- lock = self._get_lock()
83
- async with lock:
65
+ connection_queue = asyncio.Queue(maxsize=self._max_queue_size)
66
+ buffered_events = None
67
+
68
+ # Use threading lock for cross-event-loop synchronization
69
+ with self._lock:
84
70
  if task_id not in self._connections:
85
71
  self._connections[task_id] = []
86
72
 
87
- connection_queue = asyncio.Queue(maxsize=self._max_queue_size)
88
-
89
- # Flush any pending events from the buffer to the new connection
73
+ # Get buffered events atomically with adding the queue to connections
90
74
  buffered_events = self._event_buffer.get_and_remove_buffer(task_id)
91
- if buffered_events:
92
- for event in buffered_events:
93
- await connection_queue.put(event)
94
75
 
76
+ # Add queue to connections BEFORE releasing lock to ensure
77
+ # no events are buffered after we've retrieved the buffer
95
78
  self._connections[task_id].append(connection_queue)
79
+
80
+ # Mark this task as having had at least one connection
81
+ # This is used to distinguish "no connection yet" from "had connection but disconnected"
82
+ self._tasks_with_prior_connection.add(task_id)
83
+
96
84
  log.debug(
97
85
  "%s Created SSE connection queue for Task ID: %s. Total queues for task: %d",
98
86
  self.log_identifier,
99
87
  task_id,
100
88
  len(self._connections[task_id]),
101
89
  )
102
- return connection_queue
90
+
91
+ # Put buffered events into queue AFTER releasing the lock
92
+ # This is safe because the queue is already registered in _connections,
93
+ # so any new events will go directly to the queue via send_event
94
+ if buffered_events:
95
+ for event in buffered_events:
96
+ await connection_queue.put(event)
97
+
98
+ return connection_queue
103
99
 
104
100
  async def remove_sse_connection(
105
101
  self, task_id: str, connection_queue: asyncio.Queue
@@ -111,8 +107,7 @@ class SSEManager:
111
107
  task_id: The ID of the task.
112
108
  connection_queue: The specific queue instance to remove.
113
109
  """
114
- lock = self._get_lock()
115
- async with lock:
110
+ with self._lock:
116
111
  if task_id in self._connections:
117
112
  try:
118
113
  self._connections[task_id].remove(connection_queue)
@@ -142,6 +137,49 @@ class SSEManager:
142
137
  task_id,
143
138
  )
144
139
 
140
+ def _is_background_task(self, task_id: str) -> bool:
141
+ """
142
+ Check if a task is a background task by querying the database.
143
+ Uses caching to avoid repeated queries.
144
+
145
+ Args:
146
+ task_id: The ID of the task to check
147
+
148
+ Returns:
149
+ True if the task is a background task, False otherwise
150
+ """
151
+ # Check cache first
152
+ if task_id in self._background_task_cache:
153
+ return self._background_task_cache[task_id]
154
+
155
+ # If no session factory, assume not a background task
156
+ if not self._session_factory:
157
+ return False
158
+
159
+ try:
160
+ from .repository.task_repository import TaskRepository
161
+
162
+ db = self._session_factory()
163
+ try:
164
+ repo = TaskRepository()
165
+ task = repo.find_by_id(db, task_id)
166
+ is_background = task and task.background_execution_enabled
167
+
168
+ # Cache the result
169
+ self._background_task_cache[task_id] = is_background
170
+
171
+ return is_background
172
+ finally:
173
+ db.close()
174
+ except Exception as e:
175
+ log.warning(
176
+ "%s Failed to check if task %s is a background task: %s",
177
+ self.log_identifier,
178
+ task_id,
179
+ e,
180
+ )
181
+ return False
182
+
145
183
  async def send_event(
146
184
  self, task_id: str, event_data: Dict[str, Any], event_type: str = "message"
147
185
  ):
@@ -154,102 +192,130 @@ class SSEManager:
154
192
  event_data: The dictionary representing the A2A event (e.g., TaskStatusUpdateEvent).
155
193
  event_type: The type of the SSE event (default: "message").
156
194
  """
157
- lock = self._get_lock()
158
- async with lock:
195
+ # Serialize data outside the lock
196
+ try:
197
+ serialized_data = json.dumps(
198
+ self._sanitize_json(event_data), allow_nan=False
199
+ )
200
+ except Exception as json_err:
201
+ log.error(
202
+ "%s Failed to JSON serialize event data for Task ID %s: %s",
203
+ self.log_identifier,
204
+ task_id,
205
+ json_err,
206
+ )
207
+ return
208
+
209
+ sse_payload = {"event": event_type, "data": serialized_data}
210
+
211
+ # Get queues and decide action under the lock
212
+ queues_copy = None
213
+
214
+ with self._lock:
159
215
  queues = self._connections.get(task_id)
160
216
 
217
+ if not queues:
218
+ # Check if this is a background task (outside lock would be better,
219
+ # but we need the decision to be atomic with the buffering)
220
+ is_background_task = self._is_background_task(task_id)
221
+
222
+ # Check if this task has ever had a connection
223
+ has_had_connection = task_id in self._tasks_with_prior_connection
224
+
225
+ # Only drop events for background tasks that have HAD a connection before
226
+ # If no connection has ever been made, we must buffer so the first client gets the events
227
+ if is_background_task and has_had_connection:
228
+ # For background tasks where client disconnected, drop events to prevent buffer overflow
229
+ log.debug(
230
+ "%s No active SSE connections for background task %s (had prior connection). Dropping event to prevent buffer overflow.",
231
+ self.log_identifier,
232
+ task_id,
233
+ )
234
+ else:
235
+ log.debug(
236
+ "%s No active SSE connections for Task ID: %s. Buffering event.",
237
+ self.log_identifier,
238
+ task_id,
239
+ )
240
+ self._event_buffer.buffer_event(task_id, sse_payload)
241
+ return
242
+ else:
243
+ # Make a copy of queues to iterate outside the lock
244
+ queues_copy = list(queues)
245
+
246
+ # Log the payload outside the lock
247
+ if trace_logger.isEnabledFor(logging.DEBUG):
248
+ trace_logger.debug(
249
+ "%s Prepared SSE payload for Task ID %s: %s",
250
+ self.log_identifier,
251
+ task_id,
252
+ sse_payload,
253
+ )
254
+ else:
255
+ log.debug(
256
+ "%s Prepared SSE payload for Task ID %s",
257
+ self.log_identifier,
258
+ task_id,
259
+ )
260
+
261
+ # Send to queues outside the lock (async operations)
262
+ queues_to_remove = []
263
+ for connection_queue in queues_copy:
161
264
  try:
162
- serialized_data = json.dumps(
163
- self._sanitize_json(event_data), allow_nan=False
265
+ await asyncio.wait_for(
266
+ connection_queue.put(sse_payload), timeout=0.1
164
267
  )
165
- except Exception as json_err:
166
- log.error(
167
- "%s Failed to JSON serialize event data for Task ID %s: %s",
268
+ log.debug(
269
+ "%s Queued event for Task ID: %s to one connection.",
168
270
  self.log_identifier,
169
271
  task_id,
170
- json_err,
171
272
  )
172
- return
173
-
174
- sse_payload = {"event": event_type, "data": serialized_data}
175
-
176
- if not queues:
177
- log.debug(
178
- "%s No active SSE connections for Task ID: %s. Buffering event.",
273
+ except asyncio.QueueFull:
274
+ log.warning(
275
+ "%s SSE connection queue full for Task ID: %s. Event dropped for one connection.",
179
276
  self.log_identifier,
180
277
  task_id,
181
278
  )
182
- self._event_buffer.buffer_event(task_id, sse_payload)
183
- return
184
-
185
- if trace_logger.isEnabledFor(logging.DEBUG):
186
- trace_logger.debug(
187
- "%s Prepared SSE payload for Task ID %s: %s",
279
+ queues_to_remove.append(connection_queue)
280
+ except asyncio.TimeoutError:
281
+ log.warning(
282
+ "%s Timeout putting event onto SSE queue for Task ID: %s. Event dropped for one connection.",
188
283
  self.log_identifier,
189
284
  task_id,
190
- sse_payload,
191
285
  )
192
- else:
193
- log.debug(
194
- "%s Prepared SSE payload for Task ID %s",
286
+ queues_to_remove.append(connection_queue)
287
+ except Exception as e:
288
+ log.error(
289
+ "%s Error putting event onto queue for Task ID %s: %s",
195
290
  self.log_identifier,
196
291
  task_id,
292
+ e,
197
293
  )
294
+ queues_to_remove.append(connection_queue)
198
295
 
199
- queues_to_remove = []
200
- for connection_queue in list(self._connections.get(task_id, [])):
201
- try:
202
- await asyncio.wait_for(
203
- connection_queue.put(sse_payload), timeout=0.1
204
- )
205
- log.debug(
206
- "%s Queued event for Task ID: %s to one connection.",
207
- self.log_identifier,
208
- task_id,
209
- )
210
- except asyncio.QueueFull:
211
- log.warning(
212
- "%s SSE connection queue full for Task ID: %s. Event dropped for one connection.",
213
- self.log_identifier,
214
- task_id,
215
- )
216
- queues_to_remove.append(connection_queue)
217
- except asyncio.TimeoutError:
218
- log.warning(
219
- "%s Timeout putting event onto SSE queue for Task ID: %s. Event dropped for one connection.",
220
- self.log_identifier,
221
- task_id,
222
- )
223
- queues_to_remove.append(connection_queue)
224
- except Exception as e:
225
- log.error(
226
- "%s Error putting event onto queue for Task ID %s: %s",
227
- self.log_identifier,
228
- task_id,
229
- e,
230
- )
231
- queues_to_remove.append(connection_queue)
232
-
233
- if queues_to_remove and task_id in self._connections:
234
- current_queues = self._connections[task_id]
235
- for q in queues_to_remove:
236
- try:
237
- current_queues.remove(q)
238
- log.warning(
239
- "%s Removed potentially broken/full SSE queue for Task ID: %s",
296
+ # Remove broken queues under the lock
297
+ if queues_to_remove:
298
+ with self._lock:
299
+ if task_id in self._connections:
300
+ current_queues = self._connections[task_id]
301
+ for q in queues_to_remove:
302
+ try:
303
+ current_queues.remove(q)
304
+ log.warning(
305
+ "%s Removed potentially broken/full SSE queue for Task ID: %s",
306
+ self.log_identifier,
307
+ task_id,
308
+ )
309
+ except ValueError:
310
+ pass
311
+
312
+ if not current_queues:
313
+ del self._connections[task_id]
314
+ log.debug(
315
+ "%s Removed Task ID entry: %s after cleaning queues.",
240
316
  self.log_identifier,
241
317
  task_id,
242
318
  )
243
- except ValueError:
244
- pass
245
-
246
- if not current_queues:
247
- del self._connections[task_id]
248
- log.debug(
249
- "%s Removed Task ID entry: %s after cleaning queues.",
250
- self.log_identifier,
251
- task_id,
252
- )
253
319
 
254
320
  async def close_connection(self, task_id: str, connection_queue: asyncio.Queue):
255
321
  """
@@ -285,54 +351,58 @@ class SSEManager:
285
351
  finally:
286
352
  await self.remove_sse_connection(task_id, connection_queue)
287
353
 
354
+ async def drain_buffer_for_background_task(self, task_id: str):
355
+ """
356
+ Drains the event buffer for a background task when a client disconnects.
357
+ This prevents buffer overflow warnings when background tasks continue
358
+ generating events with no active consumers.
359
+
360
+ Args:
361
+ task_id: The ID of the background task
362
+ """
363
+ log.info(
364
+ "%s Draining event buffer for background task: %s",
365
+ self.log_identifier,
366
+ task_id,
367
+ )
368
+
369
+ # Remove any buffered events to prevent overflow
370
+ buffered_events = self._event_buffer.get_and_remove_buffer(task_id)
371
+ if buffered_events:
372
+ log.info(
373
+ "%s Drained %d buffered events for background task: %s",
374
+ self.log_identifier,
375
+ len(buffered_events),
376
+ task_id,
377
+ )
378
+ else:
379
+ log.debug(
380
+ "%s No buffered events to drain for background task: %s",
381
+ self.log_identifier,
382
+ task_id,
383
+ )
384
+
288
385
  async def close_all_for_task(self, task_id: str):
289
386
  """
290
387
  Closes all SSE connections associated with a specific task.
291
388
  If a connection existed, it also cleans up the event buffer.
292
389
  If no connection ever existed, the buffer is left for a late-connecting client.
293
390
  """
294
- lock = self._get_lock()
295
- async with lock:
391
+ queues_to_close = None
392
+ should_remove_buffer = False
393
+
394
+ with self._lock:
296
395
  if task_id in self._connections:
297
396
  # This is the "normal" case: a client is or was connected.
298
397
  # It's safe to clean up everything.
299
398
  queues_to_close = self._connections.pop(task_id)
399
+ should_remove_buffer = True
300
400
  log.debug(
301
401
  "%s Closing %d SSE connections for Task ID: %s and cleaning up buffer.",
302
402
  self.log_identifier,
303
403
  len(queues_to_close),
304
404
  task_id,
305
405
  )
306
- for q in queues_to_close:
307
- try:
308
- await asyncio.wait_for(q.put(None), timeout=0.1)
309
- except asyncio.QueueFull:
310
- log.warning(
311
- "%s Could not put None (close signal) on full queue during close_all for Task ID: %s.",
312
- self.log_identifier,
313
- task_id,
314
- )
315
- except asyncio.TimeoutError:
316
- log.warning(
317
- "%s Timeout putting None (close signal) on queue during close_all for Task ID: %s.",
318
- self.log_identifier,
319
- task_id,
320
- )
321
- except Exception as e:
322
- log.error(
323
- "%s Error putting None (close signal) on queue during close_all for Task ID %s: %s",
324
- self.log_identifier,
325
- task_id,
326
- e,
327
- )
328
-
329
- # Since a connection existed, the buffer is no longer needed.
330
- self._event_buffer.remove_buffer(task_id)
331
- log.debug(
332
- "%s Removed Task ID entry: %s and signaled queues to close.",
333
- self.log_identifier,
334
- task_id,
335
- )
336
406
  else:
337
407
  # This is the "race condition" case: no client has connected yet.
338
408
  # We MUST leave the buffer intact for the late-connecting client.
@@ -342,39 +412,80 @@ class SSEManager:
342
412
  task_id,
343
413
  )
344
414
 
345
- def cleanup_old_locks(self):
346
- """Remove locks for closed event loops to prevent memory leaks."""
347
- with self._locks_lock:
348
- closed_loops = [loop for loop in self._locks if loop.is_closed()]
349
- for loop in closed_loops:
350
- del self._locks[loop]
415
+ # Close queues outside the lock (async operations)
416
+ if queues_to_close:
417
+ for q in queues_to_close:
418
+ try:
419
+ await asyncio.wait_for(q.put(None), timeout=0.1)
420
+ except asyncio.QueueFull:
421
+ log.warning(
422
+ "%s Could not put None (close signal) on full queue during close_all for Task ID: %s.",
423
+ self.log_identifier,
424
+ task_id,
425
+ )
426
+ except asyncio.TimeoutError:
427
+ log.warning(
428
+ "%s Timeout putting None (close signal) on queue during close_all for Task ID: %s.",
429
+ self.log_identifier,
430
+ task_id,
431
+ )
432
+ except Exception as e:
433
+ log.error(
434
+ "%s Error putting None (close signal) on queue during close_all for Task ID %s: %s",
435
+ self.log_identifier,
436
+ task_id,
437
+ e,
438
+ )
439
+
440
+ # Since a connection existed, the buffer is no longer needed.
441
+ # This is safe to do without lock since we already removed the task from _connections
442
+ if should_remove_buffer:
443
+ self._event_buffer.remove_buffer(task_id)
444
+
445
+ # Clean up the connection tracking
446
+ with self._lock:
447
+ self._tasks_with_prior_connection.discard(task_id)
448
+
351
449
  log.debug(
352
- "%s Cleaned up lock for closed event loop %s",
450
+ "%s Removed Task ID entry: %s and signaled queues to close.",
353
451
  self.log_identifier,
354
- id(loop),
452
+ task_id,
355
453
  )
356
454
 
455
+ def cleanup_old_locks(self):
456
+ """Legacy method - no longer needed with single threading lock.
457
+ Kept for API compatibility but does nothing."""
458
+ pass
459
+
357
460
  async def close_all(self):
358
461
  """Closes all active SSE connections managed by this instance."""
359
462
  self.cleanup_old_locks()
360
- lock = self._get_lock()
361
- async with lock:
463
+
464
+ # Collect all queues to close under the lock
465
+ all_queues_to_close = []
466
+ all_task_ids = []
467
+
468
+ with self._lock:
362
469
  log.debug("%s Closing all active SSE connections...", self.log_identifier)
363
470
  all_task_ids = list(self._connections.keys())
364
- closed_count = 0
365
471
  for task_id in all_task_ids:
366
472
  if task_id in self._connections:
367
473
  queues = self._connections.pop(task_id)
368
- closed_count += len(queues)
369
- for q in queues:
370
- try:
371
- await asyncio.wait_for(q.put(None), timeout=0.1)
372
- except Exception:
373
- pass
374
- log.debug(
375
- "%s Closed %d connections for tasks: %s",
376
- self.log_identifier,
377
- closed_count,
378
- all_task_ids,
379
- )
474
+ all_queues_to_close.extend(queues)
380
475
  self._connections.clear()
476
+ self._tasks_with_prior_connection.clear()
477
+
478
+ # Close queues outside the lock (async operations)
479
+ closed_count = len(all_queues_to_close)
480
+ for q in all_queues_to_close:
481
+ try:
482
+ await asyncio.wait_for(q.put(None), timeout=0.1)
483
+ except Exception:
484
+ pass
485
+
486
+ log.debug(
487
+ "%s Closed %d connections for tasks: %s",
488
+ self.log_identifier,
489
+ closed_count,
490
+ all_task_ids,
491
+ )