solace-agent-mesh 1.0.8__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.

Files changed (162) hide show
  1. solace_agent_mesh/agent/adk/adk_llm.txt +182 -42
  2. solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
  3. solace_agent_mesh/agent/adk/callbacks.py +165 -104
  4. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +0 -18
  5. solace_agent_mesh/agent/adk/models/models_llm.txt +104 -55
  6. solace_agent_mesh/agent/adk/runner.py +7 -5
  7. solace_agent_mesh/agent/adk/setup.py +11 -0
  8. solace_agent_mesh/agent/adk/stream_parser.py +8 -1
  9. solace_agent_mesh/agent/adk/tool_wrapper.py +10 -3
  10. solace_agent_mesh/agent/agent_llm.txt +355 -18
  11. solace_agent_mesh/agent/protocol/event_handlers.py +433 -296
  12. solace_agent_mesh/agent/protocol/protocol_llm.txt +54 -7
  13. solace_agent_mesh/agent/sac/app.py +1 -1
  14. solace_agent_mesh/agent/sac/component.py +212 -517
  15. solace_agent_mesh/agent/sac/sac_llm.txt +133 -63
  16. solace_agent_mesh/agent/testing/testing_llm.txt +25 -58
  17. solace_agent_mesh/agent/tools/peer_agent_tool.py +15 -11
  18. solace_agent_mesh/agent/tools/tools_llm.txt +234 -69
  19. solace_agent_mesh/agent/utils/artifact_helpers.py +35 -1
  20. solace_agent_mesh/agent/utils/utils_llm.txt +90 -105
  21. solace_agent_mesh/assets/docs/404.html +3 -3
  22. solace_agent_mesh/assets/docs/assets/js/{3d406171.7d02a73b.js → 3d406171.0b9eeed1.js} +1 -1
  23. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/{75384d09.ccd480c4.js → 75384d09.bf78fbdb.js} +1 -1
  25. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +2 -0
  28. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +1 -0
  29. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  43. 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
  44. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +53 -0
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +8 -8
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  61. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  63. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  64. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +1 -0
  65. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  66. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +1 -0
  67. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  68. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  69. solace_agent_mesh/cli/__init__.py +1 -1
  70. solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +12 -3
  71. solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +10 -14
  72. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +2 -15
  73. solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +6 -2
  74. solace_agent_mesh/cli/utils.py +15 -0
  75. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DvlO62me.js → authCallback-BmF2l6vg.js} +1 -1
  76. solace_agent_mesh/client/webui/frontend/static/assets/{client-bp6u3qVZ.js → client-D881Dttc.js} +4 -4
  77. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +699 -0
  78. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +1 -0
  79. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +2 -2
  80. solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
  81. solace_agent_mesh/common/a2a/__init__.py +213 -0
  82. solace_agent_mesh/common/a2a/a2a_llm.txt +182 -0
  83. solace_agent_mesh/common/a2a/artifact.py +328 -0
  84. solace_agent_mesh/common/a2a/events.py +183 -0
  85. solace_agent_mesh/common/a2a/message.py +307 -0
  86. solace_agent_mesh/common/a2a/protocol.py +513 -0
  87. solace_agent_mesh/common/a2a/task.py +127 -0
  88. solace_agent_mesh/common/a2a/translation.py +653 -0
  89. solace_agent_mesh/common/a2a/types.py +54 -0
  90. solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
  91. solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +407 -0
  92. solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
  93. solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +31 -0
  94. solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +18 -0
  95. solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +235 -0
  96. solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
  97. solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +25 -0
  98. solace_agent_mesh/common/agent_registry.py +1 -1
  99. solace_agent_mesh/common/common_llm.txt +192 -70
  100. solace_agent_mesh/common/data_parts.py +99 -0
  101. solace_agent_mesh/common/middleware/middleware_llm.txt +17 -17
  102. solace_agent_mesh/common/sac/__init__.py +0 -0
  103. solace_agent_mesh/common/sac/sac_llm.txt +71 -0
  104. solace_agent_mesh/common/sac/sam_component_base.py +252 -0
  105. solace_agent_mesh/common/services/providers/providers_llm.txt +51 -84
  106. solace_agent_mesh/common/services/services_llm.txt +206 -26
  107. solace_agent_mesh/common/utils/artifact_utils.py +29 -0
  108. solace_agent_mesh/common/utils/embeds/embeds_llm.txt +176 -80
  109. solace_agent_mesh/common/utils/utils_llm.txt +323 -42
  110. solace_agent_mesh/config_portal/backend/common.py +1 -1
  111. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-MqsrTd6g.js → _index-Bym6YkMd.js} +74 -24
  112. solace_agent_mesh/config_portal/frontend/static/client/assets/{components-B7lKcHVY.js → components-Rk0n-9cK.js} +1 -1
  113. solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-CEumGClk.js → entry.client-mvZjNKiz.js} +1 -1
  114. solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DSo1AH_7.js → index-DzNKzXrc.js} +1 -1
  115. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d845808d.js +1 -0
  116. solace_agent_mesh/config_portal/frontend/static/client/assets/{root-C4XmHinv.js → root-BWvk5-gF.js} +1 -1
  117. solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
  118. solace_agent_mesh/core_a2a/core_a2a_llm.txt +10 -8
  119. solace_agent_mesh/core_a2a/service.py +20 -44
  120. solace_agent_mesh/gateway/base/app.py +27 -1
  121. solace_agent_mesh/gateway/base/base_llm.txt +177 -72
  122. solace_agent_mesh/gateway/base/component.py +294 -523
  123. solace_agent_mesh/gateway/gateway_llm.txt +299 -58
  124. solace_agent_mesh/gateway/http_sse/component.py +156 -183
  125. solace_agent_mesh/gateway/http_sse/components/components_llm.txt +29 -29
  126. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +272 -36
  127. solace_agent_mesh/gateway/http_sse/main.py +8 -10
  128. solace_agent_mesh/gateway/http_sse/routers/agents.py +1 -1
  129. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +18 -4
  130. solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +231 -5
  131. solace_agent_mesh/gateway/http_sse/routers/sessions.py +12 -7
  132. solace_agent_mesh/gateway/http_sse/routers/tasks.py +116 -169
  133. solace_agent_mesh/gateway/http_sse/services/agent_service.py +1 -1
  134. solace_agent_mesh/gateway/http_sse/services/services_llm.txt +89 -135
  135. solace_agent_mesh/gateway/http_sse/services/task_service.py +2 -5
  136. solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
  137. solace_agent_mesh/templates/gateway_component_template.py +149 -98
  138. {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/METADATA +5 -4
  139. {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/RECORD +143 -126
  140. solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +0 -1
  141. solace_agent_mesh/assets/docs/assets/js/main.6dba4a66.js +0 -2
  142. solace_agent_mesh/assets/docs/assets/js/runtime~main.6415ad00.js +0 -1
  143. solace_agent_mesh/assets/docs/lunr-index-1756153049706.json +0 -1
  144. solace_agent_mesh/assets/docs/search-doc-1756153049706.json +0 -1
  145. solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +0 -1
  146. solace_agent_mesh/client/webui/frontend/static/assets/main-BucUdn9m.js +0 -673
  147. solace_agent_mesh/common/a2a_protocol.py +0 -564
  148. solace_agent_mesh/common/client/__init__.py +0 -4
  149. solace_agent_mesh/common/client/card_resolver.py +0 -21
  150. solace_agent_mesh/common/client/client.py +0 -85
  151. solace_agent_mesh/common/client/client_llm.txt +0 -133
  152. solace_agent_mesh/common/server/__init__.py +0 -4
  153. solace_agent_mesh/common/server/server.py +0 -122
  154. solace_agent_mesh/common/server/server_llm.txt +0 -169
  155. solace_agent_mesh/common/server/task_manager.py +0 -291
  156. solace_agent_mesh/common/server/utils.py +0 -28
  157. solace_agent_mesh/common/types.py +0 -411
  158. solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-28271392.js +0 -1
  159. /solace_agent_mesh/assets/docs/assets/js/{main.6dba4a66.js.LICENSE.txt → main.a75ecc0d.js.LICENSE.txt} +0 -0
  160. {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/WHEEL +0 -0
  161. {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/entry_points.txt +0 -0
  162. {solace_agent_mesh-1.0.8.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 pydantic import BaseModel, Field
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 ....common.types import (
24
- JSONRPCResponse,
25
- InternalError,
26
- InvalidRequestError,
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
- class CancelTaskApiPayload(BaseModel):
45
- """Request body for the task cancellation endpoint."""
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: str = Field(
48
- ..., description="The name of the agent currently handling the task."
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
- external_event_data = {
91
- "agent_name": agent_name,
92
- "message": message,
93
- "files": files,
94
- "client_id": client_id,
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=target_agent,
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=False,
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
- return JSONRPCResponse(result={"taskId": task_id})
113
-
114
- except InvalidRequestError as e:
115
- log.warning("%sInvalid request: %s", log_prefix, e.message, exc_info=True)
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 = InternalError(message="Unexpected server error: %s" % e)
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("/subscribe", response_model=JSONRPCResponse)
144
- async def subscribe_task_from_agent(
138
+ @router.post("/message:send", response_model=SendMessageSuccessResponse)
139
+ async def send_task_to_agent(
145
140
  request: FastAPIRequest,
146
- agent_name: str = Form(...),
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 (`tasks/sendSubscribe`) to the specified agent.
155
- Accepts multipart/form-data.
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
- log_prefix = "[POST /api/v1/tasks/subscribe] "
159
- log.info("%sReceived streaming request for agent: %s", log_prefix, agent_name)
160
-
161
- try:
162
- user_identity = await component.authenticate_and_enrich_user(request)
163
- if user_identity is None:
164
- log.warning("%sUser authentication failed. Denying request.", log_prefix)
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
- task_id = await component.submit_a2a_task(
195
- target_agent_name=target_agent,
196
- a2a_parts=a2a_parts,
197
- external_request_context=external_req_ctx,
198
- user_identity=user_identity,
199
- is_streaming=True,
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
- return JSONRPCResponse(result={"taskId": task_id})
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
- except InvalidRequestError as e:
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=e.model_dump(exclude_none=True),
198
+ detail="Task ID in URL path does not match task ID in payload.",
213
199
  )
214
- except PermissionError as pe:
215
- log.warning("%sPermission denied: %s", log_prefix, str(pe))
200
+
201
+ context = component.task_context_manager.get_context(taskId)
202
+ if not context:
216
203
  raise HTTPException(
217
- status_code=status.HTTP_403_FORBIDDEN,
218
- detail=str(pe),
204
+ status_code=status.HTTP_404_NOT_FOUND,
205
+ detail=f"No active task context found for task ID: {taskId}",
219
206
  )
220
- except InternalError as e:
207
+
208
+ agent_name = context.get("target_agent_name")
209
+ if not agent_name:
221
210
  log.error(
222
- "%sInternal error submitting streaming task: %s",
211
+ "%sCould not determine target agent for task %s. Context is missing 'target_agent_name'.",
223
212
  log_prefix,
224
- e.message,
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=error_resp.model_dump(exclude_none=True),
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 = InternalError(message="Unexpected server error: %s" % e)
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),
@@ -8,7 +8,7 @@ from typing import List, Optional
8
8
  from solace_ai_connector.common.log import log
9
9
 
10
10
  from ....common.agent_registry import AgentRegistry
11
- from ....common.types import AgentCard
11
+ from a2a.types import AgentCard
12
12
 
13
13
 
14
14
  class AgentService:
@@ -1,179 +1,133 @@
1
- Here is the developer guide for the `services` directory.
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 encapsulates core functionalities by providing high-level services for agent management, user identity searches, and A2A (Agent-to-Agent) task operations. These services abstract the complexities of interacting with the agent registry, external identity providers, and the underlying A2A messaging protocol.
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` - Marks the directory as a Python package.
8
- - `agent_service.py` - Service for retrieving information about discovered A2A agents.
9
- - `people_service.py` - Service for searching for users via a configured identity service.
10
- - `task_service.py` - Service for handling the cancellation of tasks with A2A agents.
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:** This file marks the `services` directory as a Python package, allowing its modules to be imported.
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 high-level methods for accessing information about discovered A2A agents from the shared `AgentRegistry`.
22
- **Import:** `from src.solace_agent_mesh.gateway.http_sse.services.agent_service import AgentService`
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)` - A service that provides methods for accessing information about discovered A2A agents.
26
- - `get_all_agents() -> List[AgentCard]` - Retrieves all currently discovered and registered agent cards from the registry.
27
- - `get_agent_by_name(agent_name: str) -> Optional[AgentCard]` - Retrieves a specific agent card by its unique name. Returns `None` if the agent is not found.
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 typing import List, Optional
32
- from src.solace_agent_mesh.gateway.http_sse.services.agent_service import AgentService
33
- from src.solace_agent_mesh.common.agent_registry import AgentRegistry
34
- from src.solace_agent_mesh.common.types import AgentCard
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
- # 2. Get all available agents
36
+ # Get all available agents
46
37
  all_agents = agent_service.get_all_agents()
47
- print(f"Found {len(all_agents)} agent(s).")
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
- # 4. Try to get a non-existent agent
59
- missing_agent = agent_service.get_agent_by_name("non-existent-agent")
60
- print(f"Result for 'non-existent-agent': {missing_agent}")
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:** Acts as a layer on top of a configured `IdentityService` to provide user search functionality. If no identity service is configured, it gracefully returns empty results.
67
- **Import:** `from src.solace_agent_mesh.gateway.http_sse.services.people_service import PeopleService`
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])` - A service for searching and retrieving user information.
71
- - `search_for_users(query: str, limit: int = 10) -> List[Dict[str, Any]]` - Asynchronously searches for users via the identity service. Returns an empty list if the query is too short, no identity service is configured, or an error occurs.
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 typing import Any, Dict, List, Optional
77
- from src.solace_agent_mesh.gateway.http_sse.services.people_service import PeopleService
78
- from src.solace_agent_mesh.common.services.identity_service import BaseIdentityService
79
-
80
- # Define a mock identity service for the example
81
- class MockIdentityService(BaseIdentityService):
82
- async def search_users(self, query: str, limit: int) -> List[Dict[str, Any]]:
83
- print(f"MockIdentityService: Searching for '{query}' with limit {limit}")
84
- all_users = [
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
- print(f"Found {len(users)} user(s): {users}")
69
+ for user in users:
70
+ print(f"User: {user.get('name')} - {user.get('email')}")
98
71
 
99
- # 3. Initialize without an identity service
100
- people_service_no_id = PeopleService(identity_service=None)
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
- if __name__ == "__main__":
107
- asyncio.run(main())
76
+ asyncio.run(search_users())
108
77
  ```
109
78
 
110
- ---
111
-
112
79
  ### task_service.py
113
- **Purpose:** Handles the business logic for cancelling A2A tasks. It uses `CoreA2AService` to construct the cancellation message and a provided publisher function to send it over the messaging fabric.
114
- **Import:** `from src.solace_agent_mesh.gateway.http_sse.services.task_service import TaskService, PublishFunc`
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]` - A callable that sends a message. It takes a topic, a payload dictionary, and optional user properties.
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 typing import Callable, Dict, Optional
128
-
129
- from src.solace_agent_mesh.gateway.http_sse.services.task_service import TaskService, PublishFunc
130
- from src.solace_agent_mesh.core_a2a.service import CoreA2AService
131
- from src.solace_agent_mesh.gateway.http_sse.sse_manager import SSEManager
132
-
133
- # Mock dependencies for the example
134
- class MockCoreA2AService(CoreA2AService):
135
- def cancel_task(self, agent_name, task_id, client_id, user_id):
136
- topic = f"a2a/request/{agent_name}/cancelTask"
137
- payload = {"taskId": task_id}
138
- user_props = {"clientId": client_id, "userId": user_id}
139
- return topic, payload, user_props
140
-
141
- def my_publish_func(topic: str, payload: Dict, user_properties: Optional[Dict]):
142
- print("\n--- Publishing Message ---")
143
- print(f"Topic: {topic}")
144
- print(f"Payload: {payload}")
145
- print(f"User Properties: {user_properties}")
146
- print("------------------------")
147
-
148
- async def main():
149
- # 1. Set up dependencies
150
- core_a2a_service = MockCoreA2AService()
151
- sse_manager = SSEManager()
152
- task_context_map = {}
153
- task_context_lock = threading.Lock()
154
-
155
- # 2. Initialize TaskService
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="report-generator",
171
- task_id="task-12345",
172
- client_id="client-abcde",
173
- user_id="test.user"
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
- if __name__ == "__main__":
178
- asyncio.run(main())
179
- ```
130
+ asyncio.run(cancel_task_example())
131
+ ```
132
+
133
+ # content_hash: 83ddf6b403dc50598ed550e4b3a5445f832b3956dad75f7a3fbbb7e6e5c6c115