solace-agent-mesh 1.5.1__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.

Files changed (184) hide show
  1. solace_agent_mesh/agent/adk/callbacks.py +0 -5
  2. solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
  3. solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
  4. solace_agent_mesh/agent/protocol/event_handlers.py +213 -31
  5. solace_agent_mesh/agent/proxies/__init__.py +0 -0
  6. solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
  7. solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
  8. solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
  9. solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
  10. solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
  11. solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
  12. solace_agent_mesh/agent/proxies/base/app.py +99 -0
  13. solace_agent_mesh/agent/proxies/base/component.py +650 -0
  14. solace_agent_mesh/agent/proxies/base/config.py +85 -0
  15. solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
  16. solace_agent_mesh/agent/sac/app.py +58 -5
  17. solace_agent_mesh/agent/sac/component.py +238 -75
  18. solace_agent_mesh/agent/sac/task_execution_context.py +46 -0
  19. solace_agent_mesh/agent/tools/audio_tools.py +125 -8
  20. solace_agent_mesh/agent/tools/web_tools.py +10 -5
  21. solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
  22. solace_agent_mesh/assets/docs/404.html +3 -3
  23. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  29. solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
  30. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  31. solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
  32. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/{e3d9abda.2b916f9e.js → e3d9abda.6b9493d0.js} +1 -1
  35. solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
  37. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
  38. solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.b12eac43.js} +2 -2
  39. solace_agent_mesh/assets/docs/assets/js/runtime~main.e268214e.js +1 -0
  40. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
  52. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
  58. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  61. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  64. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
  66. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  68. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  71. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  72. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  73. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  74. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  75. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  76. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  77. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  78. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  79. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  80. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
  81. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  82. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  83. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
  84. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  85. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  86. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  87. solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +1 -0
  88. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  89. solace_agent_mesh/assets/docs/search-doc-1761248203150.json +1 -0
  90. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  91. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  92. solace_agent_mesh/cli/__init__.py +1 -1
  93. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
  94. solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
  95. solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
  96. solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
  97. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
  98. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
  99. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
  100. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
  101. solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
  102. solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +342 -0
  103. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
  104. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
  105. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  106. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  107. solace_agent_mesh/common/a2a/__init__.py +24 -0
  108. solace_agent_mesh/common/a2a/artifact.py +39 -0
  109. solace_agent_mesh/common/a2a/events.py +29 -0
  110. solace_agent_mesh/common/a2a/message.py +68 -0
  111. solace_agent_mesh/common/a2a/protocol.py +151 -1
  112. solace_agent_mesh/common/agent_registry.py +83 -3
  113. solace_agent_mesh/common/constants.py +3 -1
  114. solace_agent_mesh/common/sac/sam_component_base.py +383 -4
  115. solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
  116. solace_agent_mesh/config_portal/backend/common.py +1 -1
  117. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
  118. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
  119. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  120. solace_agent_mesh/evaluation/evaluator.py +128 -104
  121. solace_agent_mesh/evaluation/message_organizer.py +116 -110
  122. solace_agent_mesh/evaluation/report_data_processor.py +84 -86
  123. solace_agent_mesh/evaluation/report_generator.py +73 -79
  124. solace_agent_mesh/evaluation/run.py +421 -235
  125. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  126. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  127. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  128. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  129. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  130. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  131. solace_agent_mesh/evaluation/subscriber.py +111 -232
  132. solace_agent_mesh/evaluation/summary_builder.py +227 -117
  133. solace_agent_mesh/gateway/base/app.py +16 -1
  134. solace_agent_mesh/gateway/base/component.py +112 -39
  135. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  136. solace_agent_mesh/gateway/http_sse/component.py +99 -3
  137. solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
  138. solace_agent_mesh/gateway/http_sse/main.py +1 -0
  139. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
  140. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
  141. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
  142. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
  143. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
  144. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
  145. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
  146. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
  147. solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
  148. solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
  149. solace_agent_mesh/gateway/http_sse/routers/users.py +47 -1
  150. solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
  151. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
  152. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
  153. solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
  154. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
  155. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
  156. solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
  157. solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
  158. solace_agent_mesh/templates/logging_config_template.ini +10 -6
  159. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
  160. solace_agent_mesh/templates/shared_config.yaml +40 -0
  161. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/METADATA +47 -21
  162. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/RECORD +166 -145
  163. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
  164. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
  165. solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
  166. solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
  167. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
  168. solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
  169. solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
  170. solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
  171. solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
  172. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
  173. solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
  174. solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
  175. solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
  176. solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
  177. solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
  178. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
  179. solace_agent_mesh/evaluation/config_loader.py +0 -657
  180. solace_agent_mesh/evaluation/test_case_loader.py +0 -714
  181. /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.b12eac43.js.LICENSE.txt} +0 -0
  182. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/WHEEL +0 -0
  183. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/entry_points.txt +0 -0
  184. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,85 @@
