solace-agent-mesh 1.0.2__py3-none-any.whl → 1.0.3__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 (97) hide show
  1. solace_agent_mesh/agent/adk/filesystem_artifact_service.py +32 -39
  2. solace_agent_mesh/agent/adk/services.py +140 -59
  3. solace_agent_mesh/agent/protocol/event_handlers.py +70 -116
  4. solace_agent_mesh/agent/sac/app.py +12 -2
  5. solace_agent_mesh/agent/sac/component.py +37 -1
  6. solace_agent_mesh/agent/tools/builtin_artifact_tools.py +8 -1
  7. solace_agent_mesh/agent/tools/peer_agent_tool.py +92 -84
  8. solace_agent_mesh/agent/utils/artifact_helpers.py +148 -14
  9. solace_agent_mesh/assets/docs/404.html +3 -3
  10. solace_agent_mesh/assets/docs/assets/js/{04989206.674a8007.js → 04989206.da8246cd.js} +1 -1
  11. solace_agent_mesh/assets/docs/assets/js/1023fc19.8e6d174c.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/{3d406171.f722eaf5.js → 3d406171.9b081d5f.js} +1 -1
  13. solace_agent_mesh/assets/docs/assets/js/75384d09.c3991823.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/9eff14a2.036c35ea.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/{aba87c2f.d3e2dcc3.js → aba87c2f.a6b84da6.js} +1 -1
  16. solace_agent_mesh/assets/docs/assets/js/{ae4415af.8e279b5d.js → ae4415af.96189a93.js} +1 -1
  17. solace_agent_mesh/assets/docs/assets/js/b7006a3a.38c0cf3d.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/bb2ef573.56931473.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/{cc969b05.954186d4.js → cc969b05.bd3e0d6c.js} +1 -1
  20. solace_agent_mesh/assets/docs/assets/js/f284c35a.5aff74ab.js +1 -0
  21. solace_agent_mesh/assets/docs/assets/js/{f897a61a.f8c53b0f.js → f897a61a.862b0514.js} +1 -1
  22. solace_agent_mesh/assets/docs/assets/js/main.dc7db184.js +2 -0
  23. solace_agent_mesh/assets/docs/assets/js/runtime~main.aa687c82.js +1 -0
  24. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  25. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +8 -6
  27. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +9 -9
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +5 -5
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +5 -5
  43. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +140 -0
  44. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +5 -5
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  57. solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
  58. solace_agent_mesh/assets/docs/lunr-index-1755120326601.json +1 -0
  59. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  60. solace_agent_mesh/assets/docs/search-doc-1755120326601.json +1 -0
  61. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  62. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  63. solace_agent_mesh/cli/__init__.py +1 -1
  64. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +1 -1
  65. solace_agent_mesh/cli/commands/run_cmd.py +42 -3
  66. solace_agent_mesh/cli/main.py +1 -3
  67. solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +1 -0
  68. solace_agent_mesh/client/webui/frontend/static/assets/main-DzKPMTRs.js +673 -0
  69. solace_agent_mesh/client/webui/frontend/static/index.html +2 -2
  70. solace_agent_mesh/common/exceptions.py +25 -0
  71. solace_agent_mesh/common/utils/initializer.py +3 -3
  72. solace_agent_mesh/common/utils/message_utils.py +79 -0
  73. solace_agent_mesh/config_portal/backend/common.py +1 -1
  74. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-_7yox_eh.js → _index-xSu2leR8.js} +1 -1
  75. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-e5c3acfe.js → manifest-950eb3be.js} +1 -1
  76. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  77. solace_agent_mesh/gateway/base/component.py +15 -2
  78. solace_agent_mesh/gateway/http_sse/sse_manager.py +23 -1
  79. solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
  80. {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.3.dist-info}/METADATA +55 -45
  81. {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.3.dist-info}/RECORD +85 -81
  82. solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +0 -1
  83. solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +0 -1
  84. solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +0 -1
  85. solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +0 -1
  86. solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +0 -1
  87. solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +0 -2
  88. solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +0 -1
  89. solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
  90. solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +0 -1
  91. solace_agent_mesh/assets/docs/search-doc-1754075282978.json +0 -1
  92. solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +0 -1
  93. solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +0 -663
  94. /solace_agent_mesh/assets/docs/assets/js/{main.c6286d7c.js.LICENSE.txt → main.dc7db184.js.LICENSE.txt} +0 -0
  95. {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.3.dist-info}/WHEEL +0 -0
  96. {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.3.dist-info}/entry_points.txt +0 -0
  97. {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -246,8 +246,8 @@ class SamAgentApp(App):
246
246
  "type": "string",
247
247
  "required": False,
248
248
  "default": "namespace",
249
- "enum": ["namespace", "app", "custom"],
250
- "description": "Scope for filesystem artifacts: 'namespace' (default, shared by namespace), 'app' (isolated by app name), 'custom' (use artifact_scope_value).",
249
+ "enum": ["namespace", "app"],
250
+ "description": "Process-wide scope for all artifact services. 'namespace' (default): shared by all components in the namespace. 'app': isolated by agent/gateway name. This setting must be consistent for all components in the same process.",
251
251
  },
252
252
  "artifact_scope_value": {
253
253
  "type": "string",
@@ -340,6 +340,13 @@ class SamAgentApp(App):
340
340
  "default": 0,
341
341
  "description": "Minimum size in bytes for accumulated text from LLM stream before sending a status update. If 0 or less, batching is disabled and updates are sent per chunk. Final LLM chunks are always sent regardless of this threshold.",
342
342
  },
343
+ {
344
+ "name": "max_message_size_bytes",
345
+ "required": False,
346
+ "type": "integer",
347
+ "default": 10_000_000,
348
+ "description": "Maximum allowed message size in bytes before rejecting publication to prevent broker disconnections. Default: 10MB",
349
+ },
343
350
  {
344
351
  "name": "enable_artifact_content_instruction",
345
352
  "required": False,
@@ -551,6 +558,7 @@ class SamAgentApp(App):
551
558
  app_config = app_info.get("app_config", {})
552
559
  namespace = app_config.get("namespace")
553
560
  agent_name = app_config.get("agent_name")
561
+ broker_request_response = app_info.get("broker_request_response")
554
562
 
555
563
  if not namespace or not isinstance(namespace, str):
556
564
  raise ValueError(
@@ -619,6 +627,8 @@ class SamAgentApp(App):
619
627
  "component_config": {},
620
628
  "subscriptions": generated_subs,
621
629
  }
630
+ if broker_request_response:
631
+ component_definition["broker_request_response"] = broker_request_response
622
632
 
623
633
  app_info["components"] = [component_definition]
624
634
  log.debug("Replaced 'components' in app_info with programmatic definition.")
@@ -67,7 +67,9 @@ from ...common.a2a_protocol import (
67
67
  get_gateway_status_topic,
68
68
  )
69
69
  from ...agent.utils.config_parser import resolve_instruction_provider
70
- from ...agent.utils.artifact_helpers import get_latest_artifact_version
70
+ from ...agent.utils.artifact_helpers import (
71
+ get_latest_artifact_version,
72
+ )
71
73
  from ...agent.adk.services import (
72
74
  initialize_session_service,
73
75
  initialize_artifact_service,
@@ -92,6 +94,8 @@ from ...agent.adk.invocation_monitor import InvocationMonitor
92
94
  from ...common.middleware.registry import MiddlewareRegistry
93
95
  from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
94
96
  from ...agent.tools.registry import tool_registry
97
+ from ...common.utils.message_utils import validate_message_size
98
+ from ...common.exceptions import MessageSizeExceededError
95
99
 
96
100
  if TYPE_CHECKING:
97
101
  from .task_execution_context import TaskExecutionContext
@@ -232,6 +236,11 @@ class SamAgentComponent(ComponentBase):
232
236
  raise ValueError(
233
237
  "Internal Error: Inter-agent comms config missing after validation."
234
238
  )
239
+
240
+ self.max_message_size_bytes = self.get_config(
241
+ "max_message_size_bytes", 10_000_000
242
+ )
243
+
235
244
  log.info("%s Configuration retrieved successfully.", self.log_identifier)
236
245
  except Exception as e:
237
246
  log.error(
@@ -394,6 +403,7 @@ class SamAgentComponent(ComponentBase):
394
403
  self.session_service = initialize_session_service(self)
395
404
  self.artifact_service = initialize_artifact_service(self)
396
405
  self.memory_service = initialize_memory_service(self)
406
+
397
407
  log.info(
398
408
  "%s Synchronous ADK services initialized.", self.log_identifier
399
409
  )
@@ -2968,6 +2978,29 @@ class SamAgentComponent(ComponentBase):
2968
2978
  ):
2969
2979
  """Helper to publish A2A messages via the SAC App."""
2970
2980
  try:
2981
+ max_size_bytes = self.max_message_size_bytes
2982
+
2983
+ # Validate message size
2984
+ is_valid, actual_size = validate_message_size(
2985
+ payload, max_size_bytes, self.log_identifier
2986
+ )
2987
+
2988
+ if not is_valid:
2989
+ error_msg = (
2990
+ f"Message size validation failed: payload size ({actual_size} bytes) "
2991
+ f"exceeds maximum allowed size ({max_size_bytes} bytes)"
2992
+ )
2993
+ log.error("%s %s", self.log_identifier, error_msg)
2994
+ raise MessageSizeExceededError(actual_size, max_size_bytes, error_msg)
2995
+
2996
+ # Debug logging to show message size when publishing
2997
+ log.debug(
2998
+ "%s Publishing message to topic %s (size: %d bytes)",
2999
+ self.log_identifier,
3000
+ topic,
3001
+ actual_size,
3002
+ )
3003
+
2971
3004
  app = self.get_app()
2972
3005
  if app:
2973
3006
  if self.invocation_monitor:
@@ -2985,6 +3018,9 @@ class SamAgentComponent(ComponentBase):
2985
3018
  "%s Cannot publish message: Not running within a SAC App context.",
2986
3019
  self.log_identifier,
2987
3020
  )
3021
+ except MessageSizeExceededError:
3022
+ # Re-raise MessageSizeExceededError without wrapping
3023
+ raise
2988
3024
  except Exception as e:
2989
3025
  log.exception(
2990
3026
  "%s Failed to publish A2A message to topic %s: %s",
@@ -1888,12 +1888,19 @@ async def delete_artifact(
1888
1888
  "ArtifactService does not support deleting artifacts."
1889
1889
  )
1890
1890
 
1891
+ if version is not None:
1892
+ log.warning(
1893
+ "%s Deleting a specific version (%s) is not supported by the current artifact service interface. "
1894
+ "All versions of the artifact will be deleted.",
1895
+ log_identifier,
1896
+ version,
1897
+ )
1898
+
1891
1899
  await artifact_service.delete_artifact(
1892
1900
  app_name=app_name,
1893
1901
  user_id=user_id,
1894
1902
  session_id=session_id,
1895
1903
  filename=filename,
1896
- version=version,
1897
1904
  )
1898
1905
 
1899
1906
  log.info(
@@ -7,6 +7,7 @@ import uuid
7
7
 
8
8
  from google.adk.tools import BaseTool, ToolContext
9
9
  from google.genai import types as adk_types
10
+ from pydantic import BaseModel, Field
10
11
  from solace_ai_connector.common.log import log
11
12
 
12
13
  from ...common.types import (
@@ -16,6 +17,17 @@ from ...common.types import (
16
17
  AgentCard,
17
18
  )
18
19
  from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
20
+ from ...common.exceptions import MessageSizeExceededError
21
+
22
+ class ArtifactIdentifier(BaseModel):
23
+ """Identifies a specific version of an artifact."""
24
+
25
+ filename: str = Field(..., description="The filename of the artifact.")
26
+ version: Union[str, int] = Field(
27
+ "latest",
28
+ description="The version of the artifact (e.g., 'latest' or a number).",
29
+ )
30
+
19
31
 
20
32
  PEER_TOOL_PREFIX = "peer_"
21
33
  CORRELATION_DATA_PREFIX = "a2a_subtask_"
@@ -24,8 +36,14 @@ CORRELATION_DATA_PREFIX = "a2a_subtask_"
24
36
  class PeerAgentTool(BaseTool):
25
37
  """
26
38
  An ADK Tool that represents a discovered peer agent and handles task delegation
27
- via the A2A protocol over Solace. Uses an asyncio Queue to block and wait for
28
- the asynchronous response. Handles artifact passthrough from peer responses.
39
+ via the A2A protocol over Solace.
40
+
41
+ This tool is long-running and operates in a "fire-and-forget" manner. It sends a
42
+ task to a peer agent, including a `task_description` and an optional list of
43
+ `artifacts` to provide context. The artifact identifiers are passed in the
44
+ A2A message metadata, and the peer agent is responsible for fetching them.
45
+
46
+ The response from the peer is handled asynchronously by the agent's event handlers.
29
47
  """
30
48
 
31
49
  is_long_running = True
@@ -85,10 +103,24 @@ class PeerAgentTool(BaseTool):
85
103
  type=adk_types.Type.STRING,
86
104
  description="The original user query or relevant context.",
87
105
  ),
88
- "data_artifacts": adk_types.Schema(
106
+ "artifacts": adk_types.Schema(
89
107
  type=adk_types.Type.ARRAY,
90
- items=adk_types.Schema(type=adk_types.Type.STRING),
91
- description="A list of artifact filenames to be sent to the peer agent for context.",
108
+ items=adk_types.Schema(
109
+ type=adk_types.Type.OBJECT,
110
+ properties={
111
+ "filename": adk_types.Schema(
112
+ type=adk_types.Type.STRING,
113
+ description="The filename of the artifact.",
114
+ ),
115
+ "version": adk_types.Schema(
116
+ type=adk_types.Type.STRING,
117
+ description="The version of the artifact (e.g., 'latest' or a number). Defaults to 'latest'.",
118
+ nullable=True,
119
+ ),
120
+ },
121
+ required=["filename"],
122
+ ),
123
+ description="A list of artifacts to provide as context to the peer agent.",
92
124
  nullable=True,
93
125
  ),
94
126
  },
@@ -106,11 +138,11 @@ class PeerAgentTool(BaseTool):
106
138
  parameters=parameters_schema,
107
139
  )
108
140
 
109
- async def _prepare_a2a_parts(
141
+ def _prepare_a2a_parts(
110
142
  self, args: Dict[str, Any], tool_context: ToolContext
111
- ) -> List[Union[TextPart, FilePart]]:
143
+ ) -> List[TextPart]:
112
144
  """
113
- Prepares the A2A message parts from tool arguments, including text and artifacts.
145
+ Prepares the A2A message parts from tool arguments.
114
146
  """
115
147
  task_description = args.get("task_description", "No description provided.")
116
148
  calling_agent_name = self.host_component.agent_name or "Unknown Agent"
@@ -118,73 +150,16 @@ class PeerAgentTool(BaseTool):
118
150
  # Create the multi-agent context message
119
151
  context_message = (
120
152
  f"You are part of a multi-agent AI platform. The task below is being sent to you by agent '{calling_agent_name}'. "
121
- f"You must perform this task to the best of your abilities. All artifacts that you create will automatically be "
122
- f"returned to the calling agent, but you must provide context and description for which artifacts are important "
123
- f"and how they should be used. Note that the calling agent will not see any of your history - only the text "
124
- f"that you respond with.\n\n"
153
+ "You must perform this task to the best of your abilities. All artifacts that you create will automatically be "
154
+ "returned to the calling agent, but you must provide context and description for which artifacts are important "
155
+ "and how they should be used. Note that the calling agent will not see any of your history - only the text "
156
+ "that you respond with.\n\n"
157
+ "Note that if the request has not provided the information you need to do your job, you must ask for it. "
158
+ "You must not 'make up' data unless specifically instructed to do so.\n\n"
125
159
  f"Now please execute this task that was given to you:\n\n{task_description}"
126
160
  )
127
161
 
128
- a2a_message_parts = [TextPart(text=context_message)]
129
-
130
- artifact_names = args.get("data_artifacts", [])
131
- if not isinstance(artifact_names, list):
132
- log.warning(
133
- "%s 'data_artifacts' argument is not a list, ignoring. Value: %s",
134
- self.log_identifier,
135
- artifact_names,
136
- )
137
- artifact_names = []
138
-
139
- if artifact_names:
140
- log.debug(
141
- "%s Preparing to include %d artifacts in peer request.",
142
- self.log_identifier,
143
- len(artifact_names),
144
- )
145
- a2a_context = tool_context.state.get("a2a_context", {})
146
- for artifact_name in artifact_names:
147
- try:
148
- loaded_part = (
149
- await self.host_component.artifact_service.load_artifact(
150
- app_name=tool_context._invocation_context.app_name,
151
- user_id=tool_context._invocation_context.user_id,
152
- session_id=tool_context._invocation_context.session.id,
153
- filename=artifact_name,
154
- version=None,
155
- )
156
- )
157
- if loaded_part:
158
- a2a_file_part = await self.host_component._translate_adk_part_to_a2a_filepart(
159
- loaded_part, artifact_name, a2a_context
160
- )
161
- if a2a_file_part:
162
- a2a_message_parts.append(a2a_file_part)
163
- log.info(
164
- "%s Included latest artifact '%s' in peer request.",
165
- self.log_identifier,
166
- artifact_name,
167
- )
168
- else:
169
- log.warning(
170
- "%s Failed to translate loaded artifact '%s' to A2A part. Skipping.",
171
- self.log_identifier,
172
- artifact_name,
173
- )
174
- else:
175
- log.warning(
176
- "%s Could not load artifact '%s' to include in peer request. Skipping.",
177
- self.log_identifier,
178
- artifact_name,
179
- )
180
- except Exception as e:
181
- log.exception(
182
- "%s Error loading artifact '%s' for peer request: %s",
183
- self.log_identifier,
184
- artifact_name,
185
- e,
186
- )
187
- return a2a_message_parts
162
+ return [TextPart(text=context_message)]
188
163
 
189
164
  async def run_async(
190
165
  self, *, args: Dict[str, Any], tool_context: ToolContext
@@ -236,8 +211,29 @@ class PeerAgentTool(BaseTool):
236
211
  task_context_obj.parallel_tool_calls.get(invocation_id),
237
212
  )
238
213
 
239
- a2a_message_parts = await self._prepare_a2a_parts(args, tool_context)
240
- a2a_message = A2AMessage(role="user", parts=a2a_message_parts)
214
+ a2a_message_parts = self._prepare_a2a_parts(args, tool_context)
215
+ a2a_metadata = {}
216
+ raw_artifacts = args.get("artifacts", [])
217
+ if raw_artifacts and isinstance(raw_artifacts, list):
218
+ try:
219
+ # The ADK gives us a list of dicts, not Pydantic models
220
+ # so we can use them directly.
221
+ a2a_metadata["invoked_with_artifacts"] = raw_artifacts
222
+ log.debug(
223
+ "%s Included %d artifact identifiers in A2A message metadata.",
224
+ log_identifier,
225
+ len(raw_artifacts),
226
+ )
227
+ except Exception as e:
228
+ log.warning(
229
+ "%s Failed to serialize artifact identifiers: %s. Proceeding without them.",
230
+ log_identifier,
231
+ e,
232
+ )
233
+
234
+ a2a_message = A2AMessage(
235
+ role="user", parts=a2a_message_parts, metadata=a2a_metadata
236
+ )
241
237
 
242
238
  correlation_data = {
243
239
  "adk_function_call_id": tool_context.function_call_id,
@@ -259,16 +255,28 @@ class PeerAgentTool(BaseTool):
259
255
  component=self.host_component,
260
256
  )
261
257
 
262
- self.host_component.submit_a2a_task(
263
- target_agent_name=self.target_agent_name,
264
- a2a_message=a2a_message,
265
- original_session_id=original_session_id,
266
- main_logical_task_id=main_logical_task_id,
267
- user_id=user_id,
268
- user_config=user_config,
269
- sub_task_id=sub_task_id,
270
- function_call_id=tool_context.function_call_id,
271
- )
258
+ try:
259
+ self.host_component.submit_a2a_task(
260
+ target_agent_name=self.target_agent_name,
261
+ a2a_message=a2a_message,
262
+ original_session_id=original_session_id,
263
+ main_logical_task_id=main_logical_task_id,
264
+ user_id=user_id,
265
+ user_config=user_config,
266
+ sub_task_id=sub_task_id,
267
+ function_call_id=tool_context.function_call_id,
268
+ )
269
+ except MessageSizeExceededError as e:
270
+ log.error(
271
+ "%s Message size exceeded for peer agent request: %s",
272
+ log_identifier,
273
+ e,
274
+ )
275
+ return {
276
+ "status": "error",
277
+ "message": f"Error: {str(e)}. Message size exceeded for peer agent request.",
278
+ }
279
+
272
280
  log.info(
273
281
  "%s Registered active peer sub-task %s for main task %s.",
274
282
  log_identifier,
@@ -8,17 +8,21 @@ import json
8
8
  import csv
9
9
  import io
10
10
  import inspect
11
- import datetime
12
11
  import os
12
+ import yaml
13
+ import traceback
14
+ from datetime import datetime, timezone
13
15
  from typing import Any, Dict, Optional, Tuple, List, Union, TYPE_CHECKING
14
- from datetime import timezone
15
16
  from google.adk.artifacts import BaseArtifactService
16
17
  from google.genai import types as adk_types
17
18
  from solace_ai_connector.common.log import log
19
+ from ...common.types import ArtifactInfo
18
20
  from ...common.utils.mime_helpers import is_text_based_mime_type, is_text_based_file
21
+ from ...agent.utils.context_helpers import get_original_session_id
19
22
 
20
23
  if TYPE_CHECKING:
21
24
  from google.adk.tools import ToolContext
25
+ from ...agent.sac.component import SamAgentComponent
22
26
 
23
27
  METADATA_SUFFIX = ".metadata.json"
24
28
  DEFAULT_SCHEMA_MAX_KEYS = 20
@@ -155,13 +159,9 @@ def _infer_schema(
155
159
  "text/x-yaml",
156
160
  ]:
157
161
  try:
158
- import yaml
159
-
160
162
  data = yaml.safe_load(content_bytes)
161
163
  schema_info["structure"] = _inspect_structure(data, depth, max_keys)
162
164
  schema_info["inferred"] = True
163
- except ImportError:
164
- schema_info["error"] = "YAML inference skipped: PyYAML not installed."
165
165
  except (yaml.YAMLError, UnicodeDecodeError) as e:
166
166
  schema_info["error"] = f"YAML structure inference failed: {e}"
167
167
  except Exception as e:
@@ -188,7 +188,7 @@ async def save_artifact_with_metadata(
188
188
  content_bytes: bytes,
189
189
  mime_type: str,
190
190
  metadata_dict: Dict[str, Any],
191
- timestamp: datetime.datetime,
191
+ timestamp: datetime,
192
192
  explicit_schema: Optional[Dict] = None,
193
193
  schema_inference_depth: int = 2,
194
194
  schema_max_keys: int = DEFAULT_SCHEMA_MAX_KEYS,
@@ -399,6 +399,143 @@ def format_metadata_for_llm(metadata: Dict[str, Any]) -> str:
399
399
  return "\n".join(lines)
400
400
 
401
401
 
402
+ async def generate_artifact_metadata_summary(
403
+ component: "SamAgentComponent",
404
+ artifact_identifiers: List[Dict[str, Any]],
405
+ user_id: str,
406
+ session_id: str,
407
+ app_name: str,
408
+ header_text: Optional[str] = None,
409
+ ) -> str:
410
+ """
411
+ Loads metadata for a list of artifacts and formats it into a human-readable
412
+ YAML summary string, suitable for LLM context.
413
+ """
414
+ if not artifact_identifiers:
415
+ return ""
416
+
417
+ log_identifier = f"{component.log_identifier}[ArtifactSummary]"
418
+ summary_parts = []
419
+ if header_text:
420
+ summary_parts.append(header_text)
421
+
422
+ if not (component.artifact_service and user_id and session_id):
423
+ log.warning(
424
+ "%s Cannot load artifact metadata: missing artifact_service or context.",
425
+ log_identifier,
426
+ )
427
+ for artifact_ref in artifact_identifiers:
428
+ filename = artifact_ref.get("filename", "unknown")
429
+ version = artifact_ref.get("version", "latest")
430
+ summary_parts.append(
431
+ f"---\nArtifact: '{filename}' (version: {version})\nError: Could not load metadata. Host component context missing."
432
+ )
433
+ return "\n\n".join(summary_parts)
434
+
435
+ for artifact_ref in artifact_identifiers:
436
+ filename = artifact_ref.get("filename")
437
+ version = artifact_ref.get("version", "latest")
438
+ if not filename:
439
+ log.warning(
440
+ "%s Skipping artifact with no filename in identifier: %s",
441
+ log_identifier,
442
+ artifact_ref,
443
+ )
444
+ continue
445
+
446
+ try:
447
+ metadata_result = await load_artifact_content_or_metadata(
448
+ artifact_service=component.artifact_service,
449
+ app_name=app_name,
450
+ user_id=user_id,
451
+ session_id=get_original_session_id(session_id),
452
+ filename=filename,
453
+ version=version,
454
+ load_metadata_only=True,
455
+ )
456
+ if metadata_result.get("status") == "success":
457
+ metadata = metadata_result.get("metadata", {})
458
+ resolved_version = metadata_result.get("version", version)
459
+ artifact_header = (
460
+ f"Artifact: '{filename}' (version: {resolved_version})"
461
+ )
462
+
463
+ # Remove redundant fields before dumping to YAML
464
+ metadata.pop("filename", None)
465
+ metadata.pop("version", None)
466
+
467
+ TRUNCATION_LIMIT_BYTES = 1024
468
+ TRUNCATION_MESSAGE = "\n... [truncated] ..."
469
+
470
+ try:
471
+ formatted_metadata_str = yaml.safe_dump(
472
+ metadata,
473
+ default_flow_style=False,
474
+ sort_keys=False,
475
+ allow_unicode=True,
476
+ )
477
+
478
+ if (
479
+ len(formatted_metadata_str.encode("utf-8"))
480
+ > TRUNCATION_LIMIT_BYTES
481
+ ):
482
+ cutoff = TRUNCATION_LIMIT_BYTES - len(
483
+ TRUNCATION_MESSAGE.encode("utf-8")
484
+ )
485
+ # Ensure we don't cut in the middle of a multi-byte character
486
+ encoded_str = formatted_metadata_str.encode("utf-8")
487
+ if cutoff > 0:
488
+ truncated_encoded = encoded_str[:cutoff]
489
+ formatted_metadata_str = (
490
+ truncated_encoded.decode("utf-8", "ignore")
491
+ + TRUNCATION_MESSAGE
492
+ )
493
+ else:
494
+ formatted_metadata_str = TRUNCATION_MESSAGE
495
+
496
+ summary_parts.append(
497
+ f"---\n{artifact_header}\n{formatted_metadata_str}"
498
+ )
499
+ except Exception as e_format:
500
+ log.error(
501
+ "%s Error formatting metadata for %s v%s: %s",
502
+ log_identifier,
503
+ filename,
504
+ version,
505
+ e_format,
506
+ )
507
+ summary_parts.append(
508
+ f"---\n{artifact_header}\nError: Could not format metadata."
509
+ )
510
+ else:
511
+ error_message = metadata_result.get(
512
+ "message", "Could not load metadata."
513
+ )
514
+ log.warning(
515
+ "%s Failed to load metadata for %s v%s: %s",
516
+ log_identifier,
517
+ filename,
518
+ version,
519
+ error_message,
520
+ )
521
+ artifact_header = f"Artifact: '{filename}' (version: {version})"
522
+ summary_parts.append(f"---\n{artifact_header}\nError: {error_message}")
523
+ except Exception as e_meta:
524
+ log.error(
525
+ "%s Unexpected error loading metadata for %s v%s: %s",
526
+ log_identifier,
527
+ filename,
528
+ version,
529
+ e_meta,
530
+ )
531
+ artifact_header = f"Artifact: '{filename}' (version: {version})"
532
+ summary_parts.append(
533
+ f"---\n{artifact_header}\nError: An unexpected error occurred while loading metadata."
534
+ )
535
+
536
+ return "\n\n".join(summary_parts)
537
+
538
+
402
539
  def decode_and_get_bytes(
403
540
  content_str: str, mime_type: str, log_identifier: str
404
541
  ) -> Tuple[bytes, str]:
@@ -441,12 +578,6 @@ def decode_and_get_bytes(
441
578
  return file_bytes, final_mime_type
442
579
 
443
580
 
444
- from google.adk.artifacts import BaseArtifactService
445
- from datetime import datetime, timezone
446
- import traceback
447
- from ...common.types import ArtifactInfo
448
-
449
-
450
581
  async def get_latest_artifact_version(
451
582
  artifact_service: BaseArtifactService,
452
583
  app_name: str,
@@ -476,7 +607,10 @@ async def get_latest_artifact_version(
476
607
  return None
477
608
 
478
609
  versions = await artifact_service.list_versions(
479
- app_name=app_name, user_id=user_id, session_id=session_id, filename=filename
610
+ app_name=app_name,
611
+ user_id=user_id,
612
+ session_id=session_id,
613
+ filename=filename,
480
614
  )
481
615
  if not versions:
482
616
  log.debug("%s No versions found for artifact.", log_identifier)