solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (180) 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 +40 -1
  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 +619 -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 +9 -3
  17. solace_agent_mesh/agent/sac/component.py +160 -8
  18. solace_agent_mesh/agent/tools/audio_tools.py +125 -8
  19. solace_agent_mesh/agent/tools/web_tools.py +10 -5
  20. solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
  21. solace_agent_mesh/assets/docs/404.html +3 -3
  22. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
  23. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
  29. solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
  30. solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
  31. solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.20feee82.js} +2 -2
  37. solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +1 -0
  38. solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
  50. solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
  56. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
  59. solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
  64. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
  65. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
  66. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
  67. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
  68. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
  69. solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
  70. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  71. solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
  72. solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
  73. solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
  74. solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
  75. solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
  76. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  77. solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
  78. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
  79. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
  80. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
  81. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
  82. solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
  83. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  84. solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
  85. solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +1 -0
  86. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  87. solace_agent_mesh/assets/docs/search-doc-1761165361160.json +1 -0
  88. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  89. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  90. solace_agent_mesh/cli/__init__.py +1 -1
  91. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
  92. solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
  93. solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
  94. solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
  95. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
  96. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
  97. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
  98. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
  99. solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
  100. solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +342 -0
  101. solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
  102. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
  103. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  104. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  105. solace_agent_mesh/common/a2a/__init__.py +24 -0
  106. solace_agent_mesh/common/a2a/artifact.py +39 -0
  107. solace_agent_mesh/common/a2a/events.py +29 -0
  108. solace_agent_mesh/common/a2a/message.py +68 -0
  109. solace_agent_mesh/common/a2a/protocol.py +73 -1
  110. solace_agent_mesh/common/agent_registry.py +83 -3
  111. solace_agent_mesh/common/constants.py +3 -1
  112. solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
  113. solace_agent_mesh/config_portal/backend/common.py +1 -1
  114. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
  115. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
  116. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  117. solace_agent_mesh/evaluation/evaluator.py +128 -104
  118. solace_agent_mesh/evaluation/message_organizer.py +116 -110
  119. solace_agent_mesh/evaluation/report_data_processor.py +84 -86
  120. solace_agent_mesh/evaluation/report_generator.py +73 -79
  121. solace_agent_mesh/evaluation/run.py +421 -235
  122. solace_agent_mesh/evaluation/shared/__init__.py +92 -0
  123. solace_agent_mesh/evaluation/shared/constants.py +47 -0
  124. solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
  125. solace_agent_mesh/evaluation/shared/helpers.py +35 -0
  126. solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
  127. solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
  128. solace_agent_mesh/evaluation/subscriber.py +111 -232
  129. solace_agent_mesh/evaluation/summary_builder.py +227 -117
  130. solace_agent_mesh/gateway/base/app.py +1 -1
  131. solace_agent_mesh/gateway/base/component.py +8 -1
  132. solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
  133. solace_agent_mesh/gateway/http_sse/component.py +98 -2
  134. solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
  135. solace_agent_mesh/gateway/http_sse/main.py +2 -1
  136. solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
  137. solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
  138. solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
  139. solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
  140. solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
  141. solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
  142. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
  143. solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
  144. solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
  145. solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
  146. solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
  147. solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
  148. solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
  149. solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
  150. solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
  151. solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
  152. solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
  153. solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
  154. solace_agent_mesh/templates/logging_config_template.ini +10 -6
  155. solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
  156. solace_agent_mesh/templates/shared_config.yaml +40 -0
  157. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/METADATA +47 -21
  158. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/RECORD +162 -141
  159. solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
  160. solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
  161. solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
  162. solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
  163. solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
  164. solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
  165. solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
  166. solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
  167. solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
  168. solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
  169. solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
  170. solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
  171. solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
  172. solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
  173. solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
  174. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
  175. solace_agent_mesh/evaluation/config_loader.py +0 -657
  176. solace_agent_mesh/evaluation/test_case_loader.py +0 -714
  177. /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.20feee82.js.LICENSE.txt} +0 -0
  178. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/WHEEL +0 -0
  179. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/entry_points.txt +0 -0
  180. {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,6 +14,8 @@ from .artifact import (
14
14
  get_artifact_id,
15
15
  get_artifact_name,
16
16
  get_parts_from_artifact,
17
+ get_text_content_from_artifact,
18
+ is_text_only_artifact,
17
19
  update_artifact_parts,
18
20
  prepare_file_part_for_publishing,
19
21
  resolve_file_part_uri,
@@ -25,6 +27,8 @@ from .events import (
25
27
  get_artifact_from_artifact_update,
26
28
  get_data_parts_from_status_update,
27
29
  get_message_from_status_update,
30
+ is_task_artifact_update,
31
+ is_task_status_update,
28
32
  )
29
33
  from .message import (
30
34
  create_agent_data_message,
@@ -51,6 +55,11 @@ from .message import (
51
55
  get_text_from_message,
52
56
  get_text_from_text_part,
53
57
  get_uri_from_file_part,
58
+ is_data_part,
59
+ is_file_part,
60
+ is_file_part_bytes,
61
+ is_file_part_uri,
62
+ is_text_part,
54
63
  )
55
64
  from .protocol import (
56
65
  create_cancel_task_request,
@@ -92,8 +101,11 @@ from .protocol import (
92
101
  get_response_result,
93
102
  get_task_id_from_cancel_request,
94
103
  extract_task_id_from_topic,
104
+ is_client_event,
105
+ is_message_object,
95
106
  subscription_to_regex,
96
107
  topic_matches_subscription,
108
+ unpack_client_event,
97
109
  )
98
110
  from .task import (
99
111
  create_final_task,
@@ -122,6 +134,8 @@ __all__ = [
122
134
  "get_artifact_id",
123
135
  "get_artifact_name",
124
136
  "get_parts_from_artifact",
137
+ "get_text_content_from_artifact",
138
+ "is_text_only_artifact",
125
139
  "update_artifact_parts",
126
140
  "prepare_file_part_for_publishing",
127
141
  "resolve_file_part_uri",
@@ -132,6 +146,8 @@ __all__ = [
132
146
  "get_artifact_from_artifact_update",
133
147
  "get_data_parts_from_status_update",
134
148
  "get_message_from_status_update",
149
+ "is_task_artifact_update",
150
+ "is_task_status_update",
135
151
  # message.py
136
152
  "create_agent_data_message",
137
153
  "create_agent_parts_message",
@@ -157,6 +173,11 @@ __all__ = [
157
173
  "get_text_from_message",
158
174
  "get_uri_from_file_part",
159
175
  "get_text_from_text_part",
176
+ "is_data_part",
177
+ "is_file_part",
178
+ "is_file_part_bytes",
179
+ "is_file_part_uri",
180
+ "is_text_part",
160
181
  # protocol.py
161
182
  "create_cancel_task_request",
162
183
  "create_error_response",
@@ -197,8 +218,11 @@ __all__ = [
197
218
  "get_response_result",
198
219
  "get_task_id_from_cancel_request",
199
220
  "extract_task_id_from_topic",
221
+ "is_client_event",
222
+ "is_message_object",
200
223
  "subscription_to_regex",
201
224
  "topic_matches_subscription",
225
+ "unpack_client_event",
202
226
  # task.py
203
227
  "create_final_task",
204
228
  "create_initial_task",
@@ -327,3 +327,42 @@ def get_parts_from_artifact(artifact: Artifact) -> List[ContentPart]:
327
327
  A list of the unwrapped content parts.
328
328
  """
329
329
  return [part.root for part in artifact.parts]
330
+
331
+
332
+ def is_text_only_artifact(artifact: Artifact) -> bool:
333
+ """
334
+ Checks if an artifact contains only TextParts.
335
+
336
+ Args:
337
+ artifact: The Artifact object to check.
338
+
339
+ Returns:
340
+ True if all parts are TextParts, False otherwise.
341
+ """
342
+ if not artifact.parts:
343
+ return False
344
+
345
+ for part in artifact.parts:
346
+ if not isinstance(part.root, TextPart):
347
+ return False
348
+
349
+ return True
350
+
351
+
352
+ def get_text_content_from_artifact(artifact: Artifact) -> List[str]:
353
+ """
354
+ Extracts all text content from TextParts in an artifact.
355
+
356
+ Args:
357
+ artifact: The Artifact object to extract text from.
358
+
359
+ Returns:
360
+ A list of text strings from all TextParts. Returns empty list if no TextParts found.
361
+ """
362
+ text_content = []
363
+
364
+ for part in artifact.parts:
365
+ if isinstance(part.root, TextPart):
366
+ text_content.append(part.root.text)
367
+
368
+ return text_content
@@ -181,3 +181,32 @@ def get_artifact_from_artifact_update(
181
181
  if event:
182
182
  return event.artifact
183
183
  return None
184
+
185
+
186
+ # --- Type Checking Helpers ---
187
+
188
+
189
+ def is_task_status_update(obj: Any) -> bool:
190
+ """
191
+ Checks if an object is a TaskStatusUpdateEvent.
192
+
193
+ Args:
194
+ obj: The object to check.
195
+
196
+ Returns:
197
+ True if the object is a TaskStatusUpdateEvent, False otherwise.
198
+ """
199
+ return isinstance(obj, TaskStatusUpdateEvent)
200
+
201
+
202
+ def is_task_artifact_update(obj: Any) -> bool:
203
+ """
204
+ Checks if an object is a TaskArtifactUpdateEvent.
205
+
206
+ Args:
207
+ obj: The object to check.
208
+
209
+ Returns:
210
+ True if the object is a TaskArtifactUpdateEvent, False otherwise.
211
+ """
212
+ return isinstance(obj, TaskArtifactUpdateEvent)
@@ -305,3 +305,71 @@ def get_filename_from_file_part(part: FilePart) -> Optional[str]:
305
305
  def get_mimetype_from_file_part(part: FilePart) -> Optional[str]:
306
306
  """Safely retrieves the MIME type from a FilePart."""
307
307
  return part.file.mime_type
308
+
309
+
310
+ # --- Type Checking Helpers ---
311
+
312
+
313
+ def is_text_part(part: Part) -> bool:
314
+ """
315
+ Checks if a Part contains a TextPart.
316
+
317
+ Args:
318
+ part: The Part object to check.
319
+
320
+ Returns:
321
+ True if the part contains a TextPart, False otherwise.
322
+ """
323
+ return isinstance(part.root, TextPart)
324
+
325
+
326
+ def is_file_part(part: Part) -> bool:
327
+ """
328
+ Checks if a Part contains a FilePart.
329
+
330
+ Args:
331
+ part: The Part object to check.
332
+
333
+ Returns:
334
+ True if the part contains a FilePart, False otherwise.
335
+ """
336
+ return isinstance(part.root, FilePart)
337
+
338
+
339
+ def is_data_part(part: Part) -> bool:
340
+ """
341
+ Checks if a Part contains a DataPart.
342
+
343
+ Args:
344
+ part: The Part object to check.
345
+
346
+ Returns:
347
+ True if the part contains a DataPart, False otherwise.
348
+ """
349
+ return isinstance(part.root, DataPart)
350
+
351
+
352
+ def is_file_part_bytes(part: FilePart) -> bool:
353
+ """
354
+ Checks if a FilePart uses FileWithBytes (embedded content).
355
+
356
+ Args:
357
+ part: The FilePart object to check.
358
+
359
+ Returns:
360
+ True if the file content is embedded as bytes, False otherwise.
361
+ """
362
+ return isinstance(part.file, FileWithBytes)
363
+
364
+
365
+ def is_file_part_uri(part: FilePart) -> bool:
366
+ """
367
+ Checks if a FilePart uses FileWithUri (reference to external content).
368
+
369
+ Args:
370
+ part: The FilePart object to check.
371
+
372
+ Returns:
373
+ True if the file content is a URI reference, False otherwise.
374
+ """
375
+ return isinstance(part.file, FileWithUri)
@@ -5,7 +5,7 @@ parsing of JSON-RPC requests and responses.
5
5
  import logging
6
6
  import re
7
7
  import uuid
8
- from typing import Any, Dict, Optional, Union
8
+ from typing import Any, Dict, Optional, Tuple, Union
9
9
 
10
10
  from a2a.types import (
11
11
  A2ARequest,
@@ -532,3 +532,75 @@ def extract_task_id_from_topic(
532
532
  subscription_pattern,
533
533
  )
534
534
  return None
535
+
536
+
537
+ # --- Client Event Helpers ---
538
+
539
+
540
+ def is_client_event(obj: Any) -> bool:
541
+ """
542
+ Checks if an object is a ClientEvent tuple (Task, UpdateEvent).
543
+
544
+ A ClientEvent is a tuple with 2 elements where the first element is a Task
545
+ and the second is either a TaskStatusUpdateEvent, TaskArtifactUpdateEvent, or None.
546
+
547
+ Args:
548
+ obj: The object to check.
549
+
550
+ Returns:
551
+ True if the object is a ClientEvent tuple, False otherwise.
552
+ """
553
+ if not isinstance(obj, tuple) or len(obj) != 2:
554
+ return False
555
+
556
+ task, update_event = obj
557
+
558
+ # First element must be a Task
559
+ if not isinstance(task, Task):
560
+ return False
561
+
562
+ # Second element must be an update event or None
563
+ if update_event is not None and not isinstance(
564
+ update_event, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
565
+ ):
566
+ return False
567
+
568
+ return True
569
+
570
+
571
+ def is_message_object(obj: Any) -> bool:
572
+ """
573
+ Checks if an object is a Message.
574
+
575
+ Args:
576
+ obj: The object to check.
577
+
578
+ Returns:
579
+ True if the object is a Message, False otherwise.
580
+ """
581
+ return isinstance(obj, Message)
582
+
583
+
584
+ def unpack_client_event(
585
+ event: tuple,
586
+ ) -> Tuple[Task, Optional[Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent]]]:
587
+ """
588
+ Safely unpacks a ClientEvent tuple into its components.
589
+
590
+ Args:
591
+ event: A ClientEvent tuple (Task, UpdateEvent).
592
+
593
+ Returns:
594
+ A tuple of (Task, Optional[UpdateEvent]) where UpdateEvent can be
595
+ TaskStatusUpdateEvent, TaskArtifactUpdateEvent, or None.
596
+
597
+ Raises:
598
+ ValueError: If the event is not a valid ClientEvent tuple.
599
+ """
600
+ if not is_client_event(event):
601
+ raise ValueError(
602
+ f"Expected a ClientEvent tuple, got {type(event).__name__}"
603
+ )
604
+
605
+ task, update_event = event
606
+ return task, update_event
@@ -4,26 +4,37 @@ Consolidated from src/tools/common/agent_registry.py and src/tools/a2a_cli_clien
4
4
  """
5
5
 
6
6
  import threading
7
- from typing import Dict, List, Optional
7
+ import time
8
+ from typing import Dict, List, Optional, Tuple
9
+ import logging
8
10
 
9
11
  from a2a.types import AgentCard
10
12
 
13
+ log = logging.getLogger(__name__)
11
14
 
12
15
  class AgentRegistry:
13
- """Stores and manages discovered AgentCards."""
16
+ """Stores and manages discovered AgentCards with health tracking."""
14
17
 
15
18
  def __init__(self):
16
19
  self._agents: Dict[str, AgentCard] = {}
20
+ self._last_seen: Dict[str, float] = {} # Timestamp of last agent card received
17
21
  self._lock = threading.Lock()
18
22
 
19
23
  def add_or_update_agent(self, agent_card: AgentCard):
20
24
  """Adds a new agent or updates an existing one."""
25
+
21
26
  if not agent_card or not agent_card.name:
22
- return
27
+ log.warning("Attempted to register agent with invalid agent card or missing name")
28
+ return False
23
29
 
24
30
  with self._lock:
25
31
  is_new = agent_card.name not in self._agents
32
+ current_time = time.time()
33
+
34
+ # Store the agent information
26
35
  self._agents[agent_card.name] = agent_card
36
+ self._last_seen[agent_card.name] = current_time
37
+
27
38
  return is_new
28
39
 
29
40
  def get_agent(self, agent_name: str) -> Optional[AgentCard]:
@@ -35,8 +46,77 @@ class AgentRegistry:
35
46
  """Returns a sorted list of discovered agent names."""
36
47
  with self._lock:
37
48
  return sorted(list(self._agents.keys()))
49
+
50
+ def get_last_seen(self, agent_name: str) -> Optional[float]:
51
+ """Returns the timestamp when the agent was last seen."""
52
+ with self._lock:
53
+ return self._last_seen.get(agent_name)
54
+
55
+ def check_ttl_expired(self, agent_name: str, ttl_seconds: int) -> Tuple[bool, int]:
56
+ """
57
+ Checks if an agent's TTL has expired.
58
+
59
+ Args:
60
+ agent_name: The name of the agent to check
61
+ ttl_seconds: The TTL in seconds
62
+
63
+ Returns:
64
+ A tuple of (is_expired, seconds_since_last_seen)
65
+ """
66
+
67
+ with self._lock:
68
+ if agent_name not in self._last_seen:
69
+ log.debug("Attempted to check TTL for non-existent agent '%s'", agent_name)
70
+ return False, 0
71
+
72
+ last_seen_time = self._last_seen.get(agent_name)
73
+ current_time = time.time()
74
+ time_since_last_seen = int(current_time - last_seen_time) if last_seen_time else 0
75
+
76
+ is_expired = time_since_last_seen > ttl_seconds
77
+
78
+ if is_expired:
79
+ log.warning(
80
+ "AGENT HEALTH CRITICAL: Agent '%s' TTL expired. "
81
+ "Last seen: %s seconds ago, TTL: %d seconds",
82
+ agent_name,
83
+ time_since_last_seen,
84
+ ttl_seconds
85
+ )
86
+
87
+ return is_expired, time_since_last_seen
88
+
89
+ def remove_agent(self, agent_name: str) -> bool:
90
+ """Removes an agent from the registry."""
91
+
92
+ with self._lock:
93
+ if agent_name in self._agents:
94
+ # Get agent details before removal for logging
95
+ last_seen_time = self._last_seen.get(agent_name)
96
+ current_time = time.time()
97
+ time_since_last_seen = int(current_time - last_seen_time) if last_seen_time else "unknown"
98
+
99
+ # Log detailed information about the agent being removed
100
+ log.warning(
101
+ "AGENT DE-REGISTRATION: Removing agent '%s' from registry. "
102
+ "Last seen: %s seconds ago",
103
+ agent_name,
104
+ time_since_last_seen
105
+ )
106
+
107
+ # Remove the agent from all tracking dictionaries
108
+ del self._agents[agent_name]
109
+ if agent_name in self._last_seen:
110
+ del self._last_seen[agent_name]
111
+
112
+ log.info("Agent '%s' successfully removed from registry", agent_name)
113
+ return True
114
+ else:
115
+ log.debug("Attempted to remove non-existent agent '%s' from registry", agent_name)
116
+ return False
38
117
 
39
118
  def clear(self):
40
119
  """Clears all registered agents."""
41
120
  with self._lock:
42
121
  self._agents.clear()
122
+ self._last_seen.clear()
@@ -1,4 +1,6 @@
1
1
 
2
2
  DEFAULT_COMMUNICATION_TIMEOUT = 600 # 10 minutes
3
+ HEALTH_CHECK_TTL_SECONDS = 60 # 60 seconds - time after which a health check is considered stale
4
+ HEALTH_CHECK_INTERVAL_SECONDS = 10 # 10 seconds - interval between health checks
3
5
  TEXT_ARTIFACT_CONTEXT_MAX_LENGTH_CAPACITY = 200000 # maximum number of characters that can be loaded from a text artifact
4
- TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH = 100000 # default number of characters to load from a text artifact
6
+ TEXT_ARTIFACT_CONTEXT_DEFAULT_LENGTH = 100000 # default number of characters to load from a text artifact
@@ -58,3 +58,15 @@ class SamConfigBase(BaseModel):
58
58
  def __iter__(self):
59
59
  """Provides dict-like iteration over keys."""
60
60
  return iter(self.model_dump())
61
+
62
+ def pop(self, key: str, default: Any = None) -> Any:
63
+ """
64
+ Provides dict-like .pop() method.
65
+ Removes the attribute and returns its value, or default if not present.
66
+ """
67
+ if hasattr(self, key):
68
+ value = getattr(self, key)
69
+ # Set to None rather than deleting, as Pydantic models don't support delattr
70
+ setattr(self, key, None)
71
+ return value
72
+ return default
@@ -34,7 +34,7 @@ AGENT_DEFAULTS = {
34
34
  "enable_embed_resolution": True,
35
35
  "enable_artifact_content_instruction": True,
36
36
  "tools": "[]",
37
- "session_service_type": USE_DEFAULT_SHARED_SESSION,
37
+ "session_service_type": "sql",
38
38
  "session_service_behavior": "PERSISTENT",
39
39
  "artifact_service_type": USE_DEFAULT_SHARED_ARTIFACT,
40
40
  "artifact_service_base_path": "/tmp/samv2",