1
+ """
2
+ Pydantic configuration models for proxy applications.
3
+ """
4
+
5
+ from typing import List, Literal, Optional
6
+
7
+ from pydantic import Field, model_validator
8
+
9
+ from ....common.utils.pydantic_utils import SamConfigBase
10
+
11
+
12
+ class ArtifactServiceConfig(SamConfigBase):
13
+ """Configuration for the shared Artifact Service."""
14
+
15
+ type: str = Field(
16
+ ..., description="Service type (e.g., 'memory', 'gcs', 'filesystem')."
17
+ )
18
+ base_path: Optional[str] = Field(
19
+ default=None,
20
+ description="Base directory path (required for type 'filesystem').",
21
+ )
22
+ bucket_name: Optional[str] = Field(
23
+ default=None, description="GCS bucket name (required for type 'gcs')."
24
+ )
25
+ artifact_scope: Literal["namespace", "app", "custom"] = Field(
26
+ default="namespace", description="Process-wide scope for all artifact services."
27
+ )
28
+ artifact_scope_value: Optional[str] = Field(
29
+ default=None,
30
+ description="Custom identifier for artifact scope (required if artifact_scope is 'custom').",
31
+ )
32
+
33
+ @model_validator(mode="after")
34
+ def check_artifact_scope(self) -> "ArtifactServiceConfig":
35
+ if self.artifact_scope == "custom" and not self.artifact_scope_value:
36
+ raise ValueError(
37
+ "'artifact_scope_value' is required when 'artifact_scope' is 'custom'."
38
+ )
39
+ if self.artifact_scope != "custom" and self.artifact_scope_value:
40
+ from solace_ai_connector.common.log import log
41
+ log.warning(
42
+ "Configuration Warning: 'artifact_scope_value' is ignored when 'artifact_scope' is not 'custom'."
43
+ )
44
+ return self
45
+
46
+
47
+ class ProxiedAgentConfig(SamConfigBase):
48
+ """Base configuration for a proxied agent."""
49
+
50
+ name: str = Field(
51
+ ...,
52
+ description="The name the agent will have on the Solace mesh.",
53
+ )
54
+ request_timeout_seconds: Optional[int] = Field(
55
+ default=None,
56
+ description="Optional timeout override for this specific agent.",
57
+ )
58
+
59
+
60
+ class BaseProxyAppConfig(SamConfigBase):
61
+ """Base configuration for all proxy applications."""
62
+
63
+ namespace: str = Field(
64
+ ...,
65
+ description="Absolute topic prefix for A2A communication (e.g., 'myorg/dev').",
66
+ )
67
+ proxied_agents: List[ProxiedAgentConfig] = Field(
68
+ ...,
69
+ min_length=1,
70
+ description="A list of downstream agents to be proxied.",
71
+ )
72
+ artifact_service: ArtifactServiceConfig = Field(
73
+ default_factory=lambda: ArtifactServiceConfig(type="memory"),
74
+ description="Configuration for the shared Artifact Service.",
75
+ )
76
+ discovery_interval_seconds: int = Field(
77
+ default=60,
78
+ ge=0,
79
+ description="Interval (seconds) to re-fetch agent cards. <= 0 disables periodic discovery.",
80
+ )
81
+ default_request_timeout_seconds: int = Field(
82
+ default=300,
83
+ gt=0,
84
+ description="Default timeout in seconds for requests to downstream agents.",
85
+ )
@@ -0,0 +1,17 @@
1
+ """
2
+ Encapsulates the runtime state for a single, in-flight proxied agent task.
3
+ """
4
+
5
+ from typing import Any, Dict
6
+ from dataclasses import dataclass
7
+
8
+
9
+ @dataclass
10
+ class ProxyTaskContext:
11
+ """
12
+ A class to hold all runtime state and control mechanisms for a single proxied agent task.
13
+ This object is created when a task is initiated and destroyed when it completes.
14
+ """
15
+
16
+ task_id: str
17
+ a2a_context: Dict[str, Any]
@@ -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 DEFAULT_COMMUNICATION_TIMEOUT, TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY, TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH
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.",
@@ -79,6 +93,14 @@ class AgentDiscoveryConfig(SamConfigBase):
79
93
  enabled: bool = Field(
80
94
  default=True, description="Enable discovery and instruction injection."
81
95
  )
