solace-agent-mesh 1.6.3__py3-none-any.whl → 1.7.0__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 (244) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +12 -18
  2. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
  3. solace_agent_mesh/agent/adk/callbacks.py +138 -20
  4. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +2 -0
  5. solace_agent_mesh/agent/adk/models/lite_llm.py +38 -5
  6. solace_agent_mesh/agent/adk/models/models_llm.txt +82 -35
  7. solace_agent_mesh/agent/adk/runner.py +9 -0
  8. solace_agent_mesh/agent/adk/stream_parser.py +6 -1
  9. solace_agent_mesh/agent/adk/tool_wrapper.py +3 -0
  10. solace_agent_mesh/agent/agent_llm.txt +61 -70
  11. solace_agent_mesh/agent/protocol/event_handlers.py +29 -1
  12. solace_agent_mesh/agent/protocol/protocol_llm.txt +1 -1
  13. solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
  14. solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
  15. solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
  16. solace_agent_mesh/agent/sac/app.py +22 -0
  17. solace_agent_mesh/agent/sac/component.py +76 -40
  18. solace_agent_mesh/agent/sac/sac_llm.txt +1 -1
  19. solace_agent_mesh/agent/sac/task_execution_context.py +21 -0
  20. solace_agent_mesh/agent/testing/testing_llm.txt +2 -1
  21. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +13 -148
  22. solace_agent_mesh/agent/tools/dynamic_tool.py +2 -0
  23. solace_agent_mesh/agent/tools/tools_llm.txt +93 -80
  24. solace_agent_mesh/agent/tools/tools_llm_detail.txt +3 -2
  25. solace_agent_mesh/agent/utils/artifact_helpers.py +4 -0
  26. solace_agent_mesh/agent/utils/utils_llm.txt +16 -2
  27. solace_agent_mesh/assets/docs/404.html +3 -3
  28. solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +1 -0
  29. solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
  30. solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +1 -0
  31. solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
  37. solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
  38. solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
  39. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
  40. solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
  41. solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
  42. solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
  43. solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +1 -0
  44. solace_agent_mesh/assets/docs/assets/js/{ab9708a8.3e6dd091.js → ab9708a8.245ae0ef.js} +1 -1
  45. solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
  46. solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +1 -0
  47. solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
  48. solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
  49. solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
  50. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
  51. solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
  52. solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +1 -0
  53. solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +1 -0
  54. solace_agent_mesh/assets/docs/assets/js/{e6f9706b.e74a984d.js → e6f9706b.045d0fa1.js} +1 -1
  55. solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
  56. solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +1 -0
  57. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
  58. solace_agent_mesh/assets/docs/assets/js/main.e6488e8b.js +2 -0
  59. solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +1 -0
  60. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  64. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +18 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  66. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +5 -5
  68. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  71. solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +196 -0
  72. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +4 -4
  73. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +5 -5
  74. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +6 -7
  75. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +4 -4
  76. solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
  77. solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +4 -4
  78. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +4 -4
  79. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  80. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +160 -169
  81. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  82. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  83. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +4 -4
  84. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +4 -4
  85. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  86. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  87. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +5 -5
  88. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +4 -4
  89. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  90. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  91. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  92. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  93. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  94. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  95. solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +59 -0
  96. solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +62 -0
  97. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +10 -6
  98. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +4 -4
  99. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +4 -4
  100. solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
  101. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +27 -4
  102. solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
  103. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +4 -4
  104. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +5 -4
  105. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  106. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  107. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
  108. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +9 -9
  109. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +4 -4
  110. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +4 -4
  111. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +4 -4
  112. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +4 -4
  113. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
  114. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
  115. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  116. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  117. solace_agent_mesh/assets/docs/lunr-index-1762189824009.json +1 -0
  118. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  119. solace_agent_mesh/assets/docs/search-doc-1762189824009.json +1 -0
  120. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  121. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  122. solace_agent_mesh/cli/__init__.py +1 -1
  123. solace_agent_mesh/cli/commands/docs_cmd.py +4 -1
  124. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +1 -1
  125. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-D4_RMYRh.js → authCallback-tcIFZLis.js} +1 -1
  126. solace_agent_mesh/client/webui/frontend/static/assets/{client-UZ3qU6Bq.js → client-CRYdKo2Q.js} +3 -3
  127. solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +1 -0
  128. solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +353 -0
  129. solace_agent_mesh/client/webui/frontend/static/assets/vendor-CINwxvwV.js +470 -0
  130. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  131. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  132. solace_agent_mesh/common/a2a/a2a_llm.txt +13 -20
  133. solace_agent_mesh/common/a2a/protocol.py +5 -0
  134. solace_agent_mesh/common/a2a/types.py +1 -0
  135. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +49 -11
  136. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +23 -6
  137. solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
  138. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +26 -9
  139. solace_agent_mesh/common/common_llm.txt +13 -34
  140. solace_agent_mesh/common/data_parts.py +20 -4
  141. solace_agent_mesh/common/middleware/middleware_llm.txt +1 -1
  142. solace_agent_mesh/common/sac/sac_llm.txt +1 -1
  143. solace_agent_mesh/common/sam_events/sam_events_llm.txt +1 -1
  144. solace_agent_mesh/common/services/employee_service.py +1 -1
  145. solace_agent_mesh/common/services/providers/providers_llm.txt +3 -2
  146. solace_agent_mesh/common/services/services_llm.txt +9 -4
  147. solace_agent_mesh/common/utils/embeds/constants.py +1 -0
  148. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
  149. solace_agent_mesh/common/utils/embeds/modifiers.py +2 -1
  150. solace_agent_mesh/common/utils/embeds/resolver.py +58 -6
  151. solace_agent_mesh/common/utils/embeds/types.py +8 -0
  152. solace_agent_mesh/common/utils/utils_llm.txt +5 -6
  153. solace_agent_mesh/core_a2a/core_a2a_llm.txt +1 -1
  154. solace_agent_mesh/gateway/adapter/__init__.py +1 -0
  155. solace_agent_mesh/gateway/adapter/base.py +143 -0
  156. solace_agent_mesh/gateway/adapter/types.py +221 -0
  157. solace_agent_mesh/gateway/base/app.py +29 -2
  158. solace_agent_mesh/gateway/base/base_llm.txt +10 -8
  159. solace_agent_mesh/gateway/base/component.py +573 -142
  160. solace_agent_mesh/gateway/gateway_llm.txt +55 -59
  161. solace_agent_mesh/gateway/generic/__init__.py +1 -0
  162. solace_agent_mesh/gateway/generic/app.py +50 -0
  163. solace_agent_mesh/gateway/generic/component.py +650 -0
  164. solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +99 -49
  165. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +92 -0
  166. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
  167. solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +150 -0
  168. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
  169. solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
  170. solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +26 -20
  171. solace_agent_mesh/gateway/http_sse/app.py +0 -14
  172. solace_agent_mesh/gateway/http_sse/component.py +17 -56
  173. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +1 -1
  174. solace_agent_mesh/gateway/http_sse/dependencies.py +21 -3
  175. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +8 -8
  176. solace_agent_mesh/gateway/http_sse/main.py +23 -5
  177. solace_agent_mesh/gateway/http_sse/repository/__init__.py +19 -1
  178. solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +56 -98
  179. solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
  180. solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
  181. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +23 -1
  182. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +47 -0
  183. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +112 -4
  184. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -1
  185. solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +51 -60
  186. solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
  187. solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
  188. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +7 -1
  189. solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
  190. solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
  191. solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +125 -157
  192. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +269 -8
  193. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +143 -51
  194. solace_agent_mesh/gateway/http_sse/routers/config.py +69 -0
  195. solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +198 -94
  196. solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
  197. solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +68 -18
  198. solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +13 -0
  199. solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +30 -0
  200. solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +51 -35
  201. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +2 -0
  202. solace_agent_mesh/gateway/http_sse/routers/feedback.py +133 -2
  203. solace_agent_mesh/gateway/http_sse/routers/projects.py +542 -0
  204. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +9 -11
  205. solace_agent_mesh/gateway/http_sse/routers/sessions.py +154 -3
  206. solace_agent_mesh/gateway/http_sse/routers/tasks.py +296 -4
  207. solace_agent_mesh/gateway/http_sse/services/project_service.py +403 -0
  208. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +16 -10
  209. solace_agent_mesh/gateway/http_sse/services/session_service.py +178 -6
  210. solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +2 -3
  211. solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +48 -14
  212. solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
  213. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.0.dist-info}/METADATA +3 -5
  214. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.0.dist-info}/RECORD +218 -175
  215. solace_agent_mesh/assets/docs/assets/js/15ba94aa.932dd2db.js +0 -1
  216. solace_agent_mesh/assets/docs/assets/js/3ac1795d.76654dd9.js +0 -1
  217. solace_agent_mesh/assets/docs/assets/js/3ff0015d.2be20244.js +0 -1
  218. solace_agent_mesh/assets/docs/assets/js/547e15cc.2cbb060a.js +0 -1
  219. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +0 -1
  220. solace_agent_mesh/assets/docs/assets/js/631738c7.7c4594c9.js +0 -1
  221. solace_agent_mesh/assets/docs/assets/js/6a520c9d.ba015d81.js +0 -1
  222. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +0 -1
  223. solace_agent_mesh/assets/docs/assets/js/71da7b71.ddbdfbe2.js +0 -1
  224. solace_agent_mesh/assets/docs/assets/js/8024126c.56e59919.js +0 -1
  225. solace_agent_mesh/assets/docs/assets/js/94e8668d.3b883666.js +0 -1
  226. solace_agent_mesh/assets/docs/assets/js/da0b5bad.d08a9466.js +0 -1
  227. solace_agent_mesh/assets/docs/assets/js/dd817ffc.0aa9630a.js +0 -1
  228. solace_agent_mesh/assets/docs/assets/js/dd81e2b8.d590bc9e.js +0 -1
  229. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +0 -1
  230. solace_agent_mesh/assets/docs/assets/js/e3d9abda.6b9493d0.js +0 -1
  231. solace_agent_mesh/assets/docs/assets/js/e92d0134.4f395c6b.js +0 -1
  232. solace_agent_mesh/assets/docs/assets/js/f284c35a.720d2ef2.js +0 -1
  233. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +0 -1
  234. solace_agent_mesh/assets/docs/assets/js/main.ed05b14d.js +0 -2
  235. solace_agent_mesh/assets/docs/assets/js/runtime~main.a8a75e0b.js +0 -1
  236. solace_agent_mesh/assets/docs/lunr-index-1761744323675.json +0 -1
  237. solace_agent_mesh/assets/docs/search-doc-1761744323675.json +0 -1
  238. solace_agent_mesh/client/webui/frontend/static/assets/main--3yJYl7S.css +0 -1
  239. solace_agent_mesh/client/webui/frontend/static/assets/main-DojKHS49.js +0 -342
  240. solace_agent_mesh/client/webui/frontend/static/assets/vendor-DSqhjwq_.js +0 -405
  241. /solace_agent_mesh/assets/docs/assets/js/{main.ed05b14d.js.LICENSE.txt → main.e6488e8b.js.LICENSE.txt} +0 -0
  242. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.0.dist-info}/WHEEL +0 -0
  243. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.0.dist-info}/entry_points.txt +0 -0
  244. {solace_agent_mesh-1.6.3.dist-info → solace_agent_mesh-1.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,11 @@
1
1
  ## Quick Summary
2
- This directory contains SQLAlchemy ORM models and Pydantic schemas for database persistence in the HTTP SSE gateway. It provides models for managing chat sessions, messages, tasks, task events, and user feedback with proper relationships and database schema definitions.
2
+ This directory contains SQLAlchemy ORM models and Pydantic schemas for database persistence in the HTTP SSE gateway. It provides models for managing chat sessions, tasks, task events, and user feedback with proper relationships and database schema definitions.
3
3
 
4
4
  ## Files Overview
5
5
  - `__init__.py` - Package initialization exposing all SQLAlchemy and Pydantic models
6
6
  - `base.py` - SQLAlchemy declarative base configuration
7
+ - `chat_task_model.py` - ChatTaskModel for storing chat tasks with session relationships
7
8
  - `feedback_model.py` - FeedbackModel for storing user feedback on tasks
8
- - `message_model.py` - MessageModel and Pydantic schemas for chat messages with session relationships
9
9
  - `session_model.py` - SessionModel and Pydantic schemas for managing chat sessions
10
10
  - `task_event_model.py` - TaskEventModel for storing A2A task events with task relationships
11
11
  - `task_model.py` - TaskModel for managing tasks with event relationships and token usage tracking
@@ -14,7 +14,7 @@ This directory contains SQLAlchemy ORM models and Pydantic schemas for database
14
14
 
15
15
  ### __init__.py
16
16
  **Purpose:** Package entry point that exposes all SQLAlchemy models and Pydantic schemas
17
- **Import:** `from solace_agent_mesh.gateway.http_sse.repository.models import Base, MessageModel, SessionModel, CreateMessageModel, UpdateMessageModel, CreateSessionModel, UpdateSessionModel, TaskEventModel, TaskModel, FeedbackModel`
17
+ **Import:** `from solace_agent_mesh.gateway.http_sse.repository.models import Base, ChatTaskModel, FeedbackModel, SessionModel, CreateSessionModel, UpdateSessionModel, TaskEventModel, TaskModel`
18
18
 
19
19
  **Constants/Variables:**
20
20
  - `__all__: List[str]` - Public API exports including all models and schemas
@@ -36,6 +36,49 @@ engine = create_engine("sqlite:///example.db")
36
36
  Base.metadata.create_all(engine)
37
37
  ```
38
38
 
39
+ ### chat_task_model.py
40
+ **Purpose:** SQLAlchemy model for storing chat tasks with session relationships
41
+ **Import:** `from solace_agent_mesh.gateway.http_sse.repository.models.chat_task_model import ChatTaskModel`
42
+
43
+ **Classes:**
44
+ - `ChatTaskModel(Base)` - SQLAlchemy model for chat tasks
45
+ - `id: Column[String]` - Primary key task identifier
46
+ - `session_id: Column[String]` - Foreign key to sessions table with CASCADE delete (indexed)
47
+ - `user_id: Column[String]` - User identifier (indexed)
48
+ - `user_message: Column[Text]` - Optional user message content
49
+ - `message_bubbles: Column[Text]` - Required message bubbles data
50
+ - `task_metadata: Column[Text]` - Optional task metadata
51
+ - `created_time: Column[BigInteger]` - Creation timestamp in epoch milliseconds (indexed)
52
+ - `updated_time: Column[BigInteger]` - Optional update timestamp
53
+ - `session: relationship` - SQLAlchemy relationship to SessionModel
54
+
55
+ **Usage Examples:**
56
+ ```python
57
+ from solace_agent_mesh.gateway.http_sse.repository.models.chat_task_model import ChatTaskModel
58
+ from sqlalchemy.orm import sessionmaker
59
+
60
+ # Create a chat task
61
+ chat_task = ChatTaskModel(
62
+ id="task_123",
63
+ session_id="session_456",
64
+ user_id="user_789",
65
+ user_message="Hello, how can you help me?",
66
+ message_bubbles='[{"type": "user", "content": "Hello"}]',
67
+ task_metadata='{"priority": "high"}',
68
+ created_time=1640995200000,
69
+ updated_time=1640995260000
70
+ )
71
+
72
+ # Add to database
73
+ Session = sessionmaker(bind=engine)
74
+ db_session = Session()
75
+ db_session.add(chat_task)
76
+ db_session.commit()
77
+
78
+ # Access related session
79
+ session = chat_task.session
80
+ ```
81
+
39
82
  ### feedback_model.py
40
83
  **Purpose:** SQLAlchemy model for storing user feedback on tasks
41
84
  **Import:** `from solace_agent_mesh.gateway.http_sse.repository.models.feedback_model import FeedbackModel`
@@ -73,60 +116,8 @@ db_session.add(feedback)
73
116
  db_session.commit()
74
117
  ```
75
118
 
76
- ### message_model.py
77
- **Purpose:** SQLAlchemy model and Pydantic schemas for storing chat messages with session relationships
78
- **Import:** `from solace_agent_mesh.gateway.http_sse.repository.models.message_model import MessageModel, CreateMessageModel, UpdateMessageModel`
79
-
80
- **Classes:**
81
- - `MessageModel(Base)` - SQLAlchemy model for chat messages
82
- - `id: Column[String]` - Primary key message identifier
83
- - `session_id: Column[String]` - Foreign key to sessions table with CASCADE delete
84
- - `message: Column[Text]` - Message content
85
- - `created_time: Column[BigInteger]` - Creation timestamp (auto-generated)
86
- - `sender_type: Column[String]` - Type of message sender (max 50 chars)
87
- - `sender_name: Column[String]` - Name of message sender (max 255 chars)
88
- - `session: relationship` - SQLAlchemy relationship to SessionModel
89
-
90
- - `CreateMessageModel(BaseModel)` - Pydantic model for creating messages
91
- - `id: str` - Message identifier
92
- - `session_id: str` - Session identifier
93
- - `message: str` - Message content
94
- - `sender_type: str` - Sender type
95
- - `sender_name: str` - Sender name
96
- - `created_time: int` - Creation timestamp
97
-
98
- - `UpdateMessageModel(BaseModel)` - Pydantic model for updating messages
99
- - `message: str` - Updated message content
100
- - `sender_type: str` - Updated sender type
101
- - `sender_name: str` - Updated sender name
102
-
103
- **Usage Examples:**
104
- ```python
105
- from solace_agent_mesh.gateway.http_sse.repository.models.message_model import MessageModel, CreateMessageModel
106
- from sqlalchemy.orm import sessionmaker
107
-
108
- # Create using SQLAlchemy model
109
- message = MessageModel(
110
- id="msg_123",
111
- session_id="session_456",
112
- message="Hello, world!",
113
- sender_type="user",
114
- sender_name="John Doe"
115
- )
116
-
117
- # Create using Pydantic model
118
- create_data = CreateMessageModel(
119
- id="msg_124",
120
- session_id="session_456",
121
- message="How are you?",
122
- sender_type="user",
123
- sender_name="John Doe",
124
- created_time=1640995200000
125
- )
126
- ```
127
-
128
119
  ### session_model.py
129
- **Purpose:** SQLAlchemy model and Pydantic schemas for managing chat sessions with message relationships
120
+ **Purpose:** SQLAlchemy model and Pydantic schemas for managing chat sessions with chat task relationships
130
121
  **Import:** `from solace_agent_mesh.gateway.http_sse.repository.models.session_model import SessionModel, CreateSessionModel, UpdateSessionModel`
131
122
 
132
123
  **Classes:**
@@ -137,7 +128,7 @@ create_data = CreateMessageModel(
137
128
  - `agent_id: Column[String]` - Optional agent identifier
138
129
  - `created_time: Column[BigInteger]` - Creation timestamp (auto-generated)
139
130
  - `updated_time: Column[BigInteger]` - Last update timestamp (auto-updated)
140
- - `messages: relationship` - SQLAlchemy relationship to MessageModel with cascade delete
131
+ - `chat_tasks: relationship` - SQLAlchemy relationship to ChatTaskModel with cascade delete
141
132
 
142
133
  - `CreateSessionModel(BaseModel)` - Pydantic model for creating sessions
143
134
  - `id: str` - Session identifier
@@ -175,8 +166,8 @@ create_data = CreateSessionModel(
175
166
  updated_time=1640995200000
176
167
  )
177
168
 
178
- # Access related messages
179
- messages = session.messages # Returns list of MessageModel instances
169
+ # Access related chat tasks
170
+ chat_tasks = session.chat_tasks # Returns list of ChatTaskModel instances
180
171
  ```
181
172
 
182
173
  ### task_event_model.py
@@ -263,4 +254,4 @@ db_session.commit()
263
254
  events = task.events # Returns list of TaskEventModel instances
264
255
  ```
265
256
 
266
- # content_hash: 0583795702abeb023659a686bf3cfa551016bdd0284b01f2448dd9d4d2050f3b
257
+ # content_hash: 65d43bc49dd561efb9955aae2fc39945567e4302c406eea9718ce797d083646c
@@ -0,0 +1,51 @@
1
+ """
2
+ SQLAlchemy model for project data.
3
+ """
4
+
5
+ from sqlalchemy import Column, String, Boolean, BigInteger, Text
6
+ from sqlalchemy.orm import relationship
7
+ from pydantic import BaseModel
8
+
9
+ from .base import Base
10
+
11
+
12
+ class ProjectModel(Base):
13
+ """SQLAlchemy model for projects."""
14
+
15
+ __tablename__ = "projects"
16
+
17
+ id = Column(String, primary_key=True)
18
+ name = Column(String, nullable=False)
19
+ user_id = Column(String, nullable=False)
20
+ description = Column(Text, nullable=True)
21
+ system_prompt = Column(Text, nullable=True)
22
+ default_agent_id = Column(String, nullable=True)
23
+ created_at = Column(BigInteger, nullable=False)
24
+ updated_at = Column(BigInteger, nullable=True)
25
+ deleted_at = Column(BigInteger, nullable=True)
26
+ deleted_by = Column(String, nullable=True)
27
+
28
+ # Relationships
29
+ sessions = relationship("SessionModel", back_populates="project")
30
+ project_users = relationship("ProjectUserModel", back_populates="project", cascade="all, delete-orphan")
31
+
32
+
33
+ class CreateProjectModel(BaseModel):
34
+ """Pydantic model for creating a project."""
35
+ id: str
36
+ name: str
37
+ user_id: str | None = None
38
+ description: str | None = None
39
+ system_prompt: str | None = None
40
+ default_agent_id: str | None = None
41
+ created_at: int
42
+ updated_at: int | None = None
43
+
44
+
45
+ class UpdateProjectModel(BaseModel):
46
+ """Pydantic model for updating a project."""
47
+ name: str | None = None
48
+ description: str | None = None
49
+ system_prompt: str | None = None
50
+ default_agent_id: str | None = None
51
+ updated_at: int
@@ -0,0 +1,75 @@
1
+ """
2
+ SQLAlchemy model for project user access (junction table).
3
+ """
4
+
5
+ from enum import Enum
6
+ from sqlalchemy import Column, String, BigInteger, ForeignKey, UniqueConstraint, Enum as SQLEnum
7
+ from sqlalchemy.orm import relationship
8
+ from pydantic import BaseModel, field_validator
9
+ from typing import Literal
10
+
11
+ from .base import Base
12
+
13
+
14
+ class ProjectRole(str, Enum):
15
+ """Valid roles for project users."""
16
+ OWNER = "owner"
17
+ EDITOR = "editor"
18
+ VIEWER = "viewer"
19
+
20
+
21
+ class ProjectUserModel(Base):
22
+ """
23
+ SQLAlchemy model for project user access.
24
+
25
+ This junction table tracks which users have access to which projects,
26
+ enabling multi-user collaboration on projects.
27
+ """
28
+
29
+ __tablename__ = "project_users"
30
+
31
+ id = Column(String, primary_key=True)
32
+ project_id = Column(String, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False)
33
+ user_id = Column(String, nullable=False)
34
+ role = Column(SQLEnum(ProjectRole), nullable=False, default=ProjectRole.VIEWER)
35
+ added_at = Column(BigInteger, nullable=False) # Epoch timestamp in milliseconds
36
+ added_by_user_id = Column(String, nullable=False) # User who granted access
37
+
38
+ # Ensure a user can only be added once per project
39
+ __table_args__ = (
40
+ UniqueConstraint('project_id', 'user_id', name='uq_project_user'),
41
+ )
42
+
43
+ # Relationships
44
+ project = relationship("ProjectModel", back_populates="project_users")
45
+
46
+
47
+ class CreateProjectUserModel(BaseModel):
48
+ """Pydantic model for creating a project user access record."""
49
+ id: str
50
+ project_id: str
51
+ user_id: str
52
+ role: Literal["owner", "editor", "viewer"] = "viewer"
53
+ added_at: int
54
+ added_by_user_id: str
55
+
56
+ @field_validator('role')
57
+ @classmethod
58
+ def validate_role(cls, v: str) -> str:
59
+ """Validate that role is one of the allowed values."""
60
+ if v not in [role.value for role in ProjectRole]:
61
+ raise ValueError(f"Role must be one of: {', '.join([role.value for role in ProjectRole])}")
62
+ return v
63
+
64
+
65
+ class UpdateProjectUserModel(BaseModel):
66
+ """Pydantic model for updating a project user access record."""
67
+ role: Literal["owner", "editor", "viewer"] | None = None
68
+
69
+ @field_validator('role')
70
+ @classmethod
71
+ def validate_role(cls, v: str | None) -> str | None:
72
+ """Validate that role is one of the allowed values."""
73
+ if v is not None and v not in [role.value for role in ProjectRole]:
74
+ raise ValueError(f"Role must be one of: {', '.join([role.value for role in ProjectRole])}")
75
+ return v
@@ -2,8 +2,8 @@
2
2
  Session SQLAlchemy model and Pydantic models for strongly-typed operations.
3
3
  """
4
4
 
5
+ from sqlalchemy import BigInteger, Column, String, ForeignKey
5
6
  from pydantic import BaseModel
6
- from sqlalchemy import BigInteger, Column, String
7
7
  from sqlalchemy.orm import relationship
8
8
 
9
9
  from ...shared import now_epoch_ms
@@ -19,15 +19,19 @@ class SessionModel(Base):
19
19
  name = Column(String, nullable=True)
20
20
  user_id = Column(String, nullable=False)
21
21
  agent_id = Column(String, nullable=True)
22
+ project_id = Column(String, ForeignKey("projects.id"), nullable=True)
22
23
  created_time = Column(BigInteger, nullable=False, default=now_epoch_ms)
23
24
  updated_time = Column(
24
25
  BigInteger, nullable=False, default=now_epoch_ms, onupdate=now_epoch_ms
25
26
  )
27
+ deleted_at = Column(BigInteger, nullable=True)
28
+ deleted_by = Column(String, nullable=True)
26
29
 
27
30
  # Relationship to chat tasks
28
31
  chat_tasks = relationship(
29
32
  "ChatTaskModel", back_populates="session", cascade="all, delete-orphan"
30
33
  )
34
+ project = relationship("ProjectModel", back_populates="sessions")
31
35
 
32
36
 
33
37
  class CreateSessionModel(BaseModel):
@@ -36,6 +40,7 @@ class CreateSessionModel(BaseModel):
36
40
  name: str | None
37
41
  user_id: str
38
42
  agent_id: str | None
43
+ project_id: str | None = None
39
44
  created_time: int
40
45
  updated_time: int
41
46
 
@@ -44,4 +49,5 @@ class UpdateSessionModel(BaseModel):
44
49
  """Pydantic model for updating a session."""
45
50
  name: str | None = None
46
51
  agent_id: str | None = None
52
+ project_id: str | None = None
47
53
  updated_time: int
@@ -0,0 +1,172 @@
1
+ """
2
+ Repository implementation for project data access operations.
3
+ """
4
+ from typing import List, Optional
5
+ import uuid
6
+
7
+ from sqlalchemy.orm import Session as DBSession
8
+ from sqlalchemy import or_
9
+
10
+ from .interfaces import IProjectRepository
11
+ from .models import ProjectModel, ProjectUserModel
12
+ from .entities.project import Project
13
+ from ..routers.dto.requests.project_requests import ProjectFilter
14
+ from ..shared import now_epoch_ms
15
+
16
+
17
+ class ProjectRepository(IProjectRepository):
18
+ """SQLAlchemy implementation of project repository."""
19
+
20
+ def __init__(self, db: DBSession):
21
+ self.db = db
22
+
23
+ def create_project(self, name: str, user_id: str, description: Optional[str] = None,
24
+ system_prompt: Optional[str] = None, default_agent_id: Optional[str] = None) -> Project:
25
+ """Create a new user project."""
26
+ model = ProjectModel(
27
+ id=str(uuid.uuid4()),
28
+ name=name,
29
+ user_id=user_id,
30
+ description=description,
31
+ system_prompt=system_prompt,
32
+ default_agent_id=default_agent_id,
33
+ created_at=now_epoch_ms(),
34
+ )
35
+ self.db.add(model)
36
+ self.db.flush()
37
+ self.db.refresh(model)
38
+ return self._model_to_entity(model)
39
+
40
+ def get_user_projects(self, user_id: str) -> List[Project]:
41
+ """
42
+ Get all projects owned by a specific user.
43
+
44
+ Note: This returns only projects where the user is the owner (user_id matches).
45
+ For projects the user has access to via project_users table, use get_accessible_projects().
46
+ """
47
+ models = self.db.query(ProjectModel).filter(
48
+ ProjectModel.user_id == user_id,
49
+ ProjectModel.deleted_at.is_(None) # Exclude soft-deleted projects
50
+ ).all()
51
+ return [self._model_to_entity(model) for model in models]
52
+
53
+ def get_accessible_projects(self, user_id: str) -> List[Project]:
54
+ """
55
+ Get all projects accessible by a user (owned or shared).
56
+
57
+ This includes:
58
+ - Projects owned by the user (user_id matches)
59
+ - Projects shared with the user (via project_users table)
60
+
61
+ Args:
62
+ user_id: The user ID
63
+
64
+ Returns:
65
+ List[Project]: List of accessible projects
66
+ """
67
+ # Query for projects where user is owner OR has access via project_users
68
+ models = self.db.query(ProjectModel).outerjoin(
69
+ ProjectUserModel,
70
+ ProjectModel.id == ProjectUserModel.project_id
71
+ ).filter(
72
+ ProjectModel.deleted_at.is_(None), # Exclude soft-deleted projects
73
+ or_(
74
+ ProjectModel.user_id == user_id,
75
+ ProjectUserModel.user_id == user_id
76
+ )
77
+ ).distinct().all()
78
+
79
+ return [self._model_to_entity(model) for model in models]
80
+
81
+ def get_filtered_projects(self, project_filter: ProjectFilter) -> List[Project]:
82
+ """Get projects based on filter criteria."""
83
+ query = self.db.query(ProjectModel).filter(
84
+ ProjectModel.deleted_at.is_(None) # Exclude soft-deleted projects
85
+ )
86
+
87
+ if project_filter.user_id is not None:
88
+ query = query.filter(ProjectModel.user_id == project_filter.user_id)
89
+
90
+ models = query.all()
91
+ return [self._model_to_entity(model) for model in models]
92
+
93
+ def get_by_id(self, project_id: str, user_id: str) -> Optional[Project]:
94
+ """
95
+ Get a project by its ID, ensuring user access.
96
+
97
+ This checks if the user is the owner OR has access via project_users table.
98
+ """
99
+ model = self.db.query(ProjectModel).outerjoin(
100
+ ProjectUserModel,
101
+ ProjectModel.id == ProjectUserModel.project_id
102
+ ).filter(
103
+ ProjectModel.id == project_id,
104
+ ProjectModel.deleted_at.is_(None), # Exclude soft-deleted projects
105
+ or_(
106
+ ProjectModel.user_id == user_id,
107
+ ProjectUserModel.user_id == user_id
108
+ )
109
+ ).first()
110
+
111
+ return self._model_to_entity(model) if model else None
112
+
113
+ def update(self, project_id: str, user_id: str, update_data: dict) -> Optional[Project]:
114
+ """Update a project with the given data, ensuring user access."""
115
+ model = self.db.query(ProjectModel).filter(
116
+ ProjectModel.id == project_id,
117
+ ProjectModel.user_id == user_id, # Only allow updates to user's own projects
118
+ ProjectModel.deleted_at.is_(None) # Exclude soft-deleted projects
119
+ ).first()
120
+
121
+ if not model:
122
+ return None
123
+
124
+ for field, value in update_data.items():
125
+ if hasattr(model, field):
126
+ setattr(model, field, value)
127
+
128
+ model.updated_at = now_epoch_ms()
129
+ self.db.flush()
130
+ self.db.refresh(model)
131
+ return self._model_to_entity(model)
132
+
133
+ def delete(self, project_id: str, user_id: str) -> bool:
134
+ """Delete a project by its ID, ensuring user access."""
135
+ result = self.db.query(ProjectModel).filter(
136
+ ProjectModel.id == project_id,
137
+ ProjectModel.user_id == user_id # Only allow deletion of user's own projects
138
+ ).delete()
139
+ self.db.flush()
140
+ return result > 0
141
+
142
+ def soft_delete(self, project_id: str, user_id: str) -> bool:
143
+ """Soft delete a project by its ID, ensuring user access."""
144
+ model = self.db.query(ProjectModel).filter(
145
+ ProjectModel.id == project_id,
146
+ ProjectModel.user_id == user_id, # Only allow deletion of user's own projects
147
+ ProjectModel.deleted_at.is_(None) # Only delete if not already deleted
148
+ ).first()
149
+
150
+ if not model:
151
+ return False
152
+
153
+ model.deleted_at = now_epoch_ms()
154
+ model.deleted_by = user_id
155
+ model.updated_at = now_epoch_ms()
156
+ self.db.flush()
157
+ return True
158
+
159
+ def _model_to_entity(self, model: ProjectModel) -> Project:
160
+ """Convert SQLAlchemy model to domain entity."""
161
+ return Project(
162
+ id=model.id,
163
+ name=model.name,
164
+ user_id=model.user_id,
165
+ description=model.description,
166
+ system_prompt=model.system_prompt,
167
+ default_agent_id=model.default_agent_id,
168
+ created_at=model.created_at,
169
+ updated_at=model.updated_at,
170
+ deleted_at=model.deleted_at,
171
+ deleted_by=model.deleted_by,
172
+ )
@@ -0,0 +1,186 @@
1
+ """
2
+ Repository implementation for project user access data operations.
3
+ """
4
+ from typing import List, Optional
5
+ import uuid
6
+
7
+ from sqlalchemy.orm import Session as DBSession
8
+
9
+ from .models import ProjectUserModel
10
+ from .entities.project_user import ProjectUser
11
+ from ..shared import now_epoch_ms
12
+
13
+
14
+ class ProjectUserRepository:
15
+ """SQLAlchemy implementation of project user repository."""
16
+
17
+ def __init__(self, db: DBSession):
18
+ self.db = db
19
+
20
+ def add_user_to_project(
21
+ self,
22
+ project_id: str,
23
+ user_id: str,
24
+ role: str,
25
+ added_by_user_id: str
26
+ ) -> ProjectUser:
27
+ """
28
+ Add a user to a project with a specific role.
29
+
30
+ Args:
31
+ project_id: The project ID
32
+ user_id: The user ID to add
33
+ role: The role to assign (owner, editor, viewer)
34
+ added_by_user_id: The user ID who is granting access
35
+
36
+ Returns:
37
+ ProjectUser: The created project user access record
38
+ """
39
+ model = ProjectUserModel(
40
+ id=str(uuid.uuid4()),
41
+ project_id=project_id,
42
+ user_id=user_id,
43
+ role=role,
44
+ added_at=now_epoch_ms(),
45
+ added_by_user_id=added_by_user_id,
46
+ )
47
+ self.db.add(model)
48
+ self.db.commit()
49
+ self.db.refresh(model)
50
+ return self._model_to_entity(model)
51
+
52
+ def get_project_users(self, project_id: str) -> List[ProjectUser]:
53
+ """
54
+ Get all users who have access to a project.
55
+
56
+ Args:
57
+ project_id: The project ID
58
+
59
+ Returns:
60
+ List[ProjectUser]: List of users with access to the project
61
+ """
62
+ models = self.db.query(ProjectUserModel).filter(
63
+ ProjectUserModel.project_id == project_id
64
+ ).all()
65
+ return [self._model_to_entity(model) for model in models]
66
+
67
+ def get_user_projects_access(self, user_id: str) -> List[ProjectUser]:
68
+ """
69
+ Get all projects a user has access to.
70
+
71
+ Args:
72
+ user_id: The user ID
73
+
74
+ Returns:
75
+ List[ProjectUser]: List of project access records for the user
76
+ """
77
+ models = self.db.query(ProjectUserModel).filter(
78
+ ProjectUserModel.user_id == user_id
79
+ ).all()
80
+ return [self._model_to_entity(model) for model in models]
81
+
82
+ def get_user_project_access(
83
+ self,
84
+ project_id: str,
85
+ user_id: str
86
+ ) -> Optional[ProjectUser]:
87
+ """
88
+ Get a specific user's access to a project.
89
+
90
+ Args:
91
+ project_id: The project ID
92
+ user_id: The user ID
93
+
94
+ Returns:
95
+ Optional[ProjectUser]: The access record if found, None otherwise
96
+ """
97
+ model = self.db.query(ProjectUserModel).filter(
98
+ ProjectUserModel.project_id == project_id,
99
+ ProjectUserModel.user_id == user_id
100
+ ).first()
101
+
102
+ return self._model_to_entity(model) if model else None
103
+
104
+ def update_user_role(
105
+ self,
106
+ project_id: str,
107
+ user_id: str,
108
+ new_role: str
109
+ ) -> Optional[ProjectUser]:
110
+ """
111
+ Update a user's role for a project.
112
+
113
+ Args:
114
+ project_id: The project ID
115
+ user_id: The user ID
116
+ new_role: The new role to assign
117
+
118
+ Returns:
119
+ Optional[ProjectUser]: The updated access record if found, None otherwise
120
+ """
121
+ model = self.db.query(ProjectUserModel).filter(
122
+ ProjectUserModel.project_id == project_id,
123
+ ProjectUserModel.user_id == user_id
124
+ ).first()
125
+
126
+ if not model:
127
+ return None
128
+
129
+ model.role = new_role
130
+ self.db.commit()
131
+ self.db.refresh(model)
132
+ return self._model_to_entity(model)
133
+
134
+ def remove_user_from_project(
135
+ self,
136
+ project_id: str,
137
+ user_id: str
138
+ ) -> bool:
139
+ """
140
+ Remove a user's access to a project.
141
+
142
+ Args:
143
+ project_id: The project ID
144
+ user_id: The user ID to remove
145
+
146
+ Returns:
147
+ bool: True if removed successfully, False otherwise
148
+ """
149
+ result = self.db.query(ProjectUserModel).filter(
150
+ ProjectUserModel.project_id == project_id,
151
+ ProjectUserModel.user_id == user_id
152
+ ).delete()
153
+ self.db.commit()
154
+ return result > 0
155
+
156
+ def user_has_access(
157
+ self,
158
+ project_id: str,
159
+ user_id: str
160
+ ) -> bool:
161
+ """
162
+ Check if a user has access to a project.
163
+
164
+ Args:
165
+ project_id: The project ID
166
+ user_id: The user ID
167
+
168
+ Returns:
169
+ bool: True if user has access, False otherwise
170
+ """
171
+ count = self.db.query(ProjectUserModel).filter(
172
+ ProjectUserModel.project_id == project_id,
173
+ ProjectUserModel.user_id == user_id
174
+ ).count()
175
+ return count > 0
176
+
177
+ def _model_to_entity(self, model: ProjectUserModel) -> ProjectUser:
178
+ """Convert SQLAlchemy model to domain entity."""
179
+ return ProjectUser(
180
+ id=model.id,
181
+ project_id=model.project_id,
182
+ user_id=model.user_id,
183
+ role=model.role,
184
+ added_at=model.added_at,
185
+ added_by_user_id=model.added_by_user_id,
186
+ )