solace-agent-mesh 1.0.8__py3-none-any.whl → 1.1.0__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/agent/adk/adk_llm.txt +182 -42
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
- solace_agent_mesh/agent/adk/callbacks.py +165 -104
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +0 -18
- solace_agent_mesh/agent/adk/models/models_llm.txt +104 -55
- solace_agent_mesh/agent/adk/runner.py +7 -5
- solace_agent_mesh/agent/adk/setup.py +11 -0
- solace_agent_mesh/agent/adk/stream_parser.py +8 -1
- solace_agent_mesh/agent/adk/tool_wrapper.py +10 -3
- solace_agent_mesh/agent/agent_llm.txt +355 -18
- solace_agent_mesh/agent/protocol/event_handlers.py +433 -296
- solace_agent_mesh/agent/protocol/protocol_llm.txt +54 -7
- solace_agent_mesh/agent/sac/app.py +1 -1
- solace_agent_mesh/agent/sac/component.py +212 -517
- solace_agent_mesh/agent/sac/sac_llm.txt +133 -63
- solace_agent_mesh/agent/testing/testing_llm.txt +25 -58
- solace_agent_mesh/agent/tools/peer_agent_tool.py +15 -11
- solace_agent_mesh/agent/tools/tools_llm.txt +234 -69
- solace_agent_mesh/agent/utils/artifact_helpers.py +35 -1
- solace_agent_mesh/agent/utils/utils_llm.txt +90 -105
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{3d406171.7d02a73b.js → 3d406171.0b9eeed1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{75384d09.ccd480c4.js → 75384d09.bf78fbdb.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +105 -0
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +53 -0
- 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 +8 -8
- 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 +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
- 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 +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1756992446316.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/add_cmd/web_add_agent_step.py +12 -3
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +10 -14
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +2 -15
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +6 -2
- solace_agent_mesh/cli/utils.py +15 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DvlO62me.js → authCallback-BmF2l6vg.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-bp6u3qVZ.js → client-D881Dttc.js} +4 -4
- solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +699 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +2 -2
- solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
- solace_agent_mesh/common/a2a/__init__.py +213 -0
- solace_agent_mesh/common/a2a/a2a_llm.txt +182 -0
- solace_agent_mesh/common/a2a/artifact.py +328 -0
- solace_agent_mesh/common/a2a/events.py +183 -0
- solace_agent_mesh/common/a2a/message.py +307 -0
- solace_agent_mesh/common/a2a/protocol.py +513 -0
- solace_agent_mesh/common/a2a/task.py +127 -0
- solace_agent_mesh/common/a2a/translation.py +653 -0
- solace_agent_mesh/common/a2a/types.py +54 -0
- solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +407 -0
- solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +31 -0
- solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +235 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +25 -0
- solace_agent_mesh/common/agent_registry.py +1 -1
- solace_agent_mesh/common/common_llm.txt +192 -70
- solace_agent_mesh/common/data_parts.py +99 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +17 -17
- solace_agent_mesh/common/sac/__init__.py +0 -0
- solace_agent_mesh/common/sac/sac_llm.txt +71 -0
- solace_agent_mesh/common/sac/sam_component_base.py +252 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +51 -84
- solace_agent_mesh/common/services/services_llm.txt +206 -26
- solace_agent_mesh/common/utils/artifact_utils.py +29 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +176 -80
- solace_agent_mesh/common/utils/utils_llm.txt +323 -42
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-MqsrTd6g.js → _index-Bym6YkMd.js} +74 -24
- solace_agent_mesh/config_portal/frontend/static/client/assets/{components-B7lKcHVY.js → components-Rk0n-9cK.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-CEumGClk.js → entry.client-mvZjNKiz.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DSo1AH_7.js → index-DzNKzXrc.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d845808d.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{root-C4XmHinv.js → root-BWvk5-gF.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +10 -8
- solace_agent_mesh/core_a2a/service.py +20 -44
- solace_agent_mesh/gateway/base/app.py +27 -1
- solace_agent_mesh/gateway/base/base_llm.txt +177 -72
- solace_agent_mesh/gateway/base/component.py +294 -523
- solace_agent_mesh/gateway/gateway_llm.txt +299 -58
- solace_agent_mesh/gateway/http_sse/component.py +156 -183
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +29 -29
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +272 -36
- solace_agent_mesh/gateway/http_sse/main.py +8 -10
- solace_agent_mesh/gateway/http_sse/routers/agents.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +18 -4
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +231 -5
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +12 -7
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +116 -169
- solace_agent_mesh/gateway/http_sse/services/agent_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +89 -135
- solace_agent_mesh/gateway/http_sse/services/task_service.py +2 -5
- solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
- solace_agent_mesh/templates/gateway_component_template.py +149 -98
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/METADATA +5 -4
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/RECORD +143 -126
- solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.6dba4a66.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.6415ad00.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1756153049706.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1756153049706.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BucUdn9m.js +0 -673
- solace_agent_mesh/common/a2a_protocol.py +0 -564
- solace_agent_mesh/common/client/__init__.py +0 -4
- solace_agent_mesh/common/client/card_resolver.py +0 -21
- solace_agent_mesh/common/client/client.py +0 -85
- solace_agent_mesh/common/client/client_llm.txt +0 -133
- solace_agent_mesh/common/server/__init__.py +0 -4
- solace_agent_mesh/common/server/server.py +0 -122
- solace_agent_mesh/common/server/server_llm.txt +0 -169
- solace_agent_mesh/common/server/task_manager.py +0 -291
- solace_agent_mesh/common/server/utils.py +0 -28
- solace_agent_mesh/common/types.py +0 -411
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-28271392.js +0 -1
- /solace_agent_mesh/assets/docs/assets/js/{main.6dba4a66.js.LICENSE.txt → main.a75ecc0d.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,50 +3,39 @@ Contains event handling logic for the A2A_ADK_HostComponent.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
import yaml
|
|
7
6
|
import asyncio
|
|
8
|
-
from typing import
|
|
7
|
+
from typing import TYPE_CHECKING, Dict, Any
|
|
9
8
|
import fnmatch
|
|
10
9
|
from solace_ai_connector.common.log import log
|
|
11
10
|
from solace_ai_connector.common.message import Message as SolaceMessage
|
|
11
|
+
from ...agent.adk.callbacks import _publish_data_part_status_update
|
|
12
|
+
from ...common.data_parts import ToolResultData
|
|
13
|
+
from ...common.a2a.types import ToolsExtensionParams
|
|
12
14
|
from solace_ai_connector.common.event import Event, EventType
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
SendTaskRequest,
|
|
16
|
-
SendTaskStreamingRequest,
|
|
17
|
-
CancelTaskRequest,
|
|
18
|
-
GetTaskRequest,
|
|
19
|
-
SetTaskPushNotificationRequest,
|
|
20
|
-
GetTaskPushNotificationRequest,
|
|
21
|
-
TaskResubscriptionRequest,
|
|
22
|
-
TaskIdParams,
|
|
23
|
-
JSONParseError,
|
|
24
|
-
InvalidRequestError,
|
|
25
|
-
InternalError,
|
|
26
|
-
JSONRPCResponse,
|
|
15
|
+
from a2a.types import (
|
|
16
|
+
A2ARequest,
|
|
27
17
|
AgentCard,
|
|
28
18
|
AgentCapabilities,
|
|
19
|
+
AgentExtension,
|
|
20
|
+
DataPart,
|
|
21
|
+
JSONRPCResponse,
|
|
29
22
|
Task,
|
|
30
|
-
TaskStatusUpdateEvent,
|
|
31
23
|
TaskArtifactUpdateEvent,
|
|
32
|
-
|
|
33
|
-
TaskState,
|
|
34
|
-
DataPart,
|
|
24
|
+
TaskStatusUpdateEvent,
|
|
35
25
|
TextPart,
|
|
36
|
-
A2ARequest,
|
|
37
26
|
)
|
|
38
|
-
from ...common
|
|
27
|
+
from ...common import a2a
|
|
28
|
+
from ...common.a2a import (
|
|
39
29
|
get_agent_request_topic,
|
|
40
30
|
get_discovery_topic,
|
|
41
31
|
translate_a2a_to_adk_content,
|
|
42
32
|
get_client_response_topic,
|
|
43
33
|
get_agent_response_subscription_topic,
|
|
44
34
|
get_agent_status_subscription_topic,
|
|
45
|
-
|
|
35
|
+
get_text_from_message,
|
|
46
36
|
)
|
|
47
37
|
from ...agent.utils.artifact_helpers import (
|
|
48
38
|
generate_artifact_metadata_summary,
|
|
49
|
-
load_artifact_content_or_metadata,
|
|
50
39
|
)
|
|
51
40
|
from ...agent.adk.runner import run_adk_async_task_thread_wrapper
|
|
52
41
|
from ..sac.task_execution_context import TaskExecutionContext
|
|
@@ -55,10 +44,6 @@ from google.adk.agents import RunConfig
|
|
|
55
44
|
if TYPE_CHECKING:
|
|
56
45
|
from ..sac.component import SamAgentComponent
|
|
57
46
|
from google.adk.agents.run_config import StreamingMode
|
|
58
|
-
from google.adk.events import Event as ADKEvent
|
|
59
|
-
from google.genai import types as adk_types
|
|
60
|
-
|
|
61
|
-
|
|
62
47
|
|
|
63
48
|
|
|
64
49
|
def _register_peer_artifacts_in_parent_context(
|
|
@@ -119,7 +104,9 @@ async def process_event(component, event: Event):
|
|
|
119
104
|
)
|
|
120
105
|
else:
|
|
121
106
|
log.warning(
|
|
122
|
-
|
|
107
|
+
"%s InvocationMonitor not available in component for event on topic %s",
|
|
108
|
+
component.log_identifier,
|
|
109
|
+
topic,
|
|
123
110
|
)
|
|
124
111
|
namespace = component.get_config("namespace")
|
|
125
112
|
agent_name = component.get_config("agent_name")
|
|
@@ -186,6 +173,50 @@ async def process_event(component, event: Event):
|
|
|
186
173
|
component.handle_error(e, event)
|
|
187
174
|
|
|
188
175
|
|
|
176
|
+
async def _publish_peer_tool_result_notification(
|
|
177
|
+
component: "SamAgentComponent",
|
|
178
|
+
correlation_data: Dict[str, Any],
|
|
179
|
+
payload_to_queue: Any,
|
|
180
|
+
log_identifier: str,
|
|
181
|
+
):
|
|
182
|
+
"""Publishes a ToolResultData status update for a completed peer tool call."""
|
|
183
|
+
peer_tool_name = correlation_data.get("peer_tool_name")
|
|
184
|
+
function_call_id = correlation_data.get("adk_function_call_id")
|
|
185
|
+
original_task_context_data = correlation_data.get("original_task_context")
|
|
186
|
+
|
|
187
|
+
if not (peer_tool_name and function_call_id and original_task_context_data):
|
|
188
|
+
log.warning(
|
|
189
|
+
"%s Missing data in correlation_data. Cannot publish peer tool result notification.",
|
|
190
|
+
log_identifier,
|
|
191
|
+
)
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
log.info(
|
|
195
|
+
"%s Publishing tool_result notification for completed peer task '%s'.",
|
|
196
|
+
log_identifier,
|
|
197
|
+
peer_tool_name,
|
|
198
|
+
)
|
|
199
|
+
try:
|
|
200
|
+
tool_result_notification = ToolResultData(
|
|
201
|
+
tool_name=peer_tool_name,
|
|
202
|
+
result_data=payload_to_queue,
|
|
203
|
+
function_call_id=function_call_id,
|
|
204
|
+
)
|
|
205
|
+
await _publish_data_part_status_update(
|
|
206
|
+
host_component=component,
|
|
207
|
+
a2a_context=original_task_context_data,
|
|
208
|
+
data_part_model=tool_result_notification,
|
|
209
|
+
)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
log.error(
|
|
212
|
+
"%s Failed to publish peer tool result notification for '%s': %s",
|
|
213
|
+
log_identifier,
|
|
214
|
+
peer_tool_name,
|
|
215
|
+
e,
|
|
216
|
+
exc_info=True,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
189
220
|
async def handle_a2a_request(component, message: SolaceMessage):
|
|
190
221
|
"""
|
|
191
222
|
Handles an incoming A2A request message.
|
|
@@ -223,19 +254,15 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
223
254
|
payload_dict = message.get_payload()
|
|
224
255
|
if not isinstance(payload_dict, dict):
|
|
225
256
|
raise ValueError("Payload is not a dictionary.")
|
|
226
|
-
|
|
227
|
-
a2a_request
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
] = A2ARequest.validate_python(payload_dict)
|
|
236
|
-
jsonrpc_request_id = a2a_request.id
|
|
237
|
-
logical_task_id = a2a_request.params.id
|
|
238
|
-
if isinstance(a2a_request, CancelTaskRequest):
|
|
257
|
+
a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
|
|
258
|
+
jsonrpc_request_id = a2a.get_request_id(a2a_request)
|
|
259
|
+
|
|
260
|
+
# The concept of logical_task_id changes. For Cancel, it's in params.id.
|
|
261
|
+
# For Send, we will generate it.
|
|
262
|
+
logical_task_id = None
|
|
263
|
+
method = a2a.get_request_method(a2a_request)
|
|
264
|
+
if method == "tasks/cancel":
|
|
265
|
+
logical_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
|
|
239
266
|
log.info(
|
|
240
267
|
"%s Received CancelTaskRequest for Task ID: %s.",
|
|
241
268
|
component.log_identifier,
|
|
@@ -253,28 +280,38 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
253
280
|
logical_task_id,
|
|
254
281
|
)
|
|
255
282
|
|
|
256
|
-
peer_sub_tasks = task_context.active_peer_sub_tasks
|
|
283
|
+
peer_sub_tasks = task_context.active_peer_sub_tasks.copy()
|
|
257
284
|
if peer_sub_tasks:
|
|
258
|
-
for sub_task_info in peer_sub_tasks:
|
|
259
|
-
sub_task_id = sub_task_info.get("sub_task_id")
|
|
285
|
+
for sub_task_id, sub_task_info in peer_sub_tasks.items():
|
|
260
286
|
target_peer_agent_name = sub_task_info.get("peer_agent_name")
|
|
261
|
-
|
|
287
|
+
peer_task_id_to_cancel = sub_task_info.get("peer_task_id")
|
|
288
|
+
|
|
289
|
+
if not peer_task_id_to_cancel:
|
|
290
|
+
log.warning(
|
|
291
|
+
"%s Cannot cancel peer sub-task %s for main task %s because the peer's taskId is not yet known.",
|
|
292
|
+
component.log_identifier,
|
|
293
|
+
sub_task_id,
|
|
294
|
+
logical_task_id,
|
|
295
|
+
)
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
if peer_task_id_to_cancel and target_peer_agent_name:
|
|
262
299
|
log.info(
|
|
263
|
-
"%s Attempting to cancel peer sub-task %s for agent %s (main task %s).",
|
|
300
|
+
"%s Attempting to cancel peer sub-task %s (Peer Task ID: %s) for agent %s (main task %s).",
|
|
264
301
|
component.log_identifier,
|
|
265
302
|
sub_task_id,
|
|
303
|
+
peer_task_id_to_cancel,
|
|
266
304
|
target_peer_agent_name,
|
|
267
305
|
logical_task_id,
|
|
268
306
|
)
|
|
269
307
|
try:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
params=peer_cancel_params
|
|
308
|
+
peer_cancel_request = a2a.create_cancel_task_request(
|
|
309
|
+
task_id=peer_task_id_to_cancel
|
|
273
310
|
)
|
|
274
311
|
peer_cancel_user_props = {
|
|
275
312
|
"clientId": component.agent_name
|
|
276
313
|
}
|
|
277
|
-
component.
|
|
314
|
+
component.publish_a2a_message(
|
|
278
315
|
payload=peer_cancel_request.model_dump(
|
|
279
316
|
exclude_none=True
|
|
280
317
|
),
|
|
@@ -284,17 +321,17 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
284
321
|
user_properties=peer_cancel_user_props,
|
|
285
322
|
)
|
|
286
323
|
log.info(
|
|
287
|
-
"%s Sent CancelTaskRequest to peer %s for
|
|
324
|
+
"%s Sent CancelTaskRequest to peer %s for its task %s.",
|
|
288
325
|
component.log_identifier,
|
|
289
326
|
target_peer_agent_name,
|
|
290
|
-
|
|
327
|
+
peer_task_id_to_cancel,
|
|
291
328
|
)
|
|
292
329
|
except Exception as e_peer_cancel:
|
|
293
330
|
log.error(
|
|
294
|
-
"%s Failed to send CancelTaskRequest to peer %s for
|
|
331
|
+
"%s Failed to send CancelTaskRequest to peer %s for task %s: %s",
|
|
295
332
|
component.log_identifier,
|
|
296
333
|
target_peer_agent_name,
|
|
297
|
-
|
|
334
|
+
peer_task_id_to_cancel,
|
|
298
335
|
e_peer_cancel,
|
|
299
336
|
)
|
|
300
337
|
else:
|
|
@@ -325,10 +362,18 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
325
362
|
ack_e,
|
|
326
363
|
)
|
|
327
364
|
return None
|
|
328
|
-
elif
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
365
|
+
elif method in ["message/send", "message/stream"]:
|
|
366
|
+
a2a_message = a2a.get_message_from_send_request(a2a_request)
|
|
367
|
+
if not a2a_message:
|
|
368
|
+
raise ValueError("Could not extract message from SendMessageRequest")
|
|
369
|
+
|
|
370
|
+
# The gateway/client is the source of truth for the task ID.
|
|
371
|
+
# The agent adopts the ID from the JSON-RPC request envelope.
|
|
372
|
+
logical_task_id = str(a2a.get_request_id(a2a_request))
|
|
373
|
+
# The session id is now contextId on the message
|
|
374
|
+
original_session_id = a2a_message.context_id
|
|
375
|
+
message_id = a2a_message.message_id
|
|
376
|
+
task_metadata = a2a_message.metadata or {}
|
|
332
377
|
system_purpose = task_metadata.get("system_purpose")
|
|
333
378
|
response_format = task_metadata.get("response_format")
|
|
334
379
|
session_behavior_from_meta = task_metadata.get("sessionBehavior")
|
|
@@ -357,7 +402,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
357
402
|
)
|
|
358
403
|
user_id = message.get_user_properties().get("userId", "default_user")
|
|
359
404
|
agent_name = component.get_config("agent_name")
|
|
360
|
-
is_streaming_request =
|
|
405
|
+
is_streaming_request = method == "message/stream"
|
|
361
406
|
host_supports_streaming = component.get_config("supports_streaming", False)
|
|
362
407
|
if is_streaming_request and not host_supports_streaming:
|
|
363
408
|
raise ValueError(
|
|
@@ -368,14 +413,14 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
368
413
|
temporary_run_session_id_for_cleanup = None
|
|
369
414
|
if session_behavior == "RUN_BASED":
|
|
370
415
|
is_run_based_session = True
|
|
371
|
-
effective_session_id = f"{original_session_id}:{
|
|
416
|
+
effective_session_id = f"{original_session_id}:{logical_task_id}:run"
|
|
372
417
|
temporary_run_session_id_for_cleanup = effective_session_id
|
|
373
418
|
log.info(
|
|
374
419
|
"%s Session behavior is RUN_BASED. OriginalID='%s', EffectiveID for this run='%s', TaskID='%s'.",
|
|
375
420
|
component.log_identifier,
|
|
376
421
|
original_session_id,
|
|
377
422
|
effective_session_id,
|
|
378
|
-
|
|
423
|
+
logical_task_id,
|
|
379
424
|
)
|
|
380
425
|
else:
|
|
381
426
|
is_run_based_session = False
|
|
@@ -385,7 +430,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
385
430
|
"%s Session behavior is PERSISTENT. EffectiveID='%s' for TaskID='%s'.",
|
|
386
431
|
component.log_identifier,
|
|
387
432
|
effective_session_id,
|
|
388
|
-
|
|
433
|
+
logical_task_id,
|
|
389
434
|
)
|
|
390
435
|
adk_session_for_run = await component.session_service.get_session(
|
|
391
436
|
app_name=agent_name, user_id=user_id, session_id=effective_session_id
|
|
@@ -400,14 +445,14 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
400
445
|
"%s Created new ADK session '%s' for task '%s'.",
|
|
401
446
|
component.log_identifier,
|
|
402
447
|
effective_session_id,
|
|
403
|
-
|
|
448
|
+
logical_task_id,
|
|
404
449
|
)
|
|
405
450
|
else:
|
|
406
451
|
log.info(
|
|
407
452
|
"%s Reusing existing ADK session '%s' for task '%s'.",
|
|
408
453
|
component.log_identifier,
|
|
409
454
|
effective_session_id,
|
|
410
|
-
|
|
455
|
+
logical_task_id,
|
|
411
456
|
)
|
|
412
457
|
if is_run_based_session:
|
|
413
458
|
try:
|
|
@@ -447,27 +492,29 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
447
492
|
"%s No history to copy from original session '%s' for run-based task '%s'.",
|
|
448
493
|
component.log_identifier,
|
|
449
494
|
original_session_id,
|
|
450
|
-
|
|
495
|
+
logical_task_id,
|
|
451
496
|
)
|
|
452
497
|
else:
|
|
453
498
|
log.debug(
|
|
454
499
|
"%s Original session '%s' not found or has no history, cannot copy for run-based task '%s'.",
|
|
455
500
|
component.log_identifier,
|
|
456
501
|
original_session_id,
|
|
457
|
-
|
|
502
|
+
logical_task_id,
|
|
458
503
|
)
|
|
459
504
|
except Exception as e_copy:
|
|
460
505
|
log.error(
|
|
461
506
|
"%s Error copying history for run-based session '%s' (task '%s'): %s. Proceeding with empty session.",
|
|
462
507
|
component.log_identifier,
|
|
463
508
|
effective_session_id,
|
|
464
|
-
|
|
509
|
+
logical_task_id,
|
|
465
510
|
e_copy,
|
|
466
511
|
)
|
|
467
512
|
a2a_context = {
|
|
468
513
|
"jsonrpc_request_id": jsonrpc_request_id,
|
|
469
514
|
"logical_task_id": logical_task_id,
|
|
470
|
-
"
|
|
515
|
+
"contextId": original_session_id,
|
|
516
|
+
"messageId": message_id,
|
|
517
|
+
"session_id": original_session_id, # Keep for now for compatibility
|
|
471
518
|
"user_id": user_id,
|
|
472
519
|
"client_id": client_id,
|
|
473
520
|
"is_streaming": is_streaming_request,
|
|
@@ -504,7 +551,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
504
551
|
logical_task_id,
|
|
505
552
|
)
|
|
506
553
|
|
|
507
|
-
a2a_message_for_adk =
|
|
554
|
+
a2a_message_for_adk = a2a_message
|
|
508
555
|
invoked_artifacts = (
|
|
509
556
|
a2a_message_for_adk.metadata.get("invoked_with_artifacts", [])
|
|
510
557
|
if a2a_message_for_adk.metadata
|
|
@@ -515,7 +562,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
515
562
|
log.info(
|
|
516
563
|
"%s Task %s invoked with %d artifact(s). Preparing context from metadata.",
|
|
517
564
|
component.log_identifier,
|
|
518
|
-
|
|
565
|
+
logical_task_id,
|
|
519
566
|
len(invoked_artifacts),
|
|
520
567
|
)
|
|
521
568
|
header_text = (
|
|
@@ -531,24 +578,24 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
531
578
|
header_text=header_text,
|
|
532
579
|
)
|
|
533
580
|
|
|
534
|
-
task_description =
|
|
535
|
-
a2a_message_for_adk.parts
|
|
536
|
-
)
|
|
581
|
+
task_description = get_text_from_message(a2a_message_for_adk)
|
|
537
582
|
final_prompt = f"{task_description}\n\n{artifact_summary}"
|
|
538
583
|
|
|
539
|
-
a2a_message_for_adk =
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
metadata=a2a_message_for_adk.metadata,
|
|
584
|
+
a2a_message_for_adk = a2a.update_message_parts(
|
|
585
|
+
message=a2a_message_for_adk,
|
|
586
|
+
new_parts=[a2a.create_text_part(text=final_prompt)],
|
|
543
587
|
)
|
|
544
588
|
log.debug(
|
|
545
589
|
"%s Generated new prompt for task %s with artifact context.",
|
|
546
590
|
component.log_identifier,
|
|
547
|
-
|
|
591
|
+
logical_task_id,
|
|
548
592
|
)
|
|
549
593
|
|
|
550
|
-
adk_content = translate_a2a_to_adk_content(
|
|
551
|
-
a2a_message_for_adk,
|
|
594
|
+
adk_content = await translate_a2a_to_adk_content(
|
|
595
|
+
a2a_message=a2a_message_for_adk,
|
|
596
|
+
component=component,
|
|
597
|
+
user_id=user_id,
|
|
598
|
+
session_id=effective_session_id,
|
|
552
599
|
)
|
|
553
600
|
|
|
554
601
|
adk_session = await component.session_service.get_session(
|
|
@@ -621,7 +668,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
621
668
|
log.warning(
|
|
622
669
|
"%s Received unhandled A2A request type: %s. Acknowledging.",
|
|
623
670
|
component.log_identifier,
|
|
624
|
-
|
|
671
|
+
method,
|
|
625
672
|
)
|
|
626
673
|
try:
|
|
627
674
|
message.call_acknowledgements()
|
|
@@ -629,7 +676,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
629
676
|
log.error(
|
|
630
677
|
"%s Failed to ACK unhandled request type %s: %s",
|
|
631
678
|
component.log_identifier,
|
|
632
|
-
|
|
679
|
+
method,
|
|
633
680
|
ack_e,
|
|
634
681
|
)
|
|
635
682
|
return None
|
|
@@ -641,22 +688,15 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
641
688
|
e,
|
|
642
689
|
)
|
|
643
690
|
error_data = {"taskId": logical_task_id} if logical_task_id else None
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
error=InvalidRequestError(message=str(e), data=error_data),
|
|
648
|
-
)
|
|
649
|
-
else:
|
|
650
|
-
error_response = JSONRPCResponse(
|
|
651
|
-
id=jsonrpc_request_id,
|
|
652
|
-
error=JSONParseError(message=str(e), data=error_data),
|
|
653
|
-
)
|
|
691
|
+
error_response = a2a.create_internal_error_response(
|
|
692
|
+
message=str(e), request_id=jsonrpc_request_id, data=error_data
|
|
693
|
+
)
|
|
654
694
|
|
|
655
695
|
target_topic = reply_topic_from_peer or (
|
|
656
696
|
get_client_response_topic(namespace, client_id) if client_id else None
|
|
657
697
|
)
|
|
658
698
|
if target_topic:
|
|
659
|
-
component.
|
|
699
|
+
component.publish_a2a_message(
|
|
660
700
|
error_response.model_dump(exclude_none=True),
|
|
661
701
|
target_topic,
|
|
662
702
|
)
|
|
@@ -681,18 +721,16 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
681
721
|
log.exception(
|
|
682
722
|
"%s Unexpected error handling A2A request: %s", component.log_identifier, e
|
|
683
723
|
)
|
|
684
|
-
error_response =
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
data={"taskId": logical_task_id},
|
|
689
|
-
),
|
|
724
|
+
error_response = a2a.create_internal_error_response(
|
|
725
|
+
message=f"Unexpected server error: {e}",
|
|
726
|
+
request_id=jsonrpc_request_id,
|
|
727
|
+
data={"taskId": logical_task_id},
|
|
690
728
|
)
|
|
691
729
|
target_topic = reply_topic_from_peer or (
|
|
692
730
|
get_client_response_topic(namespace, client_id) if client_id else None
|
|
693
731
|
)
|
|
694
732
|
if target_topic:
|
|
695
|
-
component.
|
|
733
|
+
component.publish_a2a_message(
|
|
696
734
|
error_response.model_dump(exclude_none=True),
|
|
697
735
|
target_topic,
|
|
698
736
|
)
|
|
@@ -755,7 +793,7 @@ def handle_agent_card_message(component, message: SolaceMessage):
|
|
|
755
793
|
break
|
|
756
794
|
|
|
757
795
|
if is_allowed:
|
|
758
|
-
|
|
796
|
+
# The received card is stored as-is. We don't need to modify it.
|
|
759
797
|
component.peer_agents[agent_name] = agent_card
|
|
760
798
|
|
|
761
799
|
message.call_acknowledgements()
|
|
@@ -776,18 +814,25 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
776
814
|
|
|
777
815
|
try:
|
|
778
816
|
topic = message.get_topic()
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
817
|
+
agent_response_sub = a2a.get_agent_response_subscription_topic(
|
|
818
|
+
component.namespace, component.agent_name
|
|
819
|
+
)
|
|
820
|
+
agent_status_sub = a2a.get_agent_status_subscription_topic(
|
|
821
|
+
component.namespace, component.agent_name
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
if a2a.topic_matches_subscription(topic, agent_response_sub):
|
|
825
|
+
sub_task_id = a2a.extract_task_id_from_topic(
|
|
826
|
+
topic, agent_response_sub, component.log_identifier
|
|
827
|
+
)
|
|
828
|
+
elif a2a.topic_matches_subscription(topic, agent_status_sub):
|
|
829
|
+
sub_task_id = a2a.extract_task_id_from_topic(
|
|
830
|
+
topic, agent_status_sub, component.log_identifier
|
|
831
|
+
)
|
|
790
832
|
else:
|
|
833
|
+
sub_task_id = None
|
|
834
|
+
|
|
835
|
+
if not sub_task_id:
|
|
791
836
|
log.error(
|
|
792
837
|
"%s Could not extract sub-task ID from topic: %s",
|
|
793
838
|
component.log_identifier,
|
|
@@ -813,159 +858,185 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
813
858
|
is_final_response = True
|
|
814
859
|
else:
|
|
815
860
|
try:
|
|
816
|
-
a2a_response = JSONRPCResponse(
|
|
861
|
+
a2a_response = JSONRPCResponse.model_validate(payload_dict)
|
|
862
|
+
|
|
863
|
+
result = a2a.get_response_result(a2a_response)
|
|
864
|
+
if result:
|
|
865
|
+
payload_data = result
|
|
866
|
+
|
|
867
|
+
# Store the peer's task ID if we see it for the first time
|
|
868
|
+
peer_task_id = getattr(payload_data, "task_id", None)
|
|
869
|
+
if peer_task_id:
|
|
870
|
+
correlation_data = (
|
|
871
|
+
await component._get_correlation_data_for_sub_task(
|
|
872
|
+
sub_task_id
|
|
873
|
+
)
|
|
874
|
+
)
|
|
875
|
+
if correlation_data and "peer_task_id" not in correlation_data:
|
|
876
|
+
log.info(
|
|
877
|
+
"%s Received first response for sub-task %s. Storing peer taskId: %s",
|
|
878
|
+
component.log_identifier,
|
|
879
|
+
sub_task_id,
|
|
880
|
+
peer_task_id,
|
|
881
|
+
)
|
|
882
|
+
main_logical_task_id = correlation_data.get(
|
|
883
|
+
"logical_task_id"
|
|
884
|
+
)
|
|
885
|
+
with component.active_tasks_lock:
|
|
886
|
+
task_context = component.active_tasks.get(
|
|
887
|
+
main_logical_task_id
|
|
888
|
+
)
|
|
889
|
+
if task_context:
|
|
890
|
+
with task_context.lock:
|
|
891
|
+
if (
|
|
892
|
+
sub_task_id
|
|
893
|
+
in task_context.active_peer_sub_tasks
|
|
894
|
+
):
|
|
895
|
+
task_context.active_peer_sub_tasks[
|
|
896
|
+
sub_task_id
|
|
897
|
+
]["peer_task_id"] = peer_task_id
|
|
817
898
|
|
|
818
|
-
if a2a_response.result and isinstance(a2a_response.result, dict):
|
|
819
|
-
payload_data = a2a_response.result
|
|
820
899
|
parsed_successfully = False
|
|
821
900
|
is_final_response = False
|
|
822
901
|
payload_to_queue = None
|
|
823
902
|
|
|
824
|
-
if (
|
|
825
|
-
"final" in payload_data
|
|
826
|
-
and "status" in payload_data
|
|
827
|
-
and isinstance(payload_data.get("final"), bool)
|
|
828
|
-
):
|
|
903
|
+
if isinstance(payload_data, TaskStatusUpdateEvent):
|
|
829
904
|
try:
|
|
830
|
-
status_event =
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
905
|
+
status_event = payload_data
|
|
906
|
+
data_parts = a2a.get_data_parts_from_status_update(
|
|
907
|
+
status_event
|
|
908
|
+
)
|
|
909
|
+
if data_parts:
|
|
910
|
+
for data_part in data_parts:
|
|
911
|
+
log.info(
|
|
912
|
+
"%s Received DataPart signal from peer for sub-task %s. Forwarding...",
|
|
913
|
+
component.log_identifier,
|
|
914
|
+
sub_task_id,
|
|
915
|
+
)
|
|
916
|
+
correlation_data = await component._get_correlation_data_for_sub_task(
|
|
917
|
+
sub_task_id
|
|
918
|
+
)
|
|
919
|
+
if not correlation_data:
|
|
920
|
+
log.warning(
|
|
921
|
+
"%s Correlation data not found for sub-task %s. Cannot forward status signal.",
|
|
845
922
|
component.log_identifier,
|
|
846
923
|
sub_task_id,
|
|
847
924
|
)
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
original_task_context = correlation_data.get(
|
|
861
|
-
"original_task_context"
|
|
862
|
-
)
|
|
863
|
-
if not original_task_context:
|
|
864
|
-
log.warning(
|
|
865
|
-
"%s original_task_context not found in correlation data for sub-task %s. Cannot forward status signal.",
|
|
866
|
-
component.log_identifier,
|
|
867
|
-
sub_task_id,
|
|
868
|
-
)
|
|
869
|
-
message.call_acknowledgements()
|
|
870
|
-
return
|
|
871
|
-
|
|
872
|
-
main_logical_task_id = (
|
|
873
|
-
original_task_context.get("logical_task_id")
|
|
874
|
-
)
|
|
875
|
-
original_jsonrpc_request_id = (
|
|
876
|
-
original_task_context.get(
|
|
877
|
-
"jsonrpc_request_id"
|
|
878
|
-
)
|
|
925
|
+
message.call_acknowledgements()
|
|
926
|
+
return
|
|
927
|
+
|
|
928
|
+
original_task_context = correlation_data.get(
|
|
929
|
+
"original_task_context"
|
|
930
|
+
)
|
|
931
|
+
if not original_task_context:
|
|
932
|
+
log.warning(
|
|
933
|
+
"%s original_task_context not found in correlation data for sub-task %s. Cannot forward status signal.",
|
|
934
|
+
component.log_identifier,
|
|
935
|
+
sub_task_id,
|
|
879
936
|
)
|
|
937
|
+
message.call_acknowledgements()
|
|
938
|
+
return
|
|
939
|
+
|
|
940
|
+
main_logical_task_id = original_task_context.get(
|
|
941
|
+
"logical_task_id"
|
|
942
|
+
)
|
|
943
|
+
original_jsonrpc_request_id = (
|
|
944
|
+
original_task_context.get("jsonrpc_request_id")
|
|
945
|
+
)
|
|
946
|
+
main_context_id = original_task_context.get(
|
|
947
|
+
"contextId"
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
target_topic_for_forward = (
|
|
951
|
+
original_task_context.get("statusTopic")
|
|
952
|
+
)
|
|
880
953
|
|
|
881
|
-
|
|
882
|
-
|
|
954
|
+
if (
|
|
955
|
+
not main_logical_task_id
|
|
956
|
+
or not original_jsonrpc_request_id
|
|
957
|
+
or not target_topic_for_forward
|
|
958
|
+
):
|
|
959
|
+
log.error(
|
|
960
|
+
"%s Missing critical info (main_task_id, original_rpc_id, or target_status_topic) in context for sub-task %s. Cannot forward. Context: %s",
|
|
961
|
+
component.log_identifier,
|
|
962
|
+
sub_task_id,
|
|
963
|
+
original_task_context,
|
|
883
964
|
)
|
|
965
|
+
message.call_acknowledgements()
|
|
966
|
+
return
|
|
884
967
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
or not target_topic_for_forward
|
|
889
|
-
):
|
|
890
|
-
log.error(
|
|
891
|
-
"%s Missing critical info (main_task_id, original_rpc_id, or target_status_topic) in context for sub-task %s. Cannot forward. Context: %s",
|
|
892
|
-
component.log_identifier,
|
|
893
|
-
sub_task_id,
|
|
894
|
-
original_task_context,
|
|
895
|
-
)
|
|
896
|
-
message.call_acknowledgements()
|
|
897
|
-
return
|
|
898
|
-
|
|
899
|
-
peer_agent_name = (
|
|
900
|
-
status_event.metadata.get(
|
|
901
|
-
"agent_name", "UnknownPeer"
|
|
902
|
-
)
|
|
903
|
-
if status_event.metadata
|
|
904
|
-
else "UnknownPeer"
|
|
968
|
+
peer_agent_name = (
|
|
969
|
+
status_event.metadata.get(
|
|
970
|
+
"agent_name", "UnknownPeer"
|
|
905
971
|
)
|
|
972
|
+
if status_event.metadata
|
|
973
|
+
else "UnknownPeer"
|
|
974
|
+
)
|
|
906
975
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
"
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
976
|
+
forwarded_message = a2a.create_agent_parts_message(
|
|
977
|
+
parts=[data_part],
|
|
978
|
+
metadata={
|
|
979
|
+
"agent_name": component.agent_name,
|
|
980
|
+
"forwarded_from_peer": peer_agent_name,
|
|
981
|
+
"original_peer_event_taskId": status_event.task_id,
|
|
982
|
+
"original_peer_event_timestamp": (
|
|
983
|
+
status_event.status.timestamp
|
|
984
|
+
if status_event.status
|
|
985
|
+
and status_event.status.timestamp
|
|
986
|
+
else None
|
|
987
|
+
),
|
|
988
|
+
"function_call_id": correlation_data.get(
|
|
989
|
+
"adk_function_call_id", None
|
|
990
|
+
),
|
|
991
|
+
},
|
|
992
|
+
)
|
|
993
|
+
forwarded_event = a2a.create_status_update(
|
|
994
|
+
task_id=main_logical_task_id,
|
|
995
|
+
context_id=main_context_id,
|
|
996
|
+
message=forwarded_message,
|
|
997
|
+
is_final=False,
|
|
998
|
+
)
|
|
999
|
+
if (
|
|
1000
|
+
status_event.status
|
|
1001
|
+
and status_event.status.timestamp
|
|
1002
|
+
):
|
|
1003
|
+
forwarded_event.status.timestamp = (
|
|
1004
|
+
status_event.status.timestamp
|
|
924
1005
|
)
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1006
|
+
forwarded_rpc_response = JSONRPCResponse(
|
|
1007
|
+
id=original_jsonrpc_request_id,
|
|
1008
|
+
result=forwarded_event,
|
|
1009
|
+
)
|
|
1010
|
+
payload_to_publish = (
|
|
1011
|
+
forwarded_rpc_response.model_dump(
|
|
1012
|
+
by_alias=True, exclude_none=True
|
|
929
1013
|
)
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
try:
|
|
1017
|
+
component.publish_a2a_message(
|
|
1018
|
+
payload_to_publish,
|
|
1019
|
+
target_topic_for_forward,
|
|
934
1020
|
)
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1021
|
+
log.info(
|
|
1022
|
+
"%s Forwarded DataPart signal for main task %s (from peer %s) to %s.",
|
|
1023
|
+
component.log_identifier,
|
|
1024
|
+
main_logical_task_id,
|
|
1025
|
+
peer_agent_name,
|
|
1026
|
+
target_topic_for_forward,
|
|
938
1027
|
)
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1028
|
+
except Exception as pub_err:
|
|
1029
|
+
log.exception(
|
|
1030
|
+
"%s Failed to publish forwarded status signal for main task %s: %s",
|
|
1031
|
+
component.log_identifier,
|
|
1032
|
+
main_logical_task_id,
|
|
1033
|
+
pub_err,
|
|
943
1034
|
)
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
component._publish_a2a_message(
|
|
947
|
-
payload_to_publish,
|
|
948
|
-
target_topic_for_forward,
|
|
949
|
-
)
|
|
950
|
-
log.info(
|
|
951
|
-
"%s Forwarded agent_status_message signal for main task %s (from peer %s) to %s.",
|
|
952
|
-
component.log_identifier,
|
|
953
|
-
main_logical_task_id,
|
|
954
|
-
peer_agent_name,
|
|
955
|
-
target_topic_for_forward,
|
|
956
|
-
)
|
|
957
|
-
except Exception as pub_err:
|
|
958
|
-
log.exception(
|
|
959
|
-
"%s Failed to publish forwarded status signal for main task %s: %s",
|
|
960
|
-
component.log_identifier,
|
|
961
|
-
main_logical_task_id,
|
|
962
|
-
pub_err,
|
|
963
|
-
)
|
|
964
|
-
message.call_acknowledgements()
|
|
965
|
-
return
|
|
1035
|
+
message.call_acknowledgements()
|
|
1036
|
+
return
|
|
966
1037
|
|
|
967
1038
|
payload_to_queue = status_event.model_dump(
|
|
968
|
-
exclude_none=True
|
|
1039
|
+
by_alias=True, exclude_none=True
|
|
969
1040
|
)
|
|
970
1041
|
if status_event.final:
|
|
971
1042
|
log.debug(
|
|
@@ -974,22 +1045,15 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
974
1045
|
sub_task_id,
|
|
975
1046
|
)
|
|
976
1047
|
|
|
977
|
-
if
|
|
978
|
-
status_event.status
|
|
979
|
-
and status_event.status.message
|
|
980
|
-
and status_event.status.message.parts
|
|
981
|
-
):
|
|
1048
|
+
if status_event.status and status_event.status.message:
|
|
982
1049
|
response_parts_data = []
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
):
|
|
1050
|
+
unwrapped_parts = a2a.get_parts_from_message(
|
|
1051
|
+
status_event.status.message
|
|
1052
|
+
)
|
|
1053
|
+
for part in unwrapped_parts:
|
|
1054
|
+
if isinstance(part, TextPart):
|
|
988
1055
|
response_parts_data.append(str(part.text))
|
|
989
|
-
elif (
|
|
990
|
-
hasattr(part, "data")
|
|
991
|
-
and part.data is not None
|
|
992
|
-
):
|
|
1056
|
+
elif isinstance(part, DataPart):
|
|
993
1057
|
try:
|
|
994
1058
|
response_parts_data.append(
|
|
995
1059
|
json.dumps(part.data)
|
|
@@ -1023,7 +1087,7 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1023
1087
|
parsed_successfully = True
|
|
1024
1088
|
except Exception as e:
|
|
1025
1089
|
log.warning(
|
|
1026
|
-
"%s Failed to
|
|
1090
|
+
"%s Failed to process payload as TaskStatusUpdateEvent for sub-task %s. Payload: %s. Error: %s",
|
|
1027
1091
|
component.log_identifier,
|
|
1028
1092
|
sub_task_id,
|
|
1029
1093
|
payload_data,
|
|
@@ -1031,15 +1095,11 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1031
1095
|
)
|
|
1032
1096
|
payload_to_queue = None
|
|
1033
1097
|
|
|
1034
|
-
|
|
1035
|
-
not parsed_successfully
|
|
1036
|
-
and "artifact" in payload_data
|
|
1037
|
-
and isinstance(payload_data.get("artifact"), dict)
|
|
1038
|
-
):
|
|
1098
|
+
elif isinstance(payload_data, TaskArtifactUpdateEvent):
|
|
1039
1099
|
try:
|
|
1040
|
-
artifact_event =
|
|
1100
|
+
artifact_event = payload_data
|
|
1041
1101
|
payload_to_queue = artifact_event.model_dump(
|
|
1042
|
-
exclude_none=True
|
|
1102
|
+
by_alias=True, exclude_none=True
|
|
1043
1103
|
)
|
|
1044
1104
|
is_final_response = False
|
|
1045
1105
|
log.debug(
|
|
@@ -1058,10 +1118,12 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1058
1118
|
)
|
|
1059
1119
|
payload_to_queue = None
|
|
1060
1120
|
|
|
1061
|
-
|
|
1121
|
+
elif isinstance(payload_data, Task):
|
|
1062
1122
|
try:
|
|
1063
|
-
final_task =
|
|
1064
|
-
payload_to_queue = final_task.model_dump(
|
|
1123
|
+
final_task = payload_data
|
|
1124
|
+
payload_to_queue = final_task.model_dump(
|
|
1125
|
+
by_alias=True, exclude_none=True
|
|
1126
|
+
)
|
|
1065
1127
|
is_final_response = True
|
|
1066
1128
|
log.debug(
|
|
1067
1129
|
"%s Parsed final Task object from peer for sub-task %s.",
|
|
@@ -1071,26 +1133,31 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1071
1133
|
parsed_successfully = True
|
|
1072
1134
|
except Exception as task_parse_error:
|
|
1073
1135
|
log.error(
|
|
1074
|
-
"%s Failed to parse peer response for sub-task %s as
|
|
1136
|
+
"%s Failed to parse peer response for sub-task %s as Task. Payload: %s. Error: %s",
|
|
1075
1137
|
component.log_identifier,
|
|
1076
1138
|
sub_task_id,
|
|
1077
1139
|
payload_data,
|
|
1078
1140
|
task_parse_error,
|
|
1079
1141
|
)
|
|
1080
|
-
if not a2a_response
|
|
1081
|
-
|
|
1142
|
+
if not a2a.get_response_error(a2a_response):
|
|
1143
|
+
error = a2a.create_internal_error(
|
|
1082
1144
|
message=f"Failed to parse response from peer agent for sub-task {sub_task_id}",
|
|
1083
1145
|
data={
|
|
1084
|
-
"original_payload": payload_data
|
|
1146
|
+
"original_payload": payload_data.model_dump(
|
|
1147
|
+
by_alias=True, exclude_none=True
|
|
1148
|
+
),
|
|
1085
1149
|
"error": str(task_parse_error),
|
|
1086
1150
|
},
|
|
1087
1151
|
)
|
|
1152
|
+
a2a_response = a2a.create_error_response(
|
|
1153
|
+
error, a2a.get_response_id(a2a_response)
|
|
1154
|
+
)
|
|
1088
1155
|
payload_to_queue = None
|
|
1089
1156
|
is_final_response = True
|
|
1090
1157
|
|
|
1091
1158
|
if (
|
|
1092
1159
|
not parsed_successfully
|
|
1093
|
-
and not a2a_response
|
|
1160
|
+
and not a2a.get_response_error(a2a_response)
|
|
1094
1161
|
and payload_to_queue is None
|
|
1095
1162
|
):
|
|
1096
1163
|
log.error(
|
|
@@ -1099,23 +1166,30 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1099
1166
|
sub_task_id,
|
|
1100
1167
|
payload_data,
|
|
1101
1168
|
)
|
|
1102
|
-
|
|
1169
|
+
error = a2a.create_internal_error(
|
|
1103
1170
|
message=f"Unknown response structure from peer agent for sub-task {sub_task_id}",
|
|
1104
|
-
data={
|
|
1171
|
+
data={
|
|
1172
|
+
"original_payload": payload_data.model_dump(
|
|
1173
|
+
by_alias=True, exclude_none=True
|
|
1174
|
+
)
|
|
1175
|
+
},
|
|
1176
|
+
)
|
|
1177
|
+
a2a_response = a2a.create_error_response(
|
|
1178
|
+
error, a2a.get_response_id(a2a_response)
|
|
1105
1179
|
)
|
|
1106
1180
|
is_final_response = True
|
|
1107
1181
|
|
|
1108
|
-
elif a2a_response
|
|
1182
|
+
elif error := a2a.get_response_error(a2a_response):
|
|
1109
1183
|
log.warning(
|
|
1110
1184
|
"%s Received error response from peer for sub-task %s: %s",
|
|
1111
1185
|
component.log_identifier,
|
|
1112
1186
|
sub_task_id,
|
|
1113
|
-
|
|
1187
|
+
error,
|
|
1114
1188
|
)
|
|
1115
1189
|
payload_to_queue = {
|
|
1116
|
-
"error":
|
|
1117
|
-
"code":
|
|
1118
|
-
"data":
|
|
1190
|
+
"error": error.message,
|
|
1191
|
+
"code": error.code,
|
|
1192
|
+
"data": error.data,
|
|
1119
1193
|
}
|
|
1120
1194
|
is_final_response = True
|
|
1121
1195
|
else:
|
|
@@ -1206,9 +1280,7 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1206
1280
|
try:
|
|
1207
1281
|
task_obj = Task(**payload_to_queue)
|
|
1208
1282
|
if task_obj.status and task_obj.status.message:
|
|
1209
|
-
final_text =
|
|
1210
|
-
task_obj.status.message.parts
|
|
1211
|
-
)
|
|
1283
|
+
final_text = get_text_from_message(task_obj.status.message)
|
|
1212
1284
|
|
|
1213
1285
|
if (
|
|
1214
1286
|
task_obj.metadata
|
|
@@ -1264,6 +1336,13 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1264
1336
|
if artifact_summary:
|
|
1265
1337
|
full_response_text = f"{artifact_summary}\n\n{full_response_text}"
|
|
1266
1338
|
|
|
1339
|
+
await _publish_peer_tool_result_notification(
|
|
1340
|
+
component=component,
|
|
1341
|
+
correlation_data=correlation_data,
|
|
1342
|
+
payload_to_queue=payload_to_queue,
|
|
1343
|
+
log_identifier=log_retrigger,
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1267
1346
|
current_result = {
|
|
1268
1347
|
"adk_function_call_id": correlation_data.get("adk_function_call_id"),
|
|
1269
1348
|
"peer_tool_name": correlation_data.get("peer_tool_name"),
|
|
@@ -1355,34 +1434,92 @@ def publish_agent_card(component):
|
|
|
1355
1434
|
agent_request_topic = get_agent_request_topic(namespace, agent_name)
|
|
1356
1435
|
dynamic_url = f"solace:{agent_request_topic}"
|
|
1357
1436
|
|
|
1437
|
+
# Define unique URIs for our custom extensions.
|
|
1438
|
+
PEER_TOPOLOGY_EXTENSION_URI = (
|
|
1439
|
+
"https://solace.com/a2a/extensions/peer-agent-topology"
|
|
1440
|
+
)
|
|
1441
|
+
DISPLAY_NAME_EXTENSION_URI = "https://solace.com/a2a/extensions/display-name"
|
|
1442
|
+
TOOLS_EXTENSION_URI = "https://solace.com/a2a/extensions/sam/tools"
|
|
1443
|
+
|
|
1444
|
+
extensions_list = []
|
|
1445
|
+
|
|
1446
|
+
# Create the extension object for peer agents.
|
|
1447
|
+
if peer_agents:
|
|
1448
|
+
peer_topology_extension = AgentExtension(
|
|
1449
|
+
uri=PEER_TOPOLOGY_EXTENSION_URI,
|
|
1450
|
+
description="A list of peer agents this agent is configured to communicate with.",
|
|
1451
|
+
params={"peer_agent_names": list(peer_agents.keys())},
|
|
1452
|
+
)
|
|
1453
|
+
extensions_list.append(peer_topology_extension)
|
|
1454
|
+
|
|
1455
|
+
# Create the extension object for the UI display name.
|
|
1456
|
+
if display_name:
|
|
1457
|
+
display_name_extension = AgentExtension(
|
|
1458
|
+
uri=DISPLAY_NAME_EXTENSION_URI,
|
|
1459
|
+
description="A UI-friendly display name for the agent.",
|
|
1460
|
+
params={"display_name": display_name},
|
|
1461
|
+
)
|
|
1462
|
+
extensions_list.append(display_name_extension)
|
|
1463
|
+
|
|
1464
|
+
# Create the extension object for the agent's tools.
|
|
1465
|
+
dynamic_tools = getattr(component, "agent_card_tool_manifest", [])
|
|
1466
|
+
if dynamic_tools:
|
|
1467
|
+
# Ensure all tools have a 'tags' field to prevent validation errors.
|
|
1468
|
+
processed_tools = []
|
|
1469
|
+
for tool in dynamic_tools:
|
|
1470
|
+
if "tags" not in tool:
|
|
1471
|
+
log.debug(
|
|
1472
|
+
"%s Tool '%s' in manifest is missing 'tags' field. Defaulting to empty list.",
|
|
1473
|
+
component.log_identifier,
|
|
1474
|
+
tool.get("id", "unknown"),
|
|
1475
|
+
)
|
|
1476
|
+
tool["tags"] = []
|
|
1477
|
+
processed_tools.append(tool)
|
|
1478
|
+
|
|
1479
|
+
tools_params = ToolsExtensionParams(tools=processed_tools)
|
|
1480
|
+
tools_extension = AgentExtension(
|
|
1481
|
+
uri=TOOLS_EXTENSION_URI,
|
|
1482
|
+
description="A list of tools available to the agent.",
|
|
1483
|
+
params=tools_params.model_dump(exclude_none=True),
|
|
1484
|
+
)
|
|
1485
|
+
extensions_list.append(tools_extension)
|
|
1486
|
+
|
|
1487
|
+
# Build the capabilities object, including our custom extensions.
|
|
1358
1488
|
capabilities = AgentCapabilities(
|
|
1359
1489
|
streaming=supports_streaming,
|
|
1360
|
-
|
|
1361
|
-
|
|
1490
|
+
push_notifications=False,
|
|
1491
|
+
state_transition_history=False,
|
|
1492
|
+
extensions=extensions_list if extensions_list else None,
|
|
1362
1493
|
)
|
|
1363
1494
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1495
|
+
skills_from_config = card_config.get("skills", [])
|
|
1496
|
+
# The 'tools' field is not part of the official AgentCard spec.
|
|
1497
|
+
# The tools are now included as an extension.
|
|
1498
|
+
|
|
1499
|
+
# Ensure all skills have a 'tags' field to prevent validation errors.
|
|
1500
|
+
processed_skills = []
|
|
1501
|
+
for skill in skills_from_config:
|
|
1502
|
+
if "tags" not in skill:
|
|
1503
|
+
skill["tags"] = []
|
|
1504
|
+
processed_skills.append(skill)
|
|
1366
1505
|
|
|
1367
1506
|
agent_card = AgentCard(
|
|
1368
1507
|
name=agent_name,
|
|
1369
|
-
|
|
1508
|
+
protocol_version=card_config.get("protocolVersion", "0.3.0"),
|
|
1370
1509
|
version=component.HOST_COMPONENT_VERSION,
|
|
1371
1510
|
url=dynamic_url,
|
|
1372
1511
|
capabilities=capabilities,
|
|
1373
1512
|
description=card_config.get("description", ""),
|
|
1374
|
-
skills=
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
documentationUrl=card_config.get("documentationUrl"),
|
|
1513
|
+
skills=processed_skills,
|
|
1514
|
+
default_input_modes=card_config.get("defaultInputModes", ["text"]),
|
|
1515
|
+
default_output_modes=card_config.get("defaultOutputModes", ["text"]),
|
|
1516
|
+
documentation_url=card_config.get("documentationUrl"),
|
|
1379
1517
|
provider=card_config.get("provider"),
|
|
1380
|
-
peer_agents=peer_agents,
|
|
1381
1518
|
)
|
|
1382
1519
|
|
|
1383
1520
|
discovery_topic = get_discovery_topic(namespace)
|
|
1384
1521
|
|
|
1385
|
-
component.
|
|
1522
|
+
component.publish_a2a_message(
|
|
1386
1523
|
agent_card.model_dump(exclude_none=True), discovery_topic
|
|
1387
1524
|
)
|
|
1388
1525
|
log.debug(
|