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
@@ -1,9 +1,10 @@
1
1
  """
2
2
  API Router for submitting and managing tasks to agents.
3
+ Includes background task status endpoints.
3
4
  """
5
+ from __future__ import annotations
4
6
 
5
7
  import logging
6
- from datetime import datetime, timezone
7
8
  from typing import TYPE_CHECKING
8
9
 
9
10
  import yaml
@@ -14,16 +15,15 @@ from a2a.types import (
14
15
  SendStreamingMessageRequest,
15
16
  SendStreamingMessageSuccessResponse,
16
17
  )
17
- from fastapi import APIRouter, Depends, HTTPException, Response, status
18
+ from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
18
19
  from fastapi import Request as FastAPIRequest
20
+ from pydantic import BaseModel
19
21
  from sqlalchemy.orm import Session as DBSession
20
22
 
21
23
  from ....gateway.http_sse.services.project_service import ProjectService
22
24
 
23
25
  from ....agent.utils.artifact_helpers import (
24
26
  get_artifact_info_list,
25
- load_artifact_content_or_metadata,
26
- save_artifact_with_metadata,
27
27
  )
28
28
 
29
29
  from ....common import a2a
@@ -40,11 +40,12 @@ from ....gateway.http_sse.dependencies import (
40
40
  )
41
41
  from ....gateway.http_sse.repository.entities import Task
42
42
  from ....gateway.http_sse.repository.interfaces import ITaskRepository
43
+ from ....gateway.http_sse.repository.task_repository import TaskRepository
43
44
  from ....gateway.http_sse.services.session_service import SessionService
44
45
  from ....gateway.http_sse.services.task_service import TaskService
45
46
  from ....gateway.http_sse.session_manager import SessionManager
46
- from ....gateway.http_sse.shared.pagination import PaginationParams
47
- from ....gateway.http_sse.shared.types import UserId
47
+ from solace_agent_mesh.shared.api.pagination import PaginationParams
48
+ from solace_agent_mesh.shared.utils.types import UserId
48
49
  from ..utils.stim_utils import create_stim_from_task_data
49
50
 
50
51
  if TYPE_CHECKING:
@@ -55,6 +56,108 @@ router = APIRouter()
55
56
  log = logging.getLogger(__name__)
56
57
 
57
58
 
