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,52 +3,55 @@ Custom Solace AI Connector Component to host the FastAPI backend for the Web UI.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
import queue
|
|
7
|
-
import uuid
|
|
8
6
|
import json
|
|
7
|
+
import queue
|
|
9
8
|
import re
|
|
10
9
|
import threading
|
|
11
|
-
|
|
10
|
+
import uuid
|
|
12
11
|
from datetime import datetime, timezone
|
|
13
|
-
from
|
|
12
|
+
from typing import Any
|
|
14
13
|
|
|
15
14
|
import uvicorn
|
|
16
|
-
from fastapi import FastAPI
|
|
17
|
-
|
|
15
|
+
from fastapi import FastAPI, UploadFile
|
|
16
|
+
from fastapi import Request as FastAPIRequest
|
|
18
17
|
from solace_ai_connector.common.log import log
|
|
18
|
+
from solace_ai_connector.components.inputs_outputs.broker_input import BrokerInput
|
|
19
19
|
from solace_ai_connector.flow.app import App as SACApp
|
|
20
|
-
from solace_ai_connector.components.inputs_outputs.broker_input import (
|
|
21
|
-
BrokerInput,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
from ...gateway.http_sse.sse_manager import SSEManager
|
|
25
20
|
|
|
26
|
-
from .components import VisualizationForwarderComponent
|
|
27
|
-
from ...gateway.http_sse.session_manager import SessionManager
|
|
28
|
-
from ...gateway.base.component import BaseGatewayComponent
|
|
29
21
|
from ...common.agent_registry import AgentRegistry
|
|
30
22
|
from ...core_a2a.service import CoreA2AService
|
|
31
|
-
from
|
|
23
|
+
from ...gateway.base.component import BaseGatewayComponent
|
|
24
|
+
from ...gateway.http_sse.session_manager import SessionManager
|
|
25
|
+
from ...gateway.http_sse.sse_manager import SSEManager
|
|
26
|
+
from .components import VisualizationForwarderComponent
|
|
27
|
+
from .infrastructure.persistence_service import PersistenceService
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from google.adk.artifacts import BaseArtifactService
|
|
31
|
+
except ImportError:
|
|
32
|
+
|
|
33
|
+
class BaseArtifactService:
|
|
34
|
+
pass
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
|
|
37
|
+
from a2a.types import (
|
|
38
|
+
A2ARequest,
|
|
34
39
|
AgentCard,
|
|
35
|
-
Part as A2APart,
|
|
36
|
-
Task,
|
|
37
|
-
TaskStatusUpdateEvent,
|
|
38
|
-
TaskArtifactUpdateEvent,
|
|
39
40
|
JSONRPCError,
|
|
40
41
|
JSONRPCResponse,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
from ...common.a2a_protocol import (
|
|
46
|
-
_topic_matches_subscription,
|
|
42
|
+
Task,
|
|
43
|
+
TaskArtifactUpdateEvent,
|
|
44
|
+
TaskStatusUpdateEvent,
|
|
47
45
|
)
|
|
48
46
|
|
|
49
|
-
from ...
|
|
47
|
+
from ...common import a2a
|
|
48
|
+
from ...common.a2a.types import ContentPart
|
|
50
49
|
from ...common.middleware.config_resolver import ConfigResolver
|
|
51
|
-
|
|
50
|
+
from ...common.utils.embeds import (
|
|
51
|
+
EARLY_EMBED_TYPES,
|
|
52
|
+
evaluate_embed,
|
|
53
|
+
resolve_embeds_in_string,
|
|
54
|
+
)
|
|
52
55
|
|
|
53
56
|
info = {
|
|
54
57
|
"class_name": "WebUIBackendComponent",
|
|
@@ -82,7 +85,11 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
82
85
|
"""
|
|
83
86
|
Initializes the WebUIBackendComponent, inheriting from BaseGatewayComponent.
|
|
84
87
|
"""
|
|
85
|
-
|
|
88
|
+
component_config = kwargs.get("component_config", {})
|
|
89
|
+
app_config = component_config.get("app_config", {})
|
|
90
|
+
resolve_uris = app_config.get("resolve_artifact_uris_in_gateway", True)
|
|
91
|
+
|
|
92
|
+
super().__init__(resolve_artifact_uris_in_gateway=resolve_uris, **kwargs)
|
|
86
93
|
log.info("%s Initializing Web UI Backend Component...", self.log_identifier)
|
|
87
94
|
|
|
88
95
|
try:
|
|
@@ -97,9 +104,6 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
97
104
|
self.fastapi_https_port = self.get_config("fastapi_https_port", 8443)
|
|
98
105
|
self.session_secret_key = self.get_config("session_secret_key")
|
|
99
106
|
self.cors_allowed_origins = self.get_config("cors_allowed_origins", ["*"])
|
|
100
|
-
self.resolve_artifact_uris_in_gateway = self.get_config(
|
|
101
|
-
"resolve_artifact_uris_in_gateway", True
|
|
102
|
-
)
|
|
103
107
|
self.ssl_keyfile = self.get_config("ssl_keyfile", "")
|
|
104
108
|
self.ssl_certfile = self.get_config("ssl_certfile", "")
|
|
105
109
|
self.ssl_keyfile_password = self.get_config("ssl_keyfile_password", "")
|
|
@@ -118,27 +122,42 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
118
122
|
|
|
119
123
|
self.sse_manager = SSEManager(max_queue_size=sse_max_queue_size)
|
|
120
124
|
|
|
125
|
+
session_config = self._resolve_session_config()
|
|
126
|
+
if session_config.get("type") == "sql":
|
|
127
|
+
# SQL type explicitly configured - database_url is required
|
|
128
|
+
database_url = session_config.get("database_url")
|
|
129
|
+
if not database_url:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"{self.log_identifier} Session service type is 'sql' but no database_url provided. "
|
|
132
|
+
"Please provide a database_url in the session_service configuration or use type 'memory'."
|
|
133
|
+
)
|
|
134
|
+
self.persistence_service = PersistenceService(database_url)
|
|
135
|
+
else:
|
|
136
|
+
# Memory storage or no explicit configuration - no persistence service needed
|
|
137
|
+
self.persistence_service = None
|
|
138
|
+
|
|
121
139
|
component_config = self.get_config("component_config", {})
|
|
122
140
|
app_config = component_config.get("app_config", {})
|
|
123
141
|
|
|
124
142
|
self.session_manager = SessionManager(
|
|
125
143
|
secret_key=self.session_secret_key,
|
|
126
144
|
app_config=app_config,
|
|
145
|
+
persistence_service=self.persistence_service,
|
|
127
146
|
)
|
|
128
147
|
|
|
129
|
-
self.fastapi_app:
|
|
130
|
-
self.uvicorn_server:
|
|
131
|
-
self.fastapi_thread:
|
|
132
|
-
self.fastapi_event_loop:
|
|
148
|
+
self.fastapi_app: FastAPI | None = None
|
|
149
|
+
self.uvicorn_server: uvicorn.Server | None = None
|
|
150
|
+
self.fastapi_thread: threading.Thread | None = None
|
|
151
|
+
self.fastapi_event_loop: asyncio.AbstractEventLoop | None = None
|
|
133
152
|
|
|
134
|
-
self._visualization_internal_app:
|
|
135
|
-
self._visualization_broker_input:
|
|
153
|
+
self._visualization_internal_app: SACApp | None = None
|
|
154
|
+
self._visualization_broker_input: BrokerInput | None = None
|
|
136
155
|
self._visualization_message_queue: queue.Queue = queue.Queue(maxsize=200)
|
|
137
|
-
self._active_visualization_streams:
|
|
138
|
-
self._visualization_locks:
|
|
156
|
+
self._active_visualization_streams: dict[str, dict[str, Any]] = {}
|
|
157
|
+
self._visualization_locks: dict[asyncio.AbstractEventLoop, asyncio.Lock] = {}
|
|
139
158
|
self._visualization_locks_lock = threading.Lock()
|
|
140
|
-
self._global_visualization_subscriptions:
|
|
141
|
-
self._visualization_processor_task:
|
|
159
|
+
self._global_visualization_subscriptions: dict[str, int] = {}
|
|
160
|
+
self._visualization_processor_task: asyncio.Task | None = None
|
|
142
161
|
|
|
143
162
|
log.info("%s Web UI Backend Component initialized.", self.log_identifier)
|
|
144
163
|
|
|
@@ -301,6 +320,37 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
301
320
|
self._visualization_broker_input = None
|
|
302
321
|
raise
|
|
303
322
|
|
|
323
|
+
def _resolve_session_config(self) -> dict:
|
|
324
|
+
"""
|
|
325
|
+
Resolve session service configuration with backward compatibility.
|
|
326
|
+
|
|
327
|
+
Priority order:
|
|
328
|
+
1. Component-specific session_service config (new approach)
|
|
329
|
+
2. Shared default_session_service config (deprecated, with warning)
|
|
330
|
+
3. Hardcoded default (SQLite for Web UI)
|
|
331
|
+
"""
|
|
332
|
+
# Check component-specific session_service config first
|
|
333
|
+
component_session_config = self.get_config("session_service")
|
|
334
|
+
if component_session_config:
|
|
335
|
+
log.debug("Using component-specific session_service configuration")
|
|
336
|
+
return component_session_config
|
|
337
|
+
|
|
338
|
+
# Backward compatibility: check shared config
|
|
339
|
+
shared_session_config = self.get_config("default_session_service")
|
|
340
|
+
if shared_session_config:
|
|
341
|
+
log.warning(
|
|
342
|
+
"Using session_service from shared config is deprecated. "
|
|
343
|
+
"Move to component-specific configuration in app_config.session_service"
|
|
344
|
+
)
|
|
345
|
+
return shared_session_config
|
|
346
|
+
|
|
347
|
+
# Default configuration for Web UI (backward compatibility)
|
|
348
|
+
default_config = {"type": "memory", "default_behavior": "PERSISTENT"}
|
|
349
|
+
log.info(
|
|
350
|
+
"Using default memory session configuration for Web UI (backward compatibility)"
|
|
351
|
+
)
|
|
352
|
+
return default_config
|
|
353
|
+
|
|
304
354
|
async def _visualization_message_processor_loop(self) -> None:
|
|
305
355
|
"""
|
|
306
356
|
Asynchronously consumes messages from the _visualization_message_queue,
|
|
@@ -427,7 +477,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
427
477
|
"solace_topics", set()
|
|
428
478
|
)
|
|
429
479
|
if any(
|
|
430
|
-
|
|
480
|
+
a2a.topic_matches_subscription(topic, pattern)
|
|
431
481
|
for pattern in subscribed_topics_for_stream
|
|
432
482
|
):
|
|
433
483
|
is_permitted = True
|
|
@@ -449,6 +499,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
449
499
|
"task_id": event_details["task_id"],
|
|
450
500
|
"payload_summary": event_details["payload_summary"],
|
|
451
501
|
"full_payload": payload_dict,
|
|
502
|
+
"debug_type": event_details["debug_type"],
|
|
452
503
|
}
|
|
453
504
|
|
|
454
505
|
try:
|
|
@@ -567,7 +618,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
567
618
|
if not hasattr(
|
|
568
619
|
self._visualization_broker_input, "add_subscription"
|
|
569
620
|
) or not callable(
|
|
570
|
-
|
|
621
|
+
self._visualization_broker_input.add_subscription
|
|
571
622
|
):
|
|
572
623
|
log.error(
|
|
573
624
|
"%s Visualization BrokerInput does not support dynamic 'add_subscription'. "
|
|
@@ -682,9 +733,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
682
733
|
try:
|
|
683
734
|
if not hasattr(
|
|
684
735
|
self._visualization_broker_input, "remove_subscription"
|
|
685
|
-
) or not callable(
|
|
686
|
-
getattr(self._visualization_broker_input, "remove_subscription")
|
|
687
|
-
):
|
|
736
|
+
) or not callable(self._visualization_broker_input.remove_subscription):
|
|
688
737
|
log.error(
|
|
689
738
|
"%s Visualization BrokerInput does not support dynamic 'remove_subscription'. "
|
|
690
739
|
"Please upgrade the 'solace-ai-connector' module. Cannot remove subscription '%s'.",
|
|
@@ -771,7 +820,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
771
820
|
|
|
772
821
|
async def _extract_initial_claims(
|
|
773
822
|
self, external_event_data: Any
|
|
774
|
-
) ->
|
|
823
|
+
) -> dict[str, Any] | None:
|
|
775
824
|
"""
|
|
776
825
|
Extracts initial identity claims from the incoming external event.
|
|
777
826
|
For the WebUI, this means inspecting the FastAPIRequest.
|
|
@@ -827,18 +876,18 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
827
876
|
return
|
|
828
877
|
|
|
829
878
|
try:
|
|
830
|
-
from ...gateway.http_sse.main import
|
|
831
|
-
|
|
832
|
-
)
|
|
833
|
-
from ...gateway.http_sse.main import (
|
|
834
|
-
setup_dependencies,
|
|
835
|
-
)
|
|
879
|
+
from ...gateway.http_sse.main import app as fastapi_app_instance
|
|
880
|
+
from ...gateway.http_sse.main import setup_dependencies
|
|
836
881
|
|
|
837
882
|
self.fastapi_app = fastapi_app_instance
|
|
838
883
|
|
|
839
|
-
setup_dependencies(self)
|
|
884
|
+
setup_dependencies(self, self.persistence_service)
|
|
840
885
|
|
|
841
|
-
port =
|
|
886
|
+
port = (
|
|
887
|
+
self.fastapi_https_port
|
|
888
|
+
if self.ssl_keyfile and self.ssl_certfile
|
|
889
|
+
else self.fastapi_port
|
|
890
|
+
)
|
|
842
891
|
|
|
843
892
|
config = uvicorn.Config(
|
|
844
893
|
app=self.fastapi_app,
|
|
@@ -935,7 +984,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
935
984
|
raise
|
|
936
985
|
|
|
937
986
|
def publish_a2a(
|
|
938
|
-
self, topic: str, payload:
|
|
987
|
+
self, topic: str, payload: dict, user_properties: dict | None = None
|
|
939
988
|
):
|
|
940
989
|
"""
|
|
941
990
|
Publishes an A2A message using the SAC App's send_message method.
|
|
@@ -993,15 +1042,17 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
993
1042
|
log.info("%s Visualization resources cleaned up.", self.log_identifier)
|
|
994
1043
|
|
|
995
1044
|
def _infer_visualization_event_details(
|
|
996
|
-
self, topic: str, payload:
|
|
997
|
-
) ->
|
|
1045
|
+
self, topic: str, payload: dict[str, Any]
|
|
1046
|
+
) -> dict[str, Any]:
|
|
998
1047
|
"""
|
|
999
1048
|
Infers details for the visualization SSE payload from the Solace topic and A2A message.
|
|
1049
|
+
This version is updated to parse the official A2A SDK message formats.
|
|
1000
1050
|
"""
|
|
1001
1051
|
details = {
|
|
1002
1052
|
"direction": "unknown",
|
|
1003
1053
|
"source_entity": "unknown",
|
|
1004
1054
|
"target_entity": "unknown",
|
|
1055
|
+
"debug_type": "unknown",
|
|
1005
1056
|
"message_id": payload.get("id"),
|
|
1006
1057
|
"task_id": None,
|
|
1007
1058
|
"payload_summary": {
|
|
@@ -1010,135 +1061,141 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1010
1061
|
},
|
|
1011
1062
|
}
|
|
1012
1063
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1064
|
+
# --- Phase 1: Parse the payload to extract core info ---
|
|
1015
1065
|
try:
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
else None
|
|
1029
|
-
)
|
|
1030
|
-
entity_name = (
|
|
1031
|
-
topic_parts[entity_name_index]
|
|
1032
|
-
if len(topic_parts) > entity_name_index
|
|
1033
|
-
else None
|
|
1034
|
-
)
|
|
1035
|
-
|
|
1036
|
-
if domain == "agent":
|
|
1037
|
-
if action_type == "request":
|
|
1038
|
-
details["direction"] = "request"
|
|
1039
|
-
details["target_entity"] = entity_name
|
|
1040
|
-
user_props = (
|
|
1041
|
-
payload.get("params", {})
|
|
1042
|
-
.get("metadata", {})
|
|
1043
|
-
.get("solaceUserProperties", {})
|
|
1066
|
+
# Try to parse as a JSON-RPC response first
|
|
1067
|
+
if "result" in payload or "error" in payload:
|
|
1068
|
+
rpc_response = JSONRPCResponse.model_validate(payload)
|
|
1069
|
+
result = a2a.get_response_result(rpc_response)
|
|
1070
|
+
error = a2a.get_response_error(rpc_response)
|
|
1071
|
+
details["message_id"] = a2a.get_response_id(rpc_response)
|
|
1072
|
+
|
|
1073
|
+
if result:
|
|
1074
|
+
kind = getattr(result, "kind", None)
|
|
1075
|
+
details["direction"] = kind or "response"
|
|
1076
|
+
details["task_id"] = getattr(result, "task_id", None) or getattr(
|
|
1077
|
+
result, "id", None
|
|
1044
1078
|
)
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1079
|
+
|
|
1080
|
+
if isinstance(result, TaskStatusUpdateEvent):
|
|
1081
|
+
details["source_entity"] = (
|
|
1082
|
+
result.metadata.get("agent_name")
|
|
1083
|
+
if result.metadata
|
|
1084
|
+
else None
|
|
1085
|
+
)
|
|
1086
|
+
message = a2a.get_message_from_status_update(result)
|
|
1087
|
+
if message:
|
|
1088
|
+
if not details["source_entity"]:
|
|
1089
|
+
details["source_entity"] = (
|
|
1090
|
+
message.metadata.get("agent_name")
|
|
1091
|
+
if message.metadata
|
|
1092
|
+
else None
|
|
1093
|
+
)
|
|
1094
|
+
data_parts = a2a.get_data_parts_from_message(message)
|
|
1095
|
+
if data_parts:
|
|
1096
|
+
details["debug_type"] = data_parts[0].data.get(
|
|
1097
|
+
"type", "unknown"
|
|
1098
|
+
)
|
|
1099
|
+
elif a2a.get_text_from_message(message):
|
|
1100
|
+
details["debug_type"] = "streaming_text"
|
|
1101
|
+
elif isinstance(result, Task):
|
|
1102
|
+
details["source_entity"] = (
|
|
1103
|
+
result.metadata.get("agent_name")
|
|
1104
|
+
if result.metadata
|
|
1105
|
+
else None
|
|
1106
|
+
)
|
|
1107
|
+
elif isinstance(result, TaskArtifactUpdateEvent):
|
|
1108
|
+
artifact = a2a.get_artifact_from_artifact_update(result)
|
|
1109
|
+
if artifact:
|
|
1110
|
+
details["source_entity"] = (
|
|
1111
|
+
artifact.metadata.get("agent_name")
|
|
1112
|
+
if artifact.metadata
|
|
1113
|
+
else None
|
|
1114
|
+
)
|
|
1115
|
+
elif error:
|
|
1116
|
+
details["direction"] = "error_response"
|
|
1117
|
+
details["task_id"] = (
|
|
1118
|
+
error.data.get("taskId")
|
|
1119
|
+
if isinstance(error.data, dict)
|
|
1120
|
+
else None
|
|
1071
1121
|
)
|
|
1072
|
-
details["
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1122
|
+
details["debug_type"] = "error"
|
|
1123
|
+
|
|
1124
|
+
# Try to parse as a JSON-RPC request
|
|
1125
|
+
elif "method" in payload:
|
|
1126
|
+
rpc_request = A2ARequest.model_validate(payload)
|
|
1127
|
+
method = a2a.get_request_method(rpc_request)
|
|
1128
|
+
details["direction"] = "request"
|
|
1129
|
+
details["payload_summary"]["method"] = method
|
|
1130
|
+
details["message_id"] = a2a.get_request_id(rpc_request)
|
|
1131
|
+
|
|
1132
|
+
if method in ["message/send", "message/stream"]:
|
|
1133
|
+
details["debug_type"] = method
|
|
1134
|
+
message = a2a.get_message_from_send_request(rpc_request)
|
|
1135
|
+
details["task_id"] = a2a.get_request_id(rpc_request)
|
|
1136
|
+
if message:
|
|
1137
|
+
details["target_entity"] = (
|
|
1138
|
+
message.metadata.get("agent_name")
|
|
1139
|
+
if message.metadata
|
|
1140
|
+
else None
|
|
1141
|
+
)
|
|
1142
|
+
elif method == "tasks/cancel":
|
|
1143
|
+
details["task_id"] = a2a.get_task_id_from_cancel_request(
|
|
1144
|
+
rpc_request
|
|
1081
1145
|
)
|
|
1082
|
-
|
|
1083
|
-
|
|
1146
|
+
|
|
1147
|
+
# Handle Discovery messages (which are not JSON-RPC)
|
|
1148
|
+
elif "/a2a/v1/discovery/" in topic:
|
|
1149
|
+
agent_card = AgentCard.model_validate(payload)
|
|
1084
1150
|
details["direction"] = "discovery"
|
|
1085
|
-
details["source_entity"] =
|
|
1151
|
+
details["source_entity"] = agent_card.name
|
|
1086
1152
|
details["target_entity"] = "broadcast"
|
|
1153
|
+
details["message_id"] = None # Discovery has no ID
|
|
1087
1154
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
"
|
|
1091
|
-
"tasks/cancel",
|
|
1092
|
-
]:
|
|
1093
|
-
details["task_id"] = payload.get("params", {}).get("id")
|
|
1094
|
-
elif "result" in payload and isinstance(payload["result"], dict):
|
|
1095
|
-
details["task_id"] = payload["result"].get("id")
|
|
1096
|
-
elif len(topic_parts) > task_id_from_topic_index and (
|
|
1097
|
-
action_type == "status" or action_type == "response"
|
|
1098
|
-
):
|
|
1099
|
-
details["task_id"] = topic_parts[task_id_from_topic_index]
|
|
1100
|
-
|
|
1101
|
-
except (ValueError, IndexError):
|
|
1102
|
-
log.debug(
|
|
1103
|
-
"%s Could not parse A2A structure from topic: %s",
|
|
1155
|
+
except Exception as e:
|
|
1156
|
+
log.warning(
|
|
1157
|
+
"[%s] Failed to parse A2A payload for visualization details: %s",
|
|
1104
1158
|
self.log_identifier,
|
|
1105
|
-
|
|
1159
|
+
e,
|
|
1106
1160
|
)
|
|
1161
|
+
|
|
1162
|
+
# --- Phase 2: Refine details using topic information as a fallback ---
|
|
1163
|
+
if details["direction"] == "unknown":
|
|
1107
1164
|
if "request" in topic:
|
|
1108
1165
|
details["direction"] = "request"
|
|
1109
1166
|
elif "response" in topic:
|
|
1110
1167
|
details["direction"] = "response"
|
|
1111
1168
|
elif "status" in topic:
|
|
1112
1169
|
details["direction"] = "status_update"
|
|
1113
|
-
|
|
1114
|
-
details["
|
|
1170
|
+
# TEMP - add debug_type based on the type in the data
|
|
1171
|
+
details["debug_type"] = "unknown"
|
|
1115
1172
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
(
|
|
1173
|
+
# --- Phase 3: Create a payload summary ---
|
|
1174
|
+
try:
|
|
1175
|
+
summary_source = (
|
|
1176
|
+
payload.get("result")
|
|
1177
|
+
or payload.get("params")
|
|
1178
|
+
or payload.get("error")
|
|
1179
|
+
or payload
|
|
1120
1180
|
)
|
|
1121
|
-
|
|
1122
|
-
result_str = json.dumps(payload["result"])
|
|
1181
|
+
summary_str = json.dumps(summary_source)
|
|
1123
1182
|
details["payload_summary"]["params_preview"] = (
|
|
1124
|
-
(
|
|
1183
|
+
(summary_str[:100] + "...") if len(summary_str) > 100 else summary_str
|
|
1125
1184
|
)
|
|
1126
|
-
|
|
1127
|
-
details["payload_summary"]["method"] = "JSONRPCError"
|
|
1128
|
-
error_str = json.dumps(payload["error"])
|
|
1185
|
+
except Exception:
|
|
1129
1186
|
details["payload_summary"]["params_preview"] = (
|
|
1130
|
-
|
|
1187
|
+
"[Could not serialize payload]"
|
|
1131
1188
|
)
|
|
1132
1189
|
|
|
1133
1190
|
return details
|
|
1134
1191
|
|
|
1135
1192
|
def _extract_involved_agents_for_viz(
|
|
1136
|
-
self, topic: str, payload_dict:
|
|
1137
|
-
) ->
|
|
1193
|
+
self, topic: str, payload_dict: dict[str, Any]
|
|
1194
|
+
) -> set[str]:
|
|
1138
1195
|
"""
|
|
1139
1196
|
Extracts agent names involved in a message from its topic and payload.
|
|
1140
1197
|
"""
|
|
1141
|
-
agents:
|
|
1198
|
+
agents: set[str] = set()
|
|
1142
1199
|
log_id_prefix = f"{self.log_identifier}[ExtractAgentsViz]"
|
|
1143
1200
|
|
|
1144
1201
|
topic_agent_match = re.match(
|
|
@@ -1265,13 +1322,13 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1265
1322
|
"""Returns the unique identifier for this gateway instance."""
|
|
1266
1323
|
return self.gateway_id
|
|
1267
1324
|
|
|
1268
|
-
def get_cors_origins(self) ->
|
|
1325
|
+
def get_cors_origins(self) -> list[str]:
|
|
1269
1326
|
return self.cors_allowed_origins
|
|
1270
1327
|
|
|
1271
|
-
def get_shared_artifact_service(self) ->
|
|
1328
|
+
def get_shared_artifact_service(self) -> BaseArtifactService | None:
|
|
1272
1329
|
return self.shared_artifact_service
|
|
1273
1330
|
|
|
1274
|
-
def get_embed_config(self) ->
|
|
1331
|
+
def get_embed_config(self) -> dict[str, Any]:
|
|
1275
1332
|
"""Returns embed-related configuration needed by dependencies."""
|
|
1276
1333
|
return {
|
|
1277
1334
|
"enable_embed_resolution": self.enable_embed_resolution,
|
|
@@ -1287,6 +1344,52 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1287
1344
|
"""Returns the instance of the ConfigResolver."""
|
|
1288
1345
|
return self._config_resolver
|
|
1289
1346
|
|
|
1347
|
+
async def _resolve_embeds_for_persistence(
|
|
1348
|
+
self, message_content: str, session_id: str, user_id: str, log_identifier: str
|
|
1349
|
+
) -> str:
|
|
1350
|
+
"""
|
|
1351
|
+
Resolves embeds in a message for database storage.
|
|
1352
|
+
Returns the resolved text.
|
|
1353
|
+
|
|
1354
|
+
Args:
|
|
1355
|
+
message_content: The message text that may contain embeds
|
|
1356
|
+
session_id: The A2A session ID
|
|
1357
|
+
user_id: The user ID
|
|
1358
|
+
log_identifier: Logging identifier
|
|
1359
|
+
|
|
1360
|
+
Returns:
|
|
1361
|
+
The message with embeds resolved (or original if resolution fails)
|
|
1362
|
+
"""
|
|
1363
|
+
try:
|
|
1364
|
+
embed_context = {
|
|
1365
|
+
"artifact_service": self.shared_artifact_service,
|
|
1366
|
+
"session_context": {
|
|
1367
|
+
"app_name": self.gateway_id,
|
|
1368
|
+
"user_id": user_id,
|
|
1369
|
+
"session_id": session_id,
|
|
1370
|
+
},
|
|
1371
|
+
"config": self.get_embed_config(),
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
resolved_text, _, _ = await resolve_embeds_in_string(
|
|
1375
|
+
text=message_content,
|
|
1376
|
+
context=embed_context,
|
|
1377
|
+
resolver_func=evaluate_embed,
|
|
1378
|
+
types_to_resolve=EARLY_EMBED_TYPES,
|
|
1379
|
+
log_identifier=log_identifier,
|
|
1380
|
+
config=embed_context["config"],
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
return resolved_text
|
|
1384
|
+
|
|
1385
|
+
except Exception as e:
|
|
1386
|
+
log.warning(
|
|
1387
|
+
"%s Error resolving embeds for storage: %s. Using original message.",
|
|
1388
|
+
log_identifier,
|
|
1389
|
+
e,
|
|
1390
|
+
)
|
|
1391
|
+
return message_content
|
|
1392
|
+
|
|
1290
1393
|
def _start_listener(self) -> None:
|
|
1291
1394
|
"""
|
|
1292
1395
|
GDK Hook: Starts the FastAPI/Uvicorn server.
|
|
@@ -1308,8 +1411,8 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1308
1411
|
pass
|
|
1309
1412
|
|
|
1310
1413
|
async def _translate_external_input(
|
|
1311
|
-
self, external_event_data:
|
|
1312
|
-
) ->
|
|
1414
|
+
self, external_event_data: dict[str, Any]
|
|
1415
|
+
) -> tuple[str, list[ContentPart], dict[str, Any]]:
|
|
1313
1416
|
"""
|
|
1314
1417
|
Translates raw HTTP request data (from FastAPI form) into A2A task parameters.
|
|
1315
1418
|
|
|
@@ -1321,7 +1424,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1321
1424
|
Returns:
|
|
1322
1425
|
A tuple containing:
|
|
1323
1426
|
- target_agent_name (str): The name of the A2A agent to target.
|
|
1324
|
-
- a2a_parts (List[
|
|
1427
|
+
- a2a_parts (List[ContentPart]): A list of unwrapped A2A Part objects.
|
|
1325
1428
|
- external_request_context (Dict[str, Any]): Context for TaskContextManager.
|
|
1326
1429
|
"""
|
|
1327
1430
|
log_id_prefix = f"{self.log_identifier}[TranslateInput]"
|
|
@@ -1333,10 +1436,9 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1333
1436
|
|
|
1334
1437
|
target_agent_name: str = external_event_data.get("agent_name")
|
|
1335
1438
|
user_message: str = external_event_data.get("message", "")
|
|
1336
|
-
files:
|
|
1439
|
+
files: list[UploadFile] | None = external_event_data.get("files")
|
|
1337
1440
|
client_id: str = external_event_data.get("client_id")
|
|
1338
1441
|
a2a_session_id: str = external_event_data.get("a2a_session_id")
|
|
1339
|
-
|
|
1340
1442
|
if not target_agent_name:
|
|
1341
1443
|
raise ValueError("Target agent name is missing in external_event_data.")
|
|
1342
1444
|
if not client_id or not a2a_session_id:
|
|
@@ -1344,10 +1446,9 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1344
1446
|
"Client ID or A2A Session ID is missing in external_event_data."
|
|
1345
1447
|
)
|
|
1346
1448
|
|
|
1347
|
-
a2a_parts:
|
|
1449
|
+
a2a_parts: list[ContentPart] = []
|
|
1348
1450
|
|
|
1349
|
-
if files
|
|
1350
|
-
file_metadata_summary_parts = []
|
|
1451
|
+
if files:
|
|
1351
1452
|
for upload_file in files:
|
|
1352
1453
|
try:
|
|
1353
1454
|
content_bytes = await upload_file.read()
|
|
@@ -1358,52 +1459,21 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1358
1459
|
upload_file.filename,
|
|
1359
1460
|
)
|
|
1360
1461
|
continue
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
session_id=a2a_session_id,
|
|
1366
|
-
filename=upload_file.filename,
|
|
1462
|
+
|
|
1463
|
+
# The BaseGatewayComponent will handle normalization based on policy.
|
|
1464
|
+
# Here, we just create the FilePart with inline bytes.
|
|
1465
|
+
file_part = a2a.create_file_part_from_bytes(
|
|
1367
1466
|
content_bytes=content_bytes,
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
"web_client_id": client_id,
|
|
1378
|
-
"a2a_session_id": a2a_session_id,
|
|
1379
|
-
},
|
|
1380
|
-
timestamp=datetime.now(timezone.utc),
|
|
1467
|
+
name=upload_file.filename,
|
|
1468
|
+
mime_type=upload_file.content_type,
|
|
1469
|
+
)
|
|
1470
|
+
a2a_parts.append(file_part)
|
|
1471
|
+
log.info(
|
|
1472
|
+
"%s Created inline FilePart for uploaded file: %s (%d bytes)",
|
|
1473
|
+
log_id_prefix,
|
|
1474
|
+
upload_file.filename,
|
|
1475
|
+
len(content_bytes),
|
|
1381
1476
|
)
|
|
1382
|
-
|
|
1383
|
-
if save_result["status"] in ["success", "partial_success"]:
|
|
1384
|
-
data_version = save_result.get("data_version", 0)
|
|
1385
|
-
artifact_uri = f"artifact://{self.gateway_id}/{client_id}/{a2a_session_id}/{upload_file.filename}?version={data_version}"
|
|
1386
|
-
file_content = FileContent(
|
|
1387
|
-
name=upload_file.filename,
|
|
1388
|
-
mimeType=upload_file.content_type,
|
|
1389
|
-
uri=artifact_uri,
|
|
1390
|
-
)
|
|
1391
|
-
a2a_parts.append(FilePart(file=file_content))
|
|
1392
|
-
file_metadata_summary_parts.append(
|
|
1393
|
-
f"- {upload_file.filename} ({upload_file.content_type}, {len(content_bytes)} bytes, URI: {artifact_uri})"
|
|
1394
|
-
)
|
|
1395
|
-
log.info(
|
|
1396
|
-
"%s Processed and created URI for uploaded file: %s",
|
|
1397
|
-
log_id_prefix,
|
|
1398
|
-
artifact_uri,
|
|
1399
|
-
)
|
|
1400
|
-
else:
|
|
1401
|
-
log.error(
|
|
1402
|
-
"%s Failed to save artifact %s: %s",
|
|
1403
|
-
log_id_prefix,
|
|
1404
|
-
upload_file.filename,
|
|
1405
|
-
save_result.get("message"),
|
|
1406
|
-
)
|
|
1407
1477
|
|
|
1408
1478
|
except Exception as e:
|
|
1409
1479
|
log.exception(
|
|
@@ -1415,15 +1485,8 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1415
1485
|
finally:
|
|
1416
1486
|
await upload_file.close()
|
|
1417
1487
|
|
|
1418
|
-
if file_metadata_summary_parts:
|
|
1419
|
-
user_message = (
|
|
1420
|
-
"The user uploaded the following file(s):\n"
|
|
1421
|
-
+ "\n".join(file_metadata_summary_parts)
|
|
1422
|
-
+ f"\n\nUser message: {user_message}"
|
|
1423
|
-
)
|
|
1424
|
-
|
|
1425
1488
|
if user_message:
|
|
1426
|
-
a2a_parts.append(
|
|
1489
|
+
a2a_parts.append(a2a.create_text_part(text=user_message))
|
|
1427
1490
|
|
|
1428
1491
|
external_request_context = {
|
|
1429
1492
|
"app_name_for_artifacts": self.gateway_id,
|
|
@@ -1443,17 +1506,23 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1443
1506
|
|
|
1444
1507
|
async def _send_update_to_external(
|
|
1445
1508
|
self,
|
|
1446
|
-
external_request_context:
|
|
1447
|
-
event_data:
|
|
1509
|
+
external_request_context: dict[str, Any],
|
|
1510
|
+
event_data: TaskStatusUpdateEvent | TaskArtifactUpdateEvent,
|
|
1448
1511
|
is_final_chunk_of_update: bool,
|
|
1449
1512
|
) -> None:
|
|
1450
1513
|
"""
|
|
1451
1514
|
Sends an intermediate update (TaskStatusUpdateEvent or TaskArtifactUpdateEvent)
|
|
1452
|
-
to the external platform (Web UI via SSE).
|
|
1515
|
+
to the external platform (Web UI via SSE) and stores agent messages in the database.
|
|
1453
1516
|
"""
|
|
1454
1517
|
log_id_prefix = f"{self.log_identifier}[SendUpdate]"
|
|
1455
1518
|
sse_task_id = external_request_context.get("a2a_task_id_for_event")
|
|
1456
|
-
a2a_task_id = event_data.
|
|
1519
|
+
a2a_task_id = event_data.task_id
|
|
1520
|
+
|
|
1521
|
+
log.debug(
|
|
1522
|
+
"%s _send_update_to_external called with event_type: %s",
|
|
1523
|
+
log_id_prefix,
|
|
1524
|
+
type(event_data).__name__,
|
|
1525
|
+
)
|
|
1457
1526
|
|
|
1458
1527
|
if not sse_task_id:
|
|
1459
1528
|
log.error(
|
|
@@ -1474,9 +1543,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1474
1543
|
if isinstance(event_data, TaskArtifactUpdateEvent):
|
|
1475
1544
|
sse_event_type = "artifact_update"
|
|
1476
1545
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1546
|
+
sse_payload_model = a2a.create_success_response(
|
|
1547
|
+
result=event_data, request_id=a2a_task_id
|
|
1479
1548
|
)
|
|
1549
|
+
sse_payload = sse_payload_model.model_dump(by_alias=True, exclude_none=True)
|
|
1480
1550
|
|
|
1481
1551
|
try:
|
|
1482
1552
|
await self.sse_manager.send_event(
|
|
@@ -1488,6 +1558,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1488
1558
|
sse_event_type,
|
|
1489
1559
|
a2a_task_id,
|
|
1490
1560
|
)
|
|
1561
|
+
|
|
1562
|
+
# Note: Agent message storage is handled in _send_final_response_to_external
|
|
1563
|
+
# to avoid duplicate storage of intermediate status updates
|
|
1564
|
+
|
|
1491
1565
|
except Exception as e:
|
|
1492
1566
|
log.exception(
|
|
1493
1567
|
"%s Failed to send %s via SSE for A2A Task ID %s: %s",
|
|
@@ -1498,7 +1572,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1498
1572
|
)
|
|
1499
1573
|
|
|
1500
1574
|
async def _send_final_response_to_external(
|
|
1501
|
-
self, external_request_context:
|
|
1575
|
+
self, external_request_context: dict[str, Any], task_data: Task
|
|
1502
1576
|
) -> None:
|
|
1503
1577
|
"""
|
|
1504
1578
|
Sends the final A2A Task result to the external platform (Web UI via SSE).
|
|
@@ -1507,6 +1581,8 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1507
1581
|
sse_task_id = external_request_context.get("a2a_task_id_for_event")
|
|
1508
1582
|
a2a_task_id = task_data.id
|
|
1509
1583
|
|
|
1584
|
+
log.debug("%s _send_final_response_to_external called", log_id_prefix)
|
|
1585
|
+
|
|
1510
1586
|
if not sse_task_id:
|
|
1511
1587
|
log.error(
|
|
1512
1588
|
"%s Cannot send final response: 'a2a_task_id_for_event' missing from external_request_context.",
|
|
@@ -1521,9 +1597,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1521
1597
|
sse_task_id,
|
|
1522
1598
|
)
|
|
1523
1599
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1600
|
+
sse_payload_model = a2a.create_success_response(
|
|
1601
|
+
result=task_data, request_id=a2a_task_id
|
|
1526
1602
|
)
|
|
1603
|
+
sse_payload = sse_payload_model.model_dump(by_alias=True, exclude_none=True)
|
|
1527
1604
|
|
|
1528
1605
|
try:
|
|
1529
1606
|
await self.sse_manager.send_event(
|
|
@@ -1534,6 +1611,63 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1534
1611
|
log_id_prefix,
|
|
1535
1612
|
a2a_task_id,
|
|
1536
1613
|
)
|
|
1614
|
+
|
|
1615
|
+
# Store final agent response in persistence layer if available
|
|
1616
|
+
if hasattr(self, "persistence_service") and self.persistence_service:
|
|
1617
|
+
try:
|
|
1618
|
+
session_id = external_request_context.get("a2a_session_id")
|
|
1619
|
+
user_id = external_request_context.get("user_id_for_a2a")
|
|
1620
|
+
agent_name = external_request_context.get(
|
|
1621
|
+
"target_agent_name", "agent"
|
|
1622
|
+
)
|
|
1623
|
+
|
|
1624
|
+
# Extract message content from the task status
|
|
1625
|
+
message_text = ""
|
|
1626
|
+
if task_data.status and task_data.status.message:
|
|
1627
|
+
parts = a2a.get_parts_from_message(task_data.status.message)
|
|
1628
|
+
for part in parts:
|
|
1629
|
+
if hasattr(part, "text") and part.text:
|
|
1630
|
+
if message_text:
|
|
1631
|
+
message_text += "\n"
|
|
1632
|
+
message_text += part.text
|
|
1633
|
+
|
|
1634
|
+
log.info(
|
|
1635
|
+
"%s Final agent response storage debug - session_id: %s, user_id: %s, message_text: '%s', parts_count: %s",
|
|
1636
|
+
log_id_prefix,
|
|
1637
|
+
session_id,
|
|
1638
|
+
user_id,
|
|
1639
|
+
message_text[:100] if message_text else None,
|
|
1640
|
+
len(a2a.get_parts_from_message(task_data.status.message))
|
|
1641
|
+
if task_data.status and task_data.status.message
|
|
1642
|
+
else 0,
|
|
1643
|
+
)
|
|
1644
|
+
|
|
1645
|
+
if message_text and session_id and user_id:
|
|
1646
|
+
from .dependencies import get_session_service
|
|
1647
|
+
from .shared.enums import SenderType
|
|
1648
|
+
|
|
1649
|
+
session_service = get_session_service(self)
|
|
1650
|
+
session_service.add_message_to_session(
|
|
1651
|
+
session_id=session_id,
|
|
1652
|
+
user_id=user_id,
|
|
1653
|
+
message=message_text,
|
|
1654
|
+
sender_type=SenderType.AGENT,
|
|
1655
|
+
sender_name=agent_name,
|
|
1656
|
+
agent_id=agent_name,
|
|
1657
|
+
)
|
|
1658
|
+
log.info(
|
|
1659
|
+
"%s Final agent response stored in session %s",
|
|
1660
|
+
log_id_prefix,
|
|
1661
|
+
session_id,
|
|
1662
|
+
)
|
|
1663
|
+
except Exception as storage_error:
|
|
1664
|
+
log.warning(
|
|
1665
|
+
"%s Failed to store final agent response: %s",
|
|
1666
|
+
log_id_prefix,
|
|
1667
|
+
storage_error,
|
|
1668
|
+
)
|
|
1669
|
+
# Don't fail the SSE send if storage fails
|
|
1670
|
+
|
|
1537
1671
|
except Exception as e:
|
|
1538
1672
|
log.exception(
|
|
1539
1673
|
"%s Failed to send final_response via SSE for A2A Task ID %s: %s",
|
|
@@ -1550,7 +1684,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1550
1684
|
)
|
|
1551
1685
|
|
|
1552
1686
|
async def _send_error_to_external(
|
|
1553
|
-
self, external_request_context:
|
|
1687
|
+
self, external_request_context: dict[str, Any], error_data: JSONRPCError
|
|
1554
1688
|
) -> None:
|
|
1555
1689
|
"""
|
|
1556
1690
|
Sends an error notification to the external platform (Web UI via SSE).
|
|
@@ -1572,10 +1706,11 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1572
1706
|
error_data,
|
|
1573
1707
|
)
|
|
1574
1708
|
|
|
1575
|
-
|
|
1576
|
-
id=external_request_context.get("original_rpc_id", sse_task_id),
|
|
1709
|
+
sse_payload_model = a2a.create_error_response(
|
|
1577
1710
|
error=error_data,
|
|
1578
|
-
|
|
1711
|
+
request_id=external_request_context.get("original_rpc_id", sse_task_id),
|
|
1712
|
+
)
|
|
1713
|
+
sse_payload = sse_payload_model.model_dump(by_alias=True, exclude_none=True)
|
|
1579
1714
|
|
|
1580
1715
|
try:
|
|
1581
1716
|
await self.sse_manager.send_event(
|