solace-agent-mesh 1.0.9__py3-none-any.whl → 1.3.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 +25 -17
- solace_agent_mesh/agent/adk/services.py +3 -3
- 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 +460 -317
- solace_agent_mesh/agent/protocol/protocol_llm.txt +54 -7
- solace_agent_mesh/agent/sac/app.py +2 -2
- solace_agent_mesh/agent/sac/component.py +211 -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/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.08d30374.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 +4 -4
- 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-1757433031159.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1757433031159.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/agent_cmd.py +125 -48
- solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
- solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
- solace_agent_mesh/cli/commands/run_cmd.py +5 -3
- solace_agent_mesh/cli/utils.py +68 -12
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-vY5eu2lI.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-BeBkzgWW.js +25 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Bjys1KQs.js +339 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-CE0AeXyK.js +395 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -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/embeds/resolver.py +1 -0
- solace_agent_mesh/common/utils/utils_llm.txt +323 -42
- solace_agent_mesh/config_portal/backend/common.py +2 -2
- solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +10 -8
- solace_agent_mesh/core_a2a/service.py +20 -44
- solace_agent_mesh/evaluation/message_organizer.py +35 -56
- solace_agent_mesh/evaluation/run.py +26 -5
- solace_agent_mesh/evaluation/subscriber.py +35 -10
- solace_agent_mesh/evaluation/summary_builder.py +27 -34
- 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/ARCHITECTURE_GUIDE.md +676 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
- solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
- solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
- solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
- solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
- solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
- solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
- solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
- solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
- solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
- solace_agent_mesh/gateway/http_sse/app.py +31 -1
- solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
- solace_agent_mesh/gateway/http_sse/component.py +371 -236
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +29 -29
- solace_agent_mesh/gateway/http_sse/dependencies.py +142 -39
- solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
- solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +272 -36
- solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
- solace_agent_mesh/gateway/http_sse/main.py +293 -91
- solace_agent_mesh/gateway/http_sse/routers/agents.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +137 -56
- solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +231 -5
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +199 -171
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
- 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/gateway/http_sse/session_manager.py +64 -30
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
- solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
- solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
- solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
- solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
- solace_agent_mesh/templates/gateway_component_template.py +149 -98
- solace_agent_mesh/templates/shared_config.yaml +4 -5
- solace_agent_mesh/templates/webui.yaml +8 -10
- {solace_agent_mesh-1.0.9.dist-info → solace_agent_mesh-1.3.0.dist-info}/METADATA +9 -6
- {solace_agent_mesh-1.0.9.dist-info → solace_agent_mesh-1.3.0.dist-info}/RECORD +197 -141
- solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.3d0e7879.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.05d19492.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1757091012487.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1757091012487.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
- solace_agent_mesh/client/webui/frontend/static/assets/main-D0FnP_W4.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-Do32sFPX.js +0 -708
- 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/_index-Bym6YkMd.js +0 -98
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -80
- solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
- /solace_agent_mesh/assets/docs/assets/js/{main.3d0e7879.js.LICENSE.txt → main.08d30374.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.9.dist-info → solace_agent_mesh-1.3.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.9.dist-info → solace_agent_mesh-1.3.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.9.dist-info → solace_agent_mesh-1.3.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.
|
|
@@ -198,44 +229,29 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
198
229
|
component.log_identifier,
|
|
199
230
|
message.get_topic(),
|
|
200
231
|
)
|
|
201
|
-
a2a_context = {}
|
|
202
|
-
adk_session = None
|
|
203
|
-
jsonrpc_request_id = None
|
|
204
|
-
logical_task_id = None
|
|
205
|
-
client_id = message.get_user_properties().get("clientId", "default_client")
|
|
206
|
-
status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
|
|
207
|
-
reply_topic_from_peer = message.get_user_properties().get("replyTo")
|
|
208
|
-
namespace = component.get_config("namespace")
|
|
209
|
-
a2a_user_config = message.get_user_properties().get("a2aUserConfig", {})
|
|
210
|
-
if not isinstance(a2a_user_config, dict):
|
|
211
|
-
log.warning(
|
|
212
|
-
"%s 'a2aUserConfig' user property is not a dictionary, received: %s. Defaulting to empty dict.",
|
|
213
|
-
component.log_identifier,
|
|
214
|
-
type(a2a_user_config),
|
|
215
|
-
)
|
|
216
|
-
a2a_user_config = {}
|
|
217
|
-
log.debug(
|
|
218
|
-
"%s Extracted 'a2aUserConfig': %s",
|
|
219
|
-
component.log_identifier,
|
|
220
|
-
a2a_user_config,
|
|
221
|
-
)
|
|
222
232
|
try:
|
|
223
233
|
payload_dict = message.get_payload()
|
|
224
234
|
if not isinstance(payload_dict, dict):
|
|
225
235
|
raise ValueError("Payload is not a dictionary.")
|
|
226
|
-
|
|
227
|
-
a2a_request
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
|
|
237
|
+
jsonrpc_request_id = a2a.get_request_id(a2a_request)
|
|
238
|
+
|
|
239
|
+
# Extract properties from message user properties
|
|
240
|
+
client_id = message.get_user_properties().get("clientId", "default_client")
|
|
241
|
+
status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
|
|
242
|
+
reply_topic_from_peer = message.get_user_properties().get("replyTo")
|
|
243
|
+
namespace = component.get_config("namespace")
|
|
244
|
+
a2a_user_config = message.get_user_properties().get("a2aUserConfig", {})
|
|
245
|
+
if not isinstance(a2a_user_config, dict):
|
|
246
|
+
log.warning("a2aUserConfig is not a dict, using empty dict instead")
|
|
247
|
+
a2a_user_config = {}
|
|
248
|
+
|
|
249
|
+
# The concept of logical_task_id changes. For Cancel, it's in params.id.
|
|
250
|
+
# For Send, we will generate it.
|
|
251
|
+
logical_task_id = None
|
|
252
|
+
method = a2a.get_request_method(a2a_request)
|
|
253
|
+
if method == "tasks/cancel":
|
|
254
|
+
logical_task_id = a2a.get_task_id_from_cancel_request(a2a_request)
|
|
239
255
|
log.info(
|
|
240
256
|
"%s Received CancelTaskRequest for Task ID: %s.",
|
|
241
257
|
component.log_identifier,
|
|
@@ -253,28 +269,38 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
253
269
|
logical_task_id,
|
|
254
270
|
)
|
|
255
271
|
|
|
256
|
-
peer_sub_tasks = task_context.active_peer_sub_tasks
|
|
272
|
+
peer_sub_tasks = task_context.active_peer_sub_tasks.copy()
|
|
257
273
|
if peer_sub_tasks:
|
|
258
|
-
for sub_task_info in peer_sub_tasks:
|
|
259
|
-
sub_task_id = sub_task_info.get("sub_task_id")
|
|
274
|
+
for sub_task_id, sub_task_info in peer_sub_tasks.items():
|
|
260
275
|
target_peer_agent_name = sub_task_info.get("peer_agent_name")
|
|
261
|
-
|
|
276
|
+
peer_task_id_to_cancel = sub_task_info.get("peer_task_id")
|
|
277
|
+
|
|
278
|
+
if not peer_task_id_to_cancel:
|
|
279
|
+
log.warning(
|
|
280
|
+
"%s Cannot cancel peer sub-task %s for main task %s because the peer's taskId is not yet known.",
|
|
281
|
+
component.log_identifier,
|
|
282
|
+
sub_task_id,
|
|
283
|
+
logical_task_id,
|
|
284
|
+
)
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
if peer_task_id_to_cancel and target_peer_agent_name:
|
|
262
288
|
log.info(
|
|
263
|
-
"%s Attempting to cancel peer sub-task %s for agent %s (main task %s).",
|
|
289
|
+
"%s Attempting to cancel peer sub-task %s (Peer Task ID: %s) for agent %s (main task %s).",
|
|
264
290
|
component.log_identifier,
|
|
265
291
|
sub_task_id,
|
|
292
|
+
peer_task_id_to_cancel,
|
|
266
293
|
target_peer_agent_name,
|
|
267
294
|
logical_task_id,
|
|
268
295
|
)
|
|
269
296
|
try:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
params=peer_cancel_params
|
|
297
|
+
peer_cancel_request = a2a.create_cancel_task_request(
|
|
298
|
+
task_id=peer_task_id_to_cancel
|
|
273
299
|
)
|
|
274
300
|
peer_cancel_user_props = {
|
|
275
301
|
"clientId": component.agent_name
|
|
276
302
|
}
|
|
277
|
-
component.
|
|
303
|
+
component.publish_a2a_message(
|
|
278
304
|
payload=peer_cancel_request.model_dump(
|
|
279
305
|
exclude_none=True
|
|
280
306
|
),
|
|
@@ -284,17 +310,17 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
284
310
|
user_properties=peer_cancel_user_props,
|
|
285
311
|
)
|
|
286
312
|
log.info(
|
|
287
|
-
"%s Sent CancelTaskRequest to peer %s for
|
|
313
|
+
"%s Sent CancelTaskRequest to peer %s for its task %s.",
|
|
288
314
|
component.log_identifier,
|
|
289
315
|
target_peer_agent_name,
|
|
290
|
-
|
|
316
|
+
peer_task_id_to_cancel,
|
|
291
317
|
)
|
|
292
318
|
except Exception as e_peer_cancel:
|
|
293
319
|
log.error(
|
|
294
|
-
"%s Failed to send CancelTaskRequest to peer %s for
|
|
320
|
+
"%s Failed to send CancelTaskRequest to peer %s for task %s: %s",
|
|
295
321
|
component.log_identifier,
|
|
296
322
|
target_peer_agent_name,
|
|
297
|
-
|
|
323
|
+
peer_task_id_to_cancel,
|
|
298
324
|
e_peer_cancel,
|
|
299
325
|
)
|
|
300
326
|
else:
|
|
@@ -325,10 +351,18 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
325
351
|
ack_e,
|
|
326
352
|
)
|
|
327
353
|
return None
|
|
328
|
-
elif
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
354
|
+
elif method in ["message/send", "message/stream"]:
|
|
355
|
+
a2a_message = a2a.get_message_from_send_request(a2a_request)
|
|
356
|
+
if not a2a_message:
|
|
357
|
+
raise ValueError("Could not extract message from SendMessageRequest")
|
|
358
|
+
|
|
359
|
+
# The gateway/client is the source of truth for the task ID.
|
|
360
|
+
# The agent adopts the ID from the JSON-RPC request envelope.
|
|
361
|
+
logical_task_id = str(a2a.get_request_id(a2a_request))
|
|
362
|
+
# The session id is now contextId on the message
|
|
363
|
+
original_session_id = a2a_message.context_id
|
|
364
|
+
message_id = a2a_message.message_id
|
|
365
|
+
task_metadata = a2a_message.metadata or {}
|
|
332
366
|
system_purpose = task_metadata.get("system_purpose")
|
|
333
367
|
response_format = task_metadata.get("response_format")
|
|
334
368
|
session_behavior_from_meta = task_metadata.get("sessionBehavior")
|
|
@@ -357,7 +391,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
357
391
|
)
|
|
358
392
|
user_id = message.get_user_properties().get("userId", "default_user")
|
|
359
393
|
agent_name = component.get_config("agent_name")
|
|
360
|
-
is_streaming_request =
|
|
394
|
+
is_streaming_request = method == "message/stream"
|
|
361
395
|
host_supports_streaming = component.get_config("supports_streaming", False)
|
|
362
396
|
if is_streaming_request and not host_supports_streaming:
|
|
363
397
|
raise ValueError(
|
|
@@ -366,16 +400,30 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
366
400
|
effective_session_id = original_session_id
|
|
367
401
|
is_run_based_session = False
|
|
368
402
|
temporary_run_session_id_for_cleanup = None
|
|
403
|
+
|
|
404
|
+
session_id_from_data = None
|
|
405
|
+
if a2a_message and a2a_message.parts:
|
|
406
|
+
for part in a2a_message.parts:
|
|
407
|
+
if isinstance(part, DataPart) and "session_id" in part.data:
|
|
408
|
+
session_id_from_data = part.data["session_id"]
|
|
409
|
+
log.info(
|
|
410
|
+
f"Extracted session_id '{session_id_from_data}' from DataPart."
|
|
411
|
+
)
|
|
412
|
+
break
|
|
413
|
+
|
|
414
|
+
if session_id_from_data:
|
|
415
|
+
original_session_id = session_id_from_data
|
|
416
|
+
|
|
369
417
|
if session_behavior == "RUN_BASED":
|
|
370
418
|
is_run_based_session = True
|
|
371
|
-
effective_session_id = f"{original_session_id}:{
|
|
419
|
+
effective_session_id = f"{original_session_id}:{logical_task_id}:run"
|
|
372
420
|
temporary_run_session_id_for_cleanup = effective_session_id
|
|
373
421
|
log.info(
|
|
374
422
|
"%s Session behavior is RUN_BASED. OriginalID='%s', EffectiveID for this run='%s', TaskID='%s'.",
|
|
375
423
|
component.log_identifier,
|
|
376
424
|
original_session_id,
|
|
377
425
|
effective_session_id,
|
|
378
|
-
|
|
426
|
+
logical_task_id,
|
|
379
427
|
)
|
|
380
428
|
else:
|
|
381
429
|
is_run_based_session = False
|
|
@@ -385,8 +433,9 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
385
433
|
"%s Session behavior is PERSISTENT. EffectiveID='%s' for TaskID='%s'.",
|
|
386
434
|
component.log_identifier,
|
|
387
435
|
effective_session_id,
|
|
388
|
-
|
|
436
|
+
logical_task_id,
|
|
389
437
|
)
|
|
438
|
+
|
|
390
439
|
adk_session_for_run = await component.session_service.get_session(
|
|
391
440
|
app_name=agent_name, user_id=user_id, session_id=effective_session_id
|
|
392
441
|
)
|
|
@@ -400,15 +449,17 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
400
449
|
"%s Created new ADK session '%s' for task '%s'.",
|
|
401
450
|
component.log_identifier,
|
|
402
451
|
effective_session_id,
|
|
403
|
-
|
|
452
|
+
logical_task_id,
|
|
404
453
|
)
|
|
454
|
+
|
|
405
455
|
else:
|
|
406
456
|
log.info(
|
|
407
457
|
"%s Reusing existing ADK session '%s' for task '%s'.",
|
|
408
458
|
component.log_identifier,
|
|
409
459
|
effective_session_id,
|
|
410
|
-
|
|
460
|
+
logical_task_id,
|
|
411
461
|
)
|
|
462
|
+
|
|
412
463
|
if is_run_based_session:
|
|
413
464
|
try:
|
|
414
465
|
original_adk_session_data = (
|
|
@@ -447,27 +498,29 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
447
498
|
"%s No history to copy from original session '%s' for run-based task '%s'.",
|
|
448
499
|
component.log_identifier,
|
|
449
500
|
original_session_id,
|
|
450
|
-
|
|
501
|
+
logical_task_id,
|
|
451
502
|
)
|
|
452
503
|
else:
|
|
453
504
|
log.debug(
|
|
454
505
|
"%s Original session '%s' not found or has no history, cannot copy for run-based task '%s'.",
|
|
455
506
|
component.log_identifier,
|
|
456
507
|
original_session_id,
|
|
457
|
-
|
|
508
|
+
logical_task_id,
|
|
458
509
|
)
|
|
459
510
|
except Exception as e_copy:
|
|
460
511
|
log.error(
|
|
461
512
|
"%s Error copying history for run-based session '%s' (task '%s'): %s. Proceeding with empty session.",
|
|
462
513
|
component.log_identifier,
|
|
463
514
|
effective_session_id,
|
|
464
|
-
|
|
515
|
+
logical_task_id,
|
|
465
516
|
e_copy,
|
|
466
517
|
)
|
|
467
518
|
a2a_context = {
|
|
468
519
|
"jsonrpc_request_id": jsonrpc_request_id,
|
|
469
520
|
"logical_task_id": logical_task_id,
|
|
470
|
-
"
|
|
521
|
+
"contextId": original_session_id,
|
|
522
|
+
"messageId": message_id,
|
|
523
|
+
"session_id": original_session_id, # Keep for now for compatibility
|
|
471
524
|
"user_id": user_id,
|
|
472
525
|
"client_id": client_id,
|
|
473
526
|
"is_streaming": is_streaming_request,
|
|
@@ -504,7 +557,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
504
557
|
logical_task_id,
|
|
505
558
|
)
|
|
506
559
|
|
|
507
|
-
a2a_message_for_adk =
|
|
560
|
+
a2a_message_for_adk = a2a_message
|
|
508
561
|
invoked_artifacts = (
|
|
509
562
|
a2a_message_for_adk.metadata.get("invoked_with_artifacts", [])
|
|
510
563
|
if a2a_message_for_adk.metadata
|
|
@@ -515,7 +568,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
515
568
|
log.info(
|
|
516
569
|
"%s Task %s invoked with %d artifact(s). Preparing context from metadata.",
|
|
517
570
|
component.log_identifier,
|
|
518
|
-
|
|
571
|
+
logical_task_id,
|
|
519
572
|
len(invoked_artifacts),
|
|
520
573
|
)
|
|
521
574
|
header_text = (
|
|
@@ -531,24 +584,24 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
531
584
|
header_text=header_text,
|
|
532
585
|
)
|
|
533
586
|
|
|
534
|
-
task_description =
|
|
535
|
-
a2a_message_for_adk.parts
|
|
536
|
-
)
|
|
587
|
+
task_description = get_text_from_message(a2a_message_for_adk)
|
|
537
588
|
final_prompt = f"{task_description}\n\n{artifact_summary}"
|
|
538
589
|
|
|
539
|
-
a2a_message_for_adk =
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
metadata=a2a_message_for_adk.metadata,
|
|
590
|
+
a2a_message_for_adk = a2a.update_message_parts(
|
|
591
|
+
message=a2a_message_for_adk,
|
|
592
|
+
new_parts=[a2a.create_text_part(text=final_prompt)],
|
|
543
593
|
)
|
|
544
594
|
log.debug(
|
|
545
595
|
"%s Generated new prompt for task %s with artifact context.",
|
|
546
596
|
component.log_identifier,
|
|
547
|
-
|
|
597
|
+
logical_task_id,
|
|
548
598
|
)
|
|
549
599
|
|
|
550
|
-
adk_content = translate_a2a_to_adk_content(
|
|
551
|
-
a2a_message_for_adk,
|
|
600
|
+
adk_content = await translate_a2a_to_adk_content(
|
|
601
|
+
a2a_message=a2a_message_for_adk,
|
|
602
|
+
component=component,
|
|
603
|
+
user_id=user_id,
|
|
604
|
+
session_id=effective_session_id,
|
|
552
605
|
)
|
|
553
606
|
|
|
554
607
|
adk_session = await component.session_service.get_session(
|
|
@@ -621,7 +674,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
621
674
|
log.warning(
|
|
622
675
|
"%s Received unhandled A2A request type: %s. Acknowledging.",
|
|
623
676
|
component.log_identifier,
|
|
624
|
-
|
|
677
|
+
method,
|
|
625
678
|
)
|
|
626
679
|
try:
|
|
627
680
|
message.call_acknowledgements()
|
|
@@ -629,7 +682,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
629
682
|
log.error(
|
|
630
683
|
"%s Failed to ACK unhandled request type %s: %s",
|
|
631
684
|
component.log_identifier,
|
|
632
|
-
|
|
685
|
+
method,
|
|
633
686
|
ack_e,
|
|
634
687
|
)
|
|
635
688
|
return None
|
|
@@ -641,22 +694,15 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
641
694
|
e,
|
|
642
695
|
)
|
|
643
696
|
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
|
-
)
|
|
697
|
+
error_response = a2a.create_internal_error_response(
|
|
698
|
+
message=str(e), request_id=jsonrpc_request_id, data=error_data
|
|
699
|
+
)
|
|
654
700
|
|
|
655
701
|
target_topic = reply_topic_from_peer or (
|
|
656
702
|
get_client_response_topic(namespace, client_id) if client_id else None
|
|
657
703
|
)
|
|
658
704
|
if target_topic:
|
|
659
|
-
component.
|
|
705
|
+
component.publish_a2a_message(
|
|
660
706
|
error_response.model_dump(exclude_none=True),
|
|
661
707
|
target_topic,
|
|
662
708
|
)
|
|
@@ -681,18 +727,16 @@ async def handle_a2a_request(component, message: SolaceMessage):
|
|
|
681
727
|
log.exception(
|
|
682
728
|
"%s Unexpected error handling A2A request: %s", component.log_identifier, e
|
|
683
729
|
)
|
|
684
|
-
error_response =
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
data={"taskId": logical_task_id},
|
|
689
|
-
),
|
|
730
|
+
error_response = a2a.create_internal_error_response(
|
|
731
|
+
message=f"Unexpected server error: {e}",
|
|
732
|
+
request_id=jsonrpc_request_id,
|
|
733
|
+
data={"taskId": logical_task_id},
|
|
690
734
|
)
|
|
691
735
|
target_topic = reply_topic_from_peer or (
|
|
692
736
|
get_client_response_topic(namespace, client_id) if client_id else None
|
|
693
737
|
)
|
|
694
738
|
if target_topic:
|
|
695
|
-
component.
|
|
739
|
+
component.publish_a2a_message(
|
|
696
740
|
error_response.model_dump(exclude_none=True),
|
|
697
741
|
target_topic,
|
|
698
742
|
)
|
|
@@ -755,7 +799,7 @@ def handle_agent_card_message(component, message: SolaceMessage):
|
|
|
755
799
|
break
|
|
756
800
|
|
|
757
801
|
if is_allowed:
|
|
758
|
-
|
|
802
|
+
# The received card is stored as-is. We don't need to modify it.
|
|
759
803
|
component.peer_agents[agent_name] = agent_card
|
|
760
804
|
|
|
761
805
|
message.call_acknowledgements()
|
|
@@ -776,18 +820,25 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
776
820
|
|
|
777
821
|
try:
|
|
778
822
|
topic = message.get_topic()
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
823
|
+
agent_response_sub = a2a.get_agent_response_subscription_topic(
|
|
824
|
+
component.namespace, component.agent_name
|
|
825
|
+
)
|
|
826
|
+
agent_status_sub = a2a.get_agent_status_subscription_topic(
|
|
827
|
+
component.namespace, component.agent_name
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
if a2a.topic_matches_subscription(topic, agent_response_sub):
|
|
831
|
+
sub_task_id = a2a.extract_task_id_from_topic(
|
|
832
|
+
topic, agent_response_sub, component.log_identifier
|
|
833
|
+
)
|
|
834
|
+
elif a2a.topic_matches_subscription(topic, agent_status_sub):
|
|
835
|
+
sub_task_id = a2a.extract_task_id_from_topic(
|
|
836
|
+
topic, agent_status_sub, component.log_identifier
|
|
837
|
+
)
|
|
790
838
|
else:
|
|
839
|
+
sub_task_id = None
|
|
840
|
+
|
|
841
|
+
if not sub_task_id:
|
|
791
842
|
log.error(
|
|
792
843
|
"%s Could not extract sub-task ID from topic: %s",
|
|
793
844
|
component.log_identifier,
|
|
@@ -813,159 +864,185 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
813
864
|
is_final_response = True
|
|
814
865
|
else:
|
|
815
866
|
try:
|
|
816
|
-
a2a_response = JSONRPCResponse(
|
|
867
|
+
a2a_response = JSONRPCResponse.model_validate(payload_dict)
|
|
868
|
+
|
|
869
|
+
result = a2a.get_response_result(a2a_response)
|
|
870
|
+
if result:
|
|
871
|
+
payload_data = result
|
|
872
|
+
|
|
873
|
+
# Store the peer's task ID if we see it for the first time
|
|
874
|
+
peer_task_id = getattr(payload_data, "task_id", None)
|
|
875
|
+
if peer_task_id:
|
|
876
|
+
correlation_data = (
|
|
877
|
+
await component._get_correlation_data_for_sub_task(
|
|
878
|
+
sub_task_id
|
|
879
|
+
)
|
|
880
|
+
)
|
|
881
|
+
if correlation_data and "peer_task_id" not in correlation_data:
|
|
882
|
+
log.info(
|
|
883
|
+
"%s Received first response for sub-task %s. Storing peer taskId: %s",
|
|
884
|
+
component.log_identifier,
|
|
885
|
+
sub_task_id,
|
|
886
|
+
peer_task_id,
|
|
887
|
+
)
|
|
888
|
+
main_logical_task_id = correlation_data.get(
|
|
889
|
+
"logical_task_id"
|
|
890
|
+
)
|
|
891
|
+
with component.active_tasks_lock:
|
|
892
|
+
task_context = component.active_tasks.get(
|
|
893
|
+
main_logical_task_id
|
|
894
|
+
)
|
|
895
|
+
if task_context:
|
|
896
|
+
with task_context.lock:
|
|
897
|
+
if (
|
|
898
|
+
sub_task_id
|
|
899
|
+
in task_context.active_peer_sub_tasks
|
|
900
|
+
):
|
|
901
|
+
task_context.active_peer_sub_tasks[
|
|
902
|
+
sub_task_id
|
|
903
|
+
]["peer_task_id"] = peer_task_id
|
|
817
904
|
|
|
818
|
-
if a2a_response.result and isinstance(a2a_response.result, dict):
|
|
819
|
-
payload_data = a2a_response.result
|
|
820
905
|
parsed_successfully = False
|
|
821
906
|
is_final_response = False
|
|
822
907
|
payload_to_queue = None
|
|
823
908
|
|
|
824
|
-
if (
|
|
825
|
-
"final" in payload_data
|
|
826
|
-
and "status" in payload_data
|
|
827
|
-
and isinstance(payload_data.get("final"), bool)
|
|
828
|
-
):
|
|
909
|
+
if isinstance(payload_data, TaskStatusUpdateEvent):
|
|
829
910
|
try:
|
|
830
|
-
status_event =
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
911
|
+
status_event = payload_data
|
|
912
|
+
data_parts = a2a.get_data_parts_from_status_update(
|
|
913
|
+
status_event
|
|
914
|
+
)
|
|
915
|
+
if data_parts:
|
|
916
|
+
for data_part in data_parts:
|
|
917
|
+
log.info(
|
|
918
|
+
"%s Received DataPart signal from peer for sub-task %s. Forwarding...",
|
|
919
|
+
component.log_identifier,
|
|
920
|
+
sub_task_id,
|
|
921
|
+
)
|
|
922
|
+
correlation_data = await component._get_correlation_data_for_sub_task(
|
|
923
|
+
sub_task_id
|
|
924
|
+
)
|
|
925
|
+
if not correlation_data:
|
|
926
|
+
log.warning(
|
|
927
|
+
"%s Correlation data not found for sub-task %s. Cannot forward status signal.",
|
|
845
928
|
component.log_identifier,
|
|
846
929
|
sub_task_id,
|
|
847
930
|
)
|
|
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
|
-
)
|
|
931
|
+
message.call_acknowledgements()
|
|
932
|
+
return
|
|
933
|
+
|
|
934
|
+
original_task_context = correlation_data.get(
|
|
935
|
+
"original_task_context"
|
|
936
|
+
)
|
|
937
|
+
if not original_task_context:
|
|
938
|
+
log.warning(
|
|
939
|
+
"%s original_task_context not found in correlation data for sub-task %s. Cannot forward status signal.",
|
|
940
|
+
component.log_identifier,
|
|
941
|
+
sub_task_id,
|
|
879
942
|
)
|
|
943
|
+
message.call_acknowledgements()
|
|
944
|
+
return
|
|
945
|
+
|
|
946
|
+
main_logical_task_id = original_task_context.get(
|
|
947
|
+
"logical_task_id"
|
|
948
|
+
)
|
|
949
|
+
original_jsonrpc_request_id = (
|
|
950
|
+
original_task_context.get("jsonrpc_request_id")
|
|
951
|
+
)
|
|
952
|
+
main_context_id = original_task_context.get(
|
|
953
|
+
"contextId"
|
|
954
|
+
)
|
|
880
955
|
|
|
881
|
-
|
|
882
|
-
|
|
956
|
+
target_topic_for_forward = (
|
|
957
|
+
original_task_context.get("statusTopic")
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
if (
|
|
961
|
+
not main_logical_task_id
|
|
962
|
+
or not original_jsonrpc_request_id
|
|
963
|
+
or not target_topic_for_forward
|
|
964
|
+
):
|
|
965
|
+
log.error(
|
|
966
|
+
"%s Missing critical info (main_task_id, original_rpc_id, or target_status_topic) in context for sub-task %s. Cannot forward. Context: %s",
|
|
967
|
+
component.log_identifier,
|
|
968
|
+
sub_task_id,
|
|
969
|
+
original_task_context,
|
|
883
970
|
)
|
|
971
|
+
message.call_acknowledgements()
|
|
972
|
+
return
|
|
884
973
|
|
|
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"
|
|
974
|
+
peer_agent_name = (
|
|
975
|
+
status_event.metadata.get(
|
|
976
|
+
"agent_name", "UnknownPeer"
|
|
905
977
|
)
|
|
978
|
+
if status_event.metadata
|
|
979
|
+
else "UnknownPeer"
|
|
980
|
+
)
|
|
906
981
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
"
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
982
|
+
forwarded_message = a2a.create_agent_parts_message(
|
|
983
|
+
parts=[data_part],
|
|
984
|
+
metadata={
|
|
985
|
+
"agent_name": component.agent_name,
|
|
986
|
+
"forwarded_from_peer": peer_agent_name,
|
|
987
|
+
"original_peer_event_taskId": status_event.task_id,
|
|
988
|
+
"original_peer_event_timestamp": (
|
|
989
|
+
status_event.status.timestamp
|
|
990
|
+
if status_event.status
|
|
991
|
+
and status_event.status.timestamp
|
|
992
|
+
else None
|
|
993
|
+
),
|
|
994
|
+
"function_call_id": correlation_data.get(
|
|
995
|
+
"adk_function_call_id", None
|
|
996
|
+
),
|
|
997
|
+
},
|
|
998
|
+
)
|
|
999
|
+
forwarded_event = a2a.create_status_update(
|
|
1000
|
+
task_id=main_logical_task_id,
|
|
1001
|
+
context_id=main_context_id,
|
|
1002
|
+
message=forwarded_message,
|
|
1003
|
+
is_final=False,
|
|
1004
|
+
)
|
|
1005
|
+
if (
|
|
1006
|
+
status_event.status
|
|
1007
|
+
and status_event.status.timestamp
|
|
1008
|
+
):
|
|
1009
|
+
forwarded_event.status.timestamp = (
|
|
1010
|
+
status_event.status.timestamp
|
|
924
1011
|
)
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1012
|
+
forwarded_rpc_response = JSONRPCResponse(
|
|
1013
|
+
id=original_jsonrpc_request_id,
|
|
1014
|
+
result=forwarded_event,
|
|
1015
|
+
)
|
|
1016
|
+
payload_to_publish = (
|
|
1017
|
+
forwarded_rpc_response.model_dump(
|
|
1018
|
+
by_alias=True, exclude_none=True
|
|
929
1019
|
)
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
try:
|
|
1023
|
+
component.publish_a2a_message(
|
|
1024
|
+
payload_to_publish,
|
|
1025
|
+
target_topic_for_forward,
|
|
934
1026
|
)
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1027
|
+
log.info(
|
|
1028
|
+
"%s Forwarded DataPart signal for main task %s (from peer %s) to %s.",
|
|
1029
|
+
component.log_identifier,
|
|
1030
|
+
main_logical_task_id,
|
|
1031
|
+
peer_agent_name,
|
|
1032
|
+
target_topic_for_forward,
|
|
938
1033
|
)
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1034
|
+
except Exception as pub_err:
|
|
1035
|
+
log.exception(
|
|
1036
|
+
"%s Failed to publish forwarded status signal for main task %s: %s",
|
|
1037
|
+
component.log_identifier,
|
|
1038
|
+
main_logical_task_id,
|
|
1039
|
+
pub_err,
|
|
943
1040
|
)
|
|
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
|
|
1041
|
+
message.call_acknowledgements()
|
|
1042
|
+
return
|
|
966
1043
|
|
|
967
1044
|
payload_to_queue = status_event.model_dump(
|
|
968
|
-
exclude_none=True
|
|
1045
|
+
by_alias=True, exclude_none=True
|
|
969
1046
|
)
|
|
970
1047
|
if status_event.final:
|
|
971
1048
|
log.debug(
|
|
@@ -974,22 +1051,15 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
974
1051
|
sub_task_id,
|
|
975
1052
|
)
|
|
976
1053
|
|
|
977
|
-
if
|
|
978
|
-
status_event.status
|
|
979
|
-
and status_event.status.message
|
|
980
|
-
and status_event.status.message.parts
|
|
981
|
-
):
|
|
1054
|
+
if status_event.status and status_event.status.message:
|
|
982
1055
|
response_parts_data = []
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
):
|
|
1056
|
+
unwrapped_parts = a2a.get_parts_from_message(
|
|
1057
|
+
status_event.status.message
|
|
1058
|
+
)
|
|
1059
|
+
for part in unwrapped_parts:
|
|
1060
|
+
if isinstance(part, TextPart):
|
|
988
1061
|
response_parts_data.append(str(part.text))
|
|
989
|
-
elif (
|
|
990
|
-
hasattr(part, "data")
|
|
991
|
-
and part.data is not None
|
|
992
|
-
):
|
|
1062
|
+
elif isinstance(part, DataPart):
|
|
993
1063
|
try:
|
|
994
1064
|
response_parts_data.append(
|
|
995
1065
|
json.dumps(part.data)
|
|
@@ -1023,7 +1093,7 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1023
1093
|
parsed_successfully = True
|
|
1024
1094
|
except Exception as e:
|
|
1025
1095
|
log.warning(
|
|
1026
|
-
"%s Failed to
|
|
1096
|
+
"%s Failed to process payload as TaskStatusUpdateEvent for sub-task %s. Payload: %s. Error: %s",
|
|
1027
1097
|
component.log_identifier,
|
|
1028
1098
|
sub_task_id,
|
|
1029
1099
|
payload_data,
|
|
@@ -1031,15 +1101,11 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1031
1101
|
)
|
|
1032
1102
|
payload_to_queue = None
|
|
1033
1103
|
|
|
1034
|
-
|
|
1035
|
-
not parsed_successfully
|
|
1036
|
-
and "artifact" in payload_data
|
|
1037
|
-
and isinstance(payload_data.get("artifact"), dict)
|
|
1038
|
-
):
|
|
1104
|
+
elif isinstance(payload_data, TaskArtifactUpdateEvent):
|
|
1039
1105
|
try:
|
|
1040
|
-
artifact_event =
|
|
1106
|
+
artifact_event = payload_data
|
|
1041
1107
|
payload_to_queue = artifact_event.model_dump(
|
|
1042
|
-
exclude_none=True
|
|
1108
|
+
by_alias=True, exclude_none=True
|
|
1043
1109
|
)
|
|
1044
1110
|
is_final_response = False
|
|
1045
1111
|
log.debug(
|
|
@@ -1058,10 +1124,12 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1058
1124
|
)
|
|
1059
1125
|
payload_to_queue = None
|
|
1060
1126
|
|
|
1061
|
-
|
|
1127
|
+
elif isinstance(payload_data, Task):
|
|
1062
1128
|
try:
|
|
1063
|
-
final_task =
|
|
1064
|
-
payload_to_queue = final_task.model_dump(
|
|
1129
|
+
final_task = payload_data
|
|
1130
|
+
payload_to_queue = final_task.model_dump(
|
|
1131
|
+
by_alias=True, exclude_none=True
|
|
1132
|
+
)
|
|
1065
1133
|
is_final_response = True
|
|
1066
1134
|
log.debug(
|
|
1067
1135
|
"%s Parsed final Task object from peer for sub-task %s.",
|
|
@@ -1071,26 +1139,31 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1071
1139
|
parsed_successfully = True
|
|
1072
1140
|
except Exception as task_parse_error:
|
|
1073
1141
|
log.error(
|
|
1074
|
-
"%s Failed to parse peer response for sub-task %s as
|
|
1142
|
+
"%s Failed to parse peer response for sub-task %s as Task. Payload: %s. Error: %s",
|
|
1075
1143
|
component.log_identifier,
|
|
1076
1144
|
sub_task_id,
|
|
1077
1145
|
payload_data,
|
|
1078
1146
|
task_parse_error,
|
|
1079
1147
|
)
|
|
1080
|
-
if not a2a_response
|
|
1081
|
-
|
|
1148
|
+
if not a2a.get_response_error(a2a_response):
|
|
1149
|
+
error = a2a.create_internal_error(
|
|
1082
1150
|
message=f"Failed to parse response from peer agent for sub-task {sub_task_id}",
|
|
1083
1151
|
data={
|
|
1084
|
-
"original_payload": payload_data
|
|
1152
|
+
"original_payload": payload_data.model_dump(
|
|
1153
|
+
by_alias=True, exclude_none=True
|
|
1154
|
+
),
|
|
1085
1155
|
"error": str(task_parse_error),
|
|
1086
1156
|
},
|
|
1087
1157
|
)
|
|
1158
|
+
a2a_response = a2a.create_error_response(
|
|
1159
|
+
error, a2a.get_response_id(a2a_response)
|
|
1160
|
+
)
|
|
1088
1161
|
payload_to_queue = None
|
|
1089
1162
|
is_final_response = True
|
|
1090
1163
|
|
|
1091
1164
|
if (
|
|
1092
1165
|
not parsed_successfully
|
|
1093
|
-
and not a2a_response
|
|
1166
|
+
and not a2a.get_response_error(a2a_response)
|
|
1094
1167
|
and payload_to_queue is None
|
|
1095
1168
|
):
|
|
1096
1169
|
log.error(
|
|
@@ -1099,23 +1172,30 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1099
1172
|
sub_task_id,
|
|
1100
1173
|
payload_data,
|
|
1101
1174
|
)
|
|
1102
|
-
|
|
1175
|
+
error = a2a.create_internal_error(
|
|
1103
1176
|
message=f"Unknown response structure from peer agent for sub-task {sub_task_id}",
|
|
1104
|
-
data={
|
|
1177
|
+
data={
|
|
1178
|
+
"original_payload": payload_data.model_dump(
|
|
1179
|
+
by_alias=True, exclude_none=True
|
|
1180
|
+
)
|
|
1181
|
+
},
|
|
1182
|
+
)
|
|
1183
|
+
a2a_response = a2a.create_error_response(
|
|
1184
|
+
error, a2a.get_response_id(a2a_response)
|
|
1105
1185
|
)
|
|
1106
1186
|
is_final_response = True
|
|
1107
1187
|
|
|
1108
|
-
elif a2a_response
|
|
1188
|
+
elif error := a2a.get_response_error(a2a_response):
|
|
1109
1189
|
log.warning(
|
|
1110
1190
|
"%s Received error response from peer for sub-task %s: %s",
|
|
1111
1191
|
component.log_identifier,
|
|
1112
1192
|
sub_task_id,
|
|
1113
|
-
|
|
1193
|
+
error,
|
|
1114
1194
|
)
|
|
1115
1195
|
payload_to_queue = {
|
|
1116
|
-
"error":
|
|
1117
|
-
"code":
|
|
1118
|
-
"data":
|
|
1196
|
+
"error": error.message,
|
|
1197
|
+
"code": error.code,
|
|
1198
|
+
"data": error.data,
|
|
1119
1199
|
}
|
|
1120
1200
|
is_final_response = True
|
|
1121
1201
|
else:
|
|
@@ -1206,9 +1286,7 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1206
1286
|
try:
|
|
1207
1287
|
task_obj = Task(**payload_to_queue)
|
|
1208
1288
|
if task_obj.status and task_obj.status.message:
|
|
1209
|
-
final_text =
|
|
1210
|
-
task_obj.status.message.parts
|
|
1211
|
-
)
|
|
1289
|
+
final_text = get_text_from_message(task_obj.status.message)
|
|
1212
1290
|
|
|
1213
1291
|
if (
|
|
1214
1292
|
task_obj.metadata
|
|
@@ -1264,6 +1342,13 @@ async def handle_a2a_response(component, message: SolaceMessage):
|
|
|
1264
1342
|
if artifact_summary:
|
|
1265
1343
|
full_response_text = f"{artifact_summary}\n\n{full_response_text}"
|
|
1266
1344
|
|
|
1345
|
+
await _publish_peer_tool_result_notification(
|
|
1346
|
+
component=component,
|
|
1347
|
+
correlation_data=correlation_data,
|
|
1348
|
+
payload_to_queue=payload_to_queue,
|
|
1349
|
+
log_identifier=log_retrigger,
|
|
1350
|
+
)
|
|
1351
|
+
|
|
1267
1352
|
current_result = {
|
|
1268
1353
|
"adk_function_call_id": correlation_data.get("adk_function_call_id"),
|
|
1269
1354
|
"peer_tool_name": correlation_data.get("peer_tool_name"),
|
|
@@ -1355,34 +1440,92 @@ def publish_agent_card(component):
|
|
|
1355
1440
|
agent_request_topic = get_agent_request_topic(namespace, agent_name)
|
|
1356
1441
|
dynamic_url = f"solace:{agent_request_topic}"
|
|
1357
1442
|
|
|
1443
|
+
# Define unique URIs for our custom extensions.
|
|
1444
|
+
PEER_TOPOLOGY_EXTENSION_URI = (
|
|
1445
|
+
"https://solace.com/a2a/extensions/peer-agent-topology"
|
|
1446
|
+
)
|
|
1447
|
+
DISPLAY_NAME_EXTENSION_URI = "https://solace.com/a2a/extensions/display-name"
|
|
1448
|
+
TOOLS_EXTENSION_URI = "https://solace.com/a2a/extensions/sam/tools"
|
|
1449
|
+
|
|
1450
|
+
extensions_list = []
|
|
1451
|
+
|
|
1452
|
+
# Create the extension object for peer agents.
|
|
1453
|
+
if peer_agents:
|
|
1454
|
+
peer_topology_extension = AgentExtension(
|
|
1455
|
+
uri=PEER_TOPOLOGY_EXTENSION_URI,
|
|
1456
|
+
description="A list of peer agents this agent is configured to communicate with.",
|
|
1457
|
+
params={"peer_agent_names": list(peer_agents.keys())},
|
|
1458
|
+
)
|
|
1459
|
+
extensions_list.append(peer_topology_extension)
|
|
1460
|
+
|
|
1461
|
+
# Create the extension object for the UI display name.
|
|
1462
|
+
if display_name:
|
|
1463
|
+
display_name_extension = AgentExtension(
|
|
1464
|
+
uri=DISPLAY_NAME_EXTENSION_URI,
|
|
1465
|
+
description="A UI-friendly display name for the agent.",
|
|
1466
|
+
params={"display_name": display_name},
|
|
1467
|
+
)
|
|
1468
|
+
extensions_list.append(display_name_extension)
|
|
1469
|
+
|
|
1470
|
+
# Create the extension object for the agent's tools.
|
|
1471
|
+
dynamic_tools = getattr(component, "agent_card_tool_manifest", [])
|
|
1472
|
+
if dynamic_tools:
|
|
1473
|
+
# Ensure all tools have a 'tags' field to prevent validation errors.
|
|
1474
|
+
processed_tools = []
|
|
1475
|
+
for tool in dynamic_tools:
|
|
1476
|
+
if "tags" not in tool:
|
|
1477
|
+
log.debug(
|
|
1478
|
+
"%s Tool '%s' in manifest is missing 'tags' field. Defaulting to empty list.",
|
|
1479
|
+
component.log_identifier,
|
|
1480
|
+
tool.get("id", "unknown"),
|
|
1481
|
+
)
|
|
1482
|
+
tool["tags"] = []
|
|
1483
|
+
processed_tools.append(tool)
|
|
1484
|
+
|
|
1485
|
+
tools_params = ToolsExtensionParams(tools=processed_tools)
|
|
1486
|
+
tools_extension = AgentExtension(
|
|
1487
|
+
uri=TOOLS_EXTENSION_URI,
|
|
1488
|
+
description="A list of tools available to the agent.",
|
|
1489
|
+
params=tools_params.model_dump(exclude_none=True),
|
|
1490
|
+
)
|
|
1491
|
+
extensions_list.append(tools_extension)
|
|
1492
|
+
|
|
1493
|
+
# Build the capabilities object, including our custom extensions.
|
|
1358
1494
|
capabilities = AgentCapabilities(
|
|
1359
1495
|
streaming=supports_streaming,
|
|
1360
|
-
|
|
1361
|
-
|
|
1496
|
+
push_notifications=False,
|
|
1497
|
+
state_transition_history=False,
|
|
1498
|
+
extensions=extensions_list if extensions_list else None,
|
|
1362
1499
|
)
|
|
1363
1500
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1501
|
+
skills_from_config = card_config.get("skills", [])
|
|
1502
|
+
# The 'tools' field is not part of the official AgentCard spec.
|
|
1503
|
+
# The tools are now included as an extension.
|
|
1504
|
+
|
|
1505
|
+
# Ensure all skills have a 'tags' field to prevent validation errors.
|
|
1506
|
+
processed_skills = []
|
|
1507
|
+
for skill in skills_from_config:
|
|
1508
|
+
if "tags" not in skill:
|
|
1509
|
+
skill["tags"] = []
|
|
1510
|
+
processed_skills.append(skill)
|
|
1366
1511
|
|
|
1367
1512
|
agent_card = AgentCard(
|
|
1368
1513
|
name=agent_name,
|
|
1369
|
-
|
|
1514
|
+
protocol_version=card_config.get("protocolVersion", "0.3.0"),
|
|
1370
1515
|
version=component.HOST_COMPONENT_VERSION,
|
|
1371
1516
|
url=dynamic_url,
|
|
1372
1517
|
capabilities=capabilities,
|
|
1373
1518
|
description=card_config.get("description", ""),
|
|
1374
|
-
skills=
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
documentationUrl=card_config.get("documentationUrl"),
|
|
1519
|
+
skills=processed_skills,
|
|
1520
|
+
default_input_modes=card_config.get("defaultInputModes", ["text"]),
|
|
1521
|
+
default_output_modes=card_config.get("defaultOutputModes", ["text"]),
|
|
1522
|
+
documentation_url=card_config.get("documentationUrl"),
|
|
1379
1523
|
provider=card_config.get("provider"),
|
|
1380
|
-
peer_agents=peer_agents,
|
|
1381
1524
|
)
|
|
1382
1525
|
|
|
1383
1526
|
discovery_topic = get_discovery_topic(namespace)
|
|
1384
1527
|
|
|
1385
|
-
component.
|
|
1528
|
+
component.publish_a2a_message(
|
|
1386
1529
|
agent_card.model_dump(exclude_none=True), discovery_topic
|
|
1387
1530
|
)
|
|
1388
1531
|
log.debug(
|