96
+ health_check_ttl_seconds: int = Field(
97
+ default=HEALTH_CHECK_TTL_SECONDS,
98
+ description="Time-to-live in seconds after which an unresponsive agent is de-registered.",
99
+ )
100
+ health_check_interval_seconds: int = Field(
101
+ default=HEALTH_CHECK_INTERVAL_SECONDS,
102
+ description="Interval in seconds between health checks.",
103
+ )
82
104
 
83
105
 
84
106
  class InterAgentCommunicationConfig(SamConfigBase):
@@ -212,7 +234,9 @@ class ArtifactServiceConfig(SamConfigBase):
212
234
  class SessionServiceConfig(SamConfigBase):
213
235
  """Configuration for the ADK Session Service."""
214
236
 
215
- type: str = Field(..., description="Service type (e.g., 'memory', 'sql', 'vertex_rag').")
237
+ type: str = Field(
238
+ ..., description="Service type (e.g., 'memory', 'sql', 'vertex_rag')."
239
+ )
216
240
  default_behavior: Literal["PERSISTENT", "RUN_BASED"] = Field(
217
241
  default="PERSISTENT", description="Default behavior for session service."
218
242
  )
@@ -229,10 +253,21 @@ class SamAgentAppConfig(SamConfigBase):
229
253
  description="Absolute topic prefix for A2A communication (e.g., 'myorg/dev').",
230
254
  )
231
255
  agent_name: str = Field(..., description="Unique name for this ADK agent instance.")
232
- display_name: str = Field(default=None, description="Human-friendly display name for this ADK agent instance.")
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
+ )
233
264
  model: Union[str, Dict[str, Any]] = Field(
234
265
  ..., description="ADK model name (string) or BaseLlm config dict."
235
266
  )
