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
@@ -4,10 +4,18 @@ Business service for project-related operations.
4
4
 
5
5
  from typing import List, Optional, TYPE_CHECKING
6
6
  import logging
7
+ import json
8
+ import zipfile
9
+ from io import BytesIO
7
10
  from fastapi import UploadFile
8
11
  from datetime import datetime, timezone
9
12
 
10
- from ....agent.utils.artifact_helpers import get_artifact_info_list, save_artifact_with_metadata
13
+ from ....agent.utils.artifact_helpers import get_artifact_info_list, save_artifact_with_metadata, get_artifact_counts_batch
14
+
15
+ # Default max upload size (50MB) - matches gateway_max_upload_size_bytes default
16
+ DEFAULT_MAX_UPLOAD_SIZE_BYTES = 52428800
17
+ # Default max ZIP upload size (100MB) - for project import ZIP files
18
+ DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES = 104857600
11
19
 
12
20
  try:
13
21
  from google.adk.artifacts import BaseArtifactService
@@ -36,6 +44,29 @@ class ProjectService:
36
44
  self.artifact_service = component.get_shared_artifact_service() if component else None
37
45
  self.app_name = component.get_config("name", "WebUIBackendApp") if component else "WebUIBackendApp"
38
46
  self.logger = logging.getLogger(__name__)
47
+ # Get max upload size from component config, with fallback to default
48
+ # Ensure values are integers for proper formatting
49
+ max_upload_config = (
50
+ component.get_config("gateway_max_upload_size_bytes", DEFAULT_MAX_UPLOAD_SIZE_BYTES)
51
+ if component else DEFAULT_MAX_UPLOAD_SIZE_BYTES
52
+ )
53
+ self.max_upload_size_bytes = int(max_upload_config) if isinstance(max_upload_config, (int, float)) else DEFAULT_MAX_UPLOAD_SIZE_BYTES
54
+
55
+ # Get max ZIP upload size from component config, with fallback to default (100MB)
56
+ max_zip_config = (
57
+ component.get_config("gateway_max_zip_upload_size_bytes", DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES)
58
+ if component else DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES
59
+ )
60
+ self.max_zip_upload_size_bytes = int(max_zip_config) if isinstance(max_zip_config, (int, float)) else DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES
61
+
62
+ self.logger.info(
63
+ "[ProjectService] Initialized with max_upload_size_bytes=%d (%.2f MB), "
64
+ "max_zip_upload_size_bytes=%d (%.2f MB)",
65
+ self.max_upload_size_bytes,
66
+ self.max_upload_size_bytes / (1024*1024),
67
+ self.max_zip_upload_size_bytes,
68
+ self.max_zip_upload_size_bytes / (1024*1024)
69
+ )
39
70
 
40
71
  def _get_repositories(self, db):
41
72
  """Create project repository for the given database session."""
@@ -46,6 +77,75 @@ class ProjectService:
46
77
  """Checks if the service is configured with a persistent backend."""
47
78
  return self.component and self.component.database_url is not None
48
79
 
