solace-agent-mesh 1.3.1__py3-none-any.whl → 1.3.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 (118) hide show
  1. solace_agent_mesh/agent/protocol/event_handlers.py +91 -0
  2. solace_agent_mesh/agent/sac/app.py +2 -0
  3. solace_agent_mesh/assets/docs/404.html +3 -3
  4. solace_agent_mesh/assets/docs/assets/js/483cef9a.03d5dceb.js +1 -0
  5. solace_agent_mesh/assets/docs/assets/js/{main.1c79039d.js → main.4adc477a.js} +2 -2
  6. solace_agent_mesh/assets/docs/assets/js/{runtime~main.858117b7.js → runtime~main.cf0229ea.js} +1 -1
  7. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
  8. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +5 -5
  9. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  10. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
  11. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  12. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  13. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +3 -3
  14. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  15. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  16. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  17. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  18. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  19. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  20. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  21. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
  22. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  23. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  24. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
  25. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
  27. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
  28. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  44. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  45. solace_agent_mesh/assets/docs/lunr-index-1757704179464.json +1 -0
  46. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  47. solace_agent_mesh/assets/docs/search-doc-1757704179464.json +1 -0
  48. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  49. solace_agent_mesh/cli/__init__.py +1 -1
  50. solace_agent_mesh/client/webui/frontend/static/assets/{main-C1k9E0aC.js → main-DjoMeldu.js} +8 -8
  51. solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
  52. solace_agent_mesh/common/a2a/__init__.py +4 -0
  53. solace_agent_mesh/common/a2a/protocol.py +20 -0
  54. solace_agent_mesh/common/sac/sam_component_base.py +29 -9
  55. solace_agent_mesh/common/sam_events/__init__.py +9 -0
  56. solace_agent_mesh/common/sam_events/event_service.py +207 -0
  57. solace_agent_mesh/gateway/http_sse/alembic/env.py +1 -1
  58. solace_agent_mesh/gateway/http_sse/component.py +45 -35
  59. solace_agent_mesh/gateway/http_sse/dependencies.py +123 -60
  60. solace_agent_mesh/gateway/http_sse/main.py +20 -33
  61. solace_agent_mesh/gateway/http_sse/repository/__init__.py +37 -0
  62. solace_agent_mesh/gateway/http_sse/repository/entities/__init__.py +9 -0
  63. solace_agent_mesh/gateway/http_sse/repository/entities/message.py +41 -0
  64. solace_agent_mesh/gateway/http_sse/repository/entities/session.py +45 -0
  65. solace_agent_mesh/gateway/http_sse/repository/entities/session_history.py +16 -0
  66. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +64 -0
  67. solace_agent_mesh/gateway/http_sse/repository/message_repository.py +78 -0
  68. solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -0
  69. solace_agent_mesh/gateway/http_sse/repository/models/base.py +7 -0
  70. solace_agent_mesh/gateway/http_sse/repository/models/message_model.py +27 -0
  71. solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +27 -0
  72. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +139 -0
  73. solace_agent_mesh/gateway/http_sse/routers/config.py +1 -0
  74. solace_agent_mesh/gateway/http_sse/routers/dto/requests/__init__.py +20 -0
  75. solace_agent_mesh/gateway/http_sse/{api → routers}/dto/requests/session_requests.py +1 -8
  76. solace_agent_mesh/gateway/http_sse/routers/dto/responses/__init__.py +16 -0
  77. solace_agent_mesh/gateway/http_sse/{api → routers}/dto/responses/session_responses.py +3 -30
  78. solace_agent_mesh/gateway/http_sse/{api/controllers/session_controller.py → routers/sessions.py} +20 -77
  79. solace_agent_mesh/gateway/http_sse/routers/tasks.py +42 -49
  80. solace_agent_mesh/gateway/http_sse/{api/controllers/user_controller.py → routers/users.py} +1 -1
  81. solace_agent_mesh/gateway/http_sse/services/session_service.py +245 -0
  82. solace_agent_mesh/gateway/http_sse/session_manager.py +0 -3
  83. solace_agent_mesh/gateway/http_sse/shared/enums.py +0 -5
  84. {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/METADATA +1 -1
  85. {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/RECORD +90 -98
  86. solace_agent_mesh/assets/docs/assets/js/483cef9a.4e972867.js +0 -1
  87. solace_agent_mesh/assets/docs/lunr-index-1757531604543.json +0 -1
  88. solace_agent_mesh/assets/docs/search-doc-1757531604543.json +0 -1
  89. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +0 -676
  90. solace_agent_mesh/gateway/http_sse/api/__init__.py +0 -11
  91. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +0 -9
  92. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +0 -279
  93. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +0 -37
  94. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +0 -66
  95. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +0 -43
  96. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +0 -74
  97. solace_agent_mesh/gateway/http_sse/application/__init__.py +0 -3
  98. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +0 -3
  99. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +0 -135
  100. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +0 -3
  101. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +0 -90
  102. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +0 -3
  103. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +0 -54
  104. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +0 -4
  105. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +0 -3
  106. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +0 -123
  107. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +0 -4
  108. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +0 -16
  109. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +0 -119
  110. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +0 -31
  111. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +0 -12
  112. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +0 -3
  113. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +0 -174
  114. /solace_agent_mesh/assets/docs/assets/js/{main.1c79039d.js.LICENSE.txt → main.4adc477a.js.LICENSE.txt} +0 -0
  115. /solace_agent_mesh/gateway/http_sse/{api → routers}/dto/__init__.py +0 -0
  116. {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/WHEEL +0 -0
  117. {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/entry_points.txt +0 -0
  118. {solace_agent_mesh-1.3.1.dist-info → solace_agent_mesh-1.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/assets/favicon-BLgzUch9.ico" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Solace Agent Mesh</title>
8
- <script type="module" crossorigin src="/assets/main-C1k9E0aC.js"></script>
8
+ <script type="module" crossorigin src="/assets/main-DjoMeldu.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vendor-B0BEKoAR.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/client-DXU9SPI5.js">
11
11
  <link rel="stylesheet" crossorigin href="/assets/main-C03yrETa.css">
@@ -81,6 +81,8 @@ from .protocol import (
81
81
  get_message_from_send_request,
82
82
  get_peer_agent_status_topic,
83
83
  get_request_id,
84
+ get_sam_events_topic,
85
+ get_sam_events_subscription_topic,
84
86
  get_request_method,
85
87
  get_response_error,
86
88
  get_error_code,
@@ -184,6 +186,8 @@ __all__ = [
184
186
  "get_message_from_send_request",
185
187
  "get_peer_agent_status_topic",
186
188
  "get_request_id",
189
+ "get_sam_events_topic",
190
+ "get_sam_events_subscription_topic",
187
191
  "get_request_method",
188
192
  "get_response_error",
189
193
  "get_error_code",
@@ -176,6 +176,26 @@ def get_client_status_subscription_topic(namespace: str, client_id: str) -> str:
176
176
  return f"{get_a2a_base_topic(namespace)}/client/status/{client_id}/>"
177
177
 
178
178
 
179
+ def get_sam_events_topic(namespace: str, category: str, action: str) -> str:
180
+ """Returns SAM system events topic."""
181
+ if not namespace:
182
+ raise ValueError("Namespace cannot be empty.")
183
+ if not category:
184
+ raise ValueError("Category cannot be empty.")
185
+ if not action:
186
+ raise ValueError("Action cannot be empty.")
187
+ return f"{namespace.rstrip('/')}/sam/events/{category}/{action}"
188
+
189
+
190
+ def get_sam_events_subscription_topic(namespace: str, category: str) -> str:
191
+ """Returns SAM system events subscription topic."""
192
+ if not namespace:
193
+ raise ValueError("Namespace cannot be empty.")
194
+ if not category:
195
+ raise ValueError("Category cannot be empty.")
196
+ return f"{namespace.rstrip('/')}/sam/events/{category}/>"
197
+
198
+
179
199
  def subscription_to_regex(subscription: str) -> str:
180
200
  """Converts a Solace topic subscription string to a regex pattern."""
181
201
  # Escape regex special characters except for Solace wildcards
@@ -2,16 +2,16 @@
2
2
  Base Component class for SAM implementations in the Solace AI Connector.
3
3
  """
4
4
 
5
+ import abc
5
6
  import asyncio
6
7
  import threading
7
- import abc
8
- from typing import Any, Dict, Optional
8
+ from typing import Any
9
9
 
10
- from solace_ai_connector.components.component_base import ComponentBase
11
10
  from solace_ai_connector.common.log import log
11
+ from solace_ai_connector.components.component_base import ComponentBase
12
12
 
13
- from ..utils.message_utils import validate_message_size
14
13
  from ..exceptions import MessageSizeExceededError
14
+ from ..utils.message_utils import validate_message_size
15
15
 
16
16
 
17
17
  class SamComponentBase(ComponentBase, abc.ABC):
@@ -23,7 +23,7 @@ class SamComponentBase(ComponentBase, abc.ABC):
23
23
  - Publishing A2A messages with built-in size validation.
24
24
  """
25
25
 
26
- def __init__(self, info: Dict[str, Any], **kwargs: Any):
26
+ def __init__(self, info: dict[str, Any], **kwargs: Any):
27
27
  super().__init__(info, **kwargs)
28
28
  log.info("%s Initializing SamComponentBase...", self.log_identifier)
29
29
 
@@ -51,15 +51,19 @@ class SamComponentBase(ComponentBase, abc.ABC):
51
51
  )
52
52
  raise ValueError(f"Configuration retrieval error: {e}") from e
53
53
 
54
- self._async_loop: Optional[asyncio.AbstractEventLoop] = None
55
- self._async_thread: Optional[threading.Thread] = None
54
+ self._async_loop: asyncio.AbstractEventLoop | None = None
55
+ self._async_thread: threading.Thread | None = None
56
56
  log.info("%s SamComponentBase initialized successfully.", self.log_identifier)
57
57
 
58
58
  def publish_a2a_message(
59
- self, payload: Dict, topic: str, user_properties: Optional[Dict] = None
59
+ self, payload: dict, topic: str, user_properties: dict | None = None
60
60
  ):
61
61
  """Helper to publish A2A messages via the SAC App with size validation."""
62
62
  try:
63
+ log.debug(
64
+ f"{self.log_identifier} [publish_a2a_message] Starting - topic: {topic}, payload keys: {list(payload.keys()) if isinstance(payload, dict) else 'not_dict'}"
65
+ )
66
+
63
67
  # Validate message size
64
68
  is_valid, actual_size = validate_message_size(
65
69
  payload, self.max_message_size_bytes, self.log_identifier
@@ -85,6 +89,10 @@ class SamComponentBase(ComponentBase, abc.ABC):
85
89
 
86
90
  app = self.get_app()
87
91
  if app:
92
+ log.debug(
93
+ f"{self.log_identifier} [publish_a2a_message] Got app instance, about to call app.send_message"
94
+ )
95
+
88
96
  # Conditionally log to invocation monitor if it exists (i.e., on an agent)
89
97
  if hasattr(self, "invocation_monitor") and self.invocation_monitor:
90
98
  self.invocation_monitor.log_message_event(
@@ -93,9 +101,21 @@ class SamComponentBase(ComponentBase, abc.ABC):
93
101
  payload=payload,
94
102
  component_identifier=self.log_identifier,
95
103
  )
104
+
105
+ log.debug(
106
+ f"{self.log_identifier} [publish_a2a_message] About to call app.send_message with payload: {payload}"
107
+ )
108
+ log.debug(
109
+ f"{self.log_identifier} [publish_a2a_message] App send_message params - topic: {topic}, user_properties: {user_properties}"
110
+ )
111
+
96
112
  app.send_message(
97
113
  payload=payload, topic=topic, user_properties=user_properties
98
114
  )
115
+
116
+ log.debug(
117
+ f"{self.log_identifier} [publish_a2a_message] Successfully called app.send_message"
118
+ )
99
119
  else:
100
120
  log.error(
101
121
  "%s Cannot publish message: Not running within a SAC App context.",
@@ -231,7 +251,7 @@ class SamComponentBase(ComponentBase, abc.ABC):
231
251
  super().cleanup()
232
252
  log.info("%s SamComponentBase cleanup finished.", self.log_identifier)
233
253
 
234
- def get_async_loop(self) -> Optional[asyncio.AbstractEventLoop]:
254
+ def get_async_loop(self) -> asyncio.AbstractEventLoop | None:
235
255
  """Returns the dedicated asyncio event loop for this component's async tasks."""
236
256
  return self._async_loop
237
257
 
@@ -0,0 +1,9 @@
1
+ """
2
+ SAM Events - System-level event messaging for Solace Agent Mesh.
3
+
4
+ Provides clean separation between A2A task communication and system events.
5
+ """
6
+
7
+ from .event_service import SamEventService, SamEvent, SessionDeletedEvent
8
+
9
+ __all__ = ["SamEventService", "SamEvent", "SessionDeletedEvent"]
@@ -0,0 +1,207 @@
1
+ """
2
+ SAM Events Service - Clean system-level event messaging.
3
+
4
+ Provides event publishing and subscription for system events like session lifecycle,
5
+ agent health, configuration changes, etc.
6
+ """
7
+
8
+ import uuid
9
+ from datetime import datetime, timezone
10
+ from dataclasses import dataclass, asdict
11
+ from typing import Any, Dict, Optional, Callable, List
12
+
13
+ from solace_ai_connector.common.log import log
14
+ from ..a2a.protocol import get_sam_events_topic
15
+
16
+
17
+ @dataclass
18
+ class SamEvent:
19
+ """Base class for all SAM system events."""
20
+ event_type: str
21
+ event_id: str
22
+ timestamp: str
23
+ source_component: str
24
+ namespace: str
25
+ data: Dict[str, Any]
26
+
27
+ @classmethod
28
+ def create(cls, event_type: str, source_component: str, namespace: str, data: Dict[str, Any]) -> "SamEvent":
29
+ """Create a new SAM event with auto-generated ID and timestamp."""
30
+ return cls(
31
+ event_type=event_type,
32
+ event_id=uuid.uuid4().hex,
33
+ timestamp=datetime.now(timezone.utc).isoformat(),
34
+ source_component=source_component,
35
+ namespace=namespace,
36
+ data=data
37
+ )
38
+
39
+ def to_dict(self) -> Dict[str, Any]:
40
+ """Convert event to dictionary for messaging."""
41
+ return asdict(self)
42
+
43
+
44
+ @dataclass
45
+ class SessionDeletedEvent(SamEvent):
46
+ """System event for session deletion."""
47
+
48
+ @classmethod
49
+ def create(cls, namespace: str, source_component: str, session_id: str,
50
+ user_id: str, agent_id: str, gateway_id: str) -> "SessionDeletedEvent":
51
+ """Create a session deleted event."""
52
+ data = {
53
+ "session_id": session_id,
54
+ "user_id": user_id,
55
+ "agent_id": agent_id,
56
+ "gateway_id": gateway_id
57
+ }
58
+ return super().create("session.deleted", source_component, namespace, data)
59
+
60
+
61
+ class SamEventService:
62
+ """Service for publishing and subscribing to SAM system events."""
63
+
64
+ def __init__(self, namespace: str, component_name: str, publish_func: Callable[[str, Dict, Optional[Dict]], None]):
65
+ """
66
+ Initialize the SAM event service.
67
+
68
+ Args:
69
+ namespace: The SAM namespace
70
+ component_name: Name of the component using this service
71
+ publish_func: Function to publish messages (from WebUIBackendComponent.publish_a2a)
72
+ """
73
+ self.namespace = namespace
74
+ self.component_name = component_name
75
+ self.publish_func = publish_func
76
+ self._subscribers: Dict[str, List[Callable]] = {}
77
+
78
+ log.info(f"[SamEventService] Initialized for component {component_name} in namespace {namespace}")
79
+
80
+ def publish_event(self, event: SamEvent) -> bool:
81
+ """
82
+ Publish a SAM system event.
83
+
84
+ Args:
85
+ event: The system event to publish
86
+
87
+ Returns:
88
+ bool: True if published successfully, False otherwise
89
+ """
90
+ try:
91
+ # Extract category and action from event_type (e.g., "session.deleted" -> "session", "deleted")
92
+ parts = event.event_type.split(".", 1)
93
+ if len(parts) != 2:
94
+ raise ValueError(f"Invalid event_type format: {event.event_type}. Expected 'category.action'")
95
+
96
+ category, action = parts
97
+ topic = get_sam_events_topic(self.namespace, category, action)
98
+ payload = event.to_dict()
99
+
100
+ log.info(f"[SamEventService] Publishing {event.event_type} event (ID: {event.event_id}) to topic {topic}")
101
+
102
+ # Use the component's publish function (which goes through proper A2A infrastructure)
103
+ self.publish_func(topic, payload, {"eventType": event.event_type, "eventId": event.event_id})
104
+
105
+ log.info(f"[SamEventService] Successfully published event {event.event_id}")
106
+ return True
107
+
108
+ except Exception as e:
109
+ log.error(f"[SamEventService] Failed to publish event {event.event_id}: {e}")
110
+ return False
111
+
112
+ def publish_session_deleted(self, session_id: str, user_id: str, agent_id: str, gateway_id: str) -> bool:
113
+ """
114
+ Convenience method to publish session deleted event.
115
+
116
+ Args:
117
+ session_id: The deleted session ID
118
+ user_id: The user who owned the session
119
+ agent_id: The agent that was handling the session
120
+ gateway_id: The gateway that deleted the session
121
+
122
+ Returns:
123
+ bool: True if published successfully
124
+ """
125
+ event = SessionDeletedEvent.create(
126
+ namespace=self.namespace,
127
+ source_component=self.component_name,
128
+ session_id=session_id,
129
+ user_id=user_id,
130
+ agent_id=agent_id,
131
+ gateway_id=gateway_id
132
+ )
133
+ return self.publish_event(event)
134
+
135
+ def subscribe_to_events(self, event_type: str, handler: Callable[[SamEvent], None]) -> bool:
136
+ """
137
+ Subscribe to system events of a specific type.
138
+
139
+ Args:
140
+ event_type: The type of events to subscribe to (e.g., "session.deleted")
141
+ handler: Function to call when event is received
142
+
143
+ Returns:
144
+ bool: True if subscription was successful
145
+ """
146
+ try:
147
+ if event_type not in self._subscribers:
148
+ self._subscribers[event_type] = []
149
+
150
+ self._subscribers[event_type].append(handler)
151
+
152
+ log.info(f"[SamEventService] Subscribed to {event_type} events")
153
+ return True
154
+
155
+ except Exception as e:
156
+ log.error(f"[SamEventService] Failed to subscribe to {event_type} events: {e}")
157
+ return False
158
+
159
+ def handle_incoming_event(self, topic: str, payload: Dict[str, Any]) -> None:
160
+ """
161
+ Handle incoming SAM event from messaging system.
162
+
163
+ Args:
164
+ topic: The topic the event was received on
165
+ payload: Event payload
166
+ """
167
+ try:
168
+ event_type = payload.get("event_type")
169
+
170
+ if not event_type:
171
+ log.warning(f"[SamEventService] Received event without event_type on topic {topic}")
172
+ return
173
+
174
+ # Create event object
175
+ event = SamEvent(**payload)
176
+
177
+ # Call registered handlers
178
+ handlers = self._subscribers.get(event_type, [])
179
+ for handler in handlers:
180
+ try:
181
+ handler(event)
182
+ except Exception as e:
183
+ log.error(f"[SamEventService] Error in event handler for {event_type}: {e}")
184
+
185
+ log.debug(f"[SamEventService] Processed event {event.event_id} with {len(handlers)} handlers")
186
+
187
+ except Exception as e:
188
+ log.error(f"[SamEventService] Error handling event from topic {topic}: {e}")
189
+
190
+ @staticmethod
191
+ def get_event_topic(namespace: str, event_type: str) -> str:
192
+ """
193
+ Get the topic for a specific event type.
194
+
195
+ Args:
196
+ namespace: The SAM namespace
197
+ event_type: Event type in format "category.action"
198
+
199
+ Returns:
200
+ str: The topic for the event
201
+ """
202
+ parts = event_type.split(".", 1)
203
+ if len(parts) != 2:
204
+ raise ValueError(f"Invalid event_type format: {event_type}. Expected 'category.action'")
205
+
206
+ category, action = parts
207
+ return get_sam_events_topic(namespace, category, action)
@@ -14,7 +14,7 @@ if config.config_file_name is not None:
14
14
 
15
15
  # add your model's MetaData object here
16
16
  # for 'autogenerate' support
17
- from solace_agent_mesh.gateway.http_sse.infrastructure.persistence.models import Base
17
+ from solace_agent_mesh.gateway.http_sse.repository import Base
18
18
 
19
19
  target_metadata = Base.metadata
20
20
 
@@ -24,7 +24,6 @@ from ...gateway.base.component import BaseGatewayComponent
24
24
  from ...gateway.http_sse.session_manager import SessionManager
25
25
  from ...gateway.http_sse.sse_manager import SSEManager
26
26
  from .components import VisualizationForwarderComponent
27
- from .infrastructure.persistence_service import PersistenceService
28
27
 
29
28
  try:
30
29
  from google.adk.artifacts import BaseArtifactService
@@ -131,10 +130,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
131
130
  f"{self.log_identifier} Session service type is 'sql' but no database_url provided. "
132
131
  "Please provide a database_url in the session_service configuration or use type 'memory'."
133
132
  )
134
- self.persistence_service = PersistenceService(database_url)
133
+ self.database_url = database_url
135
134
  else:
136
135
  # Memory storage or no explicit configuration - no persistence service needed
137
- self.persistence_service = None
136
+ self.database_url = None
138
137
 
139
138
  component_config = self.get_config("component_config", {})
140
139
  app_config = component_config.get("app_config", {})
@@ -142,7 +141,6 @@ class WebUIBackendComponent(BaseGatewayComponent):
142
141
  self.session_manager = SessionManager(
143
142
  secret_key=self.session_secret_key,
144
143
  app_config=app_config,
145
- persistence_service=self.persistence_service,
146
144
  )
147
145
 
148
146
  self.fastapi_app: FastAPI | None = None
@@ -158,6 +156,14 @@ class WebUIBackendComponent(BaseGatewayComponent):
158
156
  self._visualization_locks_lock = threading.Lock()
159
157
  self._global_visualization_subscriptions: dict[str, int] = {}
160
158
  self._visualization_processor_task: asyncio.Task | None = None
159
+
160
+ # Initialize SAM Events service for system events
161
+ from ...common.sam_events import SamEventService
162
+ self.sam_events = SamEventService(
163
+ namespace=self.namespace,
164
+ component_name=f"{self.name}_gateway",
165
+ publish_func=self.publish_a2a
166
+ )
161
167
 
162
168
  log.info("%s Web UI Backend Component initialized.", self.log_identifier)
163
169
 
@@ -881,7 +887,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
881
887
 
882
888
  self.fastapi_app = fastapi_app_instance
883
889
 
884
- setup_dependencies(self, self.persistence_service)
890
+ setup_dependencies(self, self.database_url)
885
891
 
886
892
  port = (
887
893
  self.fastapi_https_port
@@ -991,7 +997,16 @@ class WebUIBackendComponent(BaseGatewayComponent):
991
997
  This method can be called from FastAPI handlers (via dependency injection).
992
998
  It's thread-safe as it uses the SAC App instance.
993
999
  """
994
- super().publish_a2a_message(topic, payload, user_properties)
1000
+ log.debug(f"[publish_a2a] Starting to publish message to topic: {topic}")
1001
+ log.debug(f"[publish_a2a] Payload type: {type(payload)}, size: {len(str(payload))} chars")
1002
+ log.debug(f"[publish_a2a] User properties: {user_properties}")
1003
+
1004
+ try:
1005
+ super().publish_a2a_message(payload, topic, user_properties)
1006
+ log.debug(f"[publish_a2a] Successfully called super().publish_a2a_message for topic: {topic}")
1007
+ except Exception as e:
1008
+ log.error(f"[publish_a2a] Exception in publish_a2a: {e}", exc_info=True)
1009
+ raise
995
1010
 
996
1011
  def _cleanup_visualization_locks(self):
997
1012
  """Remove locks for closed event loops to prevent memory leaks."""
@@ -1063,6 +1078,16 @@ class WebUIBackendComponent(BaseGatewayComponent):
1063
1078
 
1064
1079
  # --- Phase 1: Parse the payload to extract core info ---
1065
1080
  try:
1081
+ # Handle SAM Events (system events)
1082
+ event_type = payload.get("event_type")
1083
+ if event_type:
1084
+ details["direction"] = "system_event"
1085
+ details["debug_type"] = "sam_event"
1086
+ details["payload_summary"]["method"] = event_type
1087
+ details["source_entity"] = payload.get("source_component", "unknown")
1088
+ details["target_entity"] = "system"
1089
+ return details
1090
+
1066
1091
  # Try to parse as a JSON-RPC response first
1067
1092
  if "result" in payload or "error" in payload:
1068
1093
  rpc_response = JSONRPCResponse.model_validate(payload)
@@ -1613,15 +1638,12 @@ class WebUIBackendComponent(BaseGatewayComponent):
1613
1638
  )
1614
1639
 
1615
1640
  # Store final agent response in persistence layer if available
1616
- if hasattr(self, "persistence_service") and self.persistence_service:
1641
+ if hasattr(self, "database_url") and self.database_url:
1617
1642
  try:
1618
1643
  session_id = external_request_context.get("a2a_session_id")
1619
1644
  user_id = external_request_context.get("user_id_for_a2a")
1620
- agent_name = external_request_context.get(
1621
- "target_agent_name", "agent"
1622
- )
1645
+ agent_name = external_request_context.get("target_agent_name", "agent")
1623
1646
 
1624
- # Extract message content from the task status
1625
1647
  message_text = ""
1626
1648
  if task_data.status and task_data.status.message:
1627
1649
  parts = a2a.get_parts_from_message(task_data.status.message)
@@ -1631,30 +1653,19 @@ class WebUIBackendComponent(BaseGatewayComponent):
1631
1653
  message_text += "\n"
1632
1654
  message_text += part.text
1633
1655
 
1634
- log.info(
1635
- "%s Final agent response storage debug - session_id: %s, user_id: %s, message_text: '%s', parts_count: %s",
1636
- log_id_prefix,
1637
- session_id,
1638
- user_id,
1639
- message_text[:100] if message_text else None,
1640
- len(a2a.get_parts_from_message(task_data.status.message))
1641
- if task_data.status and task_data.status.message
1642
- else 0,
1643
- )
1644
-
1645
1656
  if message_text and session_id and user_id:
1646
- from .dependencies import get_session_service
1647
- from .shared.enums import SenderType
1648
-
1649
- session_service = get_session_service(self)
1650
- session_service.add_message_to_session(
1651
- session_id=session_id,
1652
- user_id=user_id,
1653
- message=message_text,
1654
- sender_type=SenderType.AGENT,
1655
- sender_name=agent_name,
1656
- agent_id=agent_name,
1657
- )
1657
+ from .dependencies import create_session_service_with_transaction
1658
+ from ...gateway.http_sse.shared.enums import SenderType
1659
+
1660
+ with create_session_service_with_transaction() as (session_service, db):
1661
+ session_service.add_message_to_session(
1662
+ session_id=session_id,
1663
+ user_id=user_id,
1664
+ message=message_text,
1665
+ sender_type=SenderType.AGENT,
1666
+ sender_name=agent_name,
1667
+ agent_id=agent_name,
1668
+ )
1658
1669
  log.info(
1659
1670
  "%s Final agent response stored in session %s",
1660
1671
  log_id_prefix,
@@ -1666,7 +1677,6 @@ class WebUIBackendComponent(BaseGatewayComponent):
1666
1677
  log_id_prefix,
1667
1678
  storage_error,
1668
1679
  )
1669
- # Don't fail the SSE send if storage fails
1670
1680
 
1671
1681
  except Exception as e:
1672
1682
  log.exception(