solace-agent-mesh 1.0.1__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.
- solace_agent_mesh/__init__.py +5 -0
- solace_agent_mesh/agent/adk/callbacks.py +23 -1
- solace_agent_mesh/agent/adk/filesystem_artifact_service.py +34 -41
- solace_agent_mesh/agent/adk/runner.py +10 -6
- solace_agent_mesh/agent/adk/services.py +141 -60
- solace_agent_mesh/agent/protocol/event_handlers.py +84 -155
- solace_agent_mesh/agent/sac/app.py +12 -2
- solace_agent_mesh/agent/sac/component.py +182 -38
- solace_agent_mesh/agent/sac/task_execution_context.py +15 -6
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +8 -1
- solace_agent_mesh/agent/tools/general_agent_tools.py +4 -2
- solace_agent_mesh/agent/tools/peer_agent_tool.py +97 -88
- 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/1c6e87d2.a8c5ce5a.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.862b0514.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.dc7db184.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 +21 -4
- 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 +10 -10
- 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-1755120326601.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1755120326601.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/env_step.py +8 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +21 -1
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +1 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +2 -1
- solace_agent_mesh/cli/commands/run_cmd.py +42 -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 +51 -0
- 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/backend/plugin_catalog/constants.py +2 -1
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +6 -2
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +1 -5
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +1 -0
- solace_agent_mesh/config_portal/backend/server.py +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-xSu2leR8.js +48 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d2b54a97.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/routers/artifacts.py +9 -3
- solace_agent_mesh/gateway/http_sse/sse_manager.py +23 -1
- solace_agent_mesh/templates/gateway_component_template.py +75 -44
- solace_agent_mesh/templates/logging_config_template.ini +64 -0
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +1 -1
- {solace_agent_mesh-1.0.1.dist-info → solace_agent_mesh-1.0.3.dist-info}/METADATA +56 -46
- {solace_agent_mesh-1.0.1.dist-info → solace_agent_mesh-1.0.3.dist-info}/RECORD +102 -96
- solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/1c6e87d2.23bccffb.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/f897a61a.2c2e152c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9520ae2.js +0 -1
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1753813536522.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1753813536522.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-An0a5j5k.js +0 -663
- solace_agent_mesh/client/webui/frontend/static/assets/main-Bu5-4Bac.css +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DNxCwAGB.js +0 -48
- /solace_agent_mesh/assets/docs/assets/js/{main.7ed3319f.js.LICENSE.txt → main.dc7db184.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.1.dist-info → solace_agent_mesh-1.0.3.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.1.dist-info → solace_agent_mesh-1.0.3.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.1.dist-info → solace_agent_mesh-1.0.3.dist-info}/licenses/LICENSE +0 -0
solace_agent_mesh/__init__.py
CHANGED
|
@@ -785,6 +785,26 @@ It can span multiple lines.
|
|
|
785
785
|
|
|
786
786
|
The system will automatically save the content and give you a confirmation in the next turn."""
|
|
787
787
|
|
|
788
|
+
def _generate_artifact_creation_instruction() -> str:
|
|
789
|
+
return """
|
|
790
|
+
**Creating Text-Based Artifacts:**
|
|
791
|
+
|
|
792
|
+
**When to Create Text-based Artifacts:**
|
|
793
|
+
Create an artifact when the content provides value as a standalone file:
|
|
794
|
+
- Content with special formatting (HTML, Markdown, CSS, structured markup) that requires proper rendering
|
|
795
|
+
- Content explicitly intended for use outside this conversation (reports, emails, presentations, reference documents)
|
|
796
|
+
- Structured reference content users will save or follow (schedules, guides, templates)
|
|
797
|
+
- Content that will be edited, expanded, or reused
|
|
798
|
+
- Substantial text documents
|
|
799
|
+
- Technical documentation meant as reference material
|
|
800
|
+
|
|
801
|
+
**When NOT to Create Text-based Artifacts:**
|
|
802
|
+
- Simple answers, explanations, or conversational responses
|
|
803
|
+
- Brief advice, opinions, or quick information
|
|
804
|
+
- Short lists, summaries, or single paragraphs
|
|
805
|
+
- Temporary content only relevant to the immediate conversation
|
|
806
|
+
- Basic explanations that don't require reference material
|
|
807
|
+
"""
|
|
788
808
|
|
|
789
809
|
def _generate_embed_instruction(
|
|
790
810
|
include_artifact_content: bool,
|
|
@@ -918,7 +938,7 @@ When faced with a complex goal or request that involves multiple steps, data ret
|
|
|
918
938
|
Simple, direct requests like 'create an image of a dog' or 'write an email to thank my boss' do not require a plan.
|
|
919
939
|
|
|
920
940
|
If a plan is created:
|
|
921
|
-
1. It should be a terse, hierarchical list describing the steps needed.
|
|
941
|
+
1. It should be a terse, hierarchical list describing the steps needed, with each checkbox item on its own line.
|
|
922
942
|
2. Use '☐' (empty checkbox emoji) for pending items and '☑' (checked checkbox emoji) for completed items.
|
|
923
943
|
3. If the plan changes significantly during execution, restate the updated plan.
|
|
924
944
|
4. As items are completed, update the plan to check them off.
|
|
@@ -926,6 +946,8 @@ If a plan is created:
|
|
|
926
946
|
"""
|
|
927
947
|
injected_instructions.append(planning_instruction)
|
|
928
948
|
log.debug("%s Added hardcoded planning instructions.", log_identifier)
|
|
949
|
+
artifact_creation_instruction = _generate_artifact_creation_instruction()
|
|
950
|
+
injected_instructions.append(artifact_creation_instruction)
|
|
929
951
|
fenced_artifact_instruction = _generate_fenced_artifact_instruction()
|
|
930
952
|
injected_instructions.append(fenced_artifact_instruction)
|
|
931
953
|
|
|
@@ -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,22 +358,17 @@ 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)
|
|
372
365
|
logger.debug("%sFound versions: %s", log_prefix, sorted_versions)
|
|
373
366
|
return sorted_versions
|
|
374
|
-
|
|
367
|
+
|
|
375
368
|
def _normalize_filename_unicode(self, filename: str) -> str:
|
|
376
369
|
"""
|
|
377
370
|
Normalizes Unicode characters in a filename to their standard form.
|
|
378
371
|
Specifically targets compatibility characters like non-breaking spaces (\u202f)
|
|
379
372
|
and converts them to their regular ASCII equivalents (a standard space).
|
|
380
373
|
"""
|
|
381
|
-
return unicodedata.normalize(
|
|
374
|
+
return unicodedata.normalize("NFKC", filename)
|
|
@@ -132,7 +132,7 @@ async def run_adk_async_task_thread_wrapper(
|
|
|
132
132
|
logical_task_id,
|
|
133
133
|
tce,
|
|
134
134
|
)
|
|
135
|
-
sub_tasks_to_cancel = task_context.
|
|
135
|
+
sub_tasks_to_cancel = task_context.active_peer_sub_tasks if task_context else {}
|
|
136
136
|
|
|
137
137
|
if sub_tasks_to_cancel:
|
|
138
138
|
log.info(
|
|
@@ -141,18 +141,22 @@ async def run_adk_async_task_thread_wrapper(
|
|
|
141
141
|
len(sub_tasks_to_cancel),
|
|
142
142
|
logical_task_id,
|
|
143
143
|
)
|
|
144
|
-
for sub_task_info in sub_tasks_to_cancel:
|
|
144
|
+
for sub_task_id, sub_task_info in sub_tasks_to_cancel.items():
|
|
145
145
|
try:
|
|
146
|
-
sub_task_id = sub_task_info.get("sub_task_id")
|
|
147
146
|
target_peer_agent_name = sub_task_info.get("peer_agent_name")
|
|
148
147
|
if not sub_task_id or not target_peer_agent_name:
|
|
149
148
|
log.warning(
|
|
150
|
-
"%s Incomplete sub-task info found, cannot cancel: %s",
|
|
149
|
+
"%s Incomplete sub-task info found for sub-task %s, cannot cancel: %s",
|
|
151
150
|
component.log_identifier,
|
|
151
|
+
sub_task_id,
|
|
152
152
|
sub_task_info,
|
|
153
153
|
)
|
|
154
154
|
continue
|
|
155
|
-
|
|
155
|
+
|
|
156
|
+
task_id_for_peer = sub_task_id.replace(
|
|
157
|
+
component.CORRELATION_DATA_PREFIX, "", 1
|
|
158
|
+
)
|
|
159
|
+
peer_cancel_params = TaskIdParams(id=task_id_for_peer)
|
|
156
160
|
peer_cancel_request = CancelTaskRequest(params=peer_cancel_params)
|
|
157
161
|
peer_cancel_user_props = {"clientId": component.agent_name}
|
|
158
162
|
peer_request_topic = component._get_agent_request_topic(
|
|
@@ -167,7 +171,7 @@ async def run_adk_async_task_thread_wrapper(
|
|
|
167
171
|
log.error(
|
|
168
172
|
"%s Failed to send CancelTaskRequest for sub-task %s: %s",
|
|
169
173
|
component.log_identifier,
|
|
170
|
-
|
|
174
|
+
sub_task_id,
|
|
171
175
|
e_peer_cancel,
|
|
172
176
|
exc_info=True,
|
|
173
177
|
)
|
|
@@ -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 (
|
|
@@ -28,13 +30,126 @@ from google.adk.memory import (
|
|
|
28
30
|
from .filesystem_artifact_service import FilesystemArtifactService
|
|
29
31
|
|
|
30
32
|
try:
|
|
31
|
-
from
|
|
33
|
+
from sam_test_infrastructure.artifact_service.service import (
|
|
32
34
|
TestInMemoryArtifactService,
|
|
33
35
|
)
|
|
34
36
|
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."""
|