80
+ async def _validate_file_size(self, file: UploadFile, log_prefix: str = "") -> bytes:
81
+ """
82
+ Validate file size and read content with size checking.
83
+
84
+ Args:
85
+ file: The uploaded file to validate
86
+ log_prefix: Prefix for log messages
87
+
88
+ Returns:
89
+ bytes: The file content if validation passes
90
+
91
+ Raises:
92
+ ValueError: If file exceeds maximum allowed size
93
+ """
94
+ # Read file content in chunks to validate size
95
+ chunk_size = 1024 * 1024 # 1MB chunks
96
+ content_bytes = bytearray()
97
+ total_bytes_read = 0
98
+
99
+ while True:
100
+ chunk = await file.read(chunk_size)
101
+ if not chunk:
102
+ break
103
+
104
+ chunk_len = len(chunk)
105
+ total_bytes_read += chunk_len
106
+
107
+ # Validate size during reading (fail fast)
108
+ if total_bytes_read > self.max_upload_size_bytes:
109
+ error_msg = (
110
+ f"File '{file.filename}' rejected: size exceeds maximum "
111
+ f"{self.max_upload_size_bytes:,} bytes "
112
+ f"({self.max_upload_size_bytes / (1024*1024):.2f} MB). "
113
+ f"Read {total_bytes_read:,} bytes so far."
114
+ )
115
+ self.logger.warning(f"{log_prefix} {error_msg}")
116
+ raise ValueError(error_msg)
117
+
118
+ content_bytes.extend(chunk)
119
+
120
+ return bytes(content_bytes)
121
+
122
+ async def _validate_files(
123
+ self,
124
+ files: List[UploadFile],
125
+ log_prefix: str = ""
126
+ ) -> List[tuple]:
127
+ """
128
+ Validate multiple files and return their content.
129
+
130
+ Args:
131
+ files: List of uploaded files to validate
132
+ log_prefix: Prefix for log messages
133
+
134
+ Returns:
135
+ List of tuples: [(file, content_bytes), ...]
136
+
137
+ Raises:
138
+ ValueError: If any file exceeds maximum allowed size
139
+ """
140
+ validated_files = []
141
+ for file in files:
142
+ content_bytes = await self._validate_file_size(file, log_prefix)
143
+ validated_files.append((file, content_bytes))
144
+ self.logger.debug(
145
+ f"{log_prefix} Validated file '{file.filename}': {len(content_bytes):,} bytes"
146
+ )
147
+ return validated_files
148
+
49
149
  async def create_project(
50
150
  self,
51
151
  db,
@@ -73,8 +173,9 @@ class ProjectService:
73
173
  DomainProject: The created project
74
174
 
75
175
  Raises:
76
- ValueError: If project name is invalid or user_id is missing
176
+ ValueError: If project name is invalid, user_id is missing, or file size exceeds limit
77
177
  """
178
+ log_prefix = f"[ProjectService:create_project] User {user_id}:"
78
179
  self.logger.info(f"Creating new project '{name}' for user {user_id}")
79
180
 
80
181
  # Business validation
@@ -84,6 +185,13 @@ class ProjectService:
84
185
  if not user_id:
85
186
  raise ValueError("User ID is required to create a project")
86
187
 
188
+ # Validate file sizes before creating project
189
+ validated_files = []
190
+ if files:
191
+ self.logger.info(f"{log_prefix} Validating {len(files)} files before project creation")
192
+ validated_files = await self._validate_files(files, log_prefix)
193
+ self.logger.info(f"{log_prefix} All {len(files)} files passed size validation")
194
+
87
195
  project_repository = self._get_repositories(db)
88
196
 
89
197
  # Check for duplicate project name for this user
@@ -100,13 +208,12 @@ class ProjectService:
100
208
  default_agent_id=default_agent_id,
101
209
  )
102
210
 
103
- if files and self.artifact_service:
211
+ if validated_files and self.artifact_service:
104
212
  self.logger.info(
105
- f"Project {project_domain.id} created, now saving {len(files)} artifacts."
213
+ f"Project {project_domain.id} created, now saving {len(validated_files)} artifacts."
106
214
  )
107
215
  project_session_id = f"project-{project_domain.id}"
108
- for file in files:
109
- content_bytes = await file.read()
216
+ for file, content_bytes in validated_files:
110
217
  metadata = {"source": "project"}
111
218
  if file_metadata and file.filename in file_metadata:
112
219
  desc = file_metadata[file.filename]
@@ -124,7 +231,7 @@ class ProjectService:
124
231
  metadata_dict=metadata,
125
232
  timestamp=datetime.now(timezone.utc),
126
233
  )
127
- self.logger.info(f"Saved {len(files)} artifacts for project {project_domain.id}")
234
+ self.logger.info(f"Saved {len(validated_files)} artifacts for project {project_domain.id}")
128
235
 
129
236
  self.logger.info(
130
237
  f"Successfully created project {project_domain.id} for user {user_id}"
@@ -162,6 +269,52 @@ class ProjectService:
162
269
  db_projects = project_repository.get_user_projects(user_id)
163
270
  return db_projects
164
271
 
272
+ async def get_user_projects_with_counts(self, db, user_id: str) -> List[tuple[Project, int]]:
273
+ """
274
+ Get all projects owned by a specific user with artifact counts.
275
+ Uses batch counting for efficiency.
276
+
277
+ Args:
278
+ db: Database session
279
+ user_id: The user ID
280
+
281
+ Returns:
282
+ List[tuple[Project, int]]: List of tuples (project, artifact_count)
283
+ """
284
+ self.logger.debug(f"Retrieving projects with artifact counts for user {user_id}")
285
+ projects = self.get_user_projects(db, user_id)
286
+
287
+ if not self.artifact_service or not projects:
288
+ # If no artifact service or no projects, return projects with 0 counts
289
+ return [(project, 0) for project in projects]
290
+
291
+ # Build list of session IDs for batch counting
292
+ session_ids = [f"project-{project.id}" for project in projects]
293
+
294
+ try:
295
+ # Get all counts in a single batch operation
296
+ counts_by_session = await get_artifact_counts_batch(
297
+ artifact_service=self.artifact_service,
298
+ app_name=self.app_name,
299
+ user_id=user_id,
300
+ session_ids=session_ids,
301
+ )
302
+
303
+ # Map counts back to projects
304
+ projects_with_counts = []
305
+ for project in projects:
306
+ storage_session_id = f"project-{project.id}"
307
+ artifact_count = counts_by_session.get(storage_session_id, 0)
308
+ projects_with_counts.append((project, artifact_count))
309
+
310
+ self.logger.debug(f"Retrieved artifact counts for {len(projects)} projects in batch")
311
+ return projects_with_counts
312
+
313
+ except Exception as e:
314
+ self.logger.error(f"Failed to get artifact counts in batch: {e}")
315
+ # Fallback to 0 counts on error
316
+ return [(project, 0) for project in projects]
317
+
165
318
  async def get_project_artifacts(self, db, project_id: str, user_id: str) -> List[ArtifactInfo]:
166
319
  """
167
320
  Get a list of artifacts for a given project.
@@ -220,8 +373,10 @@ class ProjectService:
220
373
  List[dict]: A list of results from the save operations
221
374
 
222
375
  Raises:
223
- ValueError: If project not found or access denied
376
+ ValueError: If project not found, access denied, or file size exceeds limit
224
377
  """
378
+ log_prefix = f"[ProjectService:add_artifacts] Project {project_id}, User {user_id}:"
379
+
225
380
  project = self.get_project(db, project_id, user_id)
226
381
  if not project:
227
382
  raise ValueError("Project not found or access denied")
@@ -233,12 +388,16 @@ class ProjectService:
233
388
  if not files:
234
389
  return []
235
390
 
236
- self.logger.info(f"Adding {len(files)} artifacts to project {project_id} for user {user_id}")
391
+ # Validate file sizes before saving any artifacts
392
+ self.logger.info(f"{log_prefix} Validating {len(files)} files before adding to project")
393
+ validated_files = await self._validate_files(files, log_prefix)
394
+ self.logger.info(f"{log_prefix} All {len(files)} files passed size validation")
395
+
396
+ self.logger.info(f"Adding {len(validated_files)} artifacts to project {project_id} for user {user_id}")
237
397
  storage_session_id = f"project-{project.id}"
238
398
  results = []
239
399
 
240
- for file in files:
241
- content_bytes = await file.read()
400
+ for file, content_bytes in validated_files:
242
401
  metadata = {"source": "project"}
243
402
  if file_metadata and file.filename in file_metadata:
244
403
  desc = file_metadata[file.filename]
@@ -258,9 +417,83 @@ class ProjectService:
258
417
  )
259
418
  results.append(result)
260
419
 
261
- self.logger.info(f"Finished adding {len(files)} artifacts to project {project_id}")
420
+ self.logger.info(f"Finished adding {len(validated_files)} artifacts to project {project_id}")
262
421
  return results
263
422
 
423
+ async def update_artifact_metadata(
424
+ self,
425
+ db,
426
+ project_id: str,
427
+ user_id: str,
428
+ filename: str,
429
+ description: Optional[str] = None
430
+ ) -> bool:
431
+ """
432
+ Update metadata (description) for a project artifact.
433
+
434
+ Args:
435
+ db: The database session
436
+ project_id: The project ID
437
+ user_id: The requesting user ID
438
+ filename: The filename of the artifact to update
439
+ description: New description for the artifact
440
+
441
+ Returns:
442
+ bool: True if update was successful, False if project not found
443
+
444
+ Raises:
445
+ ValueError: If user cannot modify the project or artifact service is missing
446
+ """
447
+ project = self.get_project(db, project_id, user_id)
448
+ if not project:
449
+ return False
450
+
451
+ if not self.artifact_service:
452
+ self.logger.warning(f"Attempted to update artifact metadata in project {project_id} but no artifact service is configured.")
453
+ raise ValueError("Artifact service is not configured")
454
+
455
+ storage_session_id = f"project-{project.id}"
456
+
457
+ self.logger.info(f"Updating metadata for artifact '{filename}' in project {project_id} for user {user_id}")
458
+
459
+ # Load the current artifact to get its content and existing metadata
460
+ try:
461
+ artifact_part = await self.artifact_service.load_artifact(
462
+ app_name=self.app_name,
463
+ user_id=project.user_id,
464
+ session_id=storage_session_id,
465
+ filename=filename,
466
+ )
467
+
468
+ if not artifact_part or not artifact_part.inline_data:
469
+ self.logger.warning(f"Artifact '{filename}' not found in project {project_id}")
470
+ return False
471
+
472
+ # Prepare updated metadata
473
+ metadata = {"source": "project"}
474
+ if description is not None:
475
+ metadata["description"] = description
476
+
477
+ # Save the artifact with updated metadata
478
+ await save_artifact_with_metadata(
479
+ artifact_service=self.artifact_service,
480
+ app_name=self.app_name,
481
+ user_id=project.user_id,
482
+ session_id=storage_session_id,
483
+ filename=filename,
484
+ content_bytes=artifact_part.inline_data.data,
485
+ mime_type=artifact_part.inline_data.mime_type,
486
+ metadata_dict=metadata,
487
+ timestamp=datetime.now(timezone.utc),
488
+ )
489
+
490
+ self.logger.info(f"Successfully updated metadata for artifact '{filename}' in project {project_id}")
491
+ return True
492
+
493
+ except Exception as e:
494
+ self.logger.error(f"Error updating artifact metadata: {e}")
495
+ raise
496
+
264
497
  async def delete_artifact_from_project(self, db, project_id: str, user_id: str, filename: str) -> bool:
265
498
  """
266
499
  Deletes an artifact from a project.
@@ -401,3 +634,297 @@ class ProjectService:
401
634
  self.logger.info(f"Successfully soft deleted project {project_id} and {deleted_count} associated sessions")
402
635
 
403
636
  return success
637
+
638
+ async def export_project_as_zip(
639
+ self, db, project_id: str, user_id: str
640
+ ) -> BytesIO:
641
+ """
642
+ Create ZIP file with project data and artifacts.
643
+ Returns in-memory ZIP file.
644
+
645
+ Args:
646
+ db: Database session
647
+ project_id: The project ID
648
+ user_id: The requesting user ID
649
+
650
+ Returns:
651
+ BytesIO: In-memory ZIP file
652
+
653
+ Raises:
654
+ ValueError: If project not found or access denied
655
+ """
656
+ # Get project
657
+ project = self.get_project(db, project_id, user_id)
658
+ if not project:
659
+ raise ValueError("Project not found or access denied")
660
+
661
+ # Get artifacts
662
+ artifacts = await self.get_project_artifacts(db, project_id, user_id)
663
+
664
+ # Calculate total size
665
+ total_size = sum(artifact.size for artifact in artifacts)
666
+
667
+ # Create export metadata
668
+ from ..routers.dto.project_dto import (
669
+ ProjectExportFormat,
670
+ ProjectExportData,
671
+ ProjectExportMetadata,
672
+ ArtifactMetadata,
673
+ )
674
+
675
+ export_data = ProjectExportFormat(
676
+ version="1.0",
677
+ exported_at=int(datetime.now(timezone.utc).timestamp() * 1000),
678
+ project=ProjectExportData(
679
+ name=project.name,
680
+ description=project.description,
681
+ system_prompt=project.system_prompt,
682
+ default_agent_id=project.default_agent_id,
683
+ metadata=ProjectExportMetadata(
684
+ original_created_at=project.created_at,
685
+ artifact_count=len(artifacts),
686
+ total_size_bytes=total_size,
687
+ ),
688
+ ),
689
+ artifacts=[
690
+ ArtifactMetadata(
691
+ filename=artifact.filename,
692
+ mime_type=artifact.mime_type or "application/octet-stream",
693
+ size=artifact.size,
694
+ metadata={
695
+ "description": artifact.description,
696
+ "source": artifact.source,
697
+ } if artifact.description or artifact.source else {},
698
+ )
699
+ for artifact in artifacts
700
+ ],
701
+ )
702
+
703
+ # Create ZIP in memory
704
+ zip_buffer = BytesIO()
705
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
706
+ # Add project.json
707
+ project_json = export_data.model_dump(by_alias=True, mode='json')
708
+ zip_file.writestr('project.json', json.dumps(project_json, indent=2))
709
+
710
+ # Add artifacts
711
+ if self.artifact_service and artifacts:
712
+ storage_session_id = f"project-{project.id}"
713
+ for artifact in artifacts:
714
+ try:
715
+ # Load artifact content
716
+ artifact_part = await self.artifact_service.load_artifact(
717
+ app_name=self.app_name,
718
+ user_id=project.user_id,
719
+ session_id=storage_session_id,
720
+ filename=artifact.filename,
721
+ )
722
+
723
+ if artifact_part and artifact_part.inline_data:
724
+ # Add to ZIP under artifacts/ directory
725
+ zip_file.writestr(
726
+ f'artifacts/{artifact.filename}',
727
+ artifact_part.inline_data.data
728
+ )
729
+ except Exception as e:
730
+ self.logger.warning(
731
+ f"Failed to add artifact {artifact.filename} to export: {e}"
732
+ )
733
+
734
+ zip_buffer.seek(0)
735
+ return zip_buffer
736
+
737
+ async def import_project_from_zip(
738
+ self, db, zip_file: UploadFile, user_id: str,
739
+ preserve_name: bool = False, custom_name: Optional[str] = None
740
+ ) -> tuple[Project, int, List[str]]:
741
+ """
742
+ Import project from ZIP file.
743
+
744
+ Args:
745
+ db: Database session
746
+ zip_file: Uploaded ZIP file
747
+ user_id: The importing user ID
748
+ preserve_name: Whether to preserve original name
749
+ custom_name: Custom name to use (overrides preserve_name)
750
+
751
+ Returns:
752
+ tuple: (created_project, artifacts_count, warnings)
753
+
754
+ Raises:
755
+ ValueError: If ZIP is invalid, import fails, or file size exceeds limit
756
+ """
757
+ log_prefix = f"[ProjectService:import_project] User {user_id}:"
758
+ warnings = []
759
+
760
+ # Read ZIP file content with size validation
761
+ self.logger.info(f"{log_prefix} Reading ZIP file")
762
+ zip_content = await zip_file.read()
763
+ zip_size = len(zip_content)
764
+ self.logger.info(f"{log_prefix} ZIP file read: {zip_size:,} bytes")
765
+
766
+ # Validate ZIP file size (separate, larger limit than individual artifacts)
767
+ if zip_size > self.max_zip_upload_size_bytes:
768
+ max_size_mb = self.max_zip_upload_size_bytes / (1024 * 1024)
769
+ file_size_mb = zip_size / (1024 * 1024)
770
+ error_msg = (
771
+ f"ZIP file '{zip_file.filename}' rejected: size ({file_size_mb:.2f} MB) "
772
+ f"exceeds maximum allowed ({max_size_mb:.2f} MB)"
773
+ )
774
+ self.logger.warning(f"{log_prefix} {error_msg}")
775
+ raise ValueError(error_msg)
776
+
777
+ zip_buffer = BytesIO(zip_content)
778
+
779
+ try:
780
+ with zipfile.ZipFile(zip_buffer, 'r') as zip_ref:
781
+ # Validate ZIP structure
782
+ if 'project.json' not in zip_ref.namelist():
783
+ raise ValueError("Invalid project export: missing project.json")
784
+
785
+ # Parse project.json
786
+ project_json_content = zip_ref.read('project.json').decode('utf-8')
787
+ project_data = json.loads(project_json_content)
788
+
789
+ # Validate version
790
+ if project_data.get('version') != '1.0':
791
+ raise ValueError(
792
+ f"Unsupported export version: {project_data.get('version')}"
793
+ )
794
+
795
+ # Determine project name
796
+ original_name = project_data['project']['name']
797
+ if custom_name:
798
+ desired_name = custom_name
799
+ elif preserve_name:
800
+ desired_name = original_name
801
+ else:
802
+ desired_name = original_name
803
+
804
+ # Resolve name conflicts
805
+ final_name = self._resolve_project_name_conflict(db, desired_name, user_id)
806
+ if final_name != desired_name:
807
+ warnings.append(
808
+ f"Name conflict resolved: '{desired_name}' → '{final_name}'"
809
+ )
810
+
811
+ # Get default agent ID, but set to None if not provided
812
+ # The agent may not exist in the target environment
813
+ imported_agent_id = project_data['project'].get('defaultAgentId')
814
+
815
+ # Create project (agent validation happens in create_project if needed)
816
+ project = await self.create_project(
817
+ db=db,
818
+ name=final_name,
819
+ user_id=user_id,
820
+ description=project_data['project'].get('description'),
821
+ system_prompt=project_data['project'].get('systemPrompt'),
822
+ default_agent_id=imported_agent_id,
823
+ )
824
+
825
+ # Add warning if agent was specified but may not exist
826
+ if imported_agent_id:
827
+ warnings.append(
828
+ f"Default agent '{imported_agent_id}' was imported. "
829
+ "Verify it exists in your environment."
830
+ )
831
+
832
+ # Import artifacts
833
+ artifacts_imported = 0
834
+ if self.artifact_service:
835
+ storage_session_id = f"project-{project.id}"
836
+ artifact_files = [
837
+ name for name in zip_ref.namelist()
838
+ if name.startswith('artifacts/') and name != 'artifacts/'
839
+ ]
840
+
841
+ for artifact_path in artifact_files:
842
+ try:
843
+ filename = artifact_path.replace('artifacts/', '')
844
+ content_bytes = zip_ref.read(artifact_path)
845
+
846
+ # Skip oversized artifacts with a warning (don't fail the entire import)
847
+ if len(content_bytes) > self.max_upload_size_bytes:
848
+ max_size_mb = self.max_upload_size_bytes / (1024 * 1024)
849
+ file_size_mb = len(content_bytes) / (1024 * 1024)
850
+ skip_msg = (
851
+ f"Skipped '{filename}': size ({file_size_mb:.2f} MB) "
852
+ f"exceeds maximum allowed ({max_size_mb:.2f} MB)"
853
+ )
854
+ self.logger.warning(f"{log_prefix} {skip_msg}")
855
+ warnings.append(skip_msg)
856
+ continue # Skip this artifact, continue with others
857
+
858
+ # Find metadata from project.json
859
+ artifact_meta = next(
860
+ (a for a in project_data.get('artifacts', [])
861
+ if a['filename'] == filename),
862
+ None
863
+ )
864
+
865
+ metadata = artifact_meta.get('metadata', {}) if artifact_meta else {}
866
+ mime_type = artifact_meta.get('mimeType', 'application/octet-stream') if artifact_meta else 'application/octet-stream'
867
+
868
+ # Save artifact
869
+ from ....agent.utils.artifact_helpers import save_artifact_with_metadata
870
+ await save_artifact_with_metadata(
871
+ artifact_service=self.artifact_service,
872
+ app_name=self.app_name,
873
+ user_id=project.user_id,
874
+ session_id=storage_session_id,
875
+ filename=filename,
876
+ content_bytes=content_bytes,
877
+ mime_type=mime_type,
878
+ metadata_dict=metadata,
879
+ timestamp=datetime.now(timezone.utc),
880
+ )
881
+ artifacts_imported += 1
882
+ except Exception as e:
883
+ self.logger.warning(
884
+ f"Failed to import artifact {artifact_path}: {e}"
885
+ )
886
+ warnings.append(f"Failed to import artifact: {filename}")
887
+
888
+ self.logger.info(
889
+ f"Successfully imported project {project.id} with {artifacts_imported} artifacts"
890
+ )
891
+ return project, artifacts_imported, warnings
892
+
893
+ except zipfile.BadZipFile:
894
+ raise ValueError("Invalid ZIP file")
895
+ except json.JSONDecodeError:
896
+ raise ValueError("Invalid project.json format")
897
+ except KeyError as e:
898
+ raise ValueError(f"Missing required field in project.json: {e}")
899
+
900
+ def _resolve_project_name_conflict(
901
+ self, db, desired_name: str, user_id: str
902
+ ) -> str:
903
+ """
904
+ Resolve project name conflicts by appending (2), (3), etc.
905
+ Similar to prompt import conflict resolution.
906
+
907
+ Args:
908
+ db: Database session
909
+ desired_name: The desired project name
910
+ user_id: The user ID
911
+
912
+ Returns:
913
+ str: A unique project name
914
+ """
915
+ project_repository = self._get_repositories(db)
916
+ existing_projects = project_repository.get_user_projects(user_id)
917
+ existing_names = {p.name.lower() for p in existing_projects}
918
+
919
+ if desired_name.lower() not in existing_names:
920
+ return desired_name
921
+
922
+ # Try appending (2), (3), etc.
923
+ counter = 2
924
+ while True:
925
+ candidate = f"{desired_name} ({counter})"
926
+ if candidate.lower() not in existing_names:
927
+ return candidate
928
+ counter += 1
929
+ if counter > 100: # Safety limit
930
+ raise ValueError("Unable to resolve name conflict")