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
|
@@ -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(
|
|
@@ -248,7 +142,7 @@ async def process_event(component, event: Event):
|
|
|
248
142
|
elif topic.startswith(agent_response_sub_prefix) or topic.startswith(
|
|
249
143
|
agent_status_sub_prefix
|
|
250
144
|
):
|
|
251
|
-
handle_a2a_response(component, message)
|
|
145
|
+
await handle_a2a_response(component, message)
|
|
252
146
|
else:
|
|
253
147
|
log.warning(
|
|
254
148
|
"%s Received message on unhandled topic: %s",
|
|
@@ -264,31 +158,8 @@ async def process_event(component, event: Event):
|
|
|
264
158
|
if timer_data.get("timer_id") == component._card_publish_timer_id:
|
|
265
159
|
publish_agent_card(component)
|
|
266
160
|
elif event.event_type == EventType.CACHE_EXPIRY:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"%s Received cache expiry event: %s",
|
|
270
|
-
component.log_identifier,
|
|
271
|
-
cache_data,
|
|
272
|
-
)
|
|
273
|
-
sub_task_id = cache_data.get("key")
|
|
274
|
-
if sub_task_id and sub_task_id.startswith(
|
|
275
|
-
component.CORRELATION_DATA_PREFIX
|
|
276
|
-
):
|
|
277
|
-
expired_data = cache_data.get("expired_data")
|
|
278
|
-
if expired_data:
|
|
279
|
-
await component._handle_peer_timeout(sub_task_id, expired_data)
|
|
280
|
-
else:
|
|
281
|
-
log.error(
|
|
282
|
-
"%s Missing expired_data in cache expiry event for sub-task %s. Cannot process timeout.",
|
|
283
|
-
component.log_identifier,
|
|
284
|
-
sub_task_id,
|
|
285
|
-
)
|
|
286
|
-
else:
|
|
287
|
-
log.debug(
|
|
288
|
-
"%s Cache expiry for key '%s' is not a peer sub-task timeout.",
|
|
289
|
-
component.log_identifier,
|
|
290
|
-
sub_task_id,
|
|
291
|
-
)
|
|
161
|
+
# Delegate cache expiry handling to the component itself.
|
|
162
|
+
await component.handle_cache_expiry_event(event.data)
|
|
292
163
|
else:
|
|
293
164
|
log.warning(
|
|
294
165
|
"%s Received unknown event type: %s",
|
|
@@ -633,8 +504,51 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
633
504
|
logical_task_id,
|
|
634
505
|
)
|
|
635
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
|
+
|
|
636
550
|
adk_content = translate_a2a_to_adk_content(
|
|
637
|
-
|
|
551
|
+
a2a_message_for_adk, component.log_identifier
|
|
638
552
|
)
|
|
639
553
|
|
|
640
554
|
adk_session = await component.session_service.get_session(
|
|
@@ -854,10 +768,9 @@ def handle_agent_card_message(component, message: SolaceMessage):
|
|
|
854
768
|
component.handle_error(e, Event(EventType.MESSAGE, message))
|
|
855
769
|
|
|
856
770
|
|
|
857
|
-
def handle_a2a_response(component, message: SolaceMessage):
|
|
771
|
+
async def handle_a2a_response(component, message: SolaceMessage):
|
|
858
772
|
"""Handles incoming responses/status updates from peer agents."""
|
|
859
773
|
sub_task_id = None
|
|
860
|
-
agent_name = component.get_config("agent_name")
|
|
861
774
|
payload_to_queue = None
|
|
862
775
|
is_final_response = False
|
|
863
776
|
|
|
@@ -932,10 +845,8 @@ def handle_a2a_response(component, message: SolaceMessage):
|
|
|
932
845
|
component.log_identifier,
|
|
933
846
|
sub_task_id,
|
|
934
847
|
)
|
|
935
|
-
correlation_data = (
|
|
936
|
-
|
|
937
|
-
sub_task_id
|
|
938
|
-
)
|
|
848
|
+
correlation_data = await component._get_correlation_data_for_sub_task(
|
|
849
|
+
sub_task_id
|
|
939
850
|
)
|
|
940
851
|
if not correlation_data:
|
|
941
852
|
log.warning(
|
|
@@ -1227,7 +1138,12 @@ def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1227
1138
|
"error": f"Failed to parse response from peer: {parse_error}",
|
|
1228
1139
|
"code": "PEER_PARSE_ERROR",
|
|
1229
1140
|
}
|
|
1230
|
-
|
|
1141
|
+
# Print out the stack trace for debugging
|
|
1142
|
+
log.exception(
|
|
1143
|
+
"%s Exception stack trace: %s",
|
|
1144
|
+
component.log_identifier,
|
|
1145
|
+
parse_error,
|
|
1146
|
+
)
|
|
1231
1147
|
|
|
1232
1148
|
if not is_final_response:
|
|
1233
1149
|
# This is an intermediate status update for monitoring.
|
|
@@ -1240,13 +1156,9 @@ def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1240
1156
|
message.call_acknowledgements()
|
|
1241
1157
|
return
|
|
1242
1158
|
|
|
1243
|
-
correlation_data = component.
|
|
1159
|
+
correlation_data = await component._claim_peer_sub_task_completion(sub_task_id)
|
|
1244
1160
|
if not correlation_data:
|
|
1245
|
-
|
|
1246
|
-
"%s No correlation data found for sub-task %s. Cannot process response. Ignoring.",
|
|
1247
|
-
component.log_identifier,
|
|
1248
|
-
sub_task_id,
|
|
1249
|
-
)
|
|
1161
|
+
# The helper method logs the reason (timeout, already claimed, etc.)
|
|
1250
1162
|
message.call_acknowledgements()
|
|
1251
1163
|
return
|
|
1252
1164
|
|
|
@@ -1309,14 +1221,31 @@ def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1309
1221
|
peer_agent_name = task_obj.metadata.get(
|
|
1310
1222
|
"agent_name", "A peer agent"
|
|
1311
1223
|
)
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
component,
|
|
1315
|
-
produced_artifacts,
|
|
1316
|
-
peer_agent_name,
|
|
1317
|
-
correlation_data,
|
|
1318
|
-
)
|
|
1224
|
+
original_task_context = correlation_data.get(
|
|
1225
|
+
"original_task_context", {}
|
|
1319
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 = ""
|
|
1320
1249
|
# Bubble up the peer's artifacts to the parent context
|
|
1321
1250
|
_register_peer_artifacts_in_parent_context(
|
|
1322
1251
|
task_context, task_obj, log_retrigger
|
|
@@ -246,8 +246,8 @@ class SamAgentApp(App):
|
|
|
246
246
|
"type": "string",
|
|
247
247
|
"required": False,
|
|
248
248
|
"default": "namespace",
|
|
249
|
-
"enum": ["namespace", "app"
|
|
250
|
-
"description": "
|
|
249
|
+
"enum": ["namespace", "app"],
|
|
250
|
+
"description": "Process-wide scope for all artifact services. 'namespace' (default): shared by all components in the namespace. 'app': isolated by agent/gateway name. This setting must be consistent for all components in the same process.",
|
|
251
251
|
},
|
|
252
252
|
"artifact_scope_value": {
|
|
253
253
|
"type": "string",
|
|
@@ -340,6 +340,13 @@ class SamAgentApp(App):
|
|
|
340
340
|
"default": 0,
|
|
341
341
|
"description": "Minimum size in bytes for accumulated text from LLM stream before sending a status update. If 0 or less, batching is disabled and updates are sent per chunk. Final LLM chunks are always sent regardless of this threshold.",
|
|
342
342
|
},
|
|
343
|
+
{
|
|
344
|
+
"name": "max_message_size_bytes",
|
|
345
|
+
"required": False,
|
|
346
|
+
"type": "integer",
|
|
347
|
+
"default": 10_000_000,
|
|
348
|
+
"description": "Maximum allowed message size in bytes before rejecting publication to prevent broker disconnections. Default: 10MB",
|
|
349
|
+
},
|
|
343
350
|
{
|
|
344
351
|
"name": "enable_artifact_content_instruction",
|
|
345
352
|
"required": False,
|
|
@@ -551,6 +558,7 @@ class SamAgentApp(App):
|
|
|
551
558
|
app_config = app_info.get("app_config", {})
|
|
552
559
|
namespace = app_config.get("namespace")
|
|
553
560
|
agent_name = app_config.get("agent_name")
|
|
561
|
+
broker_request_response = app_info.get("broker_request_response")
|
|
554
562
|
|
|
555
563
|
if not namespace or not isinstance(namespace, str):
|
|
556
564
|
raise ValueError(
|
|
@@ -619,6 +627,8 @@ class SamAgentApp(App):
|
|
|
619
627
|
"component_config": {},
|
|
620
628
|
"subscriptions": generated_subs,
|
|
621
629
|
}
|
|
630
|
+
if broker_request_response:
|
|
631
|
+
component_definition["broker_request_response"] = broker_request_response
|
|
622
632
|
|
|
623
633
|
app_info["components"] = [component_definition]
|
|
624
634
|
log.debug("Replaced 'components' in app_info with programmatic definition.")
|
|
@@ -53,6 +53,8 @@ from ...common.types import (
|
|
|
53
53
|
TaskStatusUpdateEvent,
|
|
54
54
|
TaskArtifactUpdateEvent,
|
|
55
55
|
SendTaskRequest,
|
|
56
|
+
CancelTaskRequest,
|
|
57
|
+
TaskIdParams,
|
|
56
58
|
)
|
|
57
59
|
from ...common.a2a_protocol import (
|
|
58
60
|
get_a2a_base_topic,
|
|
@@ -65,7 +67,9 @@ from ...common.a2a_protocol import (
|
|
|
65
67
|
get_gateway_status_topic,
|
|
66
68
|
)
|
|
67
69
|
from ...agent.utils.config_parser import resolve_instruction_provider
|
|
68
|
-
from ...agent.utils.artifact_helpers import
|
|
70
|
+
from ...agent.utils.artifact_helpers import (
|
|
71
|
+
get_latest_artifact_version,
|
|
72
|
+
)
|
|
69
73
|
from ...agent.adk.services import (
|
|
70
74
|
initialize_session_service,
|
|
71
75
|
initialize_artifact_service,
|
|
@@ -90,6 +94,8 @@ from ...agent.adk.invocation_monitor import InvocationMonitor
|
|
|
90
94
|
from ...common.middleware.registry import MiddlewareRegistry
|
|
91
95
|
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
|
|
92
96
|
from ...agent.tools.registry import tool_registry
|
|
97
|
+
from ...common.utils.message_utils import validate_message_size
|
|
98
|
+
from ...common.exceptions import MessageSizeExceededError
|
|
93
99
|
|
|
94
100
|
if TYPE_CHECKING:
|
|
95
101
|
from .task_execution_context import TaskExecutionContext
|
|
@@ -230,6 +236,11 @@ class SamAgentComponent(ComponentBase):
|
|
|
230
236
|
raise ValueError(
|
|
231
237
|
"Internal Error: Inter-agent comms config missing after validation."
|
|
232
238
|
)
|
|
239
|
+
|
|
240
|
+
self.max_message_size_bytes = self.get_config(
|
|
241
|
+
"max_message_size_bytes", 10_000_000
|
|
242
|
+
)
|
|
243
|
+
|
|
233
244
|
log.info("%s Configuration retrieved successfully.", self.log_identifier)
|
|
234
245
|
except Exception as e:
|
|
235
246
|
log.error(
|
|
@@ -392,6 +403,7 @@ class SamAgentComponent(ComponentBase):
|
|
|
392
403
|
self.session_service = initialize_session_service(self)
|
|
393
404
|
self.artifact_service = initialize_artifact_service(self)
|
|
394
405
|
self.memory_service = initialize_memory_service(self)
|
|
406
|
+
|
|
395
407
|
log.info(
|
|
396
408
|
"%s Synchronous ADK services initialized.", self.log_identifier
|
|
397
409
|
)
|
|
@@ -536,49 +548,123 @@ class SamAgentComponent(ComponentBase):
|
|
|
536
548
|
if timer_data.get("timer_id") == self._card_publish_timer_id:
|
|
537
549
|
publish_agent_card(self)
|
|
538
550
|
|
|
539
|
-
def handle_cache_expiry_event(self, cache_data: Dict[str, Any]):
|
|
540
|
-
"""
|
|
551
|
+
async def handle_cache_expiry_event(self, cache_data: Dict[str, Any]):
|
|
552
|
+
"""
|
|
553
|
+
Handles cache expiry events for peer timeouts by calling the atomic claim helper.
|
|
554
|
+
"""
|
|
541
555
|
log.debug("%s Received cache expiry event: %s", self.log_identifier, cache_data)
|
|
542
|
-
|
|
543
|
-
|
|
556
|
+
sub_task_id = cache_data.get("key")
|
|
557
|
+
logical_task_id = cache_data.get("expired_data")
|
|
558
|
+
|
|
559
|
+
if not (
|
|
560
|
+
sub_task_id
|
|
561
|
+
and sub_task_id.startswith(CORRELATION_DATA_PREFIX)
|
|
562
|
+
and logical_task_id
|
|
563
|
+
):
|
|
564
|
+
log.debug(
|
|
565
|
+
"%s Cache expiry for key '%s' is not a peer sub-task timeout or is missing data.",
|
|
566
|
+
self.log_identifier,
|
|
567
|
+
sub_task_id,
|
|
568
|
+
)
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
correlation_data = await self._claim_peer_sub_task_completion(
|
|
572
|
+
sub_task_id=sub_task_id, logical_task_id_from_event=logical_task_id
|
|
573
|
+
)
|
|
544
574
|
|
|
545
|
-
if
|
|
546
|
-
sub_task_id = expired_key
|
|
575
|
+
if correlation_data:
|
|
547
576
|
log.warning(
|
|
548
|
-
"%s Detected timeout for sub-task
|
|
577
|
+
"%s Detected timeout for sub-task %s (Main Task: %s). Claimed successfully.",
|
|
549
578
|
self.log_identifier,
|
|
550
579
|
sub_task_id,
|
|
580
|
+
logical_task_id,
|
|
551
581
|
)
|
|
552
|
-
|
|
553
|
-
try:
|
|
554
|
-
original_task_context = expired_data.get("original_task_context")
|
|
555
|
-
if original_task_context:
|
|
556
|
-
self._handle_peer_timeout(sub_task_id, expired_data)
|
|
557
|
-
else:
|
|
558
|
-
log.error(
|
|
559
|
-
"%s Missing 'original_task_context' in expired cache data for sub-task %s. Cannot process timeout.",
|
|
560
|
-
self.log_identifier,
|
|
561
|
-
sub_task_id,
|
|
562
|
-
)
|
|
563
|
-
except Exception as e:
|
|
564
|
-
log.exception(
|
|
565
|
-
"%s Error handling peer timeout for sub-task %s: %s",
|
|
566
|
-
self.log_identifier,
|
|
567
|
-
sub_task_id,
|
|
568
|
-
e,
|
|
569
|
-
)
|
|
570
|
-
else:
|
|
571
|
-
log.error(
|
|
572
|
-
"%s Missing expired_data in cache expiry event for sub-task %s. Cannot process timeout.",
|
|
573
|
-
self.log_identifier,
|
|
574
|
-
sub_task_id,
|
|
575
|
-
)
|
|
582
|
+
await self._handle_peer_timeout(sub_task_id, correlation_data)
|
|
576
583
|
else:
|
|
577
|
-
log.
|
|
578
|
-
"%s
|
|
584
|
+
log.info(
|
|
585
|
+
"%s Ignoring timeout event for sub-task %s as it was already completed.",
|
|
586
|
+
self.log_identifier,
|
|
587
|
+
sub_task_id,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
async def _get_correlation_data_for_sub_task(
|
|
591
|
+
self, sub_task_id: str
|
|
592
|
+
) -> Optional[Dict[str, Any]]:
|
|
593
|
+
"""
|
|
594
|
+
Non-destructively retrieves correlation data for a sub-task.
|
|
595
|
+
Used for intermediate events where the sub-task should remain active.
|
|
596
|
+
"""
|
|
597
|
+
logical_task_id = self.cache_service.get_data(sub_task_id)
|
|
598
|
+
if not logical_task_id:
|
|
599
|
+
log.warning(
|
|
600
|
+
"%s No cache entry for sub-task %s. Cannot get correlation data.",
|
|
579
601
|
self.log_identifier,
|
|
580
|
-
|
|
602
|
+
sub_task_id,
|
|
603
|
+
)
|
|
604
|
+
return None
|
|
605
|
+
|
|
606
|
+
with self.active_tasks_lock:
|
|
607
|
+
task_context = self.active_tasks.get(logical_task_id)
|
|
608
|
+
|
|
609
|
+
if not task_context:
|
|
610
|
+
log.error(
|
|
611
|
+
"%s TaskExecutionContext not found for task %s, but cache entry existed for sub-task %s. This may indicate a cleanup issue.",
|
|
612
|
+
self.log_identifier,
|
|
613
|
+
logical_task_id,
|
|
614
|
+
sub_task_id,
|
|
615
|
+
)
|
|
616
|
+
return None
|
|
617
|
+
|
|
618
|
+
with task_context.lock:
|
|
619
|
+
return task_context.active_peer_sub_tasks.get(sub_task_id)
|
|
620
|
+
|
|
621
|
+
async def _claim_peer_sub_task_completion(
|
|
622
|
+
self, sub_task_id: str, logical_task_id_from_event: Optional[str] = None
|
|
623
|
+
) -> Optional[Dict[str, Any]]:
|
|
624
|
+
"""
|
|
625
|
+
Atomically claims a sub-task as complete, preventing race conditions.
|
|
626
|
+
This is a destructive operation that removes state.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
sub_task_id: The ID of the sub-task to claim.
|
|
630
|
+
logical_task_id_from_event: The parent task ID, if provided by the event (e.g., a timeout).
|
|
631
|
+
If not provided, it will be looked up from the cache.
|
|
632
|
+
"""
|
|
633
|
+
log_id = f"{self.log_identifier}[ClaimSubTask:{sub_task_id}]"
|
|
634
|
+
logical_task_id = logical_task_id_from_event
|
|
635
|
+
|
|
636
|
+
if not logical_task_id:
|
|
637
|
+
logical_task_id = self.cache_service.get_data(sub_task_id)
|
|
638
|
+
if not logical_task_id:
|
|
639
|
+
log.warning(
|
|
640
|
+
"%s No cache entry found. Task has likely timed out and been cleaned up. Cannot claim.",
|
|
641
|
+
log_id,
|
|
642
|
+
)
|
|
643
|
+
return None
|
|
644
|
+
|
|
645
|
+
with self.active_tasks_lock:
|
|
646
|
+
task_context = self.active_tasks.get(logical_task_id)
|
|
647
|
+
|
|
648
|
+
if not task_context:
|
|
649
|
+
log.error(
|
|
650
|
+
"%s TaskExecutionContext not found for task %s. Cleaning up stale cache entry.",
|
|
651
|
+
log_id,
|
|
652
|
+
logical_task_id,
|
|
581
653
|
)
|
|
654
|
+
self.cache_service.remove_data(sub_task_id)
|
|
655
|
+
return None
|
|
656
|
+
|
|
657
|
+
correlation_data = task_context.claim_sub_task_completion(sub_task_id)
|
|
658
|
+
|
|
659
|
+
if correlation_data:
|
|
660
|
+
# If we successfully claimed the task, remove the timeout tracker from the cache.
|
|
661
|
+
self.cache_service.remove_data(sub_task_id)
|
|
662
|
+
log.info("%s Successfully claimed completion.", log_id)
|
|
663
|
+
return correlation_data
|
|
664
|
+
else:
|
|
665
|
+
# This means the task was already claimed by a competing event (e.g., timeout vs. response).
|
|
666
|
+
log.warning("%s Failed to claim; it was already completed.", log_id)
|
|
667
|
+
return None
|
|
582
668
|
|
|
583
669
|
async def _retrigger_agent_with_peer_responses(
|
|
584
670
|
self,
|
|
@@ -686,8 +772,9 @@ class SamAgentComponent(ComponentBase):
|
|
|
686
772
|
correlation_data: Dict[str, Any],
|
|
687
773
|
):
|
|
688
774
|
"""
|
|
689
|
-
Handles the timeout of a peer agent task
|
|
690
|
-
|
|
775
|
+
Handles the timeout of a peer agent task. It sends a cancellation request
|
|
776
|
+
to the peer, updates the local completion counter, and potentially
|
|
777
|
+
re-triggers the runner if all parallel tasks are now complete.
|
|
691
778
|
"""
|
|
692
779
|
logical_task_id = correlation_data.get("logical_task_id")
|
|
693
780
|
invocation_id = correlation_data.get("invocation_id")
|
|
@@ -700,6 +787,36 @@ class SamAgentComponent(ComponentBase):
|
|
|
700
787
|
invocation_id,
|
|
701
788
|
)
|
|
702
789
|
|
|
790
|
+
# Proactively send a cancellation request to the peer agent.
|
|
791
|
+
peer_agent_name = correlation_data.get("peer_agent_name")
|
|
792
|
+
if peer_agent_name:
|
|
793
|
+
try:
|
|
794
|
+
log.info(
|
|
795
|
+
"%s Sending CancelTaskRequest to peer '%s' for timed-out sub-task %s.",
|
|
796
|
+
log_retrigger,
|
|
797
|
+
peer_agent_name,
|
|
798
|
+
sub_task_id,
|
|
799
|
+
)
|
|
800
|
+
task_id_for_peer = sub_task_id.replace(CORRELATION_DATA_PREFIX, "", 1)
|
|
801
|
+
cancel_params = TaskIdParams(id=task_id_for_peer)
|
|
802
|
+
cancel_request = CancelTaskRequest(params=cancel_params)
|
|
803
|
+
user_props = {"clientId": self.agent_name}
|
|
804
|
+
peer_topic = self._get_agent_request_topic(peer_agent_name)
|
|
805
|
+
self._publish_a2a_message(
|
|
806
|
+
payload=cancel_request.model_dump(exclude_none=True),
|
|
807
|
+
topic=peer_topic,
|
|
808
|
+
user_properties=user_props,
|
|
809
|
+
)
|
|
810
|
+
except Exception as e:
|
|
811
|
+
log.error(
|
|
812
|
+
"%s Failed to send CancelTaskRequest to peer '%s' for sub-task %s: %s",
|
|
813
|
+
log_retrigger,
|
|
814
|
+
peer_agent_name,
|
|
815
|
+
sub_task_id,
|
|
816
|
+
e,
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
# Process the timeout locally.
|
|
703
820
|
with self.active_tasks_lock:
|
|
704
821
|
task_context = self.active_tasks.get(logical_task_id)
|
|
705
822
|
|
|
@@ -1841,7 +1958,8 @@ class SamAgentComponent(ComponentBase):
|
|
|
1841
1958
|
len(unprocessed_tail.encode("utf-8")),
|
|
1842
1959
|
)
|
|
1843
1960
|
else:
|
|
1844
|
-
|
|
1961
|
+
if unprocessed_tail is not None and unprocessed_tail != "":
|
|
1962
|
+
resolved_text = resolved_text + unprocessed_tail
|
|
1845
1963
|
|
|
1846
1964
|
if signals_found:
|
|
1847
1965
|
log.info(
|
|
@@ -2860,6 +2978,29 @@ class SamAgentComponent(ComponentBase):
|
|
|
2860
2978
|
):
|
|
2861
2979
|
"""Helper to publish A2A messages via the SAC App."""
|
|
2862
2980
|
try:
|
|
2981
|
+
max_size_bytes = self.max_message_size_bytes
|
|
2982
|
+
|
|
2983
|
+
# Validate message size
|
|
2984
|
+
is_valid, actual_size = validate_message_size(
|
|
2985
|
+
payload, max_size_bytes, self.log_identifier
|
|
2986
|
+
)
|
|
2987
|
+
|
|
2988
|
+
if not is_valid:
|
|
2989
|
+
error_msg = (
|
|
2990
|
+
f"Message size validation failed: payload size ({actual_size} bytes) "
|
|
2991
|
+
f"exceeds maximum allowed size ({max_size_bytes} bytes)"
|
|
2992
|
+
)
|
|
2993
|
+
log.error("%s %s", self.log_identifier, error_msg)
|
|
2994
|
+
raise MessageSizeExceededError(actual_size, max_size_bytes, error_msg)
|
|
2995
|
+
|
|
2996
|
+
# Debug logging to show message size when publishing
|
|
2997
|
+
log.debug(
|
|
2998
|
+
"%s Publishing message to topic %s (size: %d bytes)",
|
|
2999
|
+
self.log_identifier,
|
|
3000
|
+
topic,
|
|
3001
|
+
actual_size,
|
|
3002
|
+
)
|
|
3003
|
+
|
|
2863
3004
|
app = self.get_app()
|
|
2864
3005
|
if app:
|
|
2865
3006
|
if self.invocation_monitor:
|
|
@@ -2877,6 +3018,9 @@ class SamAgentComponent(ComponentBase):
|
|
|
2877
3018
|
"%s Cannot publish message: Not running within a SAC App context.",
|
|
2878
3019
|
self.log_identifier,
|
|
2879
3020
|
)
|
|
3021
|
+
except MessageSizeExceededError:
|
|
3022
|
+
# Re-raise MessageSizeExceededError without wrapping
|
|
3023
|
+
raise
|
|
2880
3024
|
except Exception as e:
|
|
2881
3025
|
log.exception(
|
|
2882
3026
|
"%s Failed to publish A2A message to topic %s: %s",
|