solace-agent-mesh 1.0.2__py3-none-any.whl → 1.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/__init__.py +0 -5
- solace_agent_mesh/agent/adk/filesystem_artifact_service.py +32 -39
- solace_agent_mesh/agent/adk/services.py +140 -59
- solace_agent_mesh/agent/protocol/event_handlers.py +70 -116
- solace_agent_mesh/agent/sac/app.py +12 -2
- solace_agent_mesh/agent/sac/component.py +37 -1
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +8 -1
- solace_agent_mesh/agent/tools/peer_agent_tool.py +92 -84
- solace_agent_mesh/agent/utils/artifact_helpers.py +148 -14
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{04989206.674a8007.js → 04989206.da8246cd.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/1023fc19.8e6d174c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{3d406171.f722eaf5.js → 3d406171.9b081d5f.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/75384d09.c3991823.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9eff14a2.036c35ea.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{aba87c2f.d3e2dcc3.js → aba87c2f.a6b84da6.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/{ae4415af.8e279b5d.js → ae4415af.96189a93.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.38c0cf3d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.56931473.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{cc969b05.954186d4.js → cc969b05.bd3e0d6c.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5aff74ab.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{f897a61a.f8c53b0f.js → f897a61a.862b0514.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/main.946fa17b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.aa687c82.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +8 -6
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +9 -9
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +140 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
- solace_agent_mesh/assets/docs/img/solace-logo-text.svg +18 -0
- solace_agent_mesh/assets/docs/lunr-index-1755275703209.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1755275703209.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +1 -1
- solace_agent_mesh/cli/commands/run_cmd.py +46 -3
- solace_agent_mesh/cli/main.py +1 -3
- solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-DzKPMTRs.js +673 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +2 -2
- solace_agent_mesh/common/exceptions.py +25 -0
- solace_agent_mesh/common/utils/initializer.py +4 -3
- solace_agent_mesh/common/utils/message_utils.py +79 -0
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-_7yox_eh.js → _index-xSu2leR8.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-e5c3acfe.js → manifest-950eb3be.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/gateway/base/component.py +15 -2
- solace_agent_mesh/gateway/http_sse/sse_manager.py +23 -1
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/METADATA +66 -49
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/RECORD +86 -82
- solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +0 -1
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1754075282978.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +0 -663
- /solace_agent_mesh/assets/docs/assets/js/{main.c6286d7c.js.LICENSE.txt → main.946fa17b.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.2.dist-info → solace_agent_mesh-1.0.5.dist-info}/licenses/LICENSE +0 -0
solace_agent_mesh/__init__.py
CHANGED
|
@@ -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
|
|
27
|
-
(
|
|
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
|
|
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
|
|
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.
|
|
47
|
+
os.makedirs(self.base_path, exist_ok=True)
|
|
57
48
|
logger.info(
|
|
58
|
-
"FilesystemArtifactService initialized.
|
|
59
|
-
self.
|
|
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
|
|
64
|
-
self.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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(
|
|
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 =
|
|
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'
|
|
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 =
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
|
230
|
+
k: v
|
|
231
|
+
for k, v in config.items()
|
|
232
|
+
if k not in ["type", "bucket_name", "artifact_scope"]
|
|
111
233
|
}
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1288
|
-
|
|
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
|