267
+ trust_manager: Optional[Union[TrustManagerConfig, Dict[str, Any]]] = Field(
268
+ default=None,
269
+ description="Configuration for the Trust Manager (enterprise feature)",
270
+ )
236
271
  instruction: Any = Field(
237
272
  default="",
238
273
  description="User-provided instructions for the ADK agent (string or invoke block).",
@@ -423,6 +458,20 @@ class SamAgentApp(App):
423
458
  get_agent_status_subscription_topic(namespace, agent_name),
424
459
  get_sam_events_subscription_topic(namespace, "session"),
425
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
+
426
475
  generated_subs = [{"topic": topic} for topic in required_topics]
427
476
  log.info(
428
477
  "Automatically generated subscriptions for Agent '%s': %s",
@@ -452,8 +501,12 @@ class SamAgentApp(App):
452
501
  broker_config["queue_name"] = generated_queue_name
453
502
  log.debug("Injected generated broker.queue_name: %s", generated_queue_name)
454
503
 
455
- broker_config["temporary_queue"] = True
456
- log.debug("Set broker_config.temporary_queue = True")
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
+ )
457
510
 
458
511
  super().__init__(app_info, **kwargs)
459
512
  log.debug("%s Agent initialization complete.", agent_name)
@@ -8,10 +8,8 @@ import asyncio
8
8
  import functools
9
9
  import threading
10
10
  import concurrent.futures
11
- import uuid
12
11
  import fnmatch
13
- import base64
14
- from datetime import datetime, timezone
12
+ import time
15
13
  import json
16
14
  from solace_ai_connector.common.message import (
17
15
  Message as SolaceMessage,
@@ -73,9 +71,10 @@ from ...agent.tools.peer_agent_tool import (
73
71
  PEER_TOOL_PREFIX,
74
72
  )
75
73
  from ...common.middleware.registry import MiddlewareRegistry
76
- from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
74
+ from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT, HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
77
75
  from ...agent.tools.registry import tool_registry
78
76
  from ...common.sac.sam_component_base import SamComponentBase
77
+ from ...common.agent_registry import AgentRegistry
79
78
 
80
79
  log = logging.getLogger(__name__)
81
80
 
@@ -113,6 +112,7 @@ class SamAgentComponent(SamComponentBase):
113
112
 
114
113
  CORRELATION_DATA_PREFIX = CORRELATION_DATA_PREFIX
115
114
  HOST_COMPONENT_VERSION = "1.0.0-alpha"
115
+ HEALTH_CHECK_TIMER_ID = "agent_health_check"
116
116
 
117
117
  def __init__(self, **kwargs):
118
118
  """
@@ -129,6 +129,9 @@ class SamAgentComponent(SamComponentBase):
129
129
  super().__init__(info, **kwargs)
130
130
  self.agent_name = self.get_config("agent_name")
131
131
  log.info("%s Initializing A2A ADK Host Component...", self.log_identifier)
132
+
133
+ # Initialize the agent registry for health tracking
134
+ self.agent_registry = AgentRegistry()
132
135
  try:
133
136
  self.namespace = self.get_config("namespace")
134
137
  if not self.namespace:
@@ -237,7 +240,7 @@ class SamAgentComponent(SamComponentBase):
237
240
  self.adk_agent: LlmAgent = None
238
241
  self.runner: Runner = None
239
242
  self.agent_card_tool_manifest: List[Dict[str, Any]] = []
240
- self.peer_agents: Dict[str, Any] = {}
243
+ self.peer_agents: Dict[str, Any] = {} # Keep for backward compatibility
241
244
  self._card_publish_timer_id: str = f"publish_card_{self.agent_name}"
242
245
  self._async_init_future = None
243
246
  self.peer_response_queues: Dict[str, asyncio.Queue] = {}
@@ -276,8 +279,13 @@ class SamAgentComponent(SamComponentBase):
276
279
  f"Failed to initialize synchronous ADK services: {service_err}"
277
280
  ) from service_err
278
281
 
279
- from .app import AgentInitCleanupConfig # delayed import to avoid circular dependency
280
- if init_func_details and isinstance(init_func_details, AgentInitCleanupConfig):
282
+ from .app import (
283
+ AgentInitCleanupConfig,
284
+ ) # delayed import to avoid circular dependency
285
+
286
+ if init_func_details and isinstance(
287
+ init_func_details, AgentInitCleanupConfig
288
+ ):
281
289
  module_name = init_func_details.get("module")
282
290
  func_name = init_func_details.get("name")
283
291
  base_path = init_func_details.get("base_path")
@@ -404,16 +412,39 @@ class SamAgentComponent(SamComponentBase):
404
412
  self.log_identifier,
405
413
  publish_interval_sec,
406
414
  )
415
+ # Register timer with callback
407
416
  self.add_timer(
408
417
  delay_ms=1000,
409
418
  timer_id=self._card_publish_timer_id,
410
419
  interval_ms=publish_interval_sec * 1000,
420
+ callback=lambda timer_data: publish_agent_card(self),
411
421
  )
412
422
  else:
413
423
  log.warning(
414
424
  "%s Agent card publishing interval not configured or invalid, card will not be published periodically.",
415
425
  self.log_identifier,
416
426
  )
427
+
428
+ # Set up health check timer if enabled
429
+ health_check_interval_seconds = self.agent_discovery_config.get("health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
430
+ if health_check_interval_seconds > 0:
431
+ log.info(
432
+ "%s Scheduling agent health check every %d seconds.",
433
+ self.log_identifier,
434
+ health_check_interval_seconds,
435
+ )
436
+ self.add_timer(
437
+ delay_ms=health_check_interval_seconds * 1000,
438
+ timer_id=self.HEALTH_CHECK_TIMER_ID,
439
+ interval_ms=health_check_interval_seconds * 1000,
440
+ callback=lambda timer_data: self._check_agent_health(),
441
+ )
442
+ else:
443
+ log.warning(
444
+ "%s Agent health check interval not configured or invalid, health checks will not run periodically.",
445
+ self.log_identifier,
446
+ )
447
+
417
448
  log.info(
418
449
  "%s Initialization complete for agent: %s",
419
450
  self.log_identifier,
@@ -423,75 +454,35 @@ class SamAgentComponent(SamComponentBase):
423
454
  log.exception("%s Initialization failed: %s", self.log_identifier, e)
424
455
  raise
425
456
 
457
+ def _get_component_id(self) -> str:
458
+ """Returns the agent name as the component identifier."""
459
+ return self.agent_name
460
+
461
+ def _get_component_type(self) -> str:
462
+ """Returns 'agent' as the component type."""
463
+ return "agent"
464
+
426
465
  def invoke(self, message: SolaceMessage, data: dict) -> dict:
427
- """Placeholder invoke method. Primary logic resides in process_event."""
466
+ """Placeholder invoke method. Primary logic resides in _handle_message."""
428
467
  log.warning(
429
- "%s 'invoke' method called, but primary logic resides in 'process_event'. This should not happen in normal operation.",
468
+ "%s 'invoke' method called, but primary logic resides in '_handle_message'. This should not happen in normal operation.",
430
469
  self.log_identifier,
431
470
  )
432
471
  return None
433
472
 
434
- def process_event(self, event: Event):
435
- """Processes incoming events (Messages, Timers, etc.)."""
436
- try:
437
- loop = self.get_async_loop()
438
- is_loop_running = loop.is_running() if loop else False
439
- if loop and is_loop_running:
440
- coro = process_event(self, event)
441
- future = asyncio.run_coroutine_threadsafe(coro, loop)
442
- future.add_done_callback(
443
- functools.partial(
444
- self._handle_scheduled_task_completion,
445
- event_type_for_log=event.event_type,
446
- )
447
- )
448
- else:
449
- log.error(
450
- "%s Async loop not available or not running (loop is %s, is_running: %s). Cannot process event: %s",
451
- self.log_identifier,
452
- "present" if loop else "None",
453
- is_loop_running,
454
- event.event_type,
455
- )
456
- if event.event_type == EventType.MESSAGE:
457
- try:
458
- event.data.call_negative_acknowledgements()
459
- log.warning(
460
- "%s NACKed message due to unavailable async loop for event processing.",
461
- self.log_identifier,
462
- )
463
- except Exception as nack_e:
464
- log.error(
465
- "%s Failed to NACK message after async loop issue: %s",
466
- self.log_identifier,
467
- nack_e,
468
- )
469
- except Exception as e:
470
- log.error(
471
- "%s Error processing event: %s. Exception: %s",
472
- self.log_identifier,
473
- event.event_type,
474
- e,
475
- )
476
- if event.event_type == EventType.MESSAGE:
477
- try:
478
- event.data.call_negative_acknowledgements()
479
- log.warning(
480
- "%s NACKed message due to error in event processing.",
481
- self.log_identifier,
482
- )
483
- except Exception as nack_e:
484
- log.error(
485
- "%s Failed to NACK message after error in event processing: %s",
486
- self.log_identifier,
487
- nack_e,
488
- )
473
+ async def _handle_message_async(self, message: SolaceMessage, topic: str) -> None:
474
+ """
475
+ Async handler for incoming messages.
476
+
477
+ Routes the message to the async event handler.
489
478
 
490
- def handle_timer_event(self, timer_data: Dict[str, Any]):
491
- """Handles timer events, specifically for agent card publishing."""
492
- log.debug("%s Received timer event: %s", self.log_identifier, timer_data)
493
- if timer_data.get("timer_id") == self._card_publish_timer_id:
494
- publish_agent_card(self)
479
+ Args:
480
+ message: The Solace message
481
+ topic: The topic the message was received on
482
+ """
483
+ # Create event and process asynchronously
484
+ event = Event(EventType.MESSAGE, message)
485
+ await process_event(self, event)
495
486
 
496
487
  async def handle_cache_expiry_event(self, cache_data: Dict[str, Any]):
497
488
  """
@@ -866,7 +857,8 @@ class SamAgentComponent(SamComponentBase):
866
857
  peer_tools_to_add = []
867
858
  allowed_peer_descriptions = []
868
859
 
869
- for peer_name, agent_card in self.peer_agents.items():
860
+ # Sort peer agents alphabetically to ensure consistent tool ordering for prompt caching
861
+ for peer_name, agent_card in sorted(self.peer_agents.items()):
870
862
  if not isinstance(agent_card, AgentCard) or peer_name == self_name:
871
863
  continue
872
864
 
@@ -1140,7 +1132,11 @@ class SamAgentComponent(SamComponentBase):
1140
1132
  """
1141
1133
  if hasattr(tool, "origin") and tool.origin is not None:
1142
1134
  return tool.origin
1143
- elif hasattr(tool, "func") and hasattr(tool.func, "origin") and tool.func.origin is not None:
1135
+ elif (
1136
+ hasattr(tool, "func")
1137
+ and hasattr(tool.func, "origin")
1138
+ and tool.func.origin is not None
1139
+ ):
1144
1140
  return tool.func.origin
1145
1141
  else:
1146
1142
  return getattr(tool, "origin", "unknown")
@@ -2078,7 +2074,7 @@ class SamAgentComponent(SamComponentBase):
2078
2074
  self.log_identifier,
2079
2075
  len(task_context.produced_artifacts),
2080
2076
  )
2081
-
2077
+
2082
2078
  # Add token usage summary
2083
2079
  if task_context:
2084
2080
  token_summary = task_context.get_token_usage_summary()
@@ -2833,6 +2829,35 @@ class SamAgentComponent(SamComponentBase):
2833
2829
  if isinstance(user_config, dict):
2834
2830
  user_properties["a2aUserConfig"] = user_config
2835
2831
 
2832
+ # Retrieve and propagate authentication token from parent task context
2833
+ parent_task_id = a2a_message.metadata.get("parentTaskId")
2834
+ if parent_task_id:
2835
+ with self.active_tasks_lock:
2836
+ parent_task_context = self.active_tasks.get(parent_task_id)
2837
+
2838
+ if parent_task_context:
2839
+ auth_token = parent_task_context.get_security_data("auth_token")
2840
+ if auth_token:
2841
+ user_properties["authToken"] = auth_token
2842
+ log.debug(
2843
+ "%s Propagating authentication token to peer agent %s for sub-task %s",
2844
+ log_identifier_helper,
2845
+ target_agent_name,
2846
+ sub_task_id,
2847
+ )
2848
+ else:
2849
+ log.debug(
2850
+ "%s No authentication token found in parent task context for sub-task %s",
2851
+ log_identifier_helper,
2852
+ sub_task_id,
2853
+ )
2854
+ else:
2855
+ log.warning(
2856
+ "%s Parent task context not found for task %s, cannot propagate authentication token",
2857
+ log_identifier_helper,
2858
+ parent_task_id,
2859
+ )
2860
+
2836
2861
  self.publish_a2a_message(
2837
2862
  payload=a2a_request.model_dump(by_alias=True, exclude_none=True),
2838
2863
  topic=peer_request_topic,
@@ -2991,11 +3016,15 @@ class SamAgentComponent(SamComponentBase):
2991
3016
  """Clean up resources on component shutdown."""
2992
3017
  log.info("%s Cleaning up A2A ADK Host Component.", self.log_identifier)
2993
3018
  self.cancel_timer(self._card_publish_timer_id)
3019
+ self.cancel_timer(self.HEALTH_CHECK_TIMER_ID)
2994
3020
 
2995
3021
  cleanup_func_details = self.get_config("agent_cleanup_function")
2996
3022
 
2997
- from .app import AgentInitCleanupConfig # Avoid circular import
2998
- if cleanup_func_details and isinstance(cleanup_func_details, AgentInitCleanupConfig):
3023
+ from .app import AgentInitCleanupConfig # Avoid circular import
3024
+
3025
+ if cleanup_func_details and isinstance(
3026
+ cleanup_func_details, AgentInitCleanupConfig
3027
+ ):
2999
3028
  module_name = cleanup_func_details.get("module")
3000
3029
  func_name = cleanup_func_details.get("name")
3001
3030
  base_path = cleanup_func_details.get("base_path")
@@ -3151,6 +3180,129 @@ class SamAgentComponent(SamComponentBase):
3151
3180
  For now, using the agent name, but could be made more robust (e.g., hostname + agent name).
3152
3181
  """
3153
3182
  return self.agent_name
3183
+
3184
+ def _check_agent_health(self):
3185
+ """
3186
+ Checks the health of peer agents and de-registers unresponsive ones.
3187
+ This is called periodically by the health check timer.
3188
+ Uses TTL-based expiration to determine if an agent is unresponsive.
3189
+ """
3190
+
3191
+ log.debug("%s Performing agent health check...", self.log_identifier)
3192
+
3193
+ ttl_seconds = self.agent_discovery_config.get("health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS)
3194
+ health_check_interval = self.agent_discovery_config.get("health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
3195
+
3196
+ log.debug(
3197
+ "%s Health check configuration: interval=%d seconds, TTL=%d seconds",
3198
+ self.log_identifier,
3199
+ health_check_interval,
3200
+ ttl_seconds
3201
+ )
3202
+
3203
+ # Validate configuration values
3204
+ if ttl_seconds <= 0 or health_check_interval <= 0 or ttl_seconds < health_check_interval:
3205
+ log.error(
3206
+ "%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
3207
+ self.log_identifier,
3208
+ ttl_seconds,
3209
+ health_check_interval
3210
+ )
3211
+ raise ValueError(f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval.")
3212
+
3213
+ # Get all agent names from the registry
3214
+ agent_names = self.agent_registry.get_agent_names()
3215
+ total_agents = len(agent_names)
3216
+ agents_to_deregister = []
3217
+
3218
+ log.debug("%s Checking health of %d peer agents", self.log_identifier, total_agents)
3219
+
3220
+ for agent_name in agent_names:
3221
+ # Skip our own agent
3222
+ if agent_name == self.agent_name:
3223
+ continue
3224
+
3225
+ # Check if the agent's TTL has expired
3226
+ is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(agent_name, ttl_seconds)
3227
+
3228
+ if is_expired:
3229
+ log.warning(
3230
+ "%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
3231
+ self.log_identifier,
3232
+ agent_name,
3233
+ time_since_last_seen,
3234
+ ttl_seconds
3235
+ )
3236
+ agents_to_deregister.append(agent_name)
3237
+
3238
+ # De-register unresponsive agents
3239
+ for agent_name in agents_to_deregister:
3240
+ self._deregister_agent(agent_name)
3241
+
3242
+ log.debug(
3243
+ "%s Agent health check completed. Total agents: %d, De-registered: %d",
3244
+ self.log_identifier,
3245
+ total_agents,
3246
+ len(agents_to_deregister)
3247
+ )
3248
+
3249
+ def _deregister_agent(self, agent_name: str):
3250
+ """
3251
+ De-registers an agent from the registry and publishes a de-registration event.
3252
+ """
3253
+ # Remove from registry
3254
+ registry_removed = self.agent_registry.remove_agent(agent_name)
3255
+
3256
+ # Always remove from peer_agents regardless of registry result
3257
+ peer_removed = False
3258
+ if agent_name in self.peer_agents:
3259
+ del self.peer_agents[agent_name]
3260
+ peer_removed = True
3261
+ log.info(
3262
+ "%s Removed agent '%s' from peer_agents dictionary",
3263
+ self.log_identifier,
3264
+ agent_name
3265
+ )
3266
+
3267
+ # Publish de-registration event if agent was in either data structure
3268
+ if registry_removed or peer_removed:
3269
+ try:
3270
+ # Create a de-registration event topic
3271
+ namespace = self.get_config("namespace")
3272
+ deregistration_topic = f"{namespace}/a2a/events/agent/deregistered"
3273
+
3274
+ current_time = time.time()
3275
+
3276
+ # Create the payload
3277
+ deregistration_payload = {
3278
+ "event_type": "agent.deregistered",
3279
+ "agent_name": agent_name,
3280
+ "reason": "health_check_failure",
3281
+ "metadata": {
3282
+ "timestamp": current_time,
3283
+ "deregistered_by": self.agent_name
3284
+ }
3285
+ }
3286
+
3287
+ # Publish the event
3288
+ self.publish_a2a_message(
3289
+ payload=deregistration_payload,
3290
+ topic=deregistration_topic
3291
+ )
3292
+
3293
+ log.info(
3294
+ "%s Published de-registration event for agent '%s' to topic '%s'",
3295
+ self.log_identifier,
3296
+ agent_name,
3297
+ deregistration_topic
3298
+ )
3299
+ except Exception as e:
3300
+ log.error(
3301
+ "%s Failed to publish de-registration event for agent '%s': %s",
3302
+ self.log_identifier,
3303
+ agent_name,
3304
+ e
3305
+ )
3154
3306
 
3155
3307
  async def _resolve_early_embeds_and_handle_signals(
3156
3308
  self, raw_text: str, a2a_context: Dict
@@ -3240,6 +3392,10 @@ class SamAgentComponent(SamComponentBase):
3240
3392
  Main async logic for the agent component.
3241
3393
  This is called by the base class's `_run_async_operations`.
3242
3394
  """
3395
+ # Call base class to initialize Trust Manager
3396
+ await super()._async_setup_and_run()
3397
+
3398
+ # Perform agent-specific async initialization
3243
3399
  await self._perform_async_init()
3244
3400
 
3245
3401
  def _pre_async_cleanup(self) -> None:
@@ -3247,4 +3403,11 @@ class SamAgentComponent(SamComponentBase):
3247
3403
  Pre-cleanup actions for the agent component.
3248
3404
  Called by the base class before stopping the async loop.
3249
3405
  """
3250
- pass
3406
+ # Cleanup Trust Manager if present (ENTERPRISE FEATURE)
3407
+ if self.trust_manager:
3408
+ try:
3409
+ self.trust_manager.cleanup(self.cancel_timer)
3410
+ except Exception as e:
3411
+ log.error(
3412
+ "%s Error during Trust Manager cleanup: %s", self.log_identifier, e
3413
+ )