solace-agent-mesh 1.0.7__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/adk_llm.txt +182 -42
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
- solace_agent_mesh/agent/adk/callbacks.py +165 -104
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +0 -18
- solace_agent_mesh/agent/adk/models/models_llm.txt +104 -55
- solace_agent_mesh/agent/adk/runner.py +7 -5
- solace_agent_mesh/agent/adk/services.py +9 -1
- solace_agent_mesh/agent/adk/setup.py +11 -0
- solace_agent_mesh/agent/adk/stream_parser.py +8 -1
- solace_agent_mesh/agent/adk/tool_wrapper.py +10 -3
- solace_agent_mesh/agent/agent_llm.txt +355 -18
- solace_agent_mesh/agent/protocol/event_handlers.py +433 -296
- solace_agent_mesh/agent/protocol/protocol_llm.txt +54 -7
- solace_agent_mesh/agent/sac/app.py +1 -1
- solace_agent_mesh/agent/sac/component.py +212 -517
- solace_agent_mesh/agent/sac/sac_llm.txt +133 -63
- solace_agent_mesh/agent/testing/testing_llm.txt +25 -58
- solace_agent_mesh/agent/tools/peer_agent_tool.py +15 -11
- solace_agent_mesh/agent/tools/tools_llm.txt +234 -69
- solace_agent_mesh/agent/utils/artifact_helpers.py +35 -1
- solace_agent_mesh/agent/utils/utils_llm.txt +90 -105
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{3d406171.7d02a73b.js → 3d406171.0b9eeed1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{75384d09.ccd480c4.js → 75384d09.bf78fbdb.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +105 -0
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +53 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +8 -8
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1756992446316.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +12 -3
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +10 -14
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +2 -15
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +6 -2
- solace_agent_mesh/cli/utils.py +15 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DvlO62me.js → authCallback-BmF2l6vg.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-bp6u3qVZ.js → client-D881Dttc.js} +4 -4
- solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +699 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +2 -2
- solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
- solace_agent_mesh/common/a2a/__init__.py +213 -0
- solace_agent_mesh/common/a2a/a2a_llm.txt +182 -0
- solace_agent_mesh/common/a2a/artifact.py +328 -0
- solace_agent_mesh/common/a2a/events.py +183 -0
- solace_agent_mesh/common/a2a/message.py +307 -0
- solace_agent_mesh/common/a2a/protocol.py +513 -0
- solace_agent_mesh/common/a2a/task.py +127 -0
- solace_agent_mesh/common/a2a/translation.py +653 -0
- solace_agent_mesh/common/a2a/types.py +54 -0
- solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +407 -0
- solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +31 -0
- solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +235 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +25 -0
- solace_agent_mesh/common/agent_registry.py +1 -1
- solace_agent_mesh/common/common_llm.txt +192 -70
- solace_agent_mesh/common/data_parts.py +99 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +17 -17
- solace_agent_mesh/common/sac/__init__.py +0 -0
- solace_agent_mesh/common/sac/sac_llm.txt +71 -0
- solace_agent_mesh/common/sac/sam_component_base.py +252 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +51 -84
- solace_agent_mesh/common/services/services_llm.txt +206 -26
- solace_agent_mesh/common/utils/artifact_utils.py +29 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +176 -80
- solace_agent_mesh/common/utils/utils_llm.txt +323 -42
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-MqsrTd6g.js → _index-Bym6YkMd.js} +74 -24
- solace_agent_mesh/config_portal/frontend/static/client/assets/{components-B7lKcHVY.js → components-Rk0n-9cK.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-CEumGClk.js → entry.client-mvZjNKiz.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DSo1AH_7.js → index-DzNKzXrc.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d845808d.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{root-C4XmHinv.js → root-BWvk5-gF.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +10 -8
- solace_agent_mesh/core_a2a/service.py +20 -44
- solace_agent_mesh/gateway/base/app.py +27 -1
- solace_agent_mesh/gateway/base/base_llm.txt +177 -72
- solace_agent_mesh/gateway/base/component.py +294 -523
- solace_agent_mesh/gateway/gateway_llm.txt +299 -58
- solace_agent_mesh/gateway/http_sse/component.py +156 -183
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +29 -29
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +272 -36
- solace_agent_mesh/gateway/http_sse/main.py +8 -10
- solace_agent_mesh/gateway/http_sse/routers/agents.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +18 -4
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +231 -5
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +12 -7
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +116 -169
- solace_agent_mesh/gateway/http_sse/services/agent_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +89 -135
- solace_agent_mesh/gateway/http_sse/services/task_service.py +2 -5
- solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
- solace_agent_mesh/templates/gateway_component_template.py +149 -98
- {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/METADATA +5 -4
- {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/RECORD +144 -127
- solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.d79f063b.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.6415ad00.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1756146501924.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1756146501924.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BucUdn9m.js +0 -673
- solace_agent_mesh/common/a2a_protocol.py +0 -564
- solace_agent_mesh/common/client/__init__.py +0 -4
- solace_agent_mesh/common/client/card_resolver.py +0 -21
- solace_agent_mesh/common/client/client.py +0 -85
- solace_agent_mesh/common/client/client_llm.txt +0 -133
- solace_agent_mesh/common/server/__init__.py +0 -4
- solace_agent_mesh/common/server/server.py +0 -122
- solace_agent_mesh/common/server/server_llm.txt +0 -169
- solace_agent_mesh/common/server/task_manager.py +0 -291
- solace_agent_mesh/common/server/utils.py +0 -28
- solace_agent_mesh/common/types.py +0 -411
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-28271392.js +0 -1
- /solace_agent_mesh/assets/docs/assets/js/{main.d79f063b.js.LICENSE.txt → main.a75ecc0d.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.7.dist-info → solace_agent_mesh-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,30 +8,28 @@ from fastapi import (
|
|
|
8
8
|
HTTPException,
|
|
9
9
|
Request as FastAPIRequest,
|
|
10
10
|
status,
|
|
11
|
-
Form,
|
|
12
|
-
File,
|
|
13
|
-
UploadFile,
|
|
14
11
|
)
|
|
15
|
-
from
|
|
16
|
-
from typing import List
|
|
12
|
+
from typing import Union
|
|
17
13
|
|
|
18
14
|
from solace_ai_connector.common.log import log
|
|
19
15
|
|
|
20
16
|
from ....gateway.http_sse.session_manager import SessionManager
|
|
21
17
|
from ....gateway.http_sse.services.task_service import TaskService
|
|
22
18
|
|
|
23
|
-
from
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
from a2a.types import (
|
|
20
|
+
CancelTaskRequest,
|
|
21
|
+
SendMessageRequest,
|
|
22
|
+
SendStreamingMessageRequest,
|
|
23
|
+
SendMessageSuccessResponse,
|
|
24
|
+
SendStreamingMessageSuccessResponse,
|
|
27
25
|
)
|
|
26
|
+
from ....common import a2a
|
|
28
27
|
|
|
29
28
|
from ....gateway.http_sse.dependencies import (
|
|
30
29
|
get_session_manager,
|
|
31
30
|
get_sac_component,
|
|
32
31
|
get_task_service,
|
|
33
32
|
)
|
|
34
|
-
from ....gateway.http_sse.routers.users import get_current_user
|
|
35
33
|
|
|
36
34
|
from typing import TYPE_CHECKING
|
|
37
35
|
|
|
@@ -41,29 +39,26 @@ if TYPE_CHECKING:
|
|
|
41
39
|
router = APIRouter()
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
async def _submit_task(
|
|
43
|
+
request: FastAPIRequest,
|
|
44
|
+
payload: Union[SendMessageRequest, SendStreamingMessageRequest],
|
|
45
|
+
session_manager: SessionManager,
|
|
46
|
+
component: "WebUIBackendComponent",
|
|
47
|
+
is_streaming: bool,
|
|
48
|
+
):
|
|
49
|
+
"""Helper to submit a task, handling both streaming and non-streaming cases."""
|
|
50
|
+
log_prefix = f"[POST /api/v1/message:{'stream' if is_streaming else 'send'}] "
|
|
46
51
|
|
|
47
|
-
agent_name
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
task_id: str = Field(..., description="The ID of the task to cancel.")
|
|
52
|
+
agent_name = None
|
|
53
|
+
if payload.params and payload.params.message and payload.params.message.metadata:
|
|
54
|
+
agent_name = payload.params.message.metadata.get("agent_name")
|
|
51
55
|
|
|
56
|
+
if not agent_name:
|
|
57
|
+
raise HTTPException(
|
|
58
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
59
|
+
detail="Missing 'agent_name' in request payload message metadata.",
|
|
60
|
+
)
|
|
52
61
|
|
|
53
|
-
@router.post("/send", response_model=JSONRPCResponse)
|
|
54
|
-
async def send_task_to_agent(
|
|
55
|
-
request: FastAPIRequest,
|
|
56
|
-
agent_name: str = Form(...),
|
|
57
|
-
message: str = Form(...),
|
|
58
|
-
files: List[UploadFile] = File([]),
|
|
59
|
-
session_manager: SessionManager = Depends(get_session_manager),
|
|
60
|
-
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
61
|
-
):
|
|
62
|
-
"""
|
|
63
|
-
Submits a non-streaming task request to the specified agent.
|
|
64
|
-
Accepts multipart/form-data.
|
|
65
|
-
"""
|
|
66
|
-
log_prefix = "[POST /api/v1/tasks/send] "
|
|
67
62
|
log.info("%sReceived request for agent: %s", log_prefix, agent_name)
|
|
68
63
|
|
|
69
64
|
try:
|
|
@@ -87,207 +82,159 @@ async def send_task_to_agent(
|
|
|
87
82
|
"%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
|
|
88
83
|
)
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"
|
|
85
|
+
# Use the helper to get the unwrapped parts from the incoming message.
|
|
86
|
+
a2a_parts = a2a.get_parts_from_message(payload.params.message)
|
|
87
|
+
|
|
88
|
+
external_req_ctx = {
|
|
89
|
+
"app_name_for_artifacts": component.gateway_id,
|
|
90
|
+
"user_id_for_artifacts": client_id,
|
|
95
91
|
"a2a_session_id": session_id,
|
|
92
|
+
"user_id_for_a2a": client_id,
|
|
93
|
+
"target_agent_name": agent_name,
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
target_agent, a2a_parts, external_req_ctx = (
|
|
99
|
-
await component._translate_external_input(external_event_data)
|
|
100
|
-
)
|
|
101
|
-
|
|
102
96
|
task_id = await component.submit_a2a_task(
|
|
103
|
-
target_agent_name=
|
|
97
|
+
target_agent_name=agent_name,
|
|
104
98
|
a2a_parts=a2a_parts,
|
|
105
99
|
external_request_context=external_req_ctx,
|
|
106
100
|
user_identity=user_identity,
|
|
107
|
-
is_streaming=
|
|
101
|
+
is_streaming=is_streaming,
|
|
108
102
|
)
|
|
109
103
|
|
|
110
104
|
log.info("%sTask submitted successfully. TaskID: %s", log_prefix, task_id)
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
raise HTTPException(
|
|
117
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
118
|
-
detail=e.model_dump(exclude_none=True),
|
|
106
|
+
task_object = a2a.create_initial_task(
|
|
107
|
+
task_id=task_id,
|
|
108
|
+
context_id=session_id,
|
|
109
|
+
agent_name=agent_name,
|
|
119
110
|
)
|
|
111
|
+
|
|
112
|
+
if is_streaming:
|
|
113
|
+
return a2a.create_send_streaming_message_success_response(
|
|
114
|
+
result=task_object, request_id=payload.id
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
return a2a.create_send_message_success_response(
|
|
118
|
+
result=task_object, request_id=payload.id
|
|
119
|
+
)
|
|
120
|
+
|
|
120
121
|
except PermissionError as pe:
|
|
121
122
|
log.warning("%sPermission denied: %s", log_prefix, str(pe))
|
|
122
123
|
raise HTTPException(
|
|
123
124
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
124
125
|
detail=str(pe),
|
|
125
126
|
)
|
|
126
|
-
except InternalError as e:
|
|
127
|
-
log.error(
|
|
128
|
-
"%sInternal error submitting task: %s", log_prefix, e.message, exc_info=True
|
|
129
|
-
)
|
|
130
|
-
raise HTTPException(
|
|
131
|
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
132
|
-
detail=e.model_dump(exclude_none=True),
|
|
133
|
-
)
|
|
134
127
|
except Exception as e:
|
|
135
128
|
log.exception("%sUnexpected error submitting task: %s", log_prefix, e)
|
|
136
|
-
error_resp =
|
|
129
|
+
error_resp = a2a.create_internal_error(
|
|
130
|
+
message="Unexpected server error: %s" % e
|
|
131
|
+
)
|
|
137
132
|
raise HTTPException(
|
|
138
133
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
139
134
|
detail=error_resp.model_dump(exclude_none=True),
|
|
140
135
|
)
|
|
141
136
|
|
|
142
137
|
|
|
143
|
-
@router.post("/
|
|
144
|
-
async def
|
|
138
|
+
@router.post("/message:send", response_model=SendMessageSuccessResponse)
|
|
139
|
+
async def send_task_to_agent(
|
|
145
140
|
request: FastAPIRequest,
|
|
146
|
-
|
|
147
|
-
message: str = Form(...),
|
|
148
|
-
files: List[UploadFile] = File([]),
|
|
141
|
+
payload: SendMessageRequest,
|
|
149
142
|
session_manager: SessionManager = Depends(get_session_manager),
|
|
150
143
|
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
151
|
-
user: dict = Depends(get_current_user),
|
|
152
144
|
):
|
|
153
145
|
"""
|
|
154
|
-
Submits a streaming task request
|
|
155
|
-
Accepts
|
|
156
|
-
The client should subsequently connect to the SSE endpoint using the returned taskId.
|
|
146
|
+
Submits a non-streaming task request to the specified agent.
|
|
147
|
+
Accepts application/json.
|
|
157
148
|
"""
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
raise HTTPException(
|
|
166
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
167
|
-
detail="User authentication failed or identity not found.",
|
|
168
|
-
)
|
|
169
|
-
log.info(
|
|
170
|
-
"%sAuthenticated user identity: %s",
|
|
171
|
-
log_prefix,
|
|
172
|
-
user_identity.get("id", "unknown"),
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
client_id = session_manager.get_a2a_client_id(request)
|
|
176
|
-
session_id = session_manager.ensure_a2a_session(request)
|
|
177
|
-
|
|
178
|
-
log.info(
|
|
179
|
-
"%sUsing ClientID: %s, SessionID: %s", log_prefix, client_id, session_id
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
external_event_data = {
|
|
183
|
-
"agent_name": agent_name,
|
|
184
|
-
"message": message,
|
|
185
|
-
"files": files,
|
|
186
|
-
"client_id": client_id,
|
|
187
|
-
"a2a_session_id": session_id,
|
|
188
|
-
}
|
|
149
|
+
return await _submit_task(
|
|
150
|
+
request=request,
|
|
151
|
+
payload=payload,
|
|
152
|
+
session_manager=session_manager,
|
|
153
|
+
component=component,
|
|
154
|
+
is_streaming=False,
|
|
155
|
+
)
|
|
189
156
|
|
|
190
|
-
target_agent, a2a_parts, external_req_ctx = (
|
|
191
|
-
await component._translate_external_input(external_event_data)
|
|
192
|
-
)
|
|
193
157
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
158
|
+
@router.post("/message:stream", response_model=SendStreamingMessageSuccessResponse)
|
|
159
|
+
async def subscribe_task_from_agent(
|
|
160
|
+
request: FastAPIRequest,
|
|
161
|
+
payload: SendStreamingMessageRequest,
|
|
162
|
+
session_manager: SessionManager = Depends(get_session_manager),
|
|
163
|
+
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Submits a streaming task request to the specified agent.
|
|
167
|
+
Accepts application/json.
|
|
168
|
+
The client should subsequently connect to the SSE endpoint using the returned taskId.
|
|
169
|
+
"""
|
|
170
|
+
return await _submit_task(
|
|
171
|
+
request=request,
|
|
172
|
+
payload=payload,
|
|
173
|
+
session_manager=session_manager,
|
|
174
|
+
component=component,
|
|
175
|
+
is_streaming=True,
|
|
176
|
+
)
|
|
201
177
|
|
|
202
|
-
log.info(
|
|
203
|
-
"%sStreaming task submitted successfully. TaskID: %s", log_prefix, task_id
|
|
204
|
-
)
|
|
205
178
|
|
|
206
|
-
|
|
179
|
+
@router.post("/tasks/{taskId}:cancel", status_code=status.HTTP_202_ACCEPTED)
|
|
180
|
+
async def cancel_agent_task(
|
|
181
|
+
request: FastAPIRequest,
|
|
182
|
+
taskId: str,
|
|
183
|
+
payload: CancelTaskRequest,
|
|
184
|
+
session_manager: SessionManager = Depends(get_session_manager),
|
|
185
|
+
task_service: TaskService = Depends(get_task_service),
|
|
186
|
+
component: "WebUIBackendComponent" = Depends(get_sac_component),
|
|
187
|
+
):
|
|
188
|
+
"""
|
|
189
|
+
Sends a cancellation request for a specific task to the specified agent.
|
|
190
|
+
Returns 202 Accepted, as cancellation is asynchronous.
|
|
191
|
+
"""
|
|
192
|
+
log_prefix = f"[POST /api/v1/tasks/{taskId}:cancel] "
|
|
193
|
+
log.info("%sReceived cancellation request.", log_prefix)
|
|
207
194
|
|
|
208
|
-
|
|
209
|
-
log.warning("%sInvalid request: %s", log_prefix, e.message, exc_info=True)
|
|
195
|
+
if taskId != payload.params.id:
|
|
210
196
|
raise HTTPException(
|
|
211
197
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
212
|
-
detail=
|
|
198
|
+
detail="Task ID in URL path does not match task ID in payload.",
|
|
213
199
|
)
|
|
214
|
-
|
|
215
|
-
|
|
200
|
+
|
|
201
|
+
context = component.task_context_manager.get_context(taskId)
|
|
202
|
+
if not context:
|
|
216
203
|
raise HTTPException(
|
|
217
|
-
status_code=status.
|
|
218
|
-
detail=
|
|
204
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
205
|
+
detail=f"No active task context found for task ID: {taskId}",
|
|
219
206
|
)
|
|
220
|
-
|
|
207
|
+
|
|
208
|
+
agent_name = context.get("target_agent_name")
|
|
209
|
+
if not agent_name:
|
|
221
210
|
log.error(
|
|
222
|
-
"%
|
|
211
|
+
"%sCould not determine target agent for task %s. Context is missing 'target_agent_name'.",
|
|
223
212
|
log_prefix,
|
|
224
|
-
|
|
225
|
-
exc_info=True,
|
|
226
|
-
)
|
|
227
|
-
raise HTTPException(
|
|
228
|
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
229
|
-
detail=e.model_dump(exclude_none=True),
|
|
213
|
+
taskId,
|
|
230
214
|
)
|
|
231
|
-
except Exception as e:
|
|
232
|
-
log.exception("%sUnexpected error submitting streaming task: %s", log_prefix, e)
|
|
233
|
-
error_resp = InternalError(message="Unexpected server error: %s" % e)
|
|
234
215
|
raise HTTPException(
|
|
235
216
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
236
|
-
detail=
|
|
217
|
+
detail="Could not determine target agent for the task.",
|
|
237
218
|
)
|
|
238
219
|
|
|
239
|
-
|
|
240
|
-
@router.post("/cancel", status_code=status.HTTP_202_ACCEPTED)
|
|
241
|
-
async def cancel_agent_task(
|
|
242
|
-
request: FastAPIRequest,
|
|
243
|
-
payload: CancelTaskApiPayload,
|
|
244
|
-
session_manager: SessionManager = Depends(get_session_manager),
|
|
245
|
-
task_service: TaskService = Depends(get_task_service),
|
|
246
|
-
):
|
|
247
|
-
"""
|
|
248
|
-
Sends a cancellation request for a specific task to the specified agent.
|
|
249
|
-
Returns 202 Accepted, as cancellation is asynchronous.
|
|
250
|
-
"""
|
|
251
|
-
log_prefix = "[POST /api/v1/tasks/cancel][Task:%s] " % payload.task_id
|
|
252
|
-
log.info(
|
|
253
|
-
"%sReceived cancellation request for agent: %s", log_prefix, payload.agent_name
|
|
254
|
-
)
|
|
220
|
+
log.info("%sTarget agent for cancellation is '%s'", log_prefix, agent_name)
|
|
255
221
|
|
|
256
222
|
try:
|
|
257
223
|
client_id = session_manager.get_a2a_client_id(request)
|
|
258
224
|
|
|
259
225
|
log.info("%sUsing ClientID: %s", log_prefix, client_id)
|
|
260
226
|
|
|
261
|
-
await task_service.cancel_task(
|
|
262
|
-
payload.agent_name, payload.task_id, client_id, client_id
|
|
263
|
-
)
|
|
227
|
+
await task_service.cancel_task(agent_name, taskId, client_id, client_id)
|
|
264
228
|
|
|
265
229
|
log.info("%sCancellation request published successfully.", log_prefix)
|
|
266
230
|
|
|
267
231
|
return {"message": "Cancellation request sent"}
|
|
268
232
|
|
|
269
|
-
except InvalidRequestError as e:
|
|
270
|
-
log.warning(
|
|
271
|
-
"%sInvalid cancellation request: %s", log_prefix, e.message, exc_info=True
|
|
272
|
-
)
|
|
273
|
-
raise HTTPException(
|
|
274
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
275
|
-
detail=e.model_dump(exclude_none=True),
|
|
276
|
-
)
|
|
277
|
-
except InternalError as e:
|
|
278
|
-
log.error(
|
|
279
|
-
"%sInternal error sending cancellation: %s",
|
|
280
|
-
log_prefix,
|
|
281
|
-
e.message,
|
|
282
|
-
exc_info=True,
|
|
283
|
-
)
|
|
284
|
-
raise HTTPException(
|
|
285
|
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
286
|
-
detail=e.model_dump(exclude_none=True),
|
|
287
|
-
)
|
|
288
233
|
except Exception as e:
|
|
289
234
|
log.exception("%sUnexpected error sending cancellation: %s", log_prefix, e)
|
|
290
|
-
error_resp =
|
|
235
|
+
error_resp = a2a.create_internal_error(
|
|
236
|
+
message="Unexpected server error: %s" % e
|
|
237
|
+
)
|
|
291
238
|
raise HTTPException(
|
|
292
239
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
293
240
|
detail=error_resp.model_dump(exclude_none=True),
|
|
@@ -1,179 +1,133 @@
|
|
|
1
|
-
|
|
1
|
+
# DEVELOPER GUIDE: services
|
|
2
2
|
|
|
3
3
|
## Quick Summary
|
|
4
|
-
The `services` directory contains the business logic layer for the HTTP SSE Gateway. It
|
|
4
|
+
The `services` directory contains the business logic layer for the HTTP SSE Gateway. It provides high-level services for agent management (discovering and retrieving A2A agents), user identity operations (searching for users), and task management (cancelling A2A tasks). These services abstract the complexities of interacting with agent registries, identity providers, and A2A messaging protocols.
|
|
5
5
|
|
|
6
6
|
## Files Overview
|
|
7
|
-
- `__init__.py` -
|
|
8
|
-
- `agent_service.py` - Service for retrieving information about discovered A2A agents
|
|
9
|
-
- `people_service.py` - Service for searching
|
|
10
|
-
- `task_service.py` - Service for handling
|
|
7
|
+
- `__init__.py` - Package initialization file marking the directory as a Python package
|
|
8
|
+
- `agent_service.py` - Service for retrieving information about discovered A2A agents from the registry
|
|
9
|
+
- `people_service.py` - Service for searching users via configured identity services
|
|
10
|
+
- `task_service.py` - Service for handling A2A task cancellation operations
|
|
11
11
|
|
|
12
12
|
## Developer API Reference
|
|
13
13
|
|
|
14
14
|
### __init__.py
|
|
15
|
-
**Purpose:**
|
|
16
|
-
**Import:** N/A - No public interfaces
|
|
17
|
-
|
|
18
|
-
---
|
|
15
|
+
**Purpose:** Marks the services directory as a Python package
|
|
16
|
+
**Import:** N/A - No public interfaces
|
|
19
17
|
|
|
20
18
|
### agent_service.py
|
|
21
|
-
**Purpose:** Provides
|
|
22
|
-
**Import:** `from
|
|
19
|
+
**Purpose:** Provides methods for accessing information about discovered A2A agents from the shared AgentRegistry
|
|
20
|
+
**Import:** `from solace_agent_mesh.gateway.http_sse.services.agent_service import AgentService`
|
|
23
21
|
|
|
24
22
|
**Classes:**
|
|
25
|
-
- `AgentService(agent_registry: AgentRegistry)` -
|
|
26
|
-
- `get_all_agents() -> List[AgentCard]` - Retrieves all currently discovered and registered agent cards
|
|
27
|
-
- `get_agent_by_name(agent_name: str) -> Optional[AgentCard]` - Retrieves a specific agent card by
|
|
23
|
+
- `AgentService(agent_registry: AgentRegistry)` - Service for accessing discovered A2A agent information
|
|
24
|
+
- `get_all_agents() -> List[AgentCard]` - Retrieves all currently discovered and registered agent cards
|
|
25
|
+
- `get_agent_by_name(agent_name: str) -> Optional[AgentCard]` - Retrieves a specific agent card by name, returns None if not found
|
|
28
26
|
|
|
29
27
|
**Usage Examples:**
|
|
30
28
|
```python
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# In a real application, AgentRegistry would be a shared instance.
|
|
37
|
-
# For this example, we'll create a new one and populate it.
|
|
38
|
-
agent_registry = AgentRegistry()
|
|
39
|
-
my_agent_card = AgentCard(name="data-analyzer", description="Analyzes data files.")
|
|
40
|
-
agent_registry.register_agent(my_agent_card)
|
|
41
|
-
|
|
42
|
-
# 1. Initialize the service with the agent registry
|
|
29
|
+
from solace_agent_mesh.gateway.http_sse.services.agent_service import AgentService
|
|
30
|
+
from solace_agent_mesh.common.agent_registry import AgentRegistry
|
|
31
|
+
|
|
32
|
+
# Initialize with shared agent registry
|
|
33
|
+
agent_registry = AgentRegistry() # Usually injected as shared instance
|
|
43
34
|
agent_service = AgentService(agent_registry=agent_registry)
|
|
44
35
|
|
|
45
|
-
#
|
|
36
|
+
# Get all available agents
|
|
46
37
|
all_agents = agent_service.get_all_agents()
|
|
47
|
-
print(f"Found {len(all_agents)}
|
|
48
|
-
for agent in all_agents:
|
|
49
|
-
print(f"- Agent: {agent.name}, Description: {agent.description}")
|
|
50
|
-
|
|
51
|
-
# 3. Get a specific agent by name
|
|
52
|
-
found_agent = agent_service.get_agent_by_name("data-analyzer")
|
|
53
|
-
if found_agent:
|
|
54
|
-
print(f"\nSuccessfully retrieved agent: {found_agent.name}")
|
|
55
|
-
else:
|
|
56
|
-
print("\nCould not find agent 'data-analyzer'.")
|
|
38
|
+
print(f"Found {len(all_agents)} agents")
|
|
57
39
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
# Get specific agent by name
|
|
41
|
+
agent = agent_service.get_agent_by_name("data-processor")
|
|
42
|
+
if agent:
|
|
43
|
+
print(f"Found agent: {agent.name}")
|
|
44
|
+
else:
|
|
45
|
+
print("Agent not found")
|
|
61
46
|
```
|
|
62
47
|
|
|
63
|
-
---
|
|
64
|
-
|
|
65
48
|
### people_service.py
|
|
66
|
-
**Purpose:**
|
|
67
|
-
**Import:** `from
|
|
49
|
+
**Purpose:** Provides user search functionality via configured identity services
|
|
50
|
+
**Import:** `from solace_agent_mesh.gateway.http_sse.services.people_service import PeopleService`
|
|
68
51
|
|
|
69
52
|
**Classes:**
|
|
70
|
-
- `PeopleService(identity_service: Optional[BaseIdentityService])` -
|
|
71
|
-
- `search_for_users(query: str, limit: int = 10) -> List[Dict[str, Any]]` - Asynchronously searches for users
|
|
53
|
+
- `PeopleService(identity_service: Optional[BaseIdentityService])` - Service for searching and retrieving user information
|
|
54
|
+
- `search_for_users(query: str, limit: int = 10) -> List[Dict[str, Any]]` - Asynchronously searches for users, returns empty list if no identity service configured or query too short
|
|
72
55
|
|
|
73
56
|
**Usage Examples:**
|
|
74
57
|
```python
|
|
75
58
|
import asyncio
|
|
76
|
-
from
|
|
77
|
-
from
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{"id": "jdoe", "name": "John Doe", "email": "j.doe@example.com"},
|
|
86
|
-
{"id": "jsmith", "name": "Jane Smith", "email": "j.smith@example.com"},
|
|
87
|
-
]
|
|
88
|
-
return [user for user in all_users if query.lower() in user["name"].lower()][:limit]
|
|
89
|
-
|
|
90
|
-
async def main():
|
|
91
|
-
# 1. Initialize with a configured identity service
|
|
92
|
-
identity_service = MockIdentityService()
|
|
93
|
-
people_service = PeopleService(identity_service=identity_service)
|
|
94
|
-
|
|
95
|
-
# 2. Search for users
|
|
59
|
+
from solace_agent_mesh.gateway.http_sse.services.people_service import PeopleService
|
|
60
|
+
from solace_agent_mesh.common.services.identity_service import BaseIdentityService
|
|
61
|
+
|
|
62
|
+
# Initialize with identity service
|
|
63
|
+
identity_service = SomeIdentityService() # Your identity service implementation
|
|
64
|
+
people_service = PeopleService(identity_service=identity_service)
|
|
65
|
+
|
|
66
|
+
async def search_users():
|
|
67
|
+
# Search for users
|
|
96
68
|
users = await people_service.search_for_users("john", limit=5)
|
|
97
|
-
|
|
69
|
+
for user in users:
|
|
70
|
+
print(f"User: {user.get('name')} - {user.get('email')}")
|
|
98
71
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# 4. Search will return an empty list
|
|
103
|
-
empty_results = await people_service_no_id.search_for_users("jane")
|
|
104
|
-
print(f"Results with no identity service: {empty_results}")
|
|
72
|
+
# Initialize without identity service (graceful degradation)
|
|
73
|
+
people_service_no_id = PeopleService(identity_service=None)
|
|
74
|
+
# search_for_users will return empty list
|
|
105
75
|
|
|
106
|
-
|
|
107
|
-
asyncio.run(main())
|
|
76
|
+
asyncio.run(search_users())
|
|
108
77
|
```
|
|
109
78
|
|
|
110
|
-
---
|
|
111
|
-
|
|
112
79
|
### task_service.py
|
|
113
|
-
**Purpose:** Handles
|
|
114
|
-
**Import:** `from
|
|
115
|
-
|
|
116
|
-
**Classes:**
|
|
117
|
-
- `TaskService(core_a2a_service: CoreA2AService, publish_func: PublishFunc, namespace: str, gateway_id: str, sse_manager: SSEManager, task_context_map: Dict[str, Dict], task_context_lock: threading.Lock, app_name: str)` - A service for managing A2A task operations.
|
|
118
|
-
- `cancel_task(agent_name: str, task_id: str, client_id: str, user_id: str = "web_user") -> None` - Asynchronously constructs and publishes an A2A `CancelTaskRequest` message for a specific task.
|
|
80
|
+
**Purpose:** Handles A2A task operations, specifically task cancellation using CoreA2AService and message publishing
|
|
81
|
+
**Import:** `from solace_agent_mesh.gateway.http_sse.services.task_service import TaskService, PublishFunc`
|
|
119
82
|
|
|
120
83
|
**Type Aliases:**
|
|
121
|
-
- `PublishFunc: Callable[[str, Dict, Optional[Dict]], None]` -
|
|
84
|
+
- `PublishFunc: Callable[[str, Dict, Optional[Dict]], None]` - Function type for publishing messages (topic, payload, user_properties)
|
|
85
|
+
|
|
86
|
+
**Classes:**
|
|
87
|
+
- `TaskService(core_a2a_service: CoreA2AService, publish_func: PublishFunc, namespace: str, gateway_id: str, sse_manager: SSEManager, task_context_map: Dict[str, Dict], task_context_lock: threading.Lock, app_name: str)` - Service for managing A2A task operations
|
|
88
|
+
- `cancel_task(agent_name: str, task_id: str, client_id: str, user_id: str = "web_user") -> None` - Asynchronously cancels a task by publishing A2A CancelTaskRequest message
|
|
122
89
|
|
|
123
90
|
**Usage Examples:**
|
|
124
91
|
```python
|
|
125
92
|
import asyncio
|
|
126
93
|
import threading
|
|
127
|
-
from
|
|
128
|
-
|
|
129
|
-
from
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
#
|
|
156
|
-
task_service = TaskService(
|
|
157
|
-
core_a2a_service=core_a2a_service,
|
|
158
|
-
publish_func=my_publish_func,
|
|
159
|
-
namespace="my-namespace",
|
|
160
|
-
gateway_id="gateway-01",
|
|
161
|
-
sse_manager=sse_manager,
|
|
162
|
-
task_context_map=task_context_map,
|
|
163
|
-
task_context_lock=task_context_lock,
|
|
164
|
-
app_name="my-app"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
# 3. Call the cancel_task method
|
|
168
|
-
print("Requesting task cancellation...")
|
|
94
|
+
from solace_agent_mesh.gateway.http_sse.services.task_service import TaskService, PublishFunc
|
|
95
|
+
from solace_agent_mesh.core_a2a.service import CoreA2AService
|
|
96
|
+
from solace_agent_mesh.gateway.http_sse.sse_manager import SSEManager
|
|
97
|
+
|
|
98
|
+
# Define publish function
|
|
99
|
+
def my_publish_func(topic: str, payload: dict, user_properties: dict = None):
|
|
100
|
+
print(f"Publishing to {topic}: {payload}")
|
|
101
|
+
# Your actual message publishing logic here
|
|
102
|
+
|
|
103
|
+
# Initialize dependencies
|
|
104
|
+
core_a2a_service = CoreA2AService() # Your core A2A service
|
|
105
|
+
sse_manager = SSEManager()
|
|
106
|
+
task_context_map = {}
|
|
107
|
+
task_context_lock = threading.Lock()
|
|
108
|
+
|
|
109
|
+
# Create task service
|
|
110
|
+
task_service = TaskService(
|
|
111
|
+
core_a2a_service=core_a2a_service,
|
|
112
|
+
publish_func=my_publish_func,
|
|
113
|
+
namespace="my-namespace",
|
|
114
|
+
gateway_id="gateway-01",
|
|
115
|
+
sse_manager=sse_manager,
|
|
116
|
+
task_context_map=task_context_map,
|
|
117
|
+
task_context_lock=task_context_lock,
|
|
118
|
+
app_name="my-app"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def cancel_task_example():
|
|
122
|
+
# Cancel a task
|
|
169
123
|
await task_service.cancel_task(
|
|
170
|
-
agent_name="
|
|
171
|
-
task_id="task-
|
|
172
|
-
client_id="client-
|
|
173
|
-
user_id="
|
|
124
|
+
agent_name="data-processor",
|
|
125
|
+
task_id="task-123",
|
|
126
|
+
client_id="client-456",
|
|
127
|
+
user_id="user@example.com"
|
|
174
128
|
)
|
|
175
|
-
print("Task cancellation request sent.")
|
|
176
129
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
130
|
+
asyncio.run(cancel_task_example())
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
# content_hash: 83ddf6b403dc50598ed550e4b3a5445f832b3956dad75f7a3fbbb7e6e5c6c115
|