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
|
@@ -4,19 +4,16 @@ Base Component class for Gateway implementations in the Solace AI Connector.
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import queue
|
|
7
|
-
import re
|
|
8
|
-
import threading
|
|
9
7
|
import base64
|
|
10
8
|
import uuid
|
|
11
9
|
from datetime import datetime, timezone
|
|
12
10
|
from typing import Any, Dict, Optional, List, Tuple, Union
|
|
13
|
-
from urllib.parse import urlparse, parse_qs
|
|
14
11
|
|
|
15
|
-
from solace_ai_connector.components.component_base import ComponentBase
|
|
16
12
|
from solace_ai_connector.common.log import log
|
|
17
13
|
from google.adk.artifacts import BaseArtifactService
|
|
18
14
|
|
|
19
15
|
from ...common.agent_registry import AgentRegistry
|
|
16
|
+
from ...common.sac.sam_component_base import SamComponentBase
|
|
20
17
|
from ...core_a2a.service import CoreA2AService
|
|
21
18
|
from ...agent.adk.services import initialize_artifact_service
|
|
22
19
|
from ...common.services.identity_service import (
|
|
@@ -24,8 +21,8 @@ from ...common.services.identity_service import (
|
|
|
24
21
|
create_identity_service,
|
|
25
22
|
)
|
|
26
23
|
from .task_context import TaskContextManager
|
|
27
|
-
from ...common.types import
|
|
28
|
-
|
|
24
|
+
from ...common.a2a.types import ContentPart
|
|
25
|
+
from a2a.types import (
|
|
29
26
|
Message as A2AMessage,
|
|
30
27
|
AgentCard,
|
|
31
28
|
JSONRPCResponse,
|
|
@@ -34,21 +31,11 @@ from ...common.types import (
|
|
|
34
31
|
TaskArtifactUpdateEvent,
|
|
35
32
|
JSONRPCError,
|
|
36
33
|
TextPart,
|
|
37
|
-
TaskStatus,
|
|
38
|
-
TaskState,
|
|
39
34
|
FilePart,
|
|
40
|
-
|
|
35
|
+
FileWithBytes,
|
|
41
36
|
Artifact as A2AArtifact,
|
|
42
37
|
)
|
|
43
|
-
from ...common
|
|
44
|
-
get_gateway_response_topic,
|
|
45
|
-
get_gateway_response_subscription_topic,
|
|
46
|
-
get_gateway_status_topic,
|
|
47
|
-
get_gateway_status_subscription_topic,
|
|
48
|
-
get_discovery_topic,
|
|
49
|
-
_topic_matches_subscription,
|
|
50
|
-
_subscription_to_regex,
|
|
51
|
-
)
|
|
38
|
+
from ...common import a2a
|
|
52
39
|
from ...common.utils import is_text_based_mime_type
|
|
53
40
|
from ...common.utils.embeds import (
|
|
54
41
|
resolve_embeds_in_string,
|
|
@@ -65,9 +52,6 @@ from solace_ai_connector.common.event import Event, EventType
|
|
|
65
52
|
from abc import abstractmethod
|
|
66
53
|
|
|
67
54
|
from ...common.middleware.registry import MiddlewareRegistry
|
|
68
|
-
from ...agent.utils.artifact_helpers import (
|
|
69
|
-
load_artifact_content_or_metadata,
|
|
70
|
-
)
|
|
71
55
|
|
|
72
56
|
info = {
|
|
73
57
|
"class_name": "BaseGatewayComponent",
|
|
@@ -88,7 +72,7 @@ info = {
|
|
|
88
72
|
}
|
|
89
73
|
|
|
90
74
|
|
|
91
|
-
class BaseGatewayComponent(
|
|
75
|
+
class BaseGatewayComponent(SamComponentBase):
|
|
92
76
|
"""
|
|
93
77
|
Abstract base class for Gateway components.
|
|
94
78
|
|
|
@@ -111,17 +95,16 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
111
95
|
|
|
112
96
|
return super().get_config(key, default)
|
|
113
97
|
|
|
114
|
-
def __init__(self, **kwargs: Any):
|
|
98
|
+
def __init__(self, resolve_artifact_uris_in_gateway: bool = True, **kwargs: Any):
|
|
115
99
|
super().__init__(info, **kwargs)
|
|
100
|
+
self.resolve_artifact_uris_in_gateway = resolve_artifact_uris_in_gateway
|
|
116
101
|
log.info("%s Initializing Base Gateway Component...", self.log_identifier)
|
|
117
102
|
|
|
118
103
|
try:
|
|
119
|
-
self.namespace
|
|
104
|
+
# Note: self.namespace and self.max_message_size_bytes are initialized in SamComponentBase
|
|
120
105
|
self.gateway_id: str = self.get_config("gateway_id")
|
|
121
|
-
if not self.
|
|
122
|
-
raise ValueError(
|
|
123
|
-
"Namespace and Gateway ID must be configured in the app_config."
|
|
124
|
-
)
|
|
106
|
+
if not self.gateway_id:
|
|
107
|
+
raise ValueError("Gateway ID must be configured in the app_config.")
|
|
125
108
|
|
|
126
109
|
self.enable_embed_resolution: bool = self.get_config(
|
|
127
110
|
"enable_embed_resolution", True
|
|
@@ -132,8 +115,8 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
132
115
|
self.gateway_recursive_embed_depth: int = self.get_config(
|
|
133
116
|
"gateway_recursive_embed_depth"
|
|
134
117
|
)
|
|
135
|
-
self.
|
|
136
|
-
"
|
|
118
|
+
self.artifact_handling_mode: str = self.get_config(
|
|
119
|
+
"artifact_handling_mode", "embed"
|
|
137
120
|
)
|
|
138
121
|
_ = self.get_config("artifact_service")
|
|
139
122
|
|
|
@@ -162,9 +145,6 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
162
145
|
|
|
163
146
|
self.task_context_manager: TaskContextManager = TaskContextManager()
|
|
164
147
|
self.internal_event_queue: queue.Queue = queue.Queue()
|
|
165
|
-
self.message_processor_thread: Optional[threading.Thread] = None
|
|
166
|
-
self.async_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
167
|
-
self.async_thread: Optional[threading.Thread] = None
|
|
168
148
|
|
|
169
149
|
identity_service_config = self.get_config("identity_service")
|
|
170
150
|
self.identity_service: Optional[BaseIdentityService] = create_identity_service(
|
|
@@ -181,40 +161,6 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
181
161
|
"%s Base Gateway Component initialized successfully.", self.log_identifier
|
|
182
162
|
)
|
|
183
163
|
|
|
184
|
-
def publish_a2a_message(
|
|
185
|
-
self, topic: str, payload: Dict, user_properties: Optional[Dict] = None
|
|
186
|
-
) -> None:
|
|
187
|
-
log.debug(
|
|
188
|
-
"%s Publishing A2A message to topic: %s via App", self.log_identifier, topic
|
|
189
|
-
)
|
|
190
|
-
try:
|
|
191
|
-
app = self.get_app()
|
|
192
|
-
if app:
|
|
193
|
-
app.send_message(
|
|
194
|
-
payload=payload, topic=topic, user_properties=user_properties
|
|
195
|
-
)
|
|
196
|
-
log.debug(
|
|
197
|
-
"%s Successfully published message to %s via App",
|
|
198
|
-
self.log_identifier,
|
|
199
|
-
topic,
|
|
200
|
-
)
|
|
201
|
-
else:
|
|
202
|
-
log.error(
|
|
203
|
-
"%s Cannot publish message: Not running within a SAC App context.",
|
|
204
|
-
self.log_identifier,
|
|
205
|
-
)
|
|
206
|
-
raise RuntimeError(
|
|
207
|
-
"Cannot publish message: Not running within a SAC App context."
|
|
208
|
-
)
|
|
209
|
-
except Exception as e:
|
|
210
|
-
log.exception(
|
|
211
|
-
"%s Failed to publish A2A message to topic %s via App: %s",
|
|
212
|
-
self.log_identifier,
|
|
213
|
-
topic,
|
|
214
|
-
e,
|
|
215
|
-
)
|
|
216
|
-
raise
|
|
217
|
-
|
|
218
164
|
async def authenticate_and_enrich_user(
|
|
219
165
|
self, external_event_data: Any
|
|
220
166
|
) -> Optional[Dict[str, Any]]:
|
|
@@ -255,7 +201,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
255
201
|
async def submit_a2a_task(
|
|
256
202
|
self,
|
|
257
203
|
target_agent_name: str,
|
|
258
|
-
a2a_parts: List[
|
|
204
|
+
a2a_parts: List[ContentPart],
|
|
259
205
|
external_request_context: Dict[str, Any],
|
|
260
206
|
user_identity: Any,
|
|
261
207
|
is_streaming: bool = True,
|
|
@@ -343,7 +289,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
343
289
|
)
|
|
344
290
|
external_request_context["a2a_session_id"] = a2a_session_id
|
|
345
291
|
|
|
346
|
-
a2a_metadata = {}
|
|
292
|
+
a2a_metadata = {"agent_name": target_agent_name}
|
|
347
293
|
invoked_artifacts = external_request_context.get("invoked_with_artifacts")
|
|
348
294
|
if invoked_artifacts:
|
|
349
295
|
a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
|
|
@@ -353,67 +299,46 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
353
299
|
len(invoked_artifacts),
|
|
354
300
|
)
|
|
355
301
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
self.namespace, self.gateway_id, "{task_id}"
|
|
359
|
-
)
|
|
360
|
-
status_topic_pattern = get_gateway_status_topic(
|
|
361
|
-
self.namespace, self.gateway_id, "{task_id}"
|
|
362
|
-
)
|
|
302
|
+
# This correlation ID is used by the gateway to track the task
|
|
303
|
+
task_id = f"gdk-task-{uuid.uuid4().hex}"
|
|
363
304
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
305
|
+
prepared_a2a_parts = await self._prepare_parts_for_publishing(
|
|
306
|
+
parts=a2a_parts,
|
|
307
|
+
user_id=user_id_for_a2a,
|
|
308
|
+
session_id=a2a_session_id,
|
|
309
|
+
target_agent_name=target_agent_name,
|
|
310
|
+
)
|
|
367
311
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
log.debug("%s Adding response_format to task metadata.", log_id_prefix)
|
|
312
|
+
a2a_message = a2a.create_user_message(
|
|
313
|
+
parts=prepared_a2a_parts,
|
|
314
|
+
metadata=a2a_metadata,
|
|
315
|
+
context_id=a2a_session_id,
|
|
316
|
+
)
|
|
374
317
|
|
|
375
318
|
if is_streaming:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
agent_name=target_agent_name,
|
|
379
|
-
a2a_message=a2a_message,
|
|
380
|
-
session_id=a2a_session_id,
|
|
381
|
-
client_id=self.gateway_id,
|
|
382
|
-
reply_to_topic=reply_topic_pattern,
|
|
383
|
-
status_to_topic=status_topic_pattern,
|
|
384
|
-
user_id=user_id_for_a2a,
|
|
385
|
-
a2a_user_config=user_config,
|
|
386
|
-
metadata_override=task_metadata_override,
|
|
387
|
-
)
|
|
319
|
+
a2a_request = a2a.create_send_streaming_message_request(
|
|
320
|
+
message=a2a_message, task_id=task_id
|
|
388
321
|
)
|
|
389
322
|
else:
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
a2a_message=a2a_message,
|
|
393
|
-
session_id=a2a_session_id,
|
|
394
|
-
client_id=self.gateway_id,
|
|
395
|
-
reply_to_topic=reply_topic_pattern,
|
|
396
|
-
user_id=user_id_for_a2a,
|
|
397
|
-
a2a_user_config=user_config,
|
|
398
|
-
metadata_override=task_metadata_override,
|
|
323
|
+
a2a_request = a2a.create_send_message_request(
|
|
324
|
+
message=a2a_message, task_id=task_id
|
|
399
325
|
)
|
|
400
326
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
log.error(
|
|
404
|
-
"%s CoreA2AService did not return a task ID in the payload.",
|
|
405
|
-
log_id_prefix,
|
|
406
|
-
)
|
|
407
|
-
raise ValueError("CoreA2AService did not return a task ID in the payload.")
|
|
327
|
+
payload = a2a_request.model_dump(by_alias=True, exclude_none=True)
|
|
328
|
+
target_topic = a2a.get_agent_request_topic(self.namespace, target_agent_name)
|
|
408
329
|
|
|
409
|
-
|
|
410
|
-
|
|
330
|
+
user_properties = {
|
|
331
|
+
"clientId": self.gateway_id,
|
|
332
|
+
"userId": user_id_for_a2a,
|
|
333
|
+
}
|
|
334
|
+
if user_config:
|
|
335
|
+
user_properties["a2aUserConfig"] = user_config
|
|
411
336
|
|
|
412
|
-
user_properties["replyTo"] = get_gateway_response_topic(
|
|
337
|
+
user_properties["replyTo"] = a2a.get_gateway_response_topic(
|
|
413
338
|
self.namespace, self.gateway_id, task_id
|
|
414
339
|
)
|
|
415
340
|
if is_streaming:
|
|
416
|
-
user_properties["a2aStatusTopic"] = get_gateway_status_topic(
|
|
341
|
+
user_properties["a2aStatusTopic"] = a2a.get_gateway_status_topic(
|
|
417
342
|
self.namespace, self.gateway_id, task_id
|
|
418
343
|
)
|
|
419
344
|
|
|
@@ -421,7 +346,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
421
346
|
log.info("%s Stored external context for task_id: %s", log_id_prefix, task_id)
|
|
422
347
|
|
|
423
348
|
self.publish_a2a_message(
|
|
424
|
-
|
|
349
|
+
payload=payload, topic=target_topic, user_properties=user_properties
|
|
425
350
|
)
|
|
426
351
|
log.info(
|
|
427
352
|
"%s Submitted A2A task %s to agent %s. Streaming: %s",
|
|
@@ -511,15 +436,12 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
511
436
|
)
|
|
512
437
|
continue
|
|
513
438
|
try:
|
|
514
|
-
|
|
515
|
-
data={
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
)
|
|
521
|
-
signal_task_status = TaskStatus(
|
|
522
|
-
state=TaskState.WORKING, message=signal_a2a_message
|
|
439
|
+
signal_a2a_message = a2a.create_agent_data_message(
|
|
440
|
+
data={
|
|
441
|
+
"type": "agent_progress_update",
|
|
442
|
+
"status_text": status_text,
|
|
443
|
+
},
|
|
444
|
+
part_metadata={"source": "gateway_signal"},
|
|
523
445
|
)
|
|
524
446
|
a2a_task_id_for_signal = external_request_context.get(
|
|
525
447
|
"a2a_task_id_for_event", original_rpc_id
|
|
@@ -531,10 +453,11 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
531
453
|
)
|
|
532
454
|
continue
|
|
533
455
|
|
|
534
|
-
signal_event =
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
456
|
+
signal_event = a2a.create_status_update(
|
|
457
|
+
task_id=a2a_task_id_for_signal,
|
|
458
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
459
|
+
message=signal_a2a_message,
|
|
460
|
+
is_final=False,
|
|
538
461
|
)
|
|
539
462
|
await self._send_update_to_external(
|
|
540
463
|
external_request_context=external_request_context,
|
|
@@ -556,104 +479,53 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
556
479
|
signal_type,
|
|
557
480
|
)
|
|
558
481
|
|
|
559
|
-
async def _resolve_uri_in_file_part(self,
|
|
482
|
+
async def _resolve_uri_in_file_part(self, file_part: FilePart):
|
|
560
483
|
"""
|
|
561
|
-
Checks if a
|
|
562
|
-
resolves it and mutates the part in-place.
|
|
484
|
+
Checks if a FilePart has a resolvable URI and, if so,
|
|
485
|
+
resolves it and mutates the part in-place by calling the common utility.
|
|
563
486
|
"""
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
):
|
|
570
|
-
return
|
|
571
|
-
|
|
572
|
-
if not self.shared_artifact_service:
|
|
573
|
-
log.warning(
|
|
574
|
-
"%s Cannot resolve artifact URI, shared_artifact_service is not configured.",
|
|
575
|
-
self.log_identifier,
|
|
576
|
-
)
|
|
577
|
-
return
|
|
578
|
-
|
|
579
|
-
uri = part.file.uri
|
|
580
|
-
log_id_prefix = f"{self.log_identifier}[ResolveURI]"
|
|
581
|
-
try:
|
|
582
|
-
log.info("%s Found artifact URI to resolve: %s", log_id_prefix, uri)
|
|
583
|
-
parsed_uri = urlparse(uri)
|
|
584
|
-
app_name = parsed_uri.netloc
|
|
585
|
-
path_parts = parsed_uri.path.strip("/").split("/")
|
|
586
|
-
|
|
587
|
-
if not app_name or len(path_parts) != 3:
|
|
588
|
-
raise ValueError(
|
|
589
|
-
"Invalid URI structure. Expected artifact://app_name/user_id/session_id/filename"
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
user_id, session_id, filename = path_parts
|
|
593
|
-
version = int(parse_qs(parsed_uri.query).get("version", [None])[0])
|
|
594
|
-
|
|
595
|
-
loaded_artifact = await load_artifact_content_or_metadata(
|
|
596
|
-
artifact_service=self.shared_artifact_service,
|
|
597
|
-
app_name=app_name,
|
|
598
|
-
user_id=user_id,
|
|
599
|
-
session_id=session_id,
|
|
600
|
-
filename=filename,
|
|
601
|
-
version=version,
|
|
602
|
-
return_raw_bytes=True,
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
if loaded_artifact.get("status") == "success":
|
|
606
|
-
content_bytes = loaded_artifact.get("raw_bytes")
|
|
607
|
-
part.file.bytes = base64.b64encode(content_bytes).decode("utf-8")
|
|
608
|
-
part.file.uri = None
|
|
609
|
-
log.info(
|
|
610
|
-
"%s Successfully resolved and embedded artifact: %s",
|
|
611
|
-
log_id_prefix,
|
|
612
|
-
uri,
|
|
613
|
-
)
|
|
614
|
-
else:
|
|
615
|
-
log.error(
|
|
616
|
-
"%s Failed to resolve artifact URI '%s': %s",
|
|
617
|
-
log_id_prefix,
|
|
618
|
-
uri,
|
|
619
|
-
loaded_artifact.get("message"),
|
|
620
|
-
)
|
|
621
|
-
except Exception as e:
|
|
622
|
-
log.exception(
|
|
623
|
-
"%s Error resolving artifact URI '%s': %s", log_id_prefix, uri, e
|
|
624
|
-
)
|
|
487
|
+
await a2a.resolve_file_part_uri(
|
|
488
|
+
part=file_part,
|
|
489
|
+
artifact_service=self.shared_artifact_service,
|
|
490
|
+
log_identifier=self.log_identifier,
|
|
491
|
+
)
|
|
625
492
|
|
|
626
|
-
async def _resolve_uris_in_parts_list(self, parts: List[
|
|
627
|
-
"""Iterates over a list of
|
|
493
|
+
async def _resolve_uris_in_parts_list(self, parts: List[ContentPart]):
|
|
494
|
+
"""Iterates over a list of part objects and resolves any FilePart URIs."""
|
|
628
495
|
if not parts:
|
|
629
496
|
return
|
|
630
497
|
for part in parts:
|
|
631
|
-
|
|
498
|
+
if isinstance(part, FilePart):
|
|
499
|
+
await self._resolve_uri_in_file_part(part)
|
|
632
500
|
|
|
633
501
|
async def _resolve_uris_in_payload(self, parsed_event: Any):
|
|
634
502
|
"""
|
|
635
503
|
Dispatcher that calls the appropriate targeted URI resolver based on the
|
|
636
504
|
Pydantic model type of the event.
|
|
637
505
|
"""
|
|
506
|
+
parts_to_resolve: List[ContentPart] = []
|
|
638
507
|
if isinstance(parsed_event, TaskStatusUpdateEvent):
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
)
|
|
508
|
+
message = a2a.get_message_from_status_update(parsed_event)
|
|
509
|
+
if message:
|
|
510
|
+
parts_to_resolve.extend(a2a.get_parts_from_message(message))
|
|
643
511
|
elif isinstance(parsed_event, TaskArtifactUpdateEvent):
|
|
644
|
-
|
|
645
|
-
|
|
512
|
+
artifact = a2a.get_artifact_from_artifact_update(parsed_event)
|
|
513
|
+
if artifact:
|
|
514
|
+
parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
|
|
646
515
|
elif isinstance(parsed_event, Task):
|
|
647
516
|
if parsed_event.status and parsed_event.status.message:
|
|
648
|
-
|
|
649
|
-
parsed_event.status.message
|
|
517
|
+
parts_to_resolve.extend(
|
|
518
|
+
a2a.get_parts_from_message(parsed_event.status.message)
|
|
650
519
|
)
|
|
651
520
|
if parsed_event.artifacts:
|
|
652
521
|
for artifact in parsed_event.artifacts:
|
|
653
|
-
|
|
522
|
+
parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
|
|
523
|
+
|
|
524
|
+
if parts_to_resolve:
|
|
525
|
+
await self._resolve_uris_in_parts_list(parts_to_resolve)
|
|
654
526
|
else:
|
|
655
527
|
log.debug(
|
|
656
|
-
"%s Payload type '%s'
|
|
528
|
+
"%s Payload type '%s' did not yield any parts for URI resolution. Skipping.",
|
|
657
529
|
self.log_identifier,
|
|
658
530
|
type(parsed_event).__name__,
|
|
659
531
|
)
|
|
@@ -673,78 +545,34 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
673
545
|
)
|
|
674
546
|
return False
|
|
675
547
|
|
|
676
|
-
def
|
|
677
|
-
self,
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
task_id_part = topic[match.end() :]
|
|
684
|
-
task_id = task_id_part.lstrip("/")
|
|
685
|
-
if task_id:
|
|
686
|
-
log.debug(
|
|
687
|
-
"%s Extracted Task ID '%s' from topic '%s'",
|
|
688
|
-
self.log_identifier,
|
|
689
|
-
task_id,
|
|
690
|
-
topic,
|
|
691
|
-
)
|
|
692
|
-
return task_id
|
|
693
|
-
log.warning(
|
|
694
|
-
"%s Could not extract Task ID from topic '%s' using pattern '%s'",
|
|
695
|
-
self.log_identifier,
|
|
696
|
-
topic,
|
|
697
|
-
subscription_pattern,
|
|
698
|
-
)
|
|
699
|
-
return None
|
|
700
|
-
|
|
701
|
-
def _parse_a2a_event_from_rpc_result(
|
|
702
|
-
self, rpc_result: Dict, expected_task_id: Optional[str]
|
|
703
|
-
) -> Optional[Union[Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent]]:
|
|
548
|
+
async def _prepare_parts_for_publishing(
|
|
549
|
+
self,
|
|
550
|
+
parts: List[ContentPart],
|
|
551
|
+
user_id: str,
|
|
552
|
+
session_id: str,
|
|
553
|
+
target_agent_name: str,
|
|
554
|
+
) -> List[ContentPart]:
|
|
704
555
|
"""
|
|
705
|
-
|
|
706
|
-
|
|
556
|
+
Prepares message parts for publishing according to the configured artifact_handling_mode
|
|
557
|
+
by calling the common utility function.
|
|
707
558
|
"""
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
expected_task_id,
|
|
720
|
-
actual_task_id,
|
|
721
|
-
)
|
|
722
|
-
return None
|
|
723
|
-
|
|
724
|
-
try:
|
|
725
|
-
if "status" in rpc_result and "final" in rpc_result:
|
|
726
|
-
return TaskStatusUpdateEvent(**rpc_result)
|
|
727
|
-
elif "artifact" in rpc_result:
|
|
728
|
-
return TaskArtifactUpdateEvent(**rpc_result)
|
|
729
|
-
elif "status" in rpc_result and "sessionId" in rpc_result:
|
|
730
|
-
return Task(**rpc_result)
|
|
731
|
-
else:
|
|
732
|
-
log.warning(
|
|
733
|
-
"%s Unknown result structure in RPC response for task %s: %s",
|
|
734
|
-
self.log_identifier,
|
|
735
|
-
actual_task_id or "unknown",
|
|
736
|
-
rpc_result,
|
|
559
|
+
processed_parts: List[ContentPart] = []
|
|
560
|
+
for part in parts:
|
|
561
|
+
if isinstance(part, FilePart):
|
|
562
|
+
processed_part = await a2a.prepare_file_part_for_publishing(
|
|
563
|
+
part=part,
|
|
564
|
+
mode=self.artifact_handling_mode,
|
|
565
|
+
artifact_service=self.shared_artifact_service,
|
|
566
|
+
user_id=user_id,
|
|
567
|
+
session_id=session_id,
|
|
568
|
+
target_agent_name=target_agent_name,
|
|
569
|
+
log_identifier=self.log_identifier,
|
|
737
570
|
)
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
actual_task_id or "unknown",
|
|
744
|
-
e,
|
|
745
|
-
rpc_result,
|
|
746
|
-
)
|
|
747
|
-
return None
|
|
571
|
+
if processed_part:
|
|
572
|
+
processed_parts.append(processed_part)
|
|
573
|
+
else:
|
|
574
|
+
processed_parts.append(part)
|
|
575
|
+
return processed_parts
|
|
748
576
|
|
|
749
577
|
async def _resolve_embeds_and_handle_signals(
|
|
750
578
|
self,
|
|
@@ -792,7 +620,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
792
620
|
parts_owner = event_with_parts.artifact
|
|
793
621
|
|
|
794
622
|
if parts_owner and parts_owner.parts:
|
|
795
|
-
|
|
623
|
+
new_parts: List[ContentPart] = []
|
|
796
624
|
stream_buffer_key = f"{a2a_task_id}_stream_buffer"
|
|
797
625
|
current_buffer = ""
|
|
798
626
|
|
|
@@ -801,13 +629,19 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
801
629
|
self.task_context_manager.get_context(stream_buffer_key) or ""
|
|
802
630
|
)
|
|
803
631
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
632
|
+
parts: List[ContentPart] = []
|
|
633
|
+
if isinstance(parts_owner, A2AMessage):
|
|
634
|
+
parts = a2a.get_parts_from_message(parts_owner)
|
|
635
|
+
elif isinstance(parts_owner, A2AArtifact):
|
|
636
|
+
parts = a2a.get_parts_from_artifact(parts_owner)
|
|
637
|
+
|
|
638
|
+
for part in parts:
|
|
639
|
+
if isinstance(part, TextPart) and part.text is not None:
|
|
640
|
+
text_to_resolve = part.text
|
|
641
|
+
original_part_text = part.text
|
|
808
642
|
|
|
809
643
|
if is_streaming_status_update:
|
|
810
|
-
current_buffer +=
|
|
644
|
+
current_buffer += part.text
|
|
811
645
|
text_to_resolve = current_buffer
|
|
812
646
|
|
|
813
647
|
resolved_text, processed_idx, signals = (
|
|
@@ -831,7 +665,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
831
665
|
content_modified_or_signal_handled = True
|
|
832
666
|
|
|
833
667
|
if resolved_text is not None:
|
|
834
|
-
|
|
668
|
+
new_parts.append(a2a.create_text_part(text=resolved_text))
|
|
835
669
|
if is_streaming_status_update:
|
|
836
670
|
if resolved_text != text_to_resolve[:processed_idx]:
|
|
837
671
|
content_modified_or_signal_handled = True
|
|
@@ -851,61 +685,73 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
851
685
|
)
|
|
852
686
|
content_modified_or_signal_handled = True
|
|
853
687
|
|
|
854
|
-
elif (
|
|
855
|
-
isinstance(
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
log_identifier=log_id_prefix,
|
|
877
|
-
config=embed_eval_config,
|
|
878
|
-
max_depth=self.gateway_recursive_embed_depth,
|
|
879
|
-
)
|
|
880
|
-
)
|
|
881
|
-
if resolved_content != original_content:
|
|
882
|
-
new_file_content = part_obj.file.model_copy()
|
|
883
|
-
new_file_content.bytes = base64.b64encode(
|
|
884
|
-
resolved_content.encode("utf-8")
|
|
885
|
-
).decode("utf-8")
|
|
886
|
-
new_parts_for_owner.append(
|
|
887
|
-
FilePart(
|
|
888
|
-
file=new_file_content,
|
|
889
|
-
metadata=part_obj.metadata,
|
|
688
|
+
elif isinstance(part, FilePart) and part.file:
|
|
689
|
+
if isinstance(part.file, FileWithBytes) and part.file.bytes:
|
|
690
|
+
mime_type = part.file.mime_type or ""
|
|
691
|
+
is_container = is_text_based_mime_type(mime_type)
|
|
692
|
+
try:
|
|
693
|
+
decoded_content_for_check = base64.b64decode(
|
|
694
|
+
part.file.bytes
|
|
695
|
+
).decode("utf-8", errors="ignore")
|
|
696
|
+
if (
|
|
697
|
+
is_container
|
|
698
|
+
and EMBED_DELIMITER_OPEN in decoded_content_for_check
|
|
699
|
+
):
|
|
700
|
+
original_content = decoded_content_for_check
|
|
701
|
+
resolved_content = (
|
|
702
|
+
await resolve_embeds_recursively_in_string(
|
|
703
|
+
text=original_content,
|
|
704
|
+
context=embed_eval_context,
|
|
705
|
+
resolver_func=evaluate_embed,
|
|
706
|
+
types_to_resolve=LATE_EMBED_TYPES,
|
|
707
|
+
log_identifier=log_id_prefix,
|
|
708
|
+
config=embed_eval_config,
|
|
709
|
+
max_depth=self.gateway_recursive_embed_depth,
|
|
890
710
|
)
|
|
891
711
|
)
|
|
892
|
-
|
|
712
|
+
if resolved_content != original_content:
|
|
713
|
+
new_file_content = part.file.model_copy()
|
|
714
|
+
new_file_content.bytes = base64.b64encode(
|
|
715
|
+
resolved_content.encode("utf-8")
|
|
716
|
+
).decode("utf-8")
|
|
717
|
+
new_parts.append(
|
|
718
|
+
FilePart(
|
|
719
|
+
file=new_file_content,
|
|
720
|
+
metadata=part.metadata,
|
|
721
|
+
)
|
|
722
|
+
)
|
|
723
|
+
content_modified_or_signal_handled = True
|
|
724
|
+
else:
|
|
725
|
+
new_parts.append(part)
|
|
893
726
|
else:
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
727
|
+
new_parts.append(part)
|
|
728
|
+
except Exception as e:
|
|
729
|
+
log.warning(
|
|
730
|
+
"%s Error during recursive FilePart resolution for %s: %s. Using original.",
|
|
731
|
+
log_id_prefix,
|
|
732
|
+
part.file.name,
|
|
733
|
+
e,
|
|
734
|
+
)
|
|
735
|
+
new_parts.append(part)
|
|
736
|
+
else:
|
|
737
|
+
# This is a FileWithUri or empty FileWithBytes, which we don't process for embeds here.
|
|
738
|
+
new_parts.append(part)
|
|
905
739
|
else:
|
|
906
|
-
|
|
740
|
+
new_parts.append(part)
|
|
907
741
|
|
|
908
|
-
parts_owner
|
|
742
|
+
if isinstance(parts_owner, A2AMessage):
|
|
743
|
+
if isinstance(event_with_parts, TaskStatusUpdateEvent):
|
|
744
|
+
event_with_parts.status.message = a2a.update_message_parts(
|
|
745
|
+
message=parts_owner, new_parts=new_parts
|
|
746
|
+
)
|
|
747
|
+
elif isinstance(event_with_parts, Task):
|
|
748
|
+
event_with_parts.status.message = a2a.update_message_parts(
|
|
749
|
+
message=parts_owner, new_parts=new_parts
|
|
750
|
+
)
|
|
751
|
+
elif isinstance(parts_owner, A2AArtifact):
|
|
752
|
+
event_with_parts.artifact = a2a.update_artifact_parts(
|
|
753
|
+
artifact=parts_owner, new_parts=new_parts
|
|
754
|
+
)
|
|
909
755
|
|
|
910
756
|
if is_streaming_status_update:
|
|
911
757
|
self.task_context_manager.store_context(
|
|
@@ -945,7 +791,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
945
791
|
elif isinstance(parsed_event, Task):
|
|
946
792
|
is_finalizing_context_for_embeds = True
|
|
947
793
|
|
|
948
|
-
if self.
|
|
794
|
+
if self.resolve_artifact_uris_in_gateway:
|
|
949
795
|
log.debug(
|
|
950
796
|
"%s Resolving artifact URIs before sending to external...",
|
|
951
797
|
log_id_prefix,
|
|
@@ -1002,13 +848,11 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1002
848
|
log.debug(
|
|
1003
849
|
"%s Resolving embeds in final task response...", log_id_prefix
|
|
1004
850
|
)
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
else:
|
|
1011
|
-
non_text_parts.append(part)
|
|
851
|
+
message = parsed_event.status.message
|
|
852
|
+
combined_text = a2a.get_text_from_message(message)
|
|
853
|
+
data_parts = a2a.get_data_parts_from_message(message)
|
|
854
|
+
file_parts = a2a.get_file_parts_from_message(message)
|
|
855
|
+
non_text_parts = data_parts + file_parts
|
|
1012
856
|
|
|
1013
857
|
if combined_text:
|
|
1014
858
|
embed_eval_context = {
|
|
@@ -1052,10 +896,15 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1052
896
|
)
|
|
1053
897
|
|
|
1054
898
|
new_parts = (
|
|
1055
|
-
[
|
|
899
|
+
[a2a.create_text_part(text=resolved_text)]
|
|
900
|
+
if resolved_text
|
|
901
|
+
else []
|
|
1056
902
|
)
|
|
1057
903
|
new_parts.extend(non_text_parts)
|
|
1058
|
-
parsed_event.status.message
|
|
904
|
+
parsed_event.status.message = a2a.update_message_parts(
|
|
905
|
+
message=parsed_event.status.message,
|
|
906
|
+
new_parts=new_parts,
|
|
907
|
+
)
|
|
1059
908
|
log.info(
|
|
1060
909
|
"%s Final response text updated with resolved embeds.",
|
|
1061
910
|
log_id_prefix,
|
|
@@ -1086,7 +935,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1086
935
|
},
|
|
1087
936
|
}
|
|
1088
937
|
embed_eval_config = {
|
|
1089
|
-
"
|
|
938
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1090
939
|
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1091
940
|
}
|
|
1092
941
|
resolved_remaining_text, _, signals = (
|
|
@@ -1106,17 +955,14 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1106
955
|
is_finalizing_context=True,
|
|
1107
956
|
)
|
|
1108
957
|
if resolved_remaining_text:
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
message=A2AMessage(
|
|
1112
|
-
role="agent",
|
|
1113
|
-
parts=[TextPart(text=resolved_remaining_text)],
|
|
1114
|
-
),
|
|
958
|
+
flush_message = a2a.create_agent_text_message(
|
|
959
|
+
text=resolved_remaining_text
|
|
1115
960
|
)
|
|
1116
|
-
flush_event =
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
961
|
+
flush_event = a2a.create_status_update(
|
|
962
|
+
task_id=a2a_task_id,
|
|
963
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
964
|
+
message=flush_message,
|
|
965
|
+
is_final=False,
|
|
1120
966
|
)
|
|
1121
967
|
await self._send_update_to_external(
|
|
1122
968
|
external_request_context, flush_event, True
|
|
@@ -1157,7 +1003,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1157
1003
|
Parses the payload, retrieves context using task_id_from_topic, and dispatches for processing.
|
|
1158
1004
|
"""
|
|
1159
1005
|
try:
|
|
1160
|
-
rpc_response = JSONRPCResponse(
|
|
1006
|
+
rpc_response = JSONRPCResponse.model_validate(payload)
|
|
1161
1007
|
except Exception as e:
|
|
1162
1008
|
log.error(
|
|
1163
1009
|
"%s Failed to parse payload as JSONRPCResponse for topic %s (Task ID from topic: %s): %s. Payload: %s",
|
|
@@ -1169,7 +1015,7 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1169
1015
|
)
|
|
1170
1016
|
return False
|
|
1171
1017
|
|
|
1172
|
-
original_rpc_id = str(rpc_response
|
|
1018
|
+
original_rpc_id = str(a2a.get_response_id(rpc_response))
|
|
1173
1019
|
|
|
1174
1020
|
external_request_context = self.task_context_manager.get_context(
|
|
1175
1021
|
task_id_from_topic
|
|
@@ -1190,19 +1036,43 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1190
1036
|
parsed_event_obj: Union[
|
|
1191
1037
|
Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError, None
|
|
1192
1038
|
] = None
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1039
|
+
error = a2a.get_response_error(rpc_response)
|
|
1040
|
+
if error:
|
|
1041
|
+
parsed_event_obj = error
|
|
1042
|
+
else:
|
|
1043
|
+
result = a2a.get_response_result(rpc_response)
|
|
1044
|
+
if result:
|
|
1045
|
+
# The result is already a parsed Pydantic model.
|
|
1046
|
+
parsed_event_obj = result
|
|
1047
|
+
|
|
1048
|
+
# Validate task ID match
|
|
1049
|
+
actual_task_id = None
|
|
1050
|
+
if isinstance(parsed_event_obj, Task):
|
|
1051
|
+
actual_task_id = parsed_event_obj.id
|
|
1052
|
+
elif isinstance(
|
|
1053
|
+
parsed_event_obj, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
|
|
1054
|
+
):
|
|
1055
|
+
actual_task_id = parsed_event_obj.task_id
|
|
1056
|
+
|
|
1057
|
+
if (
|
|
1058
|
+
task_id_from_topic
|
|
1059
|
+
and actual_task_id
|
|
1060
|
+
and actual_task_id != task_id_from_topic
|
|
1061
|
+
):
|
|
1062
|
+
log.error(
|
|
1063
|
+
"%s Task ID mismatch! Expected: %s, Got from payload: %s.",
|
|
1064
|
+
self.log_identifier,
|
|
1065
|
+
task_id_from_topic,
|
|
1066
|
+
actual_task_id,
|
|
1067
|
+
)
|
|
1068
|
+
parsed_event_obj = None
|
|
1199
1069
|
|
|
1200
1070
|
if not parsed_event_obj:
|
|
1201
1071
|
log.error(
|
|
1202
1072
|
"%s Failed to parse or validate A2A event from RPC result for task %s. Result: %s",
|
|
1203
1073
|
self.log_identifier,
|
|
1204
1074
|
task_id_from_topic,
|
|
1205
|
-
rpc_response
|
|
1075
|
+
a2a.get_response_result(rpc_response) or "N/A",
|
|
1206
1076
|
)
|
|
1207
1077
|
generic_error = JSONRPCError(
|
|
1208
1078
|
code=-32000, message="Invalid event structure received from agent."
|
|
@@ -1239,9 +1109,36 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1239
1109
|
)
|
|
1240
1110
|
return False
|
|
1241
1111
|
|
|
1112
|
+
async def _async_setup_and_run(self) -> None:
|
|
1113
|
+
"""Main async logic for the gateway component."""
|
|
1114
|
+
log.info(
|
|
1115
|
+
"%s Starting _start_listener() to initiate external platform connection.",
|
|
1116
|
+
self.log_identifier,
|
|
1117
|
+
)
|
|
1118
|
+
self._start_listener()
|
|
1119
|
+
|
|
1120
|
+
log.info(
|
|
1121
|
+
"%s Starting _message_processor_loop as an asyncio task.",
|
|
1122
|
+
self.log_identifier,
|
|
1123
|
+
)
|
|
1124
|
+
await self._message_processor_loop()
|
|
1125
|
+
|
|
1126
|
+
def _pre_async_cleanup(self) -> None:
|
|
1127
|
+
"""Pre-cleanup actions for the gateway component."""
|
|
1128
|
+
log.info("%s Calling _stop_listener()...", self.log_identifier)
|
|
1129
|
+
self._stop_listener()
|
|
1130
|
+
|
|
1131
|
+
if self.internal_event_queue:
|
|
1132
|
+
log.info(
|
|
1133
|
+
"%s Signaling _message_processor_loop to stop by putting sentinel on queue...",
|
|
1134
|
+
self.log_identifier,
|
|
1135
|
+
)
|
|
1136
|
+
# This unblocks the `self.internal_event_queue.get()` call in the loop
|
|
1137
|
+
self.internal_event_queue.put(None)
|
|
1138
|
+
|
|
1242
1139
|
async def _message_processor_loop(self):
|
|
1243
1140
|
log.info("%s Starting message processor loop...", self.log_identifier)
|
|
1244
|
-
loop =
|
|
1141
|
+
loop = self.get_async_loop()
|
|
1245
1142
|
|
|
1246
1143
|
while not self.stop_signal.is_set():
|
|
1247
1144
|
original_broker_message: Optional[SolaceMessage] = None
|
|
@@ -1272,38 +1169,38 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1272
1169
|
processed_successfully = False
|
|
1273
1170
|
continue
|
|
1274
1171
|
|
|
1275
|
-
if
|
|
1276
|
-
topic, get_discovery_topic(self.namespace)
|
|
1172
|
+
if a2a.topic_matches_subscription(
|
|
1173
|
+
topic, a2a.get_discovery_topic(self.namespace)
|
|
1277
1174
|
):
|
|
1278
1175
|
processed_successfully = await self._handle_discovery_message(
|
|
1279
1176
|
payload
|
|
1280
1177
|
)
|
|
1281
|
-
elif
|
|
1178
|
+
elif a2a.topic_matches_subscription(
|
|
1282
1179
|
topic,
|
|
1283
|
-
get_gateway_response_subscription_topic(
|
|
1180
|
+
a2a.get_gateway_response_subscription_topic(
|
|
1284
1181
|
self.namespace, self.gateway_id
|
|
1285
1182
|
),
|
|
1286
|
-
) or
|
|
1183
|
+
) or a2a.topic_matches_subscription(
|
|
1287
1184
|
topic,
|
|
1288
|
-
get_gateway_status_subscription_topic(
|
|
1185
|
+
a2a.get_gateway_status_subscription_topic(
|
|
1289
1186
|
self.namespace, self.gateway_id
|
|
1290
1187
|
),
|
|
1291
1188
|
):
|
|
1292
1189
|
task_id_from_topic: Optional[str] = None
|
|
1293
|
-
response_sub = get_gateway_response_subscription_topic(
|
|
1190
|
+
response_sub = a2a.get_gateway_response_subscription_topic(
|
|
1294
1191
|
self.namespace, self.gateway_id
|
|
1295
1192
|
)
|
|
1296
|
-
status_sub = get_gateway_status_subscription_topic(
|
|
1193
|
+
status_sub = a2a.get_gateway_status_subscription_topic(
|
|
1297
1194
|
self.namespace, self.gateway_id
|
|
1298
1195
|
)
|
|
1299
1196
|
|
|
1300
|
-
if
|
|
1301
|
-
task_id_from_topic =
|
|
1302
|
-
topic, response_sub
|
|
1197
|
+
if a2a.topic_matches_subscription(topic, response_sub):
|
|
1198
|
+
task_id_from_topic = a2a.extract_task_id_from_topic(
|
|
1199
|
+
topic, response_sub, self.log_identifier
|
|
1303
1200
|
)
|
|
1304
|
-
elif
|
|
1305
|
-
task_id_from_topic =
|
|
1306
|
-
topic, status_sub
|
|
1201
|
+
elif a2a.topic_matches_subscription(topic, status_sub):
|
|
1202
|
+
task_id_from_topic = a2a.extract_task_id_from_topic(
|
|
1203
|
+
topic, status_sub, self.log_identifier
|
|
1307
1204
|
)
|
|
1308
1205
|
|
|
1309
1206
|
if task_id_from_topic:
|
|
@@ -1355,145 +1252,6 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1355
1252
|
|
|
1356
1253
|
log.info("%s Message processor loop finished.", self.log_identifier)
|
|
1357
1254
|
|
|
1358
|
-
def _run_async_operations(self):
|
|
1359
|
-
log.info(
|
|
1360
|
-
"%s Initializing asyncio event loop in dedicated thread...",
|
|
1361
|
-
self.log_identifier,
|
|
1362
|
-
)
|
|
1363
|
-
self.async_loop = asyncio.new_event_loop()
|
|
1364
|
-
asyncio.set_event_loop(self.async_loop)
|
|
1365
|
-
|
|
1366
|
-
processor_task = None
|
|
1367
|
-
try:
|
|
1368
|
-
log.info(
|
|
1369
|
-
"%s Starting _message_processor_loop as an asyncio task.",
|
|
1370
|
-
self.log_identifier,
|
|
1371
|
-
)
|
|
1372
|
-
processor_task = self.async_loop.create_task(self._message_processor_loop())
|
|
1373
|
-
|
|
1374
|
-
log.info(
|
|
1375
|
-
"%s Calling _start_listener() to initiate external platform connection.",
|
|
1376
|
-
self.log_identifier,
|
|
1377
|
-
)
|
|
1378
|
-
self._start_listener()
|
|
1379
|
-
|
|
1380
|
-
log.info(
|
|
1381
|
-
"%s Running asyncio event loop forever (or until stop_signal).",
|
|
1382
|
-
self.log_identifier,
|
|
1383
|
-
)
|
|
1384
|
-
self.async_loop.run_forever()
|
|
1385
|
-
|
|
1386
|
-
except Exception as e:
|
|
1387
|
-
log.exception(
|
|
1388
|
-
"%s Unhandled exception in _run_async_operations: %s",
|
|
1389
|
-
self.log_identifier,
|
|
1390
|
-
e,
|
|
1391
|
-
)
|
|
1392
|
-
self.stop_signal.set()
|
|
1393
|
-
finally:
|
|
1394
|
-
if processor_task and not processor_task.done():
|
|
1395
|
-
log.info(
|
|
1396
|
-
"%s Cancelling _message_processor_loop task.", self.log_identifier
|
|
1397
|
-
)
|
|
1398
|
-
processor_task.cancel()
|
|
1399
|
-
try:
|
|
1400
|
-
self.async_loop.run_until_complete(
|
|
1401
|
-
asyncio.gather(processor_task, return_exceptions=True)
|
|
1402
|
-
)
|
|
1403
|
-
except RuntimeError as loop_err:
|
|
1404
|
-
log.warning(
|
|
1405
|
-
"%s Error awaiting processor task during cleanup (loop closed?): %s",
|
|
1406
|
-
self.log_identifier,
|
|
1407
|
-
loop_err,
|
|
1408
|
-
)
|
|
1409
|
-
|
|
1410
|
-
if self.async_loop.is_running():
|
|
1411
|
-
log.info(
|
|
1412
|
-
"%s Stopping asyncio event loop from _run_async_operations finally block.",
|
|
1413
|
-
self.log_identifier,
|
|
1414
|
-
)
|
|
1415
|
-
self.async_loop.stop()
|
|
1416
|
-
log.info(
|
|
1417
|
-
"%s Async operations loop finished in dedicated thread.",
|
|
1418
|
-
self.log_identifier,
|
|
1419
|
-
)
|
|
1420
|
-
|
|
1421
|
-
def run(self):
|
|
1422
|
-
log.info("%s Starting BaseGatewayComponent run method.", self.log_identifier)
|
|
1423
|
-
if not self.async_thread or not self.async_thread.is_alive():
|
|
1424
|
-
self.async_thread = threading.Thread(
|
|
1425
|
-
target=self._run_async_operations,
|
|
1426
|
-
name=f"{self.name}_AsyncOpsThread",
|
|
1427
|
-
daemon=True,
|
|
1428
|
-
)
|
|
1429
|
-
self.async_thread.start()
|
|
1430
|
-
log.info("%s Async operations thread started.", self.log_identifier)
|
|
1431
|
-
else:
|
|
1432
|
-
log.warning(
|
|
1433
|
-
"%s Async operations thread already running.", self.log_identifier
|
|
1434
|
-
)
|
|
1435
|
-
|
|
1436
|
-
super().run()
|
|
1437
|
-
log.info("%s BaseGatewayComponent run method finished.", self.log_identifier)
|
|
1438
|
-
|
|
1439
|
-
def cleanup(self):
|
|
1440
|
-
log.info("%s Starting cleanup for BaseGatewayComponent...", self.log_identifier)
|
|
1441
|
-
|
|
1442
|
-
log.info("%s Calling _stop_listener()...", self.log_identifier)
|
|
1443
|
-
try:
|
|
1444
|
-
if (
|
|
1445
|
-
self.async_loop
|
|
1446
|
-
and not self.async_loop.is_running()
|
|
1447
|
-
and self.async_thread
|
|
1448
|
-
and self.async_thread.is_alive()
|
|
1449
|
-
):
|
|
1450
|
-
log.warning(
|
|
1451
|
-
"%s Async loop not running during cleanup, _stop_listener might face issues if it needs the loop.",
|
|
1452
|
-
self.log_identifier,
|
|
1453
|
-
)
|
|
1454
|
-
self._stop_listener()
|
|
1455
|
-
except Exception as e:
|
|
1456
|
-
log.exception(
|
|
1457
|
-
"%s Error during _stop_listener(): %s", self.log_identifier, e
|
|
1458
|
-
)
|
|
1459
|
-
|
|
1460
|
-
if self.internal_event_queue:
|
|
1461
|
-
log.info(
|
|
1462
|
-
"%s Signaling _message_processor_loop to stop...", self.log_identifier
|
|
1463
|
-
)
|
|
1464
|
-
self.internal_event_queue.put(None)
|
|
1465
|
-
|
|
1466
|
-
if self.async_loop and self.async_loop.is_running():
|
|
1467
|
-
log.info("%s Requesting asyncio loop to stop...", self.log_identifier)
|
|
1468
|
-
self.async_loop.call_soon_threadsafe(self.async_loop.stop)
|
|
1469
|
-
|
|
1470
|
-
if self.async_thread and self.async_thread.is_alive():
|
|
1471
|
-
log.info(
|
|
1472
|
-
"%s Joining async operations thread (timeout 10s)...",
|
|
1473
|
-
self.log_identifier,
|
|
1474
|
-
)
|
|
1475
|
-
self.async_thread.join(timeout=10)
|
|
1476
|
-
if self.async_thread.is_alive():
|
|
1477
|
-
log.warning(
|
|
1478
|
-
"%s Async operations thread did not join cleanly.",
|
|
1479
|
-
self.log_identifier,
|
|
1480
|
-
)
|
|
1481
|
-
|
|
1482
|
-
if self.async_loop and not self.async_loop.is_closed():
|
|
1483
|
-
if self.async_loop.is_running():
|
|
1484
|
-
self.async_loop.call_soon_threadsafe(self.async_loop.stop)
|
|
1485
|
-
log.info(
|
|
1486
|
-
"%s Closing asyncio event loop (if not already closed by its thread).",
|
|
1487
|
-
self.log_identifier,
|
|
1488
|
-
)
|
|
1489
|
-
if not self.async_loop.is_running():
|
|
1490
|
-
self.async_loop.close()
|
|
1491
|
-
else:
|
|
1492
|
-
self.async_loop.call_soon_threadsafe(self.async_loop.close)
|
|
1493
|
-
|
|
1494
|
-
super().cleanup()
|
|
1495
|
-
log.info("%s BaseGatewayComponent cleanup finished.", self.log_identifier)
|
|
1496
|
-
|
|
1497
1255
|
@abstractmethod
|
|
1498
1256
|
async def _extract_initial_claims(
|
|
1499
1257
|
self, external_event_data: Any
|
|
@@ -1514,17 +1272,30 @@ class BaseGatewayComponent(ComponentBase):
|
|
|
1514
1272
|
pass
|
|
1515
1273
|
|
|
1516
1274
|
@abstractmethod
|
|
1517
|
-
def
|
|
1275
|
+
async def _translate_external_input(
|
|
1276
|
+
self, external_event: Any
|
|
1277
|
+
) -> Tuple[str, List[ContentPart], Dict[str, Any]]:
|
|
1278
|
+
"""
|
|
1279
|
+
Translates raw platform-specific event data into A2A task parameters.
|
|
1280
|
+
|
|
1281
|
+
Args:
|
|
1282
|
+
external_event: Raw event data from the external platform
|
|
1283
|
+
(e.g., FastAPIRequest, Slack event dictionary).
|
|
1284
|
+
|
|
1285
|
+
Returns:
|
|
1286
|
+
A tuple containing:
|
|
1287
|
+
- target_agent_name (str): The name of the A2A agent to target.
|
|
1288
|
+
- a2a_parts (List[ContentPart]): A list of A2A Part objects.
|
|
1289
|
+
- external_request_context (Dict[str, Any]): Context for TaskContextManager.
|
|
1290
|
+
"""
|
|
1518
1291
|
pass
|
|
1519
1292
|
|
|
1520
1293
|
@abstractmethod
|
|
1521
|
-
def
|
|
1294
|
+
def _start_listener(self) -> None:
|
|
1522
1295
|
pass
|
|
1523
1296
|
|
|
1524
1297
|
@abstractmethod
|
|
1525
|
-
def
|
|
1526
|
-
self, external_event: Any
|
|
1527
|
-
) -> Tuple[str, List[A2APart], Dict[str, Any]]:
|
|
1298
|
+
def _stop_listener(self) -> None:
|
|
1528
1299
|
pass
|
|
1529
1300
|
|
|
1530
1301
|
@abstractmethod
|