59
+ # Background Task Status Models and Endpoints
60
+ class TaskStatusResponse(BaseModel):
61
+ """Response model for task status queries."""
62
+ task: Task
63
+ is_running: bool
64
+ is_background: bool
65
+ can_reconnect: bool
66
+
67
+
68
+ @router.get("/tasks/{task_id}/status", response_model=TaskStatusResponse, tags=["Tasks"])
69
+ async def get_task_status(
70
+ task_id: str,
71
+ db: DBSession = Depends(get_db),
72
+ ):
73
+ """
74
+ Get the current status of a task.
75
+ Used by frontend to check if a background task is still running.
76
+
77
+ Args:
78
+ task_id: The task ID to query
79
+
80
+ Returns:
81
+ Task status information including whether it's running and can be reconnected to
82
+ """
83
+ log_prefix = f"[GET /api/v1/tasks/{task_id}/status] "
84
+ log.debug("%sQuerying task status", log_prefix)
85
+
86
+ repo = TaskRepository()
87
+ task = repo.find_by_id(db, task_id)
88
+
89
+ if not task:
90
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
91
+
92
+ # Determine if task is still running
93
+ is_running = task.status in [None, "running", "pending"] and task.end_time is None
94
+
95
+ # Check if it's a background task
96
+ is_background = task.background_execution_enabled or False
97
+
98
+ # Can reconnect if it's a background task and still running
99
+ can_reconnect = is_background and is_running
100
+
101
+ log.info(
102
+ "%sTask status: running=%s, background=%s, can_reconnect=%s",
103
+ log_prefix,
104
+ is_running,
105
+ is_background,
106
+ can_reconnect,
107
+ )
108
+
109
+ return TaskStatusResponse(
110
+ task=task,
111
+ is_running=is_running,
112
+ is_background=is_background,
113
+ can_reconnect=can_reconnect
114
+ )
115
+
116
+
117
+ @router.get("/tasks/background/active", tags=["Tasks"])
118
+ async def get_active_background_tasks(
119
+ user_id: str = Query(..., description="User ID to filter tasks"),
120
+ db: DBSession = Depends(get_db),
121
+ ):
122
+ """
123
+ Get all active background tasks for a user.
124
+ Used by frontend on session load to detect running background tasks.
125
+
126
+ Args:
127
+ user_id: The user ID to filter by
128
+
129
+ Returns:
130
+ List of active background tasks
131
+ """
132
+ log_prefix = "[GET /api/v1/tasks/background/active] "
133
+ log.debug("%sQuerying active background tasks for user %s", log_prefix, user_id)
134
+
135
+ repo = TaskRepository()
136
+
137
+ # Get all background tasks
138
+ all_background_tasks = repo.find_background_tasks_by_status(db, status=None)
139
+
140
+ # Filter by user and running status
141
+ active_tasks = [
142
+ task for task in all_background_tasks
143
+ if task.user_id == user_id
144
+ and task.status in [None, "running", "pending"]
145
+ and task.end_time is None
146
+ ]
147
+
148
+ log.info("%sFound %d active background tasks for user %s", log_prefix, len(active_tasks), user_id)
149
+
150
+ return {
151
+ "tasks": active_tasks,
152
+ "count": len(active_tasks)
153
+ }
154
+
155
+
156
+ # =============================================================================
157
+ # Project Context Injection Helper
158
+ # =============================================================================
159
+
160
+
58
161
  async def _inject_project_context(
59
162
  project_id: str,
60
163
  message_text: str,
@@ -80,23 +183,31 @@ async def _inject_project_context(
80
183
  return message_text
81
184
 
82
185
  from ....gateway.http_sse.dependencies import SessionLocal
186
+ from ..utils.artifact_copy_utils import copy_project_artifacts_to_session
83
187
 
84
188
  if SessionLocal is None:
85
- log.warning("%sProject context injection skipped: database not configured", log_prefix)
189
+ log.warning(
190
+ "%sProject context injection skipped: database not configured", log_prefix
191
+ )
86
192
  return message_text
87
193
 
88
194
  db = SessionLocal()
195
+ artifact_service = None
196
+ should_clear_pending_flags = False
197
+
89
198
  try:
90
199
  project = project_service.get_project(db, project_id, user_id)
91
200
  if not project:
92
201
  return message_text
93
-
202
+
94
203
  context_parts = []
95
204
 
96
205
  # Only inject full context for new sessions
97
206
  if inject_full_context:
98
207
  # Start with clear workspace framing
99
- context_parts.append(f'You are working in the project workspace: "{project.name}"')
208
+ context_parts.append(
209
+ f'You are working in the project workspace: "{project.name}"'
210
+ )
100
211
 
101
212
  # Add system prompt if exists
102
213
  if project.system_prompt and project.system_prompt.strip():
@@ -105,138 +216,74 @@ async def _inject_project_context(
105
216
  # Add project description if exists
106
217
  if project.description and project.description.strip():
107
218
  context_parts.append(f"\nProject Description: {project.description.strip()}")
108
-
219
+
109
220
  # Always copy project artifacts to session (for both new and existing sessions)
110
221
  # This ensures new project files are available to existing sessions
111
222
  artifact_service = component.get_shared_artifact_service()
112
223
  if artifact_service:
113
- try:
114
- source_user_id = project.user_id
115
- project_artifacts_session_id = f"project-{project.id}"
116
-
117
- log.info("%sChecking for artifacts in project %s (storage session: %s)", log_prefix, project.id, project_artifacts_session_id)
118
-
119
- project_artifacts = await get_artifact_info_list(
120
- artifact_service=artifact_service,
121
- app_name=project_service.app_name,
122
- user_id=source_user_id,
123
- session_id=project_artifacts_session_id,
224
+ try:
225
+ artifacts_copied, new_artifact_names = await copy_project_artifacts_to_session(
226
+ project_id=project_id,
227
+ user_id=user_id,
228
+ session_id=session_id,
229
+ project_service=project_service,
230
+ component=component,
231
+ db=db,
232
+ log_prefix=log_prefix,
124
233
  )
125
234
 
126
- if project_artifacts:
127
- log.info("%sFound %d artifacts in project %s to process.", log_prefix, len(project_artifacts), project.id)
128
-
129
- # Get list of artifacts already in session to avoid re-copying
130
- try:
131
- session_artifacts = await get_artifact_info_list(
132
- artifact_service=artifact_service,
133
- app_name=project_service.app_name,
134
- user_id=user_id,
135
- session_id=session_id,
136
- )
137
- session_artifact_names = {art.filename for art in session_artifacts}
138
- log.debug("%sSession %s currently has %d artifacts", log_prefix, session_id, len(session_artifact_names))
139
- except Exception as e:
140
- log.warning("%sFailed to get session artifacts, will copy all project artifacts: %s", log_prefix, e)
141
- session_artifact_names = set()
142
-
143
- all_artifact_descriptions = [] # For new sessions - all files
144
- new_artifact_descriptions = [] # For existing sessions - only new files
145
- artifacts_copied = 0
146
-
147
- for artifact_info in project_artifacts:
148
- # Build description for all artifacts (for new sessions)
149
- desc_str = f"- {artifact_info.filename}"
150
- if artifact_info.description:
151
- desc_str += f": {artifact_info.description}"
152
- all_artifact_descriptions.append(desc_str)
153
-
154
- # Skip if artifact already exists in session (any source)
155
- if artifact_info.filename in session_artifact_names:
156
- log.debug("%sSkipping artifact %s - already exists in session", log_prefix, artifact_info.filename)
157
- continue
158
-
159
- # Track new artifacts for existing sessions
160
- new_artifact_descriptions.append(desc_str)
161
-
162
- log.info("%sCopying new artifact %s to session %s", log_prefix, artifact_info.filename, session_id)
163
-
164
- try:
165
- # Load artifact content from project storage
166
- loaded_artifact = await load_artifact_content_or_metadata(
167
- artifact_service=artifact_service,
168
- app_name=project_service.app_name,
169
- user_id=source_user_id,
170
- session_id=project_artifacts_session_id,
171
- filename=artifact_info.filename,
172
- return_raw_bytes=True,
173
- version="latest"
174
- )
175
-
176
- # Load the full metadata separately
177
- loaded_metadata = await load_artifact_content_or_metadata(
178
- artifact_service=artifact_service,
179
- app_name=project_service.app_name,
180
- user_id=source_user_id,
181
- session_id=project_artifacts_session_id,
182
- filename=artifact_info.filename,
183
- load_metadata_only=True,
184
- version="latest"
185
- )
186
-
187
- # Save a copy to the current chat session
188
- if loaded_artifact.get("status") == "success":
189
- full_metadata = loaded_metadata.get("metadata", {}) if loaded_metadata.get("status") == "success" else {}
190
-
191
- # Ensure the source is always set for copied project artifacts
192
- full_metadata["source"] = "project"
193
-
194
- await save_artifact_with_metadata(
195
- artifact_service=artifact_service,
196
- app_name=project_service.app_name,
197
- user_id=user_id,
198
- session_id=session_id,
199
- filename=artifact_info.filename,
200
- content_bytes=loaded_artifact.get("raw_bytes"),
201
- mime_type=loaded_artifact.get("mime_type"),
202
- metadata_dict=full_metadata,
203
- timestamp=datetime.now(timezone.utc),
204
- )
205
- artifacts_copied += 1
206
- log.info("%sSuccessfully copied artifact %s to session", log_prefix, artifact_info.filename)
207
- else:
208
- log.warning("%sFailed to load artifact %s: %s", log_prefix, artifact_info.filename, loaded_artifact.get("status"))
209
- except Exception as e:
210
- log.error("%sError copying artifact %s to session: %s", log_prefix, artifact_info.filename, e)
211
- # Continue with other artifacts even if one fails
212
-
213
- # Add artifact descriptions to context
214
- if inject_full_context and all_artifact_descriptions:
215
- # New session: show all project files
216
- artifacts_context = (
217
- "\nFiles in Session:\n"
218
- "The following files are available in your session and can be viewed using your tools if required:\n"
219
- + "\n".join(all_artifact_descriptions)
220
- )
221
- context_parts.append(artifacts_context)
222
- elif not inject_full_context and new_artifact_descriptions:
223
- # Existing session: notify about newly added files
224
- new_files_context = (
225
- "\nNew Files Added to Project:\n"
226
- "The following files have been added to the project and are now available in your session:\n"
227
- + "\n".join(new_artifact_descriptions)
235
+ if inject_full_context and artifacts_copied > 0:
236
+ # need to clear the pending flags even if injection fails
237
+ should_clear_pending_flags = True
238
+
239
+ # Get artifact descriptions for context injection
240
+ if artifacts_copied > 0 or inject_full_context:
241
+ source_user_id = project.user_id
242
+ project_artifacts_session_id = f"project-{project.id}"
243
+
244
+ project_artifacts = await get_artifact_info_list(
245
+ artifact_service=artifact_service,
246
+ app_name=project_service.app_name,
247
+ user_id=source_user_id,
248
+ session_id=project_artifacts_session_id,
249
+ )
250
+
251
+ if project_artifacts:
252
+ # For new sessions - all files
253
+ all_artifact_descriptions = []
254
+ # For existing sessions - only new files
255
+ new_artifact_descriptions = []
256
+
257
+ for artifact_info in project_artifacts:
258
+ # Build description for all artifacts (for new sessions)
259
+ desc_str = f"- {artifact_info.filename}"
260
+ if artifact_info.description:
261
+ desc_str += f": {artifact_info.description}"
262
+ all_artifact_descriptions.append(desc_str)
263
+
264
+ # Track new artifacts for existing sessions
265
+ if artifact_info.filename in new_artifact_names:
266
+ new_artifact_descriptions.append(desc_str)
267
+
268
+ # Add artifact descriptions to context
269
+ files_added_header = (
270
+ "\nNew Files Added to Session:\n"
271
+ "The following files have been added to your session (in addition to any files already present):\n"
228
272
  )
229
- context_parts.append(new_files_context)
230
-
231
- if artifacts_copied > 0:
232
- log.info("%sCopied %d new artifacts to session %s.", log_prefix, artifacts_copied, session_id)
233
- else:
234
- log.debug("%sNo new artifacts to copy to session %s.", log_prefix, session_id)
235
- else:
236
- log.info("%sNo artifacts found in project %s to copy.", log_prefix, project.id)
273
+
274
+ if inject_full_context and all_artifact_descriptions:
275
+ # New session: show all project files
276
+ artifacts_context = files_added_header + "\n".join(all_artifact_descriptions)
277
+ context_parts.append(artifacts_context)
278
+ elif not inject_full_context and new_artifact_descriptions:
279
+ # Existing session: notify about newly added files
280
+ new_files_context = files_added_header + "\n".join(new_artifact_descriptions)
281
+ context_parts.append(new_files_context)
237
282
 
238
283
  except Exception as e:
239
- log.warning("%sFailed to copy project artifacts to session: %s", log_prefix, e)
284
+ log.warning(
285
+ "%sFailed to copy project artifacts to session: %s", log_prefix, e
286
+ )
240
287
  # Do not fail the entire request, just log the warning
241
288
 
242
289
  # Inject all gathered context into the message, ending with user query
@@ -245,10 +292,10 @@ async def _inject_project_context(
245
292
  if context_parts:
246
293
  project_context = "\n".join(context_parts)
247
294
  modified_message_text = f"{project_context}\n\nUSER QUERY:\n{message_text}"
248
- log.info("%sInjected full project context for project: %s", log_prefix, project_id)
295
+ log.debug("%sInjected full project context for project: %s", log_prefix, project_id)
249
296
  else:
250
297
  log.debug("%sSkipped full context injection for existing session, but ensured new artifacts are copied", log_prefix)
251
-
298
+
252
299
  return modified_message_text
253
300
 
254
301
  except Exception as e:
@@ -256,6 +303,22 @@ async def _inject_project_context(
256
303
  # Continue without injection - don't fail the request
257
304
  return message_text
258
305
  finally:
306
+ # Clear the pending project context flags from all artifacts
307
+ if should_clear_pending_flags and artifact_service:
308
+ from ..utils.artifact_copy_utils import clear_pending_project_context
309
+ try:
310
+ await clear_pending_project_context(
311
+ user_id=user_id,
312
+ session_id=session_id,
313
+ artifact_service=artifact_service,
314
+ app_name=project_service.app_name,
315
+ db=db,
316
+ log_prefix=log_prefix,
317
+ )
318
+ log.debug("%sCleared pending project context flags", log_prefix)
319
+ except Exception as e:
320
+ log.warning("%sFailed to clear pending project context flags: %s", log_prefix, e)
321
+
259
322
  db.close()
260
323
 
261
324
 
@@ -325,12 +388,21 @@ async def _submit_task(
325
388
  if SessionLocal is not None:
326
389
  db = SessionLocal()
327
390
  try:
328
- session_details = session_service.get_session_details(db, frontend_session_id, user_id)
391
+ session_details = session_service.get_session_details(
392
+ db, frontend_session_id, user_id
393
+ )
329
394
  if session_details and session_details.project_id:
330
395
  project_id = session_details.project_id
331
- log.info("%sFound project_id %s from session database for session %s", log_prefix, project_id, frontend_session_id)
396
+ log.info(
397
+ "%sFound project_id %s from session database for session %s",
398
+ log_prefix,
399
+ project_id,
400
+ frontend_session_id,
401
+ )
332
402
  except Exception as e:
333
- log.warning("%sFailed to lookup session project_id: %s", log_prefix, e)
403
+ log.warning(
404
+ "%sFailed to lookup session project_id: %s", log_prefix, e
405
+ )
334
406
  finally:
335
407
  db.close()
336
408
 
@@ -384,15 +456,40 @@ async def _submit_task(
384
456
  if hasattr(part, "text"):
385
457
  message_text = part.text
386
458
  break
387
-
459
+
388
460
  # Project context injection - always inject for project sessions to ensure new files are available
389
461
  # Skip if project_service is None (persistence disabled)
390
462
  modified_message = payload.params.message
391
463
  if project_service and project_id and message_text:
392
- # Inject context for new sessions (includes full context + artifact copy)
393
- # For existing sessions, only copy new artifacts without re-injecting full context
464
+ # Determine if we should inject full context:
394
465
  should_inject_full_context = not frontend_session_id
395
466
 
467
+ # Check if there are artifacts with pending project context
468
+ if frontend_session_id and not should_inject_full_context:
469
+ from ..utils.artifact_copy_utils import has_pending_project_context
470
+ from ....gateway.http_sse.dependencies import SessionLocal
471
+
472
+ artifact_service = component.get_shared_artifact_service()
473
+ if artifact_service and SessionLocal:
474
+ db = SessionLocal()
475
+ try:
476
+ has_pending = await has_pending_project_context(
477
+ user_id=client_id,
478
+ session_id=session_id,
479
+ artifact_service=artifact_service,
480
+ app_name=component.gateway_id,
481
+ db=db,
482
+ )
483
+ if has_pending:
484
+ should_inject_full_context = True
485
+ log.info(
486
+ "%sDetected pending project context for session %s, will inject full context",
487
+ log_prefix,
488
+ session_id,
489
+ )
490
+ finally:
491
+ db.close()
492
+
396
493
  modified_message_text = await _inject_project_context(
397
494
  project_id=project_id,
398
495
  message_text=message_text,
@@ -426,7 +523,9 @@ async def _submit_task(
426
523
  new_parts.insert(0, new_text_part)
427
524
 
428
525
  # Update the message with the new parts
429
- modified_message = a2a.update_message_parts(payload.params.message, new_parts)
526
+ modified_message = a2a.update_message_parts(
527
+ payload.params.message, new_parts
528
+ )
430
529
 
431
530
  # Use the helper to get the unwrapped parts from the modified message (with project context if applied).
432
531
  a2a_parts = a2a.get_parts_from_message(modified_message)
@@ -439,12 +538,24 @@ async def _submit_task(
439
538
  "target_agent_name": agent_name,
440
539
  }
441
540
 
541
+ # Extract additional metadata from the message (e.g., background execution settings)
542
+ # This metadata will be passed through to the A2A message for the task logger
543
+ additional_metadata = {}
544
+ if payload.params and payload.params.message and payload.params.message.metadata:
545
+ msg_metadata = payload.params.message.metadata
546
+ # Pass through background execution settings
547
+ if msg_metadata.get("backgroundExecutionEnabled"):
548
+ additional_metadata["backgroundExecutionEnabled"] = msg_metadata.get("backgroundExecutionEnabled")
549
+ if msg_metadata.get("maxExecutionTimeMs"):
550
+ additional_metadata["maxExecutionTimeMs"] = msg_metadata.get("maxExecutionTimeMs")
551
+
442
552
  task_id = await component.submit_a2a_task(
443
553
  target_agent_name=agent_name,
444
554
  a2a_parts=a2a_parts,
445
555
  external_request_context=external_req_ctx,
446
556
  user_identity=user_identity,
447
557
  is_streaming=is_streaming,
558
+ metadata=additional_metadata if additional_metadata else None,
448
559
  )
449
560
 
450
561
  log.info("%sTask submitted successfully. TaskID: %s", log_prefix, task_id)
@@ -563,8 +674,8 @@ async def search_tasks(
563
674
  )
564
675
 
565
676
 
566
- @router.get("/tasks/{task_id}", tags=["Tasks"])
567
- async def get_task_as_stim_file(
677
+ @router.get("/tasks/{task_id}/events", tags=["Tasks"])
678
+ async def get_task_events(
568
679
  task_id: str,
569
680
  request: FastAPIRequest,
570
681
  db: DBSession = Depends(get_db),
@@ -573,9 +684,11 @@ async def get_task_as_stim_file(
573
684
  repo: ITaskRepository = Depends(get_task_repository),
574
685
  ):
575
686
  """
576
- Retrieves the complete event history for a single task and returns it as a `.stim` file.
687
+ Retrieves the complete event history for a task and all its child tasks as JSON.
688
+ Returns events in the same format as the SSE stream for workflow visualization.
689
+ Recursively loads all descendant tasks to enable full workflow rendering.
577
690
  """
578
- log_prefix = f"[GET /api/v1/tasks/{task_id}] "
691
+ log_prefix = f"[GET /api/v1/tasks/{task_id}/events] "
579
692
  log.info("%sRequest from user %s", log_prefix, user_id)
580
693
 
581
694
  try:
@@ -595,8 +708,255 @@ async def get_task_as_stim_file(
595
708
  detail="You do not have permission to view this task.",
596
709
  )
597
710
 
598
- # Format into .stim structure
599
- stim_data = create_stim_from_task_data(task, events)
711
+ # Transform task events into A2AEventSSEPayload format for the frontend
712
+ # Need to reconstruct the SSE structure from stored data
713
+ formatted_events = []
714
+
715
+ for event in events:
716
+ # event.payload contains the raw A2A JSON-RPC message
717
+ # event.created_time is epoch milliseconds
718
+ # event.direction is simplified (request, response, status, error, etc)
719
+
720
+ # Convert timestamp from epoch milliseconds to ISO 8601
721
+ from datetime import datetime, timezone
722
+ timestamp_dt = datetime.fromtimestamp(event.created_time / 1000, tz=timezone.utc)
723
+ timestamp_iso = timestamp_dt.isoformat()
724
+
725
+ # Extract metadata from payload using similar logic to SSE component
726
+ payload = event.payload
727
+ message_id = payload.get("id")
728
+ source_entity = "unknown"
729
+ target_entity = "unknown"
730
+ method = "N/A"
731
+
732
+ # Parse based on direction
733
+ if event.direction == "request":
734
+ # It's a request - extract target from message metadata
735
+ method = payload.get("method", "N/A")
736
+ if "params" in payload and "message" in payload.get("params", {}):
737
+ message = payload["params"]["message"]
738
+ if isinstance(message, dict) and "metadata" in message:
739
+ target_entity = message["metadata"].get("agent_name", "unknown")
740
+ elif event.direction in ["status", "response", "error"]:
741
+ # It's a response - extract source from result metadata
742
+ if "result" in payload:
743
+ result = payload["result"]
744
+ if isinstance(result, dict):
745
+ # Check for agent_name in metadata
746
+ if "metadata" in result:
747
+ source_entity = result["metadata"].get("agent_name", "unknown")
748
+ # For status updates, check the message inside
749
+ if "message" in result:
750
+ message = result["message"]
751
+ if isinstance(message, dict) and "metadata" in message:
752
+ if source_entity == "unknown":
753
+ source_entity = message["metadata"].get("agent_name", "unknown")
754
+
755
+ # Map stored direction to SSE direction format
756
+ direction_map = {
757
+ "request": "request",
758
+ "response": "task",
759
+ "status": "status-update",
760
+ "error": "error_response",
761
+ }
762
+ sse_direction = direction_map.get(event.direction, event.direction)
763
+
764
+ # Build the A2AEventSSEPayload structure
765
+ formatted_event = {
766
+ "event_type": "a2a_message",
767
+ "timestamp": timestamp_iso,
768
+ "solace_topic": event.topic,
769
+ "direction": sse_direction,
770
+ "source_entity": source_entity,
771
+ "target_entity": target_entity,
772
+ "message_id": message_id,
773
+ "task_id": task_id,
774
+ "payload_summary": {
775
+ "method": method,
776
+ "params_preview": None,
777
+ },
778
+ "full_payload": payload,
779
+ }
780
+ formatted_events.append(formatted_event)
781
+
782
+ # Use database-level query to get all related tasks efficiently
783
+ related_task_ids = repo.find_all_by_parent_chain(db, task_id)
784
+ log.info(
785
+ "%sFound %d related tasks for task_id %s",
786
+ log_prefix,
787
+ len(related_task_ids),
788
+ task_id,
789
+ )
790
+
791
+ # Load and format all related tasks
792
+ all_tasks = {}
793
+ all_tasks[task_id] = {
794
+ "events": formatted_events,
795
+ "initial_request_text": task.initial_request_text or "",
796
+ }
797
+
798
+ # Load remaining related tasks
799
+ for tid in related_task_ids:
800
+ if tid == task_id:
801
+ continue # Already loaded
802
+
803
+ task_result = repo.find_by_id_with_events(db, tid)
804
+ if not task_result:
805
+ continue
806
+
807
+ related_task, related_events = task_result
808
+
809
+ # Check permissions for each related task
810
+ if related_task.user_id != user_id and not can_read_all:
811
+ log.warning(
812
+ "%sSkipping related task %s due to permission check",
813
+ log_prefix,
814
+ tid,
815
+ )
816
+ continue
817
+
818
+ # Format events for this related task
819
+ related_formatted_events = []
820
+
821
+ for event in related_events:
822
+ from datetime import datetime, timezone
823
+
824
+ timestamp_dt = datetime.fromtimestamp(
825
+ event.created_time / 1000, tz=timezone.utc
826
+ )
827
+ timestamp_iso = timestamp_dt.isoformat()
828
+ payload = event.payload
829
+ message_id = payload.get("id")
830
+ source_entity = "unknown"
831
+ target_entity = "unknown"
832
+ method = "N/A"
833
+
834
+ if event.direction == "request":
835
+ method = payload.get("method", "N/A")
836
+ if "params" in payload and "message" in payload.get("params", {}):
837
+ message = payload["params"]["message"]
838
+ if isinstance(message, dict) and "metadata" in message:
839
+ target_entity = message["metadata"].get(
840
+ "agent_name", "unknown"
841
+ )
842
+ elif event.direction in ["status", "response", "error"]:
843
+ if "result" in payload:
844
+ result = payload["result"]
845
+ if isinstance(result, dict):
846
+ if "metadata" in result:
847
+ source_entity = result["metadata"].get(
848
+ "agent_name", "unknown"
849
+ )
850
+ if "message" in result:
851
+ message = result["message"]
852
+ if isinstance(message, dict) and "metadata" in message:
853
+ if source_entity == "unknown":
854
+ source_entity = message["metadata"].get(
855
+ "agent_name", "unknown"
856
+ )
857
+
858
+ direction_map = {
859
+ "request": "request",
860
+ "response": "task",
861
+ "status": "status-update",
862
+ "error": "error_response",
863
+ }
864
+ sse_direction = direction_map.get(event.direction, event.direction)
865
+
866
+ formatted_event = {
867
+ "event_type": "a2a_message",
868
+ "timestamp": timestamp_iso,
869
+ "solace_topic": event.topic,
870
+ "direction": sse_direction,
871
+ "source_entity": source_entity,
872
+ "target_entity": target_entity,
873
+ "message_id": message_id,
874
+ "task_id": tid,
875
+ "payload_summary": {"method": method, "params_preview": None},
876
+ "full_payload": payload,
877
+ }
878
+ related_formatted_events.append(formatted_event)
879
+
880
+ all_tasks[tid] = {
881
+ "events": related_formatted_events,
882
+ "initial_request_text": related_task.initial_request_text or "",
883
+ }
884
+
885
+ # Return all tasks (parent + children) for the frontend to process
886
+ return {"tasks": all_tasks}
887
+
888
+ except HTTPException:
889
+ # Re-raise HTTPExceptions (404, 403, etc.) without modification
890
+ raise
891
+ except Exception as e:
892
+ log.exception("%sError retrieving task events: %s", log_prefix, e)
893
+ raise HTTPException(
894
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
895
+ detail="An error occurred while retrieving the task events.",
896
+ )
897
+
898
+
899
+ @router.get("/tasks/{task_id}", tags=["Tasks"])
900
+ async def get_task_as_stim_file(
901
+ task_id: str,
902
+ request: FastAPIRequest,
903
+ db: DBSession = Depends(get_db),
904
+ user_id: UserId = Depends(get_user_id),
905
+ user_config: dict = Depends(get_user_config),
906
+ repo: ITaskRepository = Depends(get_task_repository),
907
+ ):
908
+ """
909
+ Retrieves the complete event history for a task and all its child tasks, returning it as a `.stim` file.
910
+ """
911
+ log_prefix = f"[GET /api/v1/tasks/{task_id}] "
912
+ log.info("%sRequest from user %s", log_prefix, user_id)
913
+
914
+ try:
915
+ # Find all related task IDs (parent chain + all children)
916
+ related_task_ids = repo.find_all_by_parent_chain(db, task_id)
917
+
918
+ if not related_task_ids:
919
+ raise HTTPException(
920
+ status_code=status.HTTP_404_NOT_FOUND,
921
+ detail=f"Task with ID '{task_id}' not found.",
922
+ )
923
+
924
+ # Load all tasks and their events
925
+ tasks_dict = {}
926
+ events_dict = {}
927
+ can_read_all = user_config.get("scopes", {}).get("tasks:read:all", False)
928
+
929
+ for tid in related_task_ids:
930
+ result = repo.find_by_id_with_events(db, tid)
931
+ if result:
932
+ task, events = result
933
+
934
+ # Check permissions for each task
935
+ if task.user_id != user_id and not can_read_all:
936
+ raise HTTPException(
937
+ status_code=status.HTTP_403_FORBIDDEN,
938
+ detail="You do not have permission to view this task.",
939
+ )
940
+
941
+ tasks_dict[tid] = task
942
+ events_dict[tid] = events
943
+
944
+ if task_id not in tasks_dict:
945
+ raise HTTPException(
946
+ status_code=status.HTTP_404_NOT_FOUND,
947
+ detail=f"Task with ID '{task_id}' not found.",
948
+ )
949
+
950
+ # Determine the root task (the one without a parent)
951
+ root_task_id = task_id
952
+ for tid, task in tasks_dict.items():
953
+ if task.parent_task_id is None:
954
+ root_task_id = tid
955
+ break
956
+
957
+ # Format into .stim structure with all tasks
958
+ from ..utils.stim_utils import create_stim_from_task_hierarchy
959
+ stim_data = create_stim_from_task_hierarchy(tasks_dict, events_dict, root_task_id)
600
960
 
601
961
  yaml_content = yaml.dump(
602
962
  stim_data,
@@ -608,8 +968,8 @@ async def get_task_as_stim_file(
608
968
 
609
969
  return Response(
610
970
  content=yaml_content,
611
- media_type="application/x-yaml",
612
- headers={"Content-Disposition": f'attachment; filename="{task_id}.stim"'},
971
+ media_type="application/yaml",
972
+ headers={"Content-Disposition": f'attachment; filename="{root_task_id}.stim"'},
613
973
  )
614
974
 
615
975
  except HTTPException: