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
@@ -23,49 +23,40 @@ class FilesystemArtifactService(BaseArtifactService):
23
23
  """
24
24
  An artifact service implementation using the local filesystem.
25
25
 
26
- Stores artifacts in a structured directory based on a configured scope
27
- (namespace, app name, or custom), user ID, session ID (or 'user' namespace),
26
+ Stores artifacts in a structured directory based on the effective app name
27
+ (which represents the scope), user ID, session ID (or 'user' namespace),
28
28
  filename, and version. Metadata (like mime_type) is stored in a companion file.
29
29
  """
30
30
 
31
- def __init__(self, base_path: str, scope_identifier: str):
31
+ def __init__(self, base_path: str):
32
32
  """
33
33
  Initializes the FilesystemArtifactService.
34
34
 
35
35
  Args:
36
36
  base_path: The root directory where all artifacts will be stored.
37
- scope_identifier: The sanitized identifier representing the storage scope
38
- (e.g., sanitized namespace, app name, or custom value).
39
37
 
40
38
  Raises:
41
- ValueError: If base_path or scope_identifier is not provided or the
42
- scoped base path cannot be created.
39
+ ValueError: If base_path is not provided or cannot be created.
43
40
  """
44
41
  if not base_path:
45
42
  raise ValueError("base_path cannot be empty for FilesystemArtifactService")
46
- if not scope_identifier:
47
- raise ValueError(
48
- "scope_identifier cannot be empty for FilesystemArtifactService"
49
- )
50
43
 
51
44
  self.base_path = os.path.abspath(base_path)
52
- self.scope_identifier = scope_identifier
53
- self.scope_base_path = os.path.join(self.base_path, self.scope_identifier)
54
45
 
55
46
  try:
56
- os.makedirs(self.scope_base_path, exist_ok=True)
47
+ os.makedirs(self.base_path, exist_ok=True)
57
48
  logger.info(
58
- "FilesystemArtifactService initialized. Scoped base path: %s",
59
- self.scope_base_path,
49
+ "FilesystemArtifactService initialized. Base path: %s",
50
+ self.base_path,
60
51
  )
61
52
  except OSError as e:
62
53
  logger.error(
63
- "Failed to create scoped base directory '%s': %s",
64
- self.scope_base_path,
54
+ "Failed to create base directory '%s': %s",
55
+ self.base_path,
65
56
  e,
66
57
  )
67
58
  raise ValueError(
68
- f"Could not create or access scoped base_path '{self.scope_base_path}': {e}"
59
+ f"Could not create or access base_path '{self.base_path}': {e}"
69
60
  ) from e
70
61
 
71
62
  def _file_has_user_namespace(self, filename: str) -> bool:
@@ -76,10 +67,10 @@ class FilesystemArtifactService(BaseArtifactService):
76
67
  self, app_name: str, user_id: str, session_id: str, filename: str
77
68
  ) -> str:
78
69
  """
79
- Constructs the directory path for a specific artifact (all versions)
80
- within the configured scope.
81
- The app_name parameter is ignored for path construction but kept for signature compatibility.
70
+ Constructs the directory path for a specific artifact (all versions).
71
+ The `app_name` is now the effective scope identifier, resolved by the caller.
82
72
  """
73
+ app_name_sanitized = os.path.basename(app_name)
83
74
  user_id_sanitized = os.path.basename(user_id)
84
75
  session_id_sanitized = os.path.basename(session_id)
85
76
  filename_sanitized = os.path.basename(filename)
@@ -87,11 +78,16 @@ class FilesystemArtifactService(BaseArtifactService):
87
78
  if self._file_has_user_namespace(filename):
88
79
  filename_dir = os.path.basename(filename.split(":", 1)[1])
89
80
  return os.path.join(
90
- self.scope_base_path, user_id_sanitized, "user", filename_dir
81
+ self.base_path,
82
+ app_name_sanitized,
83
+ user_id_sanitized,
84
+ "user",
85
+ filename_dir,
91
86
  )
92
87
  else:
93
88
  return os.path.join(
94
- self.scope_base_path,
89
+ self.base_path,
90
+ app_name_sanitized,
95
91
  user_id_sanitized,
96
92
  session_id_sanitized,
97
93
  filename_sanitized,
@@ -115,7 +111,7 @@ class FilesystemArtifactService(BaseArtifactService):
115
111
  filename: str,
116
112
  artifact: adk_types.Part,
117
113
  ) -> int:
118
- log_prefix = f"[FSArtifact:Save:{filename}] "
114
+ log_prefix = "[FSArtifact:Save] "
119
115
 
120
116
  filename = self._normalize_filename_unicode(filename)
121
117
  artifact_dir = self._get_artifact_dir(app_name, user_id, session_id, filename)
@@ -192,7 +188,7 @@ class FilesystemArtifactService(BaseArtifactService):
192
188
  filename: str,
193
189
  version: Optional[int] = None,
194
190
  ) -> Optional[adk_types.Part]:
195
- log_prefix = f"[FSArtifact:Load:{filename}] "
191
+ log_prefix = "[FSArtifact:Load] "
196
192
  filename = self._normalize_filename_unicode(filename)
197
193
  artifact_dir = self._get_artifact_dir(app_name, user_id, session_id, filename)
198
194
 
@@ -271,13 +267,14 @@ class FilesystemArtifactService(BaseArtifactService):
271
267
  async def list_artifact_keys(
272
268
  self, *, app_name: str, user_id: str, session_id: str
273
269
  ) -> List[str]:
274
- log_prefix = f"[FSArtifact:ListKeys] "
270
+ log_prefix = "[FSArtifact:ListKeys] "
275
271
  filenames = set()
272
+ app_name_sanitized = os.path.basename(app_name)
276
273
  user_id_sanitized = os.path.basename(user_id)
277
274
  session_id_sanitized = os.path.basename(session_id)
278
275
 
279
276
  session_base_dir = os.path.join(
280
- self.scope_base_path, user_id_sanitized, session_id_sanitized
277
+ self.base_path, app_name_sanitized, user_id_sanitized, session_id_sanitized
281
278
  )
282
279
  if await asyncio.to_thread(os.path.isdir, session_base_dir):
283
280
  try:
@@ -293,7 +290,9 @@ class FilesystemArtifactService(BaseArtifactService):
293
290
  e,
294
291
  )
295
292
 
296
- user_base_dir = os.path.join(self.scope_base_path, user_id_sanitized, "user")
293
+ user_base_dir = os.path.join(
294
+ self.base_path, app_name_sanitized, user_id_sanitized, "user"
295
+ )
297
296
  if await asyncio.to_thread(os.path.isdir, user_base_dir):
298
297
  try:
299
298
  for item in await asyncio.to_thread(os.listdir, user_base_dir):
@@ -316,7 +315,7 @@ class FilesystemArtifactService(BaseArtifactService):
316
315
  async def delete_artifact(
317
316
  self, *, app_name: str, user_id: str, session_id: str, filename: str
318
317
  ) -> None:
319
- log_prefix = f"[FSArtifact:Delete:{filename}] "
318
+ log_prefix = "[FSArtifact:Delete] "
320
319
  artifact_dir = self._get_artifact_dir(app_name, user_id, session_id, filename)
321
320
 
322
321
  if not await asyncio.to_thread(os.path.isdir, artifact_dir):
@@ -332,9 +331,8 @@ class FilesystemArtifactService(BaseArtifactService):
332
331
  )
333
332
  except OSError as e:
334
333
  logger.error(
335
- "%sError deleting artifact directory '%s': %s",
334
+ "%sError deleting artifact directory '%s'",
336
335
  log_prefix,
337
- artifact_dir,
338
336
  e,
339
337
  )
340
338
 
@@ -342,7 +340,7 @@ class FilesystemArtifactService(BaseArtifactService):
342
340
  async def list_versions(
343
341
  self, *, app_name: str, user_id: str, session_id: str, filename: str
344
342
  ) -> List[int]:
345
- log_prefix = f"[FSArtifact:ListVersions:{filename}] "
343
+ log_prefix = "[FSArtifact:ListVersions] "
346
344
  artifact_dir = self._get_artifact_dir(app_name, user_id, session_id, filename)
347
345
  versions = []
348
346
 
@@ -360,12 +358,7 @@ class FilesystemArtifactService(BaseArtifactService):
360
358
  ):
361
359
  versions.append(int(item))
362
360
  except OSError as e:
363
- logger.error(
364
- "%sError listing versions in directory '%s': %s",
365
- log_prefix,
366
- artifact_dir,
367
- e,
368
- )
361
+ logger.error("%sError listing versions in directory '%s'", log_prefix, e)
369
362
  return []
370
363
 
371
364
  sorted_versions = sorted(versions)
@@ -4,8 +4,10 @@ Initializes ADK Services based on configuration.
4
4
 
5
5
  import os
6
6
  import re
7
- from typing import Dict
7
+ from typing import Dict, Optional, List, Any
8
+ from typing_extensions import override
8
9
 
10
+ from google.genai import types as adk_types
9
11
  from solace_ai_connector.common.log import log
10
12
 
11
13
  from google.adk.sessions import (
@@ -35,6 +37,119 @@ except ImportError:
35
37
  TestInMemoryArtifactService = None
36
38
 
37
39
 
40
+ class ScopedArtifactServiceWrapper(BaseArtifactService):
41
+ """
42
+ A wrapper for an artifact service that transparently applies a configured scope.
43
+ This ensures all artifact operations respect either 'namespace' or 'app' scoping
44
+ without requiring changes at the call site. It dynamically checks the component's
45
+ configuration on each call to support test-specific overrides.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ wrapped_service: BaseArtifactService,
51
+ component: Any,
52
+ ):
53
+ """
54
+ Initializes the ScopedArtifactServiceWrapper.
55
+
56
+ Args:
57
+ wrapped_service: The concrete artifact service instance (e.g., InMemory, GCS).
58
+ component: The component instance (agent or gateway) that owns this service.
59
+ """
60
+ self.wrapped_service = wrapped_service
61
+ self.component = component
62
+
63
+ def _get_scoped_app_name(self, app_name: str) -> str:
64
+ """
65
+ Determines the effective app_name for an artifact operation by dynamically
66
+ checking the component's configuration.
67
+ """
68
+ # The component's get_config will handle test-injected overrides.
69
+ # The default scope is 'namespace' as defined in the app schema.
70
+ scope_type = self.component.get_config("artifact_scope", "namespace")
71
+
72
+ if scope_type == "namespace":
73
+ # For namespace scope, the value is always the component's namespace.
74
+ return self.component.namespace
75
+
76
+ # For 'app' scope, use the app_name that was passed into the method, which is
77
+ # typically the agent_name or gateway_id.
78
+ return app_name
79
+
80
+ @override
81
+ async def save_artifact(
82
+ self,
83
+ *,
84
+ app_name: str,
85
+ user_id: str,
86
+ session_id: str,
87
+ filename: str,
88
+ artifact: adk_types.Part,
89
+ ) -> int:
90
+ scoped_app_name = self._get_scoped_app_name(app_name)
91
+ return await self.wrapped_service.save_artifact(
92
+ app_name=scoped_app_name,
93
+ user_id=user_id,
94
+ session_id=session_id,
95
+ filename=filename,
96
+ artifact=artifact,
97
+ )
98
+
99
+ @override
100
+ async def load_artifact(
101
+ self,
102
+ *,
103
+ app_name: str,
104
+ user_id: str,
105
+ session_id: str,
106
+ filename: str,
107
+ version: Optional[int] = None,
108
+ ) -> Optional[adk_types.Part]:
109
+ scoped_app_name = self._get_scoped_app_name(app_name)
110
+ return await self.wrapped_service.load_artifact(
111
+ app_name=scoped_app_name,
112
+ user_id=user_id,
113
+ session_id=session_id,
114
+ filename=filename,
115
+ version=version,
116
+ )
117
+
118
+ @override
119
+ async def list_artifact_keys(
120
+ self, *, app_name: str, user_id: str, session_id: str
121
+ ) -> List[str]:
122
+ scoped_app_name = self._get_scoped_app_name(app_name)
123
+ return await self.wrapped_service.list_artifact_keys(
124
+ app_name=scoped_app_name, user_id=user_id, session_id=session_id
125
+ )
126
+
127
+ @override
128
+ async def delete_artifact(
129
+ self, *, app_name: str, user_id: str, session_id: str, filename: str
130
+ ) -> None:
131
+ scoped_app_name = self._get_scoped_app_name(app_name)
132
+ await self.wrapped_service.delete_artifact(
133
+ app_name=scoped_app_name,
134
+ user_id=user_id,
135
+ session_id=session_id,
136
+ filename=filename,
137
+ )
138
+ return
139
+
140
+ @override
141
+ async def list_versions(
142
+ self, *, app_name: str, user_id: str, session_id: str, filename: str
143
+ ) -> List[int]:
144
+ scoped_app_name = self._get_scoped_app_name(app_name)
145
+ return await self.wrapped_service.list_versions(
146
+ app_name=scoped_app_name,
147
+ user_id=user_id,
148
+ session_id=session_id,
149
+ filename=filename,
150
+ )
151
+
152
+
38
153
  def _sanitize_for_path(identifier: str) -> str:
39
154
  """Sanitizes a string to be safe for use as a directory name."""
40
155
  if not identifier:
@@ -88,7 +203,11 @@ def initialize_session_service(component) -> BaseSessionService:
88
203
 
89
204
 
90
205
  def initialize_artifact_service(component) -> BaseArtifactService:
91
- """Initializes the ADK Artifact Service based on configuration."""
206
+ """
207
+ Initializes the ADK Artifact Service based on configuration.
208
+ This factory creates the concrete service instance and then wraps it with
209
+ the ScopedArtifactServiceWrapper to enforce artifact scoping rules dynamically.
210
+ """
92
211
  config: Dict = component.get_config("artifact_service", {"type": "memory"})
93
212
  service_type = config.get("type", "memory").lower()
94
213
  log.info(
@@ -97,8 +216,9 @@ def initialize_artifact_service(component) -> BaseArtifactService:
97
216
  service_type,
98
217
  )
99
218
 
219
+ concrete_service: BaseArtifactService
100
220
  if service_type == "memory":
101
- return InMemoryArtifactService()
221
+ concrete_service = InMemoryArtifactService()
102
222
  elif service_type == "gcs":
103
223
  bucket_name = config.get("bucket_name")
104
224
  if not bucket_name:
@@ -107,9 +227,11 @@ def initialize_artifact_service(component) -> BaseArtifactService:
107
227
  )
108
228
  try:
109
229
  gcs_args = {
110
- k: v for k, v in config.items() if k not in ["type", "bucket_name"]
230
+ k: v
231
+ for k, v in config.items()
232
+ if k not in ["type", "bucket_name", "artifact_scope"]
111
233
  }
112
- return GcsArtifactService(bucket_name=bucket_name, **gcs_args)
234
+ concrete_service = GcsArtifactService(bucket_name=bucket_name, **gcs_args)
113
235
  except ImportError:
114
236
  log.error(
115
237
  "%s google-cloud-storage not installed. Please install 'google-adk[gcs]' or 'google-cloud-storage'.",
@@ -123,60 +245,8 @@ def initialize_artifact_service(component) -> BaseArtifactService:
123
245
  f"{component.log_identifier} 'base_path' is required for filesystem artifact service."
124
246
  )
125
247
 
126
- artifact_scope = config.get("artifact_scope", "namespace").lower()
127
- scope_identifier_raw = None
128
-
129
- if artifact_scope == "app":
130
- app_instance = component.get_app()
131
- if not app_instance or not app_instance.name:
132
- raise ValueError(
133
- f"{component.log_identifier} Cannot determine app name for 'app' scope."
134
- )
135
- scope_identifier_raw = app_instance.name
136
- log.info(
137
- "%s Using 'app' scope for filesystem artifacts: %s",
138
- component.log_identifier,
139
- scope_identifier_raw,
140
- )
141
- elif artifact_scope == "namespace":
142
- scope_identifier_raw = component.get_config("namespace")
143
- log.info(
144
- "%s Using 'namespace' scope for filesystem artifacts: %s",
145
- component.log_identifier,
146
- scope_identifier_raw,
147
- )
148
- elif artifact_scope == "custom":
149
- scope_identifier_raw = config.get("artifact_scope_value")
150
- if not scope_identifier_raw:
151
- raise ValueError(
152
- f"{component.log_identifier} 'artifact_scope_value' is required when artifact_scope is 'custom'."
153
- )
154
- log.info(
155
- "%s Using 'custom' scope for filesystem artifacts: %s",
156
- component.log_identifier,
157
- scope_identifier_raw,
158
- )
159
- else:
160
- raise ValueError(
161
- f"{component.log_identifier} Invalid 'artifact_scope' value: {artifact_scope}"
162
- )
163
-
164
- if not scope_identifier_raw:
165
- raise ValueError(
166
- f"{component.log_identifier} Failed to determine scope identifier for filesystem artifacts."
167
- )
168
-
169
- scope_identifier_sanitized = _sanitize_for_path(scope_identifier_raw)
170
- log.info(
171
- "%s Sanitized scope identifier: %s",
172
- component.log_identifier,
173
- scope_identifier_sanitized,
174
- )
175
-
176
248
  try:
177
- return FilesystemArtifactService(
178
- base_path=base_path, scope_identifier=scope_identifier_sanitized
179
- )
249
+ concrete_service = FilesystemArtifactService(base_path=base_path)
180
250
  except Exception as e:
181
251
  log.error(
182
252
  "%s Failed to initialize FilesystemArtifactService: %s",
@@ -196,12 +266,23 @@ def initialize_artifact_service(component) -> BaseArtifactService:
196
266
  "%s Using TestInMemoryArtifactService for testing.",
197
267
  component.log_identifier,
198
268
  )
199
- return TestInMemoryArtifactService()
269
+ concrete_service = TestInMemoryArtifactService()
200
270
  else:
201
271
  raise ValueError(
202
272
  f"{component.log_identifier} Unsupported artifact service type: {service_type}"
203
273
  )
204
274
 
275
+ # Wrap the concrete service to enforce scoping dynamically.
276
+ # The wrapper will query the component's config at runtime.
277
+ log.info(
278
+ "%s Wrapping artifact service with dynamic ScopedArtifactServiceWrapper.",
279
+ component.log_identifier,
280
+ )
281
+ return ScopedArtifactServiceWrapper(
282
+ wrapped_service=concrete_service,
283
+ component=component,
284
+ )
285
+
205
286
 
206
287
  def initialize_memory_service(component) -> BaseMemoryService:
207
288
  """Initializes the ADK Memory Service based on configuration."""
@@ -32,6 +32,7 @@ from ...common.types import (
32
32
  TaskStatus,
33
33
  TaskState,
34
34
  DataPart,
35
+ TextPart,
35
36
  A2ARequest,
36
37
  )
37
38
  from ...common.a2a_protocol import (
@@ -44,6 +45,7 @@ from ...common.a2a_protocol import (
44
45
  _extract_text_from_parts,
45
46
  )
46
47
  from ...agent.utils.artifact_helpers import (
48
+ generate_artifact_metadata_summary,
47
49
  load_artifact_content_or_metadata,
48
50
  )
49
51
  from ...agent.adk.runner import run_adk_async_task_thread_wrapper
@@ -57,114 +59,6 @@ from google.adk.events import Event as ADKEvent
57
59
  from google.genai import types as adk_types
58
60
 
59
61
 
60
- async def _format_artifact_summary_from_manifest(
61
- component: "SamAgentComponent",
62
- produced_artifacts: List[Dict[str, Any]],
63
- peer_agent_name: str,
64
- correlation_data: Dict[str, Any],
65
- ) -> str:
66
- """
67
- Loads metadata for a list of produced artifacts and formats it into a
68
- human-readable YAML summary string.
69
- """
70
- if not produced_artifacts:
71
- return ""
72
-
73
- artifact_summary_lines = [
74
- f"Peer agent `{peer_agent_name}` created {len(produced_artifacts)} artifact(s):"
75
- ]
76
-
77
- original_task_context = correlation_data.get("original_task_context", {})
78
- user_id = original_task_context.get("user_id")
79
- session_id = original_task_context.get("session_id")
80
-
81
- if not (component.artifact_service and user_id and session_id):
82
- log.warning(
83
- "%s Cannot load artifact metadata: missing artifact_service or context.",
84
- component.log_identifier,
85
- )
86
- for artifact_ref in produced_artifacts:
87
- artifact_summary_lines.append(
88
- f"- `{artifact_ref.get('filename')}` (v{artifact_ref.get('version')})"
89
- )
90
- return "\n".join(artifact_summary_lines)
91
-
92
- peer_agent_name_for_artifact = peer_agent_name
93
- if (
94
- not peer_agent_name_for_artifact
95
- or peer_agent_name_for_artifact == "A peer agent"
96
- ):
97
- log.warning(
98
- "%s Peer agent name not in task metadata, using self agent name for artifact loading.",
99
- component.log_identifier,
100
- )
101
- peer_agent_name_for_artifact = component.agent_name
102
-
103
- for artifact_ref in produced_artifacts:
104
- filename = artifact_ref.get("filename")
105
- version = artifact_ref.get("version")
106
- if not filename or version is None:
107
- continue
108
-
109
- try:
110
- metadata_result = await load_artifact_content_or_metadata(
111
- artifact_service=component.artifact_service,
112
- app_name=peer_agent_name_for_artifact,
113
- user_id=user_id,
114
- session_id=session_id,
115
- filename=filename,
116
- version=version,
117
- load_metadata_only=True,
118
- )
119
- if metadata_result.get("status") == "success":
120
- metadata = metadata_result.get("metadata", {})
121
- TRUNCATION_LIMIT_BYTES = 1024
122
- TRUNCATION_MESSAGE = "\n... [truncated] ..."
123
-
124
- try:
125
- formatted_metadata_str = yaml.safe_dump(
126
- metadata,
127
- default_flow_style=False,
128
- sort_keys=False,
129
- allow_unicode=True,
130
- )
131
-
132
- if (
133
- len(formatted_metadata_str.encode("utf-8"))
134
- > TRUNCATION_LIMIT_BYTES
135
- ):
136
- cutoff = TRUNCATION_LIMIT_BYTES - len(
137
- TRUNCATION_MESSAGE.encode("utf-8")
138
- )
139
- formatted_metadata_str = (
140
- formatted_metadata_str[:cutoff] + TRUNCATION_MESSAGE
141
- )
142
-
143
- summary_line = f"- `{filename}` (v{version}):\n ```yaml\n{formatted_metadata_str}\n ```"
144
- artifact_summary_lines.append(summary_line)
145
- except Exception as e_format:
146
- log.error(
147
- "Error formatting metadata for %s v%s: %s",
148
- filename,
149
- version,
150
- e_format,
151
- )
152
- artifact_summary_lines.append(
153
- f"- `{filename}` (v{version}): Error formatting metadata."
154
- )
155
- else:
156
- artifact_summary_lines.append(
157
- f"- `{filename}` (v{version}): Could not load metadata."
158
- )
159
- except Exception as e_meta:
160
- log.error(
161
- "Error loading metadata for %s v%s: %s", filename, version, e_meta
162
- )
163
- artifact_summary_lines.append(
164
- f"- `{filename}` (v{version}): Error loading metadata."
165
- )
166
-
167
- return "\n".join(artifact_summary_lines)
168
62
 
169
63
 
170
64
  def _register_peer_artifacts_in_parent_context(
@@ -610,8 +504,51 @@ async def handle_a2a_request(component, message: SolaceMessage):
610
504
  logical_task_id,
611
505
  )
612
506
 
507
+ a2a_message_for_adk = a2a_request.params.message
508
+ invoked_artifacts = (
509
+ a2a_message_for_adk.metadata.get("invoked_with_artifacts", [])
510
+ if a2a_message_for_adk.metadata
511
+ else []
512
+ )
513
+
514
+ if invoked_artifacts:
515
+ log.info(
516
+ "%s Task %s invoked with %d artifact(s). Preparing context from metadata.",
517
+ component.log_identifier,
518
+ task_id,
519
+ len(invoked_artifacts),
520
+ )
521
+ header_text = (
522
+ "The user has provided the following artifacts as context for your task. "
523
+ "Use the information contained within their metadata to complete your objective."
524
+ )
525
+ artifact_summary = await generate_artifact_metadata_summary(
526
+ component=component,
527
+ artifact_identifiers=invoked_artifacts,
528
+ user_id=user_id,
529
+ session_id=effective_session_id,
530
+ app_name=agent_name,
531
+ header_text=header_text,
532
+ )
533
+
534
+ task_description = _extract_text_from_parts(
535
+ a2a_message_for_adk.parts
536
+ )
537
+ final_prompt = f"{task_description}\n\n{artifact_summary}"
538
+
539
+ a2a_message_for_adk = A2AMessage(
540
+ role="user",
541
+ parts=[TextPart(text=final_prompt)],
542
+ metadata=a2a_message_for_adk.metadata,
543
+ )
544
+ log.debug(
545
+ "%s Generated new prompt for task %s with artifact context.",
546
+ component.log_identifier,
547
+ task_id,
548
+ )
549
+
613
550
  adk_content = translate_a2a_to_adk_content(
614
- a2a_request.params.message, component.log_identifier
551
+ a2a_message_for_adk, component.log_identifier
615
552
  )
616
553
 
617
554
  adk_session = await component.session_service.get_session(
@@ -1284,14 +1221,31 @@ async def handle_a2a_response(component, message: SolaceMessage):
1284
1221
  peer_agent_name = task_obj.metadata.get(
1285
1222
  "agent_name", "A peer agent"
1286
1223
  )
1287
- artifact_summary = (
1288
- await _format_artifact_summary_from_manifest(
1289
- component,
1290
- produced_artifacts,
1291
- peer_agent_name,
1292
- correlation_data,
1293
- )
1224
+ original_task_context = correlation_data.get(
1225
+ "original_task_context", {}
1294
1226
  )
1227
+ user_id = original_task_context.get("user_id")
1228
+ session_id = original_task_context.get("session_id")
1229
+
1230
+ header_text = f"Peer agent `{peer_agent_name}` created {len(produced_artifacts)} artifact(s):"
1231
+
1232
+ if user_id and session_id:
1233
+ artifact_summary = (
1234
+ await generate_artifact_metadata_summary(
1235
+ component=component,
1236
+ artifact_identifiers=produced_artifacts,
1237
+ user_id=user_id,
1238
+ session_id=session_id,
1239
+ app_name=peer_agent_name,
1240
+ header_text=header_text,
1241
+ )
1242
+ )
1243
+ else:
1244
+ log.warning(
1245
+ "%s Could not generate artifact summary: missing user_id or session_id in correlation data.",
1246
+ log_retrigger,
1247
+ )
1248
+ artifact_summary = ""
1295
1249
  # Bubble up the peer's artifacts to the parent context
1296
1250
  _register_peer_artifacts_in_parent_context(
1297
1251
  task_context, task_obj, log_retrigger