solace-agent-mesh 1.6.0__py3-none-any.whl → 1.6.1__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.
- solace_agent_mesh/agent/protocol/event_handlers.py +173 -30
- solace_agent_mesh/agent/proxies/base/component.py +35 -4
- solace_agent_mesh/agent/sac/app.py +54 -7
- solace_agent_mesh/agent/sac/component.py +84 -73
- solace_agent_mesh/agent/sac/task_execution_context.py +46 -0
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{e3d9abda.2b916f9e.js → e3d9abda.6b9493d0.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/{main.20feee82.js → main.b12eac43.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/{runtime~main.0d198646.js → runtime~main.e268214e.js} +1 -1
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1761248203150.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +342 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
- solace_agent_mesh/common/a2a/protocol.py +78 -0
- solace_agent_mesh/common/sac/sam_component_base.py +383 -4
- solace_agent_mesh/gateway/base/app.py +15 -0
- solace_agent_mesh/gateway/base/component.py +104 -38
- solace_agent_mesh/gateway/http_sse/component.py +1 -1
- solace_agent_mesh/gateway/http_sse/main.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/users.py +47 -1
- {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/METADATA +1 -1
- {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/RECORD +76 -76
- solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1761165361160.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +0 -342
- /solace_agent_mesh/assets/docs/assets/js/{main.20feee82.js.LICENSE.txt → main.b12eac43.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.6.0.dist-info → solace_agent_mesh-1.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -49,6 +49,7 @@ from google.adk.agents.run_config import StreamingMode
|
|
|
49
49
|
|
|
50
50
|
log = logging.getLogger(__name__)
|
|
51
51
|
|
|
52
|
+
|
|
52
53
|
def _register_peer_artifacts_in_parent_context(
|
|
53
54
|
parent_task_context: "TaskExecutionContext",
|
|
54
55
|
peer_task_object: Task,
|
|
@@ -123,6 +124,30 @@ async def process_event(component, event: Event):
|
|
|
123
124
|
agent_status_sub_prefix
|
|
124
125
|
):
|
|
125
126
|
await handle_a2a_response(component, message)
|
|
127
|
+
elif hasattr(component, "trust_manager") and component.trust_manager:
|
|
128
|
+
# Check if this is a trust card message (enterprise feature)
|
|
129
|
+
try:
|
|
130
|
+
if component.trust_manager.is_trust_card_topic(topic):
|
|
131
|
+
await component.trust_manager.handle_trust_card_message(
|
|
132
|
+
message, topic
|
|
133
|
+
)
|
|
134
|
+
message.call_acknowledgements()
|
|
135
|
+
return
|
|
136
|
+
except Exception as e:
|
|
137
|
+
log.error(
|
|
138
|
+
"%s Error handling trust card message: %s",
|
|
139
|
+
component.log_identifier,
|
|
140
|
+
e,
|
|
141
|
+
)
|
|
142
|
+
message.call_acknowledgements()
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
log.warning(
|
|
146
|
+
"%s Received message on unhandled topic: %s",
|
|
147
|
+
component.log_identifier,
|
|
148
|
+
topic,
|
|
149
|
+
)
|
|
150
|
+
message.call_acknowledgements()
|
|
126
151
|
else:
|
|
127
152
|
log.warning(
|
|
128
153
|
"%s Received message on unhandled topic: %s",
|
|
@@ -229,12 +254,11 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
229
254
|
payload_dict = message.get_payload()
|
|
230
255
|
if not isinstance(payload_dict, dict):
|
|
231
256
|
raise ValueError("Payload is not a dictionary.")
|
|
232
|
-
|
|
233
|
-
|
|
257
|
+
|
|
234
258
|
a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
|
|
235
259
|
jsonrpc_request_id = a2a.get_request_id(a2a_request)
|
|
236
260
|
|
|
237
|
-
# Extract properties from message user properties
|
|
261
|
+
# Extract properties from message user properties
|
|
238
262
|
client_id = message.get_user_properties().get("clientId", "default_client")
|
|
239
263
|
status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
|
|
240
264
|
reply_topic_from_peer = message.get_user_properties().get("replyTo")
|
|
@@ -248,6 +272,90 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
248
272
|
# For Send, we will generate it.
|
|
249
273
|
logical_task_id = None
|
|
250
274
|
method = a2a.get_request_method(a2a_request)
|
|
275
|
+
|
|
276
|
+
# Enterprise feature: Verify user authentication if trust manager enabled
|
|
277
|
+
verified_user_identity = None
|
|
278
|
+
if hasattr(component, "trust_manager") and component.trust_manager:
|
|
279
|
+
# Determine task_id for verification
|
|
280
|
+
if method == "tasks/cancel":
|
|
281
|
+
verification_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
|
|
282
|
+
elif method in ["message/send", "message/stream"]:
|
|
283
|
+
verification_task_id = str(a2a.get_request_id(a2a_request))
|
|
284
|
+
else:
|
|
285
|
+
verification_task_id = None
|
|
286
|
+
|
|
287
|
+
if verification_task_id:
|
|
288
|
+
try:
|
|
289
|
+
# Enterprise handles all verification logic
|
|
290
|
+
verified_user_identity = (
|
|
291
|
+
component.trust_manager.verify_request_authentication(
|
|
292
|
+
message=message,
|
|
293
|
+
task_id=verification_task_id,
|
|
294
|
+
namespace=namespace,
|
|
295
|
+
jsonrpc_request_id=jsonrpc_request_id,
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
if verified_user_identity:
|
|
300
|
+
log.info(
|
|
301
|
+
"%s Successfully authenticated user '%s' for task %s",
|
|
302
|
+
component.log_identifier,
|
|
303
|
+
verified_user_identity.get("user_id"),
|
|
304
|
+
verification_task_id,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
# Authentication failed - enterprise provides error details
|
|
309
|
+
log.error(
|
|
310
|
+
"%s Authentication failed for task %s: %s",
|
|
311
|
+
component.log_identifier,
|
|
312
|
+
verification_task_id,
|
|
313
|
+
e,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Build error response using enterprise exception data if available
|
|
317
|
+
error_data = {
|
|
318
|
+
"reason": "authentication_failed",
|
|
319
|
+
"task_id": verification_task_id,
|
|
320
|
+
}
|
|
321
|
+
if hasattr(e, "create_error_response_data"):
|
|
322
|
+
error_data = e.create_error_response_data()
|
|
323
|
+
|
|
324
|
+
error_response = a2a.create_invalid_request_error_response(
|
|
325
|
+
message="Authentication failed",
|
|
326
|
+
request_id=jsonrpc_request_id,
|
|
327
|
+
data=error_data,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Determine reply topic
|
|
331
|
+
reply_topic = message.get_user_properties().get("replyTo")
|
|
332
|
+
if not reply_topic:
|
|
333
|
+
client_id = message.get_user_properties().get(
|
|
334
|
+
"clientId", "default_client"
|
|
335
|
+
)
|
|
336
|
+
reply_topic = a2a.get_client_response_topic(
|
|
337
|
+
namespace, client_id
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
component.publish_a2a_message(
|
|
341
|
+
payload=error_response.model_dump(exclude_none=True),
|
|
342
|
+
topic=reply_topic,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
message.call_acknowledgements()
|
|
347
|
+
log.debug(
|
|
348
|
+
"%s ACKed message with failed authentication",
|
|
349
|
+
component.log_identifier,
|
|
350
|
+
)
|
|
351
|
+
except Exception as ack_e:
|
|
352
|
+
log.error(
|
|
353
|
+
"%s Failed to ACK message after authentication failure: %s",
|
|
354
|
+
component.log_identifier,
|
|
355
|
+
ack_e,
|
|
356
|
+
)
|
|
357
|
+
return None
|
|
358
|
+
|
|
251
359
|
if method == "tasks/cancel":
|
|
252
360
|
logical_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
|
|
253
361
|
log.info(
|
|
@@ -537,6 +645,15 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
537
645
|
"response_format": response_format,
|
|
538
646
|
"host_agent_name": agent_name,
|
|
539
647
|
}
|
|
648
|
+
|
|
649
|
+
# Store verified user identity claims in a2a_context (not the raw token)
|
|
650
|
+
if verified_user_identity:
|
|
651
|
+
a2a_context["verified_user_identity"] = verified_user_identity
|
|
652
|
+
log.debug(
|
|
653
|
+
"%s Stored verified user identity in a2a_context for task %s",
|
|
654
|
+
component.log_identifier,
|
|
655
|
+
logical_task_id,
|
|
656
|
+
)
|
|
540
657
|
log.debug(
|
|
541
658
|
"%s A2A Context (shared service model): %s",
|
|
542
659
|
component.log_identifier,
|
|
@@ -547,6 +664,18 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
547
664
|
task_context = TaskExecutionContext(
|
|
548
665
|
task_id=logical_task_id, a2a_context=a2a_context
|
|
549
666
|
)
|
|
667
|
+
|
|
668
|
+
# Store auth token for peer delegation using generic security storage
|
|
669
|
+
if hasattr(component, "trust_manager") and component.trust_manager:
|
|
670
|
+
auth_token = message.get_user_properties().get("authToken")
|
|
671
|
+
if auth_token:
|
|
672
|
+
task_context.set_security_data("auth_token", auth_token)
|
|
673
|
+
log.debug(
|
|
674
|
+
"%s Stored authentication token in TaskExecutionContext security storage for task %s",
|
|
675
|
+
component.log_identifier,
|
|
676
|
+
logical_task_id,
|
|
677
|
+
)
|
|
678
|
+
|
|
550
679
|
with component.active_tasks_lock:
|
|
551
680
|
component.active_tasks[logical_task_id] = task_context
|
|
552
681
|
log.info(
|
|
@@ -1581,81 +1710,95 @@ def handle_sam_event(component, message, topic):
|
|
|
1581
1710
|
"""Handle incoming SAM system events."""
|
|
1582
1711
|
try:
|
|
1583
1712
|
payload = message.get_payload()
|
|
1584
|
-
|
|
1713
|
+
|
|
1585
1714
|
if not isinstance(payload, dict):
|
|
1586
1715
|
log.warning("Invalid SAM event payload - not a dict")
|
|
1587
1716
|
message.call_acknowledgements()
|
|
1588
1717
|
return
|
|
1589
|
-
|
|
1718
|
+
|
|
1590
1719
|
event_type = payload.get("event_type")
|
|
1591
1720
|
if not event_type:
|
|
1592
1721
|
log.warning("SAM event missing event_type field")
|
|
1593
1722
|
message.call_acknowledgements()
|
|
1594
1723
|
return
|
|
1595
|
-
|
|
1724
|
+
|
|
1596
1725
|
log.info("%s Received SAM event: %s", component.log_identifier, event_type)
|
|
1597
|
-
|
|
1726
|
+
|
|
1598
1727
|
if event_type == "session.deleted":
|
|
1599
1728
|
data = payload.get("data", {})
|
|
1600
1729
|
session_id = data.get("session_id")
|
|
1601
1730
|
user_id = data.get("user_id")
|
|
1602
1731
|
agent_id = data.get("agent_id")
|
|
1603
|
-
|
|
1732
|
+
|
|
1604
1733
|
if not all([session_id, user_id, agent_id]):
|
|
1605
1734
|
log.warning("Missing required fields in session.deleted event")
|
|
1606
1735
|
message.call_acknowledgements()
|
|
1607
1736
|
return
|
|
1608
|
-
|
|
1737
|
+
|
|
1609
1738
|
current_agent = component.get_config("agent_name")
|
|
1610
|
-
|
|
1739
|
+
|
|
1611
1740
|
if agent_id == current_agent:
|
|
1612
|
-
log.info(
|
|
1613
|
-
|
|
1614
|
-
|
|
1741
|
+
log.info(
|
|
1742
|
+
"%s Processing session.deleted event for session %s",
|
|
1743
|
+
component.log_identifier,
|
|
1744
|
+
session_id,
|
|
1745
|
+
)
|
|
1746
|
+
asyncio.create_task(
|
|
1747
|
+
cleanup_agent_session(component, session_id, user_id)
|
|
1748
|
+
)
|
|
1615
1749
|
else:
|
|
1616
|
-
log.debug(
|
|
1750
|
+
log.debug(
|
|
1751
|
+
"Session deletion event for different agent: %s != %s",
|
|
1752
|
+
agent_id,
|
|
1753
|
+
current_agent,
|
|
1754
|
+
)
|
|
1617
1755
|
else:
|
|
1618
1756
|
log.debug("Unhandled SAM event type: %s", event_type)
|
|
1619
|
-
|
|
1757
|
+
|
|
1620
1758
|
message.call_acknowledgements()
|
|
1621
|
-
|
|
1759
|
+
|
|
1622
1760
|
except Exception as e:
|
|
1623
1761
|
log.error("Error handling SAM event %s: %s", topic, e)
|
|
1624
1762
|
message.call_acknowledgements()
|
|
1625
1763
|
|
|
1626
1764
|
|
|
1627
|
-
|
|
1628
1765
|
async def cleanup_agent_session(component, session_id: str, user_id: str):
|
|
1629
1766
|
"""Clean up agent-side session data."""
|
|
1630
1767
|
try:
|
|
1631
1768
|
log.info("Starting cleanup for session %s, user %s", session_id, user_id)
|
|
1632
|
-
|
|
1633
|
-
if hasattr(component,
|
|
1769
|
+
|
|
1770
|
+
if hasattr(component, "session_service") and component.session_service:
|
|
1634
1771
|
agent_name = component.get_config("agent_name")
|
|
1635
|
-
log.info(
|
|
1772
|
+
log.info(
|
|
1773
|
+
"Deleting session %s from agent %s session service",
|
|
1774
|
+
session_id,
|
|
1775
|
+
agent_name,
|
|
1776
|
+
)
|
|
1636
1777
|
await component.session_service.delete_session(
|
|
1637
|
-
app_name=agent_name,
|
|
1638
|
-
user_id=user_id,
|
|
1639
|
-
session_id=session_id
|
|
1778
|
+
app_name=agent_name, user_id=user_id, session_id=session_id
|
|
1640
1779
|
)
|
|
1641
1780
|
log.info("Successfully deleted session %s from session service", session_id)
|
|
1642
1781
|
else:
|
|
1643
1782
|
log.info("No session service available for cleanup")
|
|
1644
|
-
|
|
1783
|
+
|
|
1645
1784
|
with component.active_tasks_lock:
|
|
1646
1785
|
tasks_to_cancel = []
|
|
1647
1786
|
for task_id, context in component.active_tasks.items():
|
|
1648
|
-
if (
|
|
1649
|
-
context
|
|
1787
|
+
if (
|
|
1788
|
+
hasattr(context, "a2a_context")
|
|
1789
|
+
and context.a2a_context.get("session_id") == session_id
|
|
1790
|
+
):
|
|
1650
1791
|
tasks_to_cancel.append(task_id)
|
|
1651
|
-
|
|
1792
|
+
|
|
1652
1793
|
for task_id in tasks_to_cancel:
|
|
1653
1794
|
context = component.active_tasks.get(task_id)
|
|
1654
1795
|
if context:
|
|
1655
1796
|
context.cancel()
|
|
1656
|
-
log.info(
|
|
1657
|
-
|
|
1797
|
+
log.info(
|
|
1798
|
+
"Cancelled task %s for deleted session %s", task_id, session_id
|
|
1799
|
+
)
|
|
1800
|
+
|
|
1658
1801
|
log.info("Session cleanup completed for session %s", session_id)
|
|
1659
|
-
|
|
1802
|
+
|
|
1660
1803
|
except Exception as e:
|
|
1661
1804
|
log.error("Error cleaning up session %s: %s", session_id, e)
|
|
@@ -140,12 +140,23 @@ class BaseProxyComponent(ComponentBase, ABC):
|
|
|
140
140
|
future = asyncio.run_coroutine_threadsafe(
|
|
141
141
|
self._process_event_async(event), self._async_loop
|
|
142
142
|
)
|
|
143
|
-
|
|
143
|
+
# Pass the event to the completion handler so it can NACK on failure
|
|
144
|
+
future.add_done_callback(
|
|
145
|
+
lambda f: self._handle_scheduled_task_completion(f, event)
|
|
146
|
+
)
|
|
144
147
|
|
|
145
148
|
async def _process_event_async(self, event: Event):
|
|
146
149
|
"""Asynchronous event processing logic."""
|
|
147
150
|
if event.event_type == EventType.MESSAGE:
|
|
148
|
-
|
|
151
|
+
message_handled = False
|
|
152
|
+
try:
|
|
153
|
+
await self._handle_a2a_request(event.data)
|
|
154
|
+
message_handled = True
|
|
155
|
+
finally:
|
|
156
|
+
# Mark that we attempted to handle the message
|
|
157
|
+
# (success/failure ack/nack is done inside _handle_a2a_request)
|
|
158
|
+
if not hasattr(event, "_proxy_message_handled"):
|
|
159
|
+
event._proxy_message_handled = message_handled
|
|
149
160
|
elif event.event_type == EventType.TIMER:
|
|
150
161
|
if event.data.get("timer_id") == self._discovery_timer_id:
|
|
151
162
|
await self._discover_and_publish_agents()
|
|
@@ -510,8 +521,10 @@ class BaseProxyComponent(ComponentBase, ABC):
|
|
|
510
521
|
self._async_init_future.set_exception, e
|
|
511
522
|
)
|
|
512
523
|
|
|
513
|
-
def _handle_scheduled_task_completion(
|
|
514
|
-
|
|
524
|
+
def _handle_scheduled_task_completion(
|
|
525
|
+
self, future: concurrent.futures.Future, event: Event
|
|
526
|
+
):
|
|
527
|
+
"""Callback to log exceptions from tasks scheduled on the async loop and NACK messages on failure."""
|
|
515
528
|
if future.done() and future.exception():
|
|
516
529
|
log.error(
|
|
517
530
|
"%s Coroutine scheduled on async loop failed: %s",
|
|
@@ -519,6 +532,24 @@ class BaseProxyComponent(ComponentBase, ABC):
|
|
|
519
532
|
future.exception(),
|
|
520
533
|
exc_info=future.exception(),
|
|
521
534
|
)
|
|
535
|
+
# NACK the message if this was a MESSAGE event that failed before being handled
|
|
536
|
+
# The _proxy_message_handled flag is set in _process_event_async to track
|
|
537
|
+
# whether _handle_a2a_request was entered (where ack/nack is normally done)
|
|
538
|
+
if event.event_type == EventType.MESSAGE:
|
|
539
|
+
message_handled = getattr(event, "_proxy_message_handled", False)
|
|
540
|
+
if not message_handled:
|
|
541
|
+
try:
|
|
542
|
+
event.data.call_negative_acknowledgements()
|
|
543
|
+
log.warning(
|
|
544
|
+
"%s NACKed message due to async processing failure before entering request handler.",
|
|
545
|
+
self.log_identifier,
|
|
546
|
+
)
|
|
547
|
+
except Exception as nack_e:
|
|
548
|
+
log.error(
|
|
549
|
+
"%s Failed to NACK message after async processing failure: %s",
|
|
550
|
+
self.log_identifier,
|
|
551
|
+
nack_e,
|
|
552
|
+
)
|
|
522
553
|
|
|
523
554
|
def _publish_discovered_cards(self):
|
|
524
555
|
"""Publishes all agent cards currently in the registry."""
|
|
@@ -23,7 +23,13 @@ from ...common.a2a import (
|
|
|
23
23
|
get_agent_status_subscription_topic,
|
|
24
24
|
get_sam_events_subscription_topic,
|
|
25
25
|
)
|
|
26
|
-
from ...common.constants import
|
|
26
|
+
from ...common.constants import (
|
|
27
|
+
DEFAULT_COMMUNICATION_TIMEOUT,
|
|
28
|
+
TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY,
|
|
29
|
+
TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH,
|
|
30
|
+
HEALTH_CHECK_TTL_SECONDS,
|
|
31
|
+
HEALTH_CHECK_INTERVAL_SECONDS,
|
|
32
|
+
)
|
|
27
33
|
from ...agent.sac.component import SamAgentComponent
|
|
28
34
|
from ...agent.utils.artifact_helpers import DEFAULT_SCHEMA_MAX_KEYS
|
|
29
35
|
from ...common.utils.pydantic_utils import SamConfigBase
|
|
@@ -31,6 +37,14 @@ from ..tools.tool_config_types import AnyToolConfig
|
|
|
31
37
|
|
|
32
38
|
log = logging.getLogger(__name__)
|
|
33
39
|
|
|
40
|
+
# Try to import TrustManagerConfig from enterprise repo
|
|
41
|
+
try:
|
|
42
|
+
from solace_agent_mesh_enterprise.common.trust.config import TrustManagerConfig
|
|
43
|
+
|
|
44
|
+
except ImportError:
|
|
45
|
+
# Enterprise features not available - create a placeholder type
|
|
46
|
+
TrustManagerConfig = Dict[str, Any] # type: ignore
|
|
47
|
+
|
|
34
48
|
info = {
|
|
35
49
|
"class_name": "SamAgentApp",
|
|
36
50
|
"description": "Custom App class for SAM Agent Host with namespace prefixing and automatic subscription generation.",
|
|
@@ -80,10 +94,12 @@ class AgentDiscoveryConfig(SamConfigBase):
|
|
|
80
94
|
default=True, description="Enable discovery and instruction injection."
|
|
81
95
|
)
|
|
82
96
|
health_check_ttl_seconds: int = Field(
|
|
83
|
-
default=HEALTH_CHECK_TTL_SECONDS,
|
|
97
|
+
default=HEALTH_CHECK_TTL_SECONDS,
|
|
98
|
+
description="Time-to-live in seconds after which an unresponsive agent is de-registered.",
|
|
84
99
|
)
|
|
85
100
|
health_check_interval_seconds: int = Field(
|
|
86
|
-
default=HEALTH_CHECK_INTERVAL_SECONDS,
|
|
101
|
+
default=HEALTH_CHECK_INTERVAL_SECONDS,
|
|
102
|
+
description="Interval in seconds between health checks.",
|
|
87
103
|
)
|
|
88
104
|
|
|
89
105
|
|
|
@@ -218,7 +234,9 @@ class ArtifactServiceConfig(SamConfigBase):
|
|
|
218
234
|
class SessionServiceConfig(SamConfigBase):
|
|
219
235
|
"""Configuration for the ADK Session Service."""
|
|
220
236
|
|
|
221
|
-
type: str = Field(
|
|
237
|
+
type: str = Field(
|
|
238
|
+
..., description="Service type (e.g., 'memory', 'sql', 'vertex_rag')."
|
|
239
|
+
)
|
|
222
240
|
default_behavior: Literal["PERSISTENT", "RUN_BASED"] = Field(
|
|
223
241
|
default="PERSISTENT", description="Default behavior for session service."
|
|
224
242
|
)
|
|
@@ -235,10 +253,21 @@ class SamAgentAppConfig(SamConfigBase):
|
|
|
235
253
|
description="Absolute topic prefix for A2A communication (e.g., 'myorg/dev').",
|
|
236
254
|
)
|
|
237
255
|
agent_name: str = Field(..., description="Unique name for this ADK agent instance.")
|
|
238
|
-
display_name: str = Field(
|
|
256
|
+
display_name: str = Field(
|
|
257
|
+
default=None,
|
|
258
|
+
description="Human-friendly display name for this ADK agent instance.",
|
|
259
|
+
)
|
|
260
|
+
deployment: Optional[Dict[str, Any]] = Field(
|
|
261
|
+
default=None,
|
|
262
|
+
description="Deployment tracking information for rolling updates and version control.",
|
|
263
|
+
)
|
|
239
264
|
model: Union[str, Dict[str, Any]] = Field(
|
|
240
265
|
..., description="ADK model name (string) or BaseLlm config dict."
|
|
241
266
|
)
|
|
267
|
+
trust_manager: Optional[Union[TrustManagerConfig, Dict[str, Any]]] = Field(
|
|
268
|
+
default=None,
|
|
269
|
+
description="Configuration for the Trust Manager (enterprise feature)",
|
|
270
|
+
)
|
|
242
271
|
instruction: Any = Field(
|
|
243
272
|
default="",
|
|
244
273
|
description="User-provided instructions for the ADK agent (string or invoke block).",
|
|
@@ -429,6 +458,20 @@ class SamAgentApp(App):
|
|
|
429
458
|
get_agent_status_subscription_topic(namespace, agent_name),
|
|
430
459
|
get_sam_events_subscription_topic(namespace, "session"),
|
|
431
460
|
]
|
|
461
|
+
|
|
462
|
+
# Add trust card subscription if trust manager is enabled
|
|
463
|
+
trust_config = app_config.get("trust_manager")
|
|
464
|
+
if trust_config and trust_config.get("enabled", False):
|
|
465
|
+
from ...common.a2a.protocol import get_trust_card_subscription_topic
|
|
466
|
+
|
|
467
|
+
trust_card_topic = get_trust_card_subscription_topic(namespace)
|
|
468
|
+
required_topics.append(trust_card_topic)
|
|
469
|
+
log.info(
|
|
470
|
+
"Trust Manager enabled for agent '%s', added trust card subscription: %s",
|
|
471
|
+
agent_name,
|
|
472
|
+
trust_card_topic,
|
|
473
|
+
)
|
|
474
|
+
|
|
432
475
|
generated_subs = [{"topic": topic} for topic in required_topics]
|
|
433
476
|
log.info(
|
|
434
477
|
"Automatically generated subscriptions for Agent '%s': %s",
|
|
@@ -458,8 +501,12 @@ class SamAgentApp(App):
|
|
|
458
501
|
broker_config["queue_name"] = generated_queue_name
|
|
459
502
|
log.debug("Injected generated broker.queue_name: %s", generated_queue_name)
|
|
460
503
|
|
|
461
|
-
broker_config["temporary_queue"] = app_info.get("broker", {}).get(
|
|
462
|
-
|
|
504
|
+
broker_config["temporary_queue"] = app_info.get("broker", {}).get(
|
|
505
|
+
"temporary_queue", True
|
|
506
|
+
)
|
|
507
|
+
log.debug(
|
|
508
|
+
"Set broker_config.temporary_queue = %s", broker_config["temporary_queue"]
|
|
509
|
+
)
|
|
463
510
|
|
|
464
511
|
super().__init__(app_info, **kwargs)
|
|
465
512
|
log.debug("%s Agent initialization complete.", agent_name)
|