solace-agent-mesh 1.6.2__py3-none-any.whl → 1.7.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 +12 -18
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
- solace_agent_mesh/agent/adk/callbacks.py +138 -20
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +2 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +38 -5
- solace_agent_mesh/agent/adk/models/models_llm.txt +82 -35
- solace_agent_mesh/agent/adk/runner.py +9 -0
- solace_agent_mesh/agent/adk/services.py +3 -3
- solace_agent_mesh/agent/adk/stream_parser.py +6 -1
- solace_agent_mesh/agent/adk/tool_wrapper.py +3 -0
- solace_agent_mesh/agent/agent_llm.txt +61 -70
- solace_agent_mesh/agent/protocol/event_handlers.py +29 -1
- solace_agent_mesh/agent/protocol/protocol_llm.txt +1 -1
- solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
- solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
- solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
- solace_agent_mesh/agent/sac/app.py +22 -0
- solace_agent_mesh/agent/sac/component.py +76 -40
- solace_agent_mesh/agent/sac/sac_llm.txt +1 -1
- solace_agent_mesh/agent/sac/task_execution_context.py +21 -0
- solace_agent_mesh/agent/testing/testing_llm.txt +2 -1
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +13 -148
- solace_agent_mesh/agent/tools/dynamic_tool.py +2 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +93 -80
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +3 -2
- solace_agent_mesh/agent/utils/artifact_helpers.py +4 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +16 -2
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{ab9708a8.3e6dd091.js → ab9708a8.245ae0ef.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{e6f9706b.e74a984d.js → e6f9706b.045d0fa1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.e6488e8b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +18 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +196 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +6 -7
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +160 -169
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +59 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +10 -6
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +24 -29
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +27 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +5 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +9 -9
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1762189824009.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1762189824009.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/docs_cmd.py +4 -1
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-D4_RMYRh.js → authCallback-tcIFZLis.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-UZ3qU6Bq.js → client-CRYdKo2Q.js} +3 -3
- solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +353 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-CINwxvwV.js +470 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/common/a2a/a2a_llm.txt +13 -20
- solace_agent_mesh/common/a2a/protocol.py +5 -0
- solace_agent_mesh/common/a2a/types.py +1 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +49 -11
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +23 -6
- solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +26 -9
- solace_agent_mesh/common/common_llm.txt +13 -34
- solace_agent_mesh/common/data_parts.py +20 -4
- solace_agent_mesh/common/middleware/middleware_llm.txt +1 -1
- solace_agent_mesh/common/sac/sac_llm.txt +1 -1
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +1 -1
- solace_agent_mesh/common/services/employee_service.py +1 -1
- solace_agent_mesh/common/services/providers/providers_llm.txt +3 -2
- solace_agent_mesh/common/services/services_llm.txt +9 -4
- solace_agent_mesh/common/utils/embeds/constants.py +1 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
- solace_agent_mesh/common/utils/embeds/modifiers.py +2 -1
- solace_agent_mesh/common/utils/embeds/resolver.py +58 -6
- solace_agent_mesh/common/utils/embeds/types.py +8 -0
- solace_agent_mesh/common/utils/utils_llm.txt +5 -6
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +1 -1
- solace_agent_mesh/gateway/adapter/__init__.py +1 -0
- solace_agent_mesh/gateway/adapter/base.py +143 -0
- solace_agent_mesh/gateway/adapter/types.py +221 -0
- solace_agent_mesh/gateway/base/app.py +29 -2
- solace_agent_mesh/gateway/base/base_llm.txt +10 -8
- solace_agent_mesh/gateway/base/component.py +573 -142
- solace_agent_mesh/gateway/gateway_llm.txt +55 -59
- solace_agent_mesh/gateway/generic/__init__.py +1 -0
- solace_agent_mesh/gateway/generic/app.py +50 -0
- solace_agent_mesh/gateway/generic/component.py +650 -0
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +99 -49
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +92 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +150 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +26 -20
- solace_agent_mesh/gateway/http_sse/app.py +19 -14
- solace_agent_mesh/gateway/http_sse/component.py +150 -118
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +1 -1
- solace_agent_mesh/gateway/http_sse/dependencies.py +21 -3
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +8 -8
- solace_agent_mesh/gateway/http_sse/main.py +55 -14
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +19 -1
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +56 -98
- solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +23 -1
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +112 -4
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -1
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +51 -60
- solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +7 -1
- solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
- solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +125 -157
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +269 -8
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +143 -51
- solace_agent_mesh/gateway/http_sse/routers/config.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +198 -94
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +68 -18
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +13 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +30 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +51 -35
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +2 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +133 -2
- solace_agent_mesh/gateway/http_sse/routers/projects.py +542 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +9 -11
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +154 -3
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +296 -4
- solace_agent_mesh/gateway/http_sse/services/project_service.py +403 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +16 -10
- solace_agent_mesh/gateway/http_sse/services/session_service.py +178 -6
- solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +2 -3
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +48 -14
- solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/METADATA +3 -5
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/RECORD +219 -176
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.932dd2db.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.76654dd9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.2be20244.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2cbb060a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/631738c7.7c4594c9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.ba015d81.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.ddbdfbe2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/8024126c.56e59919.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/94e8668d.b5ddb7a1.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.d08a9466.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.0aa9630a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.d590bc9e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.6b9493d0.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e92d0134.4f395c6b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.720d2ef2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.d1643f0b.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.97f920d4.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1761663789856.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1761663789856.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main--3yJYl7S.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-DojKHS49.js +0 -342
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-DSqhjwq_.js +0 -405
- /solace_agent_mesh/assets/docs/assets/js/{main.d1643f0b.js.LICENSE.txt → main.e6488e8b.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The GenericGatewayComponent, the engine that hosts and orchestrates GatewayAdapters.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib
|
|
7
|
+
import logging
|
|
8
|
+
import uuid
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
11
|
+
|
|
12
|
+
from a2a.types import (
|
|
13
|
+
DataPart as A2ADataPart,
|
|
14
|
+
FilePart,
|
|
15
|
+
JSONRPCError,
|
|
16
|
+
Task,
|
|
17
|
+
TaskArtifactUpdateEvent,
|
|
18
|
+
TaskState,
|
|
19
|
+
TaskStatusUpdateEvent,
|
|
20
|
+
TextPart,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from ...common import a2a
|
|
24
|
+
from ...common.a2a.protocol import get_feedback_topic
|
|
25
|
+
from ...agent.utils.artifact_helpers import (
|
|
26
|
+
get_artifact_info_list,
|
|
27
|
+
load_artifact_content_or_metadata,
|
|
28
|
+
)
|
|
29
|
+
from ...common.a2a.types import ArtifactInfo
|
|
30
|
+
from ..adapter.base import GatewayAdapter
|
|
31
|
+
from ..adapter.types import (
|
|
32
|
+
GatewayContext,
|
|
33
|
+
ResponseContext,
|
|
34
|
+
SamDataPart,
|
|
35
|
+
SamError,
|
|
36
|
+
SamFeedback,
|
|
37
|
+
SamFilePart,
|
|
38
|
+
SamTextPart,
|
|
39
|
+
SamUpdate,
|
|
40
|
+
)
|
|
41
|
+
from ..base.component import BaseGatewayComponent
|
|
42
|
+
|
|
43
|
+
log = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
info = {
|
|
46
|
+
"class_name": "GenericGatewayComponent",
|
|
47
|
+
"description": "A generic gateway component that hosts a pluggable GatewayAdapter.",
|
|
48
|
+
"config_parameters": [],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _load_adapter_class(adapter_path: str) -> type[GatewayAdapter]:
|
|
53
|
+
"""Dynamically loads the adapter class from a module path."""
|
|
54
|
+
try:
|
|
55
|
+
module_path, class_name = adapter_path.rsplit(".", 1)
|
|
56
|
+
module = importlib.import_module(module_path)
|
|
57
|
+
adapter_class = getattr(module, class_name)
|
|
58
|
+
if not issubclass(adapter_class, GatewayAdapter):
|
|
59
|
+
raise TypeError(
|
|
60
|
+
f"Class {adapter_path} is not a subclass of GatewayAdapter."
|
|
61
|
+
)
|
|
62
|
+
return adapter_class
|
|
63
|
+
except (ImportError, AttributeError, ValueError, TypeError) as e:
|
|
64
|
+
log.exception(f"Failed to load gateway adapter from path: {adapter_path}")
|
|
65
|
+
raise ImportError(
|
|
66
|
+
f"Could not load gateway adapter '{adapter_path}': {e}"
|
|
67
|
+
) from e
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class GenericGatewayComponent(BaseGatewayComponent, GatewayContext):
|
|
71
|
+
"""
|
|
72
|
+
The engine that hosts and orchestrates a GatewayAdapter.
|
|
73
|
+
|
|
74
|
+
This component implements the `BaseGatewayComponent` abstract methods by
|
|
75
|
+
delegating platform-specific logic to a dynamically loaded adapter. It also
|
|
76
|
+
serves as the concrete implementation of the `GatewayContext` provided to
|
|
77
|
+
the adapter.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, **kwargs: Any):
|
|
81
|
+
component_config = kwargs.get("component_config", {})
|
|
82
|
+
app_config = component_config.get("app_config", {})
|
|
83
|
+
resolve_uris = app_config.get("resolve_artifact_uris_in_gateway", True)
|
|
84
|
+
|
|
85
|
+
# Generic gateway configuration:
|
|
86
|
+
# - supports_inline_artifact_resolution=True: Artifacts are converted to FileParts
|
|
87
|
+
# during embed resolution and can be rendered inline
|
|
88
|
+
# - filter_tool_data_parts=False: Gateway displays all parts including tool execution details
|
|
89
|
+
super().__init__(
|
|
90
|
+
resolve_artifact_uris_in_gateway=resolve_uris,
|
|
91
|
+
supports_inline_artifact_resolution=True,
|
|
92
|
+
filter_tool_data_parts=False,
|
|
93
|
+
**kwargs
|
|
94
|
+
)
|
|
95
|
+
log.info("%s Initializing Generic Gateway Component...", self.log_identifier)
|
|
96
|
+
|
|
97
|
+
# --- Adapter Loading ---
|
|
98
|
+
adapter_path = self.get_config("gateway_adapter")
|
|
99
|
+
if not adapter_path:
|
|
100
|
+
raise ValueError("'gateway_adapter' path is not configured.")
|
|
101
|
+
|
|
102
|
+
log.info(
|
|
103
|
+
"%s Loading gateway adapter from: %s", self.log_identifier, adapter_path
|
|
104
|
+
)
|
|
105
|
+
AdapterClass = _load_adapter_class(adapter_path)
|
|
106
|
+
self.adapter: GatewayAdapter = AdapterClass()
|
|
107
|
+
log.info(
|
|
108
|
+
"%s Gateway adapter '%s' loaded successfully.",
|
|
109
|
+
self.log_identifier,
|
|
110
|
+
adapter_path,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# --- GatewayContext properties ---
|
|
114
|
+
adapter_config_dict = self.get_config("adapter_config", {})
|
|
115
|
+
if self.adapter.ConfigModel:
|
|
116
|
+
log.info(
|
|
117
|
+
"%s Validating adapter_config against %s...",
|
|
118
|
+
self.log_identifier,
|
|
119
|
+
self.adapter.ConfigModel.__name__,
|
|
120
|
+
)
|
|
121
|
+
self.adapter_config = self.adapter.ConfigModel(**adapter_config_dict)
|
|
122
|
+
else:
|
|
123
|
+
self.adapter_config = adapter_config_dict
|
|
124
|
+
|
|
125
|
+
self.artifact_service = self.shared_artifact_service
|
|
126
|
+
# `gateway_id`, `namespace`, `config` are available from base classes.
|
|
127
|
+
|
|
128
|
+
# --- GatewayContext Implementation ---
|
|
129
|
+
|
|
130
|
+
async def handle_external_input(
|
|
131
|
+
self, external_input: Any, endpoint_context: Optional[Dict[str, Any]] = None
|
|
132
|
+
) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Processes an external input event through the full gateway flow.
|
|
135
|
+
Orchestrates auth, task preparation, and A2A submission.
|
|
136
|
+
"""
|
|
137
|
+
log_id_prefix = f"{self.log_identifier}[HandleInput]"
|
|
138
|
+
user_identity = None
|
|
139
|
+
try:
|
|
140
|
+
# 1. Authentication & Enrichment
|
|
141
|
+
auth_claims = await self.adapter.extract_auth_claims(
|
|
142
|
+
external_input, endpoint_context
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# The final user_identity is a dictionary, not the Pydantic model.
|
|
146
|
+
# It's built from claims and potentially enriched by an identity service.
|
|
147
|
+
if auth_claims:
|
|
148
|
+
if self.identity_service:
|
|
149
|
+
# Pass the rich claims object to the identity service
|
|
150
|
+
enriched_profile = await self.identity_service.get_user_profile(
|
|
151
|
+
auth_claims
|
|
152
|
+
)
|
|
153
|
+
if enriched_profile:
|
|
154
|
+
# Merge claims and profile, with profile taking precedence
|
|
155
|
+
user_identity = {
|
|
156
|
+
**auth_claims.model_dump(),
|
|
157
|
+
**enriched_profile,
|
|
158
|
+
}
|
|
159
|
+
else:
|
|
160
|
+
user_identity = auth_claims.model_dump()
|
|
161
|
+
else:
|
|
162
|
+
# No identity service, just use the claims from the adapter
|
|
163
|
+
user_identity = auth_claims.model_dump()
|
|
164
|
+
else:
|
|
165
|
+
# Fallback to default identity if no claims are extracted
|
|
166
|
+
default_identity = self.get_config("default_user_identity")
|
|
167
|
+
if default_identity:
|
|
168
|
+
user_identity = {"id": default_identity, "name": default_identity}
|
|
169
|
+
|
|
170
|
+
if not user_identity or not user_identity.get("id"):
|
|
171
|
+
raise PermissionError(
|
|
172
|
+
"Authentication failed: No identity could be determined."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
log.info(
|
|
176
|
+
"%s Authenticated user: %s", log_id_prefix, user_identity.get("id")
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# 2. Task Preparation
|
|
180
|
+
sam_task = await self.adapter.prepare_task(external_input, endpoint_context)
|
|
181
|
+
log.info(
|
|
182
|
+
"%s Adapter prepared task for agent '%s' with %d parts.",
|
|
183
|
+
log_id_prefix,
|
|
184
|
+
sam_task.target_agent,
|
|
185
|
+
len(sam_task.parts),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# 3. A2A Submission
|
|
189
|
+
a2a_parts = self._sam_parts_to_a2a_parts(sam_task.parts)
|
|
190
|
+
|
|
191
|
+
external_request_context = {
|
|
192
|
+
"a2a_session_id": sam_task.session_id,
|
|
193
|
+
"user_id_for_artifacts": user_identity.get("id"),
|
|
194
|
+
**sam_task.platform_context,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
task_id = await self.submit_a2a_task(
|
|
198
|
+
target_agent_name=sam_task.target_agent,
|
|
199
|
+
a2a_parts=a2a_parts,
|
|
200
|
+
external_request_context=external_request_context,
|
|
201
|
+
user_identity=user_identity,
|
|
202
|
+
is_streaming=sam_task.is_streaming,
|
|
203
|
+
)
|
|
204
|
+
return task_id
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
log.exception(
|
|
208
|
+
"%s Error during external input processing: %s", log_id_prefix, e
|
|
209
|
+
)
|
|
210
|
+
# Try to report error back to the platform if possible
|
|
211
|
+
if (
|
|
212
|
+
user_identity
|
|
213
|
+
and user_identity.get("id")
|
|
214
|
+
and isinstance(e, (ValueError, PermissionError))
|
|
215
|
+
):
|
|
216
|
+
try:
|
|
217
|
+
# Create a dummy context to report the error
|
|
218
|
+
error_context = ResponseContext(
|
|
219
|
+
task_id="pre-task-error",
|
|
220
|
+
conversation_id=None,
|
|
221
|
+
user_id=user_identity.get("id"),
|
|
222
|
+
platform_context={},
|
|
223
|
+
)
|
|
224
|
+
error = SamError(
|
|
225
|
+
message=str(e), code=-32001, category="GATEWAY_ERROR"
|
|
226
|
+
)
|
|
227
|
+
await self.adapter.handle_error(error, error_context)
|
|
228
|
+
except Exception as report_err:
|
|
229
|
+
log.error(
|
|
230
|
+
"%s Failed to report initial processing error to adapter: %s",
|
|
231
|
+
log_id_prefix,
|
|
232
|
+
report_err,
|
|
233
|
+
)
|
|
234
|
+
raise
|
|
235
|
+
|
|
236
|
+
async def cancel_task(self, task_id: str) -> None:
|
|
237
|
+
"""Cancels an in-flight A2A task."""
|
|
238
|
+
log_id_prefix = f"{self.log_identifier}[CancelTask]"
|
|
239
|
+
context = self.task_context_manager.get_context(task_id)
|
|
240
|
+
if not context:
|
|
241
|
+
log.warning(
|
|
242
|
+
"%s Cannot cancel task %s: context not found.", log_id_prefix, task_id
|
|
243
|
+
)
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
target_agent_name = context.get("target_agent_name")
|
|
247
|
+
user_id = context.get("user_id_for_a2a")
|
|
248
|
+
|
|
249
|
+
if not target_agent_name:
|
|
250
|
+
log.error(
|
|
251
|
+
"%s Cannot cancel task %s: target_agent_name missing from context.",
|
|
252
|
+
log_id_prefix,
|
|
253
|
+
task_id,
|
|
254
|
+
)
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
log.info(
|
|
258
|
+
"%s Requesting cancellation for task %s on agent %s",
|
|
259
|
+
log_id_prefix,
|
|
260
|
+
task_id,
|
|
261
|
+
target_agent_name,
|
|
262
|
+
)
|
|
263
|
+
topic, payload, user_properties = self.core_a2a_service.cancel_task(
|
|
264
|
+
agent_name=target_agent_name,
|
|
265
|
+
task_id=task_id,
|
|
266
|
+
client_id=self.gateway_id,
|
|
267
|
+
user_id=user_id,
|
|
268
|
+
)
|
|
269
|
+
self.publish_a2a_message(
|
|
270
|
+
topic=topic, payload=payload, user_properties=user_properties
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
async def load_artifact_content(
|
|
274
|
+
self,
|
|
275
|
+
context: "ResponseContext",
|
|
276
|
+
filename: str,
|
|
277
|
+
version: Union[int, str] = "latest",
|
|
278
|
+
) -> Optional[bytes]:
|
|
279
|
+
"""Loads the raw byte content of an artifact using the shared service."""
|
|
280
|
+
log_id_prefix = f"{self.log_identifier}[LoadArtifact]"
|
|
281
|
+
if not self.artifact_service:
|
|
282
|
+
log.error("%s Artifact service is not configured.", log_id_prefix)
|
|
283
|
+
return None
|
|
284
|
+
try:
|
|
285
|
+
artifact_data = await load_artifact_content_or_metadata(
|
|
286
|
+
artifact_service=self.artifact_service,
|
|
287
|
+
app_name=self.gateway_id,
|
|
288
|
+
user_id=context.user_id,
|
|
289
|
+
session_id=context.session_id,
|
|
290
|
+
filename=filename,
|
|
291
|
+
version=version,
|
|
292
|
+
return_raw_bytes=True,
|
|
293
|
+
log_identifier_prefix=log_id_prefix,
|
|
294
|
+
)
|
|
295
|
+
if artifact_data.get("status") == "success":
|
|
296
|
+
content_bytes = artifact_data.get("raw_bytes")
|
|
297
|
+
if content_bytes:
|
|
298
|
+
log.info(
|
|
299
|
+
"%s Successfully loaded %d bytes for artifact '%s'.",
|
|
300
|
+
log_id_prefix,
|
|
301
|
+
len(content_bytes),
|
|
302
|
+
filename,
|
|
303
|
+
)
|
|
304
|
+
return content_bytes
|
|
305
|
+
else:
|
|
306
|
+
log.warning(
|
|
307
|
+
"%s Artifact '%s' (version: %s) loaded but has no content.",
|
|
308
|
+
log_id_prefix,
|
|
309
|
+
filename,
|
|
310
|
+
version,
|
|
311
|
+
)
|
|
312
|
+
return None
|
|
313
|
+
else:
|
|
314
|
+
log.warning(
|
|
315
|
+
"%s Failed to load artifact '%s' (version: %s). Status: %s",
|
|
316
|
+
log_id_prefix,
|
|
317
|
+
filename,
|
|
318
|
+
version,
|
|
319
|
+
artifact_data.get("status"),
|
|
320
|
+
)
|
|
321
|
+
return None
|
|
322
|
+
except Exception as e:
|
|
323
|
+
log.exception(
|
|
324
|
+
"%s Failed to load artifact '%s': %s", log_id_prefix, filename, e
|
|
325
|
+
)
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
async def list_artifacts(
|
|
329
|
+
self, context: "ResponseContext"
|
|
330
|
+
) -> List[ArtifactInfo]:
|
|
331
|
+
"""Lists all artifacts available in the user's context."""
|
|
332
|
+
log_id_prefix = f"{self.log_identifier}[ListArtifacts]"
|
|
333
|
+
if not self.artifact_service:
|
|
334
|
+
log.error("%s Artifact service is not configured.", log_id_prefix)
|
|
335
|
+
return []
|
|
336
|
+
try:
|
|
337
|
+
artifact_infos = await get_artifact_info_list(
|
|
338
|
+
artifact_service=self.artifact_service,
|
|
339
|
+
app_name=self.gateway_id,
|
|
340
|
+
user_id=context.user_id,
|
|
341
|
+
session_id=context.session_id,
|
|
342
|
+
)
|
|
343
|
+
log.info(
|
|
344
|
+
"%s Found %d artifacts for user %s in session %s.",
|
|
345
|
+
log_id_prefix,
|
|
346
|
+
len(artifact_infos),
|
|
347
|
+
context.user_id,
|
|
348
|
+
context.session_id,
|
|
349
|
+
)
|
|
350
|
+
return artifact_infos
|
|
351
|
+
except Exception as e:
|
|
352
|
+
log.exception(
|
|
353
|
+
"%s Failed to list artifacts for user %s: %s",
|
|
354
|
+
log_id_prefix,
|
|
355
|
+
context.user_id,
|
|
356
|
+
e,
|
|
357
|
+
)
|
|
358
|
+
return []
|
|
359
|
+
|
|
360
|
+
async def submit_feedback(self, feedback: "SamFeedback") -> None:
|
|
361
|
+
"""Handles feedback submission from an adapter."""
|
|
362
|
+
log_id_prefix = f"{self.log_identifier}[SubmitFeedback]"
|
|
363
|
+
feedback_config = self.get_config("feedback_publishing", {})
|
|
364
|
+
|
|
365
|
+
if not feedback_config.get("enabled", False):
|
|
366
|
+
log.debug("%s Feedback received but publishing is disabled.", log_id_prefix)
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
log.info(
|
|
370
|
+
"%s Received feedback for task %s: %s",
|
|
371
|
+
log_id_prefix,
|
|
372
|
+
feedback.task_id,
|
|
373
|
+
feedback.rating,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
feedback_payload = {
|
|
377
|
+
"id": f"feedback-{uuid.uuid4().hex}",
|
|
378
|
+
"session_id": feedback.session_id,
|
|
379
|
+
"task_id": feedback.task_id,
|
|
380
|
+
"user_id": feedback.user_id,
|
|
381
|
+
"rating": feedback.rating,
|
|
382
|
+
"comment": feedback.comment,
|
|
383
|
+
"created_time": datetime.now(timezone.utc).isoformat(),
|
|
384
|
+
"gateway_id": self.gateway_id,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
topic = get_feedback_topic(self.namespace)
|
|
388
|
+
self.publish_a2a_message(topic=topic, payload=feedback_payload)
|
|
389
|
+
log.info(
|
|
390
|
+
"%s Published feedback event for task %s to topic '%s'.",
|
|
391
|
+
log_id_prefix,
|
|
392
|
+
feedback.task_id,
|
|
393
|
+
topic,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
def add_timer(
|
|
397
|
+
self, delay_ms: int, callback: Callable, interval_ms: Optional[int] = None
|
|
398
|
+
) -> str:
|
|
399
|
+
timer_id = f"adapter-timer-{len(self.timer_manager.timers)}"
|
|
400
|
+
super().add_timer(delay_ms, timer_id, interval_ms, {"callback": callback})
|
|
401
|
+
return timer_id
|
|
402
|
+
|
|
403
|
+
def handle_timer_event(self, timer_data: Dict[str, Any]):
|
|
404
|
+
"""Handles timer events and calls the adapter's callback."""
|
|
405
|
+
callback = timer_data.get("payload", {}).get("callback")
|
|
406
|
+
if callable(callback):
|
|
407
|
+
# Run async callback in the component's event loop
|
|
408
|
+
asyncio.run_coroutine_threadsafe(callback(), self.get_async_loop())
|
|
409
|
+
else:
|
|
410
|
+
log.warning("Timer fired but no valid callback found in payload.")
|
|
411
|
+
|
|
412
|
+
def get_task_state(self, task_id: str, key: str, default: Any = None) -> Any:
|
|
413
|
+
cache_key = f"task_state:{task_id}:{key}"
|
|
414
|
+
value = self.cache_service.get_data(cache_key)
|
|
415
|
+
return value if value is not None else default
|
|
416
|
+
|
|
417
|
+
def set_task_state(self, task_id: str, key: str, value: Any) -> None:
|
|
418
|
+
cache_key = f"task_state:{task_id}:{key}"
|
|
419
|
+
# Use a reasonable expiry to prevent orphaned state
|
|
420
|
+
self.cache_service.add_data(cache_key, value, expiry=3600) # 1 hour
|
|
421
|
+
|
|
422
|
+
def get_session_state(self, session_id: str, key: str, default: Any = None) -> Any:
|
|
423
|
+
cache_key = f"session_state:{session_id}:{key}"
|
|
424
|
+
value = self.cache_service.get_data(cache_key)
|
|
425
|
+
return value if value is not None else default
|
|
426
|
+
|
|
427
|
+
def set_session_state(self, session_id: str, key: str, value: Any) -> None:
|
|
428
|
+
cache_key = f"session_state:{session_id}:{key}"
|
|
429
|
+
# Use a longer expiry for session state
|
|
430
|
+
self.cache_service.add_data(cache_key, value, expiry=86400) # 24 hours
|
|
431
|
+
|
|
432
|
+
def process_sac_template(
|
|
433
|
+
self,
|
|
434
|
+
template: str,
|
|
435
|
+
payload: Any = None,
|
|
436
|
+
headers: Optional[Dict[str, str]] = None,
|
|
437
|
+
query_params: Optional[Dict[str, str]] = None,
|
|
438
|
+
user_data: Optional[Dict[str, Any]] = None,
|
|
439
|
+
) -> str:
|
|
440
|
+
# This is a complex feature of SAC that requires careful implementation.
|
|
441
|
+
# For now, we raise an error.
|
|
442
|
+
raise NotImplementedError(
|
|
443
|
+
"process_sac_template is not yet implemented in GenericGatewayComponent."
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# --- BaseGatewayComponent Abstract Method Implementations ---
|
|
447
|
+
|
|
448
|
+
def _start_listener(self) -> None:
|
|
449
|
+
"""Starts the adapter's listener."""
|
|
450
|
+
log.info("%s Calling adapter.init()...", self.log_identifier)
|
|
451
|
+
# The adapter's init method is responsible for starting any listeners
|
|
452
|
+
# (e.g., an HTTP server, a websocket client).
|
|
453
|
+
# We run it in the component's event loop.
|
|
454
|
+
asyncio.run_coroutine_threadsafe(self.adapter.init(self), self.get_async_loop())
|
|
455
|
+
|
|
456
|
+
def _stop_listener(self) -> None:
|
|
457
|
+
"""Stops the adapter's listener."""
|
|
458
|
+
log.info("%s Calling adapter.cleanup()...", self.log_identifier)
|
|
459
|
+
# The adapter's cleanup method should handle graceful shutdown.
|
|
460
|
+
if self.adapter:
|
|
461
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
462
|
+
self.adapter.cleanup(), self.get_async_loop()
|
|
463
|
+
)
|
|
464
|
+
try:
|
|
465
|
+
future.result(timeout=10) # Wait for cleanup to finish
|
|
466
|
+
except Exception as e:
|
|
467
|
+
log.error("%s Error during adapter cleanup: %s", self.log_identifier, e)
|
|
468
|
+
|
|
469
|
+
async def _send_update_to_external(
|
|
470
|
+
self,
|
|
471
|
+
external_request_context: Dict[str, Any],
|
|
472
|
+
event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
|
|
473
|
+
is_final_chunk_of_update: bool,
|
|
474
|
+
) -> None:
|
|
475
|
+
"""Translates an A2A update event to SAM types and calls the adapter."""
|
|
476
|
+
response_context = self._create_response_context(external_request_context)
|
|
477
|
+
sam_update = SamUpdate(is_final=False)
|
|
478
|
+
|
|
479
|
+
parts: List[a2a.ContentPart] = []
|
|
480
|
+
if isinstance(event_data, TaskStatusUpdateEvent):
|
|
481
|
+
if event_data.status and event_data.status.message:
|
|
482
|
+
parts = a2a.get_parts_from_message(event_data.status.message)
|
|
483
|
+
elif isinstance(event_data, TaskArtifactUpdateEvent):
|
|
484
|
+
if event_data.artifact:
|
|
485
|
+
parts = a2a.get_parts_from_artifact(event_data.artifact)
|
|
486
|
+
|
|
487
|
+
sam_update.parts = self._a2a_parts_to_sam_parts(parts)
|
|
488
|
+
await self.adapter.handle_update(sam_update, response_context)
|
|
489
|
+
|
|
490
|
+
async def _send_final_response_to_external(
|
|
491
|
+
self, external_request_context: Dict[str, Any], task_data: Task
|
|
492
|
+
) -> None:
|
|
493
|
+
"""Translates a final A2A Task object to SAM types and calls the adapter."""
|
|
494
|
+
response_context = self._create_response_context(external_request_context)
|
|
495
|
+
sam_update = SamUpdate(is_final=True)
|
|
496
|
+
|
|
497
|
+
all_final_parts: List[a2a.ContentPart] = []
|
|
498
|
+
if task_data.status and task_data.status.message:
|
|
499
|
+
all_final_parts.extend(a2a.get_parts_from_message(task_data.status.message))
|
|
500
|
+
if task_data.artifacts:
|
|
501
|
+
for artifact in task_data.artifacts:
|
|
502
|
+
all_final_parts.extend(a2a.get_parts_from_artifact(artifact))
|
|
503
|
+
|
|
504
|
+
# If the original request was streaming, filter out text and file parts
|
|
505
|
+
# from the final response to avoid duplication, as they were already streamed.
|
|
506
|
+
was_streaming = external_request_context.get("is_streaming", False)
|
|
507
|
+
if was_streaming:
|
|
508
|
+
log.debug(
|
|
509
|
+
"%s Filtering final response parts for streaming task %s.",
|
|
510
|
+
self.log_identifier,
|
|
511
|
+
response_context.task_id,
|
|
512
|
+
)
|
|
513
|
+
filtered_parts = [
|
|
514
|
+
part
|
|
515
|
+
for part in all_final_parts
|
|
516
|
+
if not isinstance(part, (TextPart, FilePart))
|
|
517
|
+
]
|
|
518
|
+
sam_update.parts = self._a2a_parts_to_sam_parts(filtered_parts)
|
|
519
|
+
else:
|
|
520
|
+
sam_update.parts = self._a2a_parts_to_sam_parts(all_final_parts)
|
|
521
|
+
|
|
522
|
+
# Send the final content update (which might be empty for streaming tasks)
|
|
523
|
+
await self.adapter.handle_update(sam_update, response_context)
|
|
524
|
+
|
|
525
|
+
# Then, signal completion
|
|
526
|
+
await self.adapter.handle_task_complete(response_context)
|
|
527
|
+
|
|
528
|
+
async def _send_error_to_external(
|
|
529
|
+
self, external_request_context: Dict[str, Any], error_data: JSONRPCError
|
|
530
|
+
) -> None:
|
|
531
|
+
"""Translates an A2A error to a SamError and calls the adapter."""
|
|
532
|
+
response_context = self._create_response_context(external_request_context)
|
|
533
|
+
sam_error = self._a2a_error_to_sam_error(error_data)
|
|
534
|
+
|
|
535
|
+
await self.adapter.handle_error(sam_error, response_context)
|
|
536
|
+
|
|
537
|
+
# Also signal task completion, as an error is a final state
|
|
538
|
+
await self.adapter.handle_task_complete(response_context)
|
|
539
|
+
|
|
540
|
+
# --- Unused BaseGatewayComponent Abstract Methods ---
|
|
541
|
+
# These are part of the old gateway pattern and are replaced by the adapter flow.
|
|
542
|
+
|
|
543
|
+
async def _extract_initial_claims(
|
|
544
|
+
self, external_event_data: Any
|
|
545
|
+
) -> Optional[Dict[str, Any]]:
|
|
546
|
+
# This is now handled by `handle_external_input` calling the adapter directly.
|
|
547
|
+
# This method should not be called in the generic gateway flow.
|
|
548
|
+
log.warning(
|
|
549
|
+
"%s _extract_initial_claims called on GenericGatewayComponent. This should not happen.",
|
|
550
|
+
self.log_identifier,
|
|
551
|
+
)
|
|
552
|
+
return None
|
|
553
|
+
|
|
554
|
+
async def _translate_external_input(
|
|
555
|
+
self, external_event: Any
|
|
556
|
+
) -> Tuple[str, List[a2a.ContentPart], Dict[str, Any]]:
|
|
557
|
+
# This is now handled by `handle_external_input` calling `adapter.prepare_task`.
|
|
558
|
+
# This method should not be called in the generic gateway flow.
|
|
559
|
+
log.warning(
|
|
560
|
+
"%s _translate_external_input called on GenericGatewayComponent. This should not happen.",
|
|
561
|
+
self.log_identifier,
|
|
562
|
+
)
|
|
563
|
+
raise NotImplementedError(
|
|
564
|
+
"_translate_external_input is not used in GenericGatewayComponent"
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
# --- Private Helper Methods ---
|
|
568
|
+
|
|
569
|
+
def _create_response_context(
|
|
570
|
+
self, external_request_context: Dict[str, Any]
|
|
571
|
+
) -> ResponseContext:
|
|
572
|
+
"""Builds a ResponseContext from the stored external request context."""
|
|
573
|
+
user_identity = external_request_context.get("user_identity", {})
|
|
574
|
+
return ResponseContext(
|
|
575
|
+
task_id=external_request_context.get("a2a_task_id_for_event"),
|
|
576
|
+
session_id=external_request_context.get("a2a_session_id"),
|
|
577
|
+
user_id=user_identity.get("id"),
|
|
578
|
+
platform_context=external_request_context,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
def _sam_parts_to_a2a_parts(
|
|
582
|
+
self, sam_parts: List[Union[SamTextPart, SamFilePart, SamDataPart]]
|
|
583
|
+
) -> List[a2a.ContentPart]:
|
|
584
|
+
"""Converts a list of SAM parts to A2A parts."""
|
|
585
|
+
a2a_parts = []
|
|
586
|
+
for part in sam_parts:
|
|
587
|
+
if isinstance(part, SamTextPart):
|
|
588
|
+
a2a_parts.append(a2a.create_text_part(part.text))
|
|
589
|
+
elif isinstance(part, SamFilePart):
|
|
590
|
+
if part.content_bytes:
|
|
591
|
+
a2a_parts.append(
|
|
592
|
+
a2a.create_file_part_from_bytes(
|
|
593
|
+
content_bytes=part.content_bytes,
|
|
594
|
+
name=part.name,
|
|
595
|
+
mime_type=part.mime_type,
|
|
596
|
+
)
|
|
597
|
+
)
|
|
598
|
+
elif part.uri:
|
|
599
|
+
a2a_parts.append(
|
|
600
|
+
a2a.create_file_part_from_uri(
|
|
601
|
+
uri=part.uri,
|
|
602
|
+
name=part.name,
|
|
603
|
+
mime_type=part.mime_type,
|
|
604
|
+
)
|
|
605
|
+
)
|
|
606
|
+
elif isinstance(part, SamDataPart):
|
|
607
|
+
a2a_parts.append(a2a.create_data_part(part.data))
|
|
608
|
+
return a2a_parts
|
|
609
|
+
|
|
610
|
+
def _a2a_parts_to_sam_parts(
|
|
611
|
+
self, a2a_parts: List[a2a.ContentPart]
|
|
612
|
+
) -> List[Union[SamTextPart, SamFilePart, SamDataPart]]:
|
|
613
|
+
"""Converts a list of A2A parts to SAM parts."""
|
|
614
|
+
sam_parts = []
|
|
615
|
+
for part in a2a_parts:
|
|
616
|
+
if isinstance(part, TextPart):
|
|
617
|
+
sam_parts.append(SamTextPart(text=part.text))
|
|
618
|
+
elif isinstance(part, FilePart):
|
|
619
|
+
sam_parts.append(
|
|
620
|
+
SamFilePart(
|
|
621
|
+
name=a2a.get_filename_from_file_part(part),
|
|
622
|
+
content_bytes=a2a.get_bytes_from_file_part(part),
|
|
623
|
+
uri=a2a.get_uri_from_file_part(part),
|
|
624
|
+
mime_type=a2a.get_mimetype_from_file_part(part),
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
elif isinstance(part, A2ADataPart):
|
|
628
|
+
sam_parts.append(
|
|
629
|
+
SamDataPart(
|
|
630
|
+
data=a2a.get_data_from_data_part(part),
|
|
631
|
+
metadata=a2a.get_metadata_from_part(part),
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
return sam_parts
|
|
635
|
+
|
|
636
|
+
def _a2a_error_to_sam_error(self, error: JSONRPCError) -> SamError:
|
|
637
|
+
"""Converts an A2A JSONRPCError to a SamError."""
|
|
638
|
+
category = "PROTOCOL_ERROR"
|
|
639
|
+
if isinstance(error.data, dict):
|
|
640
|
+
task_status = error.data.get("taskStatus")
|
|
641
|
+
if task_status == TaskState.failed:
|
|
642
|
+
category = "FAILED"
|
|
643
|
+
elif task_status == TaskState.canceled:
|
|
644
|
+
category = "CANCELED"
|
|
645
|
+
|
|
646
|
+
return SamError(
|
|
647
|
+
message=error.message,
|
|
648
|
+
code=error.code,
|
|
649
|
+
category=category,
|
|
650
|
